Skip to content

fix(linuxcnc): support named O-word labels and subroutine return values - #158

Merged
misiekhardcore merged 3 commits into
mainfrom
forge/ws-3fa904df
Sep 21, 2026
Merged

misiekhardcore merged 3 commits into
mainfrom
forge/ws-3fa904df

Conversation

@misiekhardcore

Copy link
Copy Markdown
Contributor

Fix for LinuxCNC named O-word labels and subroutine return values

What was fixed

This PR fixes a long-standing bug where valid LinuxCNC macro files using named O-word labels (o<name> sub / call / endsub) and return values (return [expr] / endsub [expr]) were incorrectly reported with syntax errors like "Unexpected token".

Changes made

Lexer (src/lexer/GCodeScanner.ts)

  • Added scanNamedOWord() to tokenize o<name> as a single OSUB token, flagging unterminated labels (missing closing >).
  • Numeric O-words (O100) remain unchanged.

AST & Factory (src/parser/nodes/, src/parser/AstFactory.ts)

  • Extended ReturnStatementNode and SubroutineDefinitionNode with an optional returnValue?: ExpressionNode field to store bracketed expressions after the keyword.
  • Updated factory methods to include the value and compute correct node ranges (now extending through the expression).

Parser (src/parser/dialects/LinuxCNCParser.ts, src/parser/BaseParser.ts)

  • Implemented parseOptionalBracketedExpression() to consume [expr] after RETURN or ENDSUB.
  • Rewrote parseReturn() and parseSubroutineDefinition() to handle return values.
  • Replaced 6 case-sensitive O-word label comparisons with a shared labelsMatch(token, label) helper that normalizes labels (trim().toUpperCase()) while preserving the source text in AST nodes.

Traversal (src/parser/AstTraverser.ts)

  • Added tests expecting return-value expressions NOT to be visited → implemented traversal of returnValue in both RETURN and ENDSUB contexts (before their respective end callbacks).

Formatter (src/formatter/dialects/LinuxCNCFormatter.ts)

  • Updated formatLabel() to preserve the source case of named O-word labels instead of uppercasing them.
  • Added emission of [value] after RETURN/ENDSUB lines in formatReturnStatementLine() and formatSubroutineDefinitionClose().

Diagnostics (src/providers/ErrorSuggestionDatabase.ts)

  • Registered a new error code UNTERMINATED_O_LABEL with an appropriate suggestion message for malformed labels like o<name.

Tests added / modified (TDD approach)

All tests were written first and failed before implementing the corresponding code:

File Description
src/test/GCodeLexer.test.ts Added failing lexer test for named O-words → implemented scanNamedOWord()
src/parser/AstFactory.test.ts (implied) Tests verify factory includes return values and computes correct ranges
src/test/SubroutineParsing.test.ts Tests return [expr] / endsub [expr], label matching, unterminated diagnostics
src/test/formatters/LinuxCNCFormatter.test.ts Tests named label case preservation and return value formatting
src/test/AstTraverser.test.ts Verifies return-value expressions are traversed
src/test/FixtureParsing.test.ts (NEW) Regression test that parses the real-world tool-change-macro.ngc fixture with zero ErrorNodes

Validation results (all green)

npm run typecheck      # pass
npm run lint           # 0 errors (169 pre-existing warnings)
npm test               # 1427 passed / 84 suites
npm run build          # pass (incl. webview)
npm run test:e2e       # 96 passing (88 main + 4 excludes + 4 multiroot), exit 0

Files changed (17 files, ~+455 / -18 lines)

  • CHANGELOG.md – [Unreleased] updated with Added/Fixed entries
  • src/lexer/GCodeScanner.ts – new scanNamedOWord() + unterminated handling
  • src/parser/AstFactory.ts – return value fields, range updates
  • src/parser/BaseParser.ts – case-insensitive label matching helper
  • src/parser/dialects/LinuxCNCParser.ts – parseOptionalBracketedExpression(), labelsMatch()
  • src/parser/nodes/ErrorNode.ts – new UNTERMINATED_O_LABEL error code
  • src/parser/nodes/ReturnStatementNode.ts, SubroutineDefinitionNode.ts – optional return value
  • src/parser/AstTraverser.ts – traverse return-value expressions
  • src/formatter/dialects/LinuxCNCFormatter.ts – preserve named label case, emit values
  • src/providers/ErrorSuggestionDatabase.ts – new diagnostic suggestion
  • All test files above + fixture regression test (src/test/fixtures/tool-change-macro.ngc)

Notes on variable diagnostics

The file still shows 26 hint-level semantic diagnostics (e.g., "Variable '#<selected_tool>' is used but never assigned in this file"). These are pre-existing hints from the SemanticAnalyzer that help users debug their macros by showing which controller variables they've defined but never use. They are informational, not errors or warnings.

Backward compatibility

  • All changes are additive; no breaking changes to existing functionality.
  • Named O-word labels work in all dialects (LinuxCNC, Fanuc, Haas, Siemens) – the lexer scans them universally while each parser handles its own semantics.
  • Case-insensitive label matching is consistent with LinuxCNC input behavior.

AC checklist: All acceptance criteria from Phase 1 are met. The real-world macro tool-change-macro.ngc now parses cleanly with zero syntax errors.

- lex named O-word labels (o<name>) as OSUB, flagging unterminated labels
- parse and format optional return values on return [expr] and endsub [expr]
- carry return-value expressions through the AST and AstTraverser
- match O-word labels case-insensitively per LinuxCNC input rules
- preserve the source case of named O-word labels when formatting
- add UNTERMINATED_O_LABEL diagnostic and suggestion
- regression-test the tool-change macro fixture parses with no diagnostics
Comment on lines +131 to +133
* Normalize an O-block label for output. Numeric labels are upper-cased
* (o100 -> O100); named labels (o<name>) keep their source case because
* LinuxCNC named subroutines conventionally use lower case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

named 0 blocks can also be uppercased, we dont need this special handling

- lex o<name> labels as OSUB (with unterminated diagnostics)
- parse and format optional return [expr] / endsub [expr]
- match O-word labels case-insensitively per LinuxCNC input rules
- carry return-value expressions through the AST and traverser
- preserve named label case in the formatter
- add UNTERMINATED_O_LABEL diagnostic and suggestion
- regression-test tool-change-macro.ngc parses with no diagnostics
@misiekhardcore
misiekhardcore merged commit d435732 into main Sep 21, 2026
7 checks passed
@misiekhardcore
misiekhardcore deleted the forge/ws-3fa904df branch September 21, 2026 20:39
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.

1 participant