Skip to content

Enhanced blocks logic#491

Closed
buzzia2001 wants to merge 16 commits into
codefori:mainfrom
buzzia2001:main
Closed

Enhanced blocks logic#491
buzzia2001 wants to merge 16 commits into
codefori:mainfrom
buzzia2001:main

Conversation

@buzzia2001

@buzzia2001 buzzia2001 commented Mar 25, 2026

Copy link
Copy Markdown
Member

Changes

This release improves block logic. Specifically, support has been added for highlighting different functional blocks, including the start and end of each block. Support has also been added for folding blocks and for displaying information within the navigation bar.

image image image image

The support works for:

  • { open: ['if', 'ifeq', 'ifne', 'ifgt', 'iflt', 'ifge', 'ifle'], close: ['endif'], middle: ['else', 'elseif'] }
  • { open: ['dow', 'doweq', 'downe', 'dowgt', 'dowlt', 'dowge', 'dowle'], close: ['enddo'] }
  • { open: ['dou', 'doueq', 'doune', 'dougt', 'doult', 'douge', 'doule'], close: ['enddo'] }
  • { open: ['do'], close: ['enddo'] }
  • { open: ['for', 'for-each'], close: ['endfor'] }
  • { open: ['select'], close: ['endsl'], middle: ['when', 'wheneq', 'whenne', 'whengt', 'whenlt', 'whenge', 'whenle', 'when-is', 'when-in', 'other'] }
  • { open: ['monitor'], close: ['endmon'], middle: ['on-error', 'on-excp'] }
  • { open: ['dcl-proc'], close: ['end-proc'] }
  • { open: ['dcl-ds'], close: ['end-ds'] }
  • { open: ['dcl-pr'], close: ['end-pr'] }
  • { open: ['dcl-pi'], close: ['end-pi'] }
  • { open: ['dcl-enum'], close: ['end-enum'] }
  • { open: ['begsr'], close: ['endsr'] }
  • { open: ['casxx', 'caseq', 'casne', 'casgt', 'caslt', 'casge', 'casle'], close: ['endcs'] }

Checklist

  • have tested my change
  • Remove any/all console.logs I added
  • have added myself to the contributors' list in the README
  • for feature PRs: PR only includes one feature enhancement.

closes #486
closes #521

@bobcozzi

bobcozzi commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator

You have for listed as supported, can you confirm if this includes support for the for-each opcode as well?

@buzzia2001

Copy link
Copy Markdown
Member Author

You have for listed as supported, can you confirm if this includes support for the for-each opcode as well?

Hi @bobcozzi,
supported keywords are the following:

  • { open: ['if', 'ifeq', 'ifne', 'ifgt', 'iflt', 'ifge', 'ifle'], close: ['endif'], middle: ['else', 'elseif'] }
  • { open: ['dow', 'doweq', 'downe', 'dowgt', 'dowlt', 'dowge', 'dowle'], close: ['enddo'] }
  • { open: ['dou', 'doueq', 'doune', 'dougt', 'doult', 'douge', 'doule'], close: ['enddo'] }
  • { open: ['do'], close: ['enddo'] }
  • { open: ['for', 'for-each'], close: ['endfor'] }
  • { open: ['select'], close: ['endsl'], middle: ['when', 'wheneq', 'whenne', 'whengt', 'whenlt', 'whenge', 'whenle', 'when-is', 'when-in', 'other'] }
  • { open: ['monitor'], close: ['endmon'], middle: ['on-error', 'on-excp'] }
  • { open: ['dcl-proc'], close: ['end-proc'] }
  • { open: ['dcl-ds'], close: ['end-ds'] }
  • { open: ['dcl-pr'], close: ['end-pr'] }
  • { open: ['dcl-pi'], close: ['end-pi'] }
  • { open: ['dcl-enum'], close: ['end-enum'] }
  • { open: ['begsr'], close: ['endsr'] }
  • { open: ['casxx', 'caseq', 'casne', 'casgt', 'caslt', 'casge', 'casle'], close: ['endcs'] }

Let me know if I missed anything.
Thank you

@bobcozzi

bobcozzi commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

Thank you! I see that now.

Are you supporting fixed-format as well? I am asking because this line:
{ open: ['if', 'ifeq', 'ifne', 'ifgt', 'iflt', 'ifge', 'ifle'], close: ['endif'] },

contains fixed-format opcode. Thus replicating the open:['if']... on a new line--wondering if that plays nice with the other entires.

Also, have you considered seeing if you can support RPG IV Conditional Compiler Directive?
/IF
/ELSEIF
/ELSE
/ENDIF

https://www.ibm.com/docs/en/i/7.4.0?topic=statements-conditional-directives-within-free-form-statement

@buzzia2001

buzzia2001 commented Apr 8, 2026

Copy link
Copy Markdown
Member Author

Thank you! I see that now.

Are you supporting fixed-format as well? I am asking because this line: { open: ['if', 'ifeq', 'ifne', 'ifgt', 'iflt', 'ifge', 'ifle'], close: ['endif'] },

contains fixed-format opcode. Thus replicating the open:['if']... on a new line--wondering if that plays nice with the other entires.

Also, have you considered seeing if you can support RPG IV Conditional Compiler Directive? /IF /ELSEIF /ELSE /ENDIF

https://www.ibm.com/docs/en/i/7.4.0?topic=statements-conditional-directives-within-free-form-statement

Hi @bobcozzi
My idea is to cover FIXED and FREE rpg without differences.

I didn't unserstand what you mean with "Thus replicating the open:['if']... on a new line--wondering if that plays nice with the other entires."

For /IF and so on, I see that highlighting and foldinf is already supported:
image

I'm pushing also the support for breadcrumb
image

@charles-slc

Copy link
Copy Markdown

Just poking around, so I may be way off base. But don't you need to consider end by itself as a possible closure for fixed-format CASxx, DO, DOU, DOUxx, DOW, DOWxx, FOR, IF, IFxx, or SELECT group

@bobcozzi

bobcozzi commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

Just poking around, so I may be way off base. But don't you need to consider end by itself as a possible closure for fixed-format CASxx, DO, DOU, DOUxx, DOW, DOWxx, FOR, IF, IFxx, or SELECT group

I suppose if Developers still have that old System/38 RPG III "END" opcode, then yes, it should potentially be there. If they're still using it. I see no reason to put any extra effort into supporting it, however.

@charles-slc

Copy link
Copy Markdown

I suppose if Developers still have that old System/38 RPG III "END" opcode, then yes, it should potentially be there. If there's still using it, I see no reason to put any extra effort into supporting it, however.

I'd agree it's not worth a lot of extra effort. I was pointing it out incase it was simply a matter of having close: ['endif', 'end']

I still run into it once and a while. Some may have originated in RPG III, but just as likely to have come from a (lazy?) RPG IV developer. It is valid fixed format, may consider how not supporting it would break the new functionality.

@buzzia2001

Copy link
Copy Markdown
Member Author

I suppose if Developers still have that old System/38 RPG III "END" opcode, then yes, it should potentially be there. If there's still using it, I see no reason to put any extra effort into supporting it, however.

I'd agree it's not worth a lot of extra effort. I was pointing it out incase it was simply a matter of having close: ['endif', 'end']

I still run into it once and a while. Some may have originated in RPG III, but just as likely to have come from a (lazy?) RPG IV developer. It is valid fixed format, may consider how not supporting it would break the new functionality.

@bobcozzi @charles-slc

Help me understand this—I’ll admit I’m by no means an expert on fixed-length RPGs…
Does END let me close all those blocks mentioned above? If so, I can try adding it to see if the algorithm I used for nesting calculations needs to be modified or if it’s already fine as is, but I don’t expect any major issues.
Let me know

@charles-slc

Copy link
Copy Markdown

@bobcozzi @charles-slc

Help me understand this—I’ll admit I’m by no means an expert on fixed-length RPGs… Does END let me close all those blocks mentioned above? If so, I can try adding it to see if the algorithm I used for nesting calculations needs to be modified or if it’s already fine as is, but I don’t expect any major issues. Let me know

Yes, https://www.ibm.com/docs/en/i/7.6.0?topic=codes-endyy-end-structured-group

image

@buzzia2001

Copy link
Copy Markdown
Member Author

@bobcozzi @charles-slc
Help me understand this—I’ll admit I’m by no means an expert on fixed-length RPGs… Does END let me close all those blocks mentioned above? If so, I can try adding it to see if the algorithm I used for nesting calculations needs to be modified or if it’s already fine as is, but I don’t expect any major issues. Let me know

Yes, https://www.ibm.com/docs/en/i/7.6.0?topic=codes-endyy-end-structured-group

Okay, I was immediately proven wrong... Adding “END” isn't as straightforward as I'd hoped; it's causing quite a few issues when trying to identify the block that the generic keyword belongs to. In fact, there could be several possibilities, especially when blocks are nested... I quickly tried changing the data structure for storing the block structure, but even in this case there are errors... which I hadn't noticed before, since it was unique

I'll try to look into it again in the next few days, but I can't guarantee that it will be feasible in the short term...

@bobcozzi @charles-slc I'd like to know if this issue with the generic END is a recurring problem (in which case it would be worth the effort to completely overhaul the logic) or if it's a fairly isolated case

@charles-slc

charles-slc commented Apr 17, 2026

Copy link
Copy Markdown

@bobcozzi @charles-slc I'd like to know if this issue with the generic END is a recurring problem (in which case it would be worth the effort to completely overhaul the logic) or if it's a fairly isolated case

Not sure I understand the question. Do you mean:

  1. are there any other "generic" op-codes like END.
  2. is the generic END commonly used.

For 1, I can't think of anything similar. For 2, as Bob pointed out it's a relic from RPG III. But it is still valid RPG IV. A quick search of our codebase turned up about 3% of our programs using it at least once. (high score was 193 occurrences)

@bobcozzi

bobcozzi commented Apr 18, 2026

Copy link
Copy Markdown
Collaborator

The "END" opcode in fixed format:
Originated on System/38 RPG III. It was used to end the original IFxx, DO, DOWxx, DOUxx, CASxx, and SELCT/WHEN opcodes.
Originally there were no ENDIF, ENDDO, ENDCS, ENDSL opcodes.
Then IBM added them to RPG III to help with obvious things, readability and validation during compiling.

"END" was deprecated about 10 years prior to RPG IV being released. It was included in RPG IV to enable simpler CVTRPGSRC conversion; so that conversion tool could simply convert (i.e., reformat) RPGIII into RPG IV without maintaining context during conversion. It is not now, nor has it ever been advocated as a thing--kind of like using an Input field "status" indicator where if blank then set on indicator 71. Sure it is supported in the compiler, but should it be used?

So in summary, "END" is used in place of any of the following:
ENDDO
ENDIF
ENDCS
ENDSL

So when "END" is detected, it matches the most recently detected conditional statement above it.

@buzzia2001

Copy link
Copy Markdown
Member Author

@bobcozzi @charles-slc

ok, I think I’ve managed it—at least it worked in all the test cases I generated.
I also realized I hadn’t handled “enddo” properly, since that’s a generic keyword too.
Unfortunately, I can’t upload the VSIX here, but if you could test it by building the extension from my branch, I’d really appreciate it—that way, we’d all have peace of mind.

@charles-slc

Copy link
Copy Markdown

I'll give it a shot this week. I don't know if it helps or hurts, but note that END or ENDxx always closes the most recent block. If the xx doesn't match the start of the block it's a syntax/compile issue. Maybe this is covered in your code, I didn't look very hard at it. But the impression I got was that when you hit and endif you were looking for the that prior matching if. But for instance:

  * valid
C        dow ...
C        if ...
C        ...
C        end;
C        end;
  * not valid syntax error
C        dow ...
C        if ...
C        ...
C        end;
C        endif;

Think of it as a stack, pushing the condition op-codes (and line nbr), popping when you hit ENDxx the popped op-code needs to match the xx unless it's one that accepts just END and that's what you've just gotten.

@worksofliam worksofliam self-requested a review May 20, 2026 14:54
@buzzia2001 buzzia2001 requested review from bobcozzi and removed request for worksofliam June 2, 2026 12:57

@bobcozzi bobcozzi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #491 Review: Enhanced blocks logic

PR: #491
Author: @buzzia2001
Reviewer: @bobcozzi
Date: June 3, 2026

Overview


I downloaded and ran a few test instances and reviewed the code--making notes along the way. Then I asked claude to summarize my notes into this .md file for me (hence the lengthy review).

This PR adds block highlighting, code folding, and breadcrumb navigation support for RPGLE block structures. The implementation includes support for:

  • IF/ENDIF blocks (and all variants: IFEQ, IFNE, etc.)
  • Loop blocks (DO, DOW, DOU, FOR, FOR-EACH)
  • SELECT/WHEN/OTHER/ENDSL blocks
  • MONITOR/ON-ERROR blocks
  • Declaration blocks (DCL-PROC, DCL-DS, DCL-PI, etc.)
  • Subroutine blocks (BEGSR/ENDSR)
  • Legacy fixed-format support including generic END opcode

Total Changes: 4,976 additions / 2,828 deletions across 19 files


Features Tested

Working Features

Feature Status Notes
Block highlighting Pass Highlights matching open/close keywords with middle keywords
Code folding Pass Folding arrows appear for all supported block types
Breadcrumb navigation Pass Shows current block context in navigation bar
Generic END support Pass Properly closes most recent compatible block
SQL block exclusion Pass FOR/SELECT inside SQL blocks correctly ignored
Comment/string exclusion Pass Keywords in comments/strings ignored
Multiple IF variants Pass IFEQ, IFNE, IFGT, etc. all supported
Fixed-format support Pass Works with both free-format and fixed-format code

Critical Issue: Mismatched Block Terminators

The algorithm matches POSSIBLE closing keywords, not CORRECT closing keywords.

The Problem

When the cursor is placed on a block opening keyword (e.g., FOR), the matcher searches forward for any closing keyword that CAN close that block type, not the CORRECT closing keyword based on proper nesting rules.

Example Code with Issue

for x = 1 to 10;           // Opens FOR block
  x = 1 + 2;
  if (x > 4);              // Opens IF block (nested)
    C = X;
  endfor;                  // WRONG! Should be ENDIF
endif;                     // WRONG! Should be ENDFOR

What happens:

  • When cursor is on FOR, the highlighter matches it with the first ENDFOR it finds
  • The algorithm doesn't validate that the IF block should be closed before the FOR block
  • Result: The matching appears correct visually, but the code has a syntax error

Visual Evidence

Block matching issue screenshot

The screenshot shows the for loop being matched with endfor even though there's an improperly nested if block in between. The highlighting makes it appear correct when it's actually a syntax error.
image


How the Algorithm Works

The implementation uses a stack-based nesting algorithm:

  1. Builds a stack of all open blocks as it scans forward
  2. When finding a closing keyword, searches the stack from most-recent to oldest
  3. Matches with the first block that CAN be closed by that keyword

File: extension/client/src/language/bracketMatcher.ts

// Simplified logic from findMatchingClose()
for (let i = openIndex + 1; i < matches.length; i++) {
  const word = matches[i].word;

  // Check if this word closes a block
  for (let j = stack.length - 1; j >= 0; j--) {
    if (stack[j].close.includes(word)) {  // NOTE: Checks CAN close, not SHOULD close
      if (j === 0) {
        return i;  // Found our match
      }
      stack.splice(j, 1);
      break;
    }
  }
}

Missing Validation

The algorithm doesn't verify that the closing keyword type matches the opening keyword type. It only checks if the closing keyword is in the list of possible closers.

For example:

  • FOR can be closed by ['endfor', 'end']
  • IF can be closed by ['endif', 'end']

When it finds endfor, it sees "this CAN close a FOR block" and matches it, even if there's an unclosed IF block in between that should use endif first.


Test Coverage Analysis

The PR includes good test coverage in tests/suite/blockParser.test.ts and tests/test_end_folding.rpgle:

Tests included:

  • Proper nesting with correct pairs
  • Generic END opcode closing most recent compatible block
  • SQL block exclusions (FOR/SELECT in SQL)
  • Comments and strings exclusions
  • All opcode variants (IFEQ, DOWNE, etc.)
  • Triple-level nesting with proper closure

Missing test cases:

  • Mismatched block terminators (the critical issue)
  • Syntax error detection (using wrong closing keyword)
  • Validation of proper nesting order

Test Results Summary

Category Result Details
Functionality Partial Pass All features work but with design flaw
Nested blocks (correct) Pass Properly matches correctly nested blocks
Nested blocks (incorrect) Fail Accepts and highlights mismatched pairs
Generic END Pass Correctly closes most recent compatible block
Edge cases Pass SQL/comment/string exclusions work
Test coverage Adequate Good coverage but missing syntax validation tests

Recommendations

1. Add Syntax Validation (High Priority)

The matcher should validate that closing keywords match the block type:

Option A: Don't highlight mismatched pairs

  • If endfor is found but top of stack is an IF block, don't match

Option B: Highlight with error decoration

  • Show mismatched pairs in red/error color to alert the programmer
  • Add diagnostic message explaining the mismatch

2. Add Test Cases for Invalid Syntax

Add tests to blockParser.test.ts:

it('should NOT match mismatched block terminators', () => {
  const code = `for x = 1 to 10;
  if (x > 4);
    y = x;
  endfor;  // Wrong! Should be endif
endif;     // Wrong! Should be endfor`;

  // Should either not match, or flag as error
});

3. Consider Compiler Validation

The ideal solution would validate that:

  • Each closing keyword matches its opening keyword type
  • The nesting order is correct (inner blocks close before outer blocks)
  • Proper error messages guide the programmer to fix the issue

Code Quality Notes

Positive aspects:

  • Clean, well-structured code
  • Good separation of concerns (blockParser, sqlDetection, bracketMatcher)
  • Comprehensive support for RPGLE opcodes
  • Good hover info showing block start/end lines

Areas for improvement:

  • Need validation layer to ensure syntactic correctness
  • Consider adding LSP diagnostics for mismatched blocks
  • Could benefit from more inline documentation

Decision

Status: REQUEST CHANGES

While this PR adds valuable functionality and works well for correctly-written code, the lack of validation for mismatched block terminators is a critical issue. The highlighter could give programmers false confidence that their code is correct when it actually has syntax errors.

Suggested approach:

  1. Merge this PR if the goal is just visual highlighting without validation
  2. OR add validation logic to detect and warn about mismatched pairs before merging
  3. File a follow-up issue to add comprehensive syntax validation if merged as-is

Files Changed

New files:

  • extension/client/src/language/bracketMatcher.ts (531 lines) - Block matching logic
  • extension/server/src/providers/foldingRange.ts (131 lines) - Code folding provider
  • language/utils/blockParser.ts (60 lines) - Block parsing utilities
  • language/utils/sqlDetection.ts (91 lines) - SQL block detection
  • tests/suite/blockParser.test.ts (151 lines) - Unit tests
  • tests/suite/sqlDetection.test.ts (187 lines) - SQL detection tests
  • tests/test_end_folding.rpgle (78 lines) - Integration test file

Modified files:

  • extension/server/src/providers/documentSymbols.ts - Breadcrumb support
  • extension/client/src/extension.ts - Register bracket matcher
  • extension/server/src/server.ts - Register folding provider
  • Various package.json and package-lock.json updates

Additional Testing Performed

Created test file test_mismatched_blocks.rpgle to verify behavior with incorrect nesting - confirmed the issue exists as described above.

Built and tested the extension in VS Code Extension Development Host - all features work as designed but the validation gap exists.

@buzzia2001 buzzia2001 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @bobcozzi ,
When I first designed the change, I hadn't planned on including a validity check, but it actually makes sense to add one... I've uploaded a new version, and I also asked my friend Bob (the fake one) to create a test case for me using some incorrect code

@bobcozzi bobcozzi mentioned this pull request Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

failed testcase for includeUri.test.ts - Cannot find module '../../language/parser' "Show block nesting" in VS Code

3 participants