Table of Contents

Namespace Hi.NcParsers.EvaluationSyntaxs.Heidenhain

Classes

HeidenhainExpressionParser

Recursive-descent parser for Heidenhain klartext FN value expressions. Produces the same NcExpr AST as the Fanuc NcExpressionParser so NcExpressionEvaluator is reused unchanged. Pure: no variable lookup, no evaluation.

Grammar (lowest precedence at top):

expr     := add-expr
add-expr := term (('+' | '-') term)*
term     := factor (('*' | '/' | 'DIV') factor)*
factor   := ('+' | '-')? primary
primary  := number
         | '(' expr ')'
         | 'Q' digits | 'QR' digits | 'QL' digits | 'QS' digits
         | func primary                    → prefix form "SQRT 4" (FN 5)
         | func '(' arglist ')'            → paren form "SQRT(Q2)"

Dialect notes: Q-family tokens are canonicalised to uppercase (q1Q1) so Parsing.Assignments keys, table lookups and same-block references agree. (Caveat shared with the Siemens R canonicalisation: a lowercase-captured Assignments key keeps its raw spelling in the evaluator's same-block dictionary, so a later same-block reference resolves to the pre-block value instead — klartext posts emit uppercase, corpus-zero.) DIV is the FN 4 division spelling (FN 4: Q4 = +8 DIV +Q2) and maps onto the shared divide operator. Function names are limited to the klartext math vocabulary (SQRT SIN COS TAN ASIN ACOS ATAN ABS INT FRAC SGN NEG LN LOG EXP); INT normalises to the evaluator's FIX (truncate toward zero), names the shared evaluator lacks (FRAC/SGN/NEG/LOG) parse fine and fail soft at evaluation. Any other identifier is a parse error — klartext has no named variables, and rejecting bare words keeps the evaluator's Parsing-tree pass from touching non-expression strings (MM, MAX, tool-axis letters). Comparison/logical operators are deliberately absent: FN 9–12 jump conditions are pre-normalised by HeidenhainGotoParsingSyntax into separate value operands plus a shared comparison word — never parsed here.

HeidenhainGotoSyntax

Resolves Heidenhain FN 9–12 conditional jumps — the FanucGotoSyntax pattern with klartext LBL targets and a structural condition. Triggered by Parsing.HeidenhainGoto (written by HeidenhainGotoParsingSyntax); decides whether to fire, and on fire calls ReplaceSource(IEnumerable<T>) on layers[0] with the re-segmented file content starting at the target label line (inclusive — the definition marker is consumed by HeidenhainSubProgramReturnSyntax downstream).

The condition is compared here, not by the expression grammar: the parsing owner pre-normalised the statement into two value operands plus a shared comparison word (the P2 decision that keeps the Heidenhain dialect free of comparison/logical layers). Each operand is read polymorphically — numeric (typed at capture for literals, or substituted in place by VariableEvaluatorSyntax for resolved Q references) fires the comparison; a still-string operand means unresolved (the FN 18 SYSREAD target staying vacant is the designed source) and the jump warns HeidenhainGoto--ConditionNotEvaluated and falls through — no fabricated values, both endings stay fail-soft.

The label scan is whole-file first-match through the runner's own segmenter (SegmenterDependency) with the P4 call-path probe stack — klartext has no direction mnemonic and a TNC label is unique per program, so the anchored directional overloads would add a distinction the language does not have. Numeric labels canonicalize ("01"1); GOTO LBL 0 targets the end-of-subprogram sentinel and is refused (HeidenhainGoto--Lbl0Target, the HeidenhainSubProgramCallSyntax precedent). Jumps hosted inside a P4 inlined body (CALL LBL/CALL PGM splice or a REP section pass) are recognized but not simulated — the redirect would discard the pending inline tail (HeidenhainGoto--InlinedContextUnsupported, the Siemens P5 guard).

Pipeline placement: tail of the Heidenhain Evaluation bundle, after VariableEvaluatorSyntax (operand substitution) and the Q reader. The HeidenhainGotoIterationDependency watchdog caps fired jumps per (file, label); a missing watchdog disables the cap (Fanuc parity).

HeidenhainQParameterReadingSyntax

Obtains values for Heidenhain Q parameters by consuming literal numeric assignments from Parsing.Assignments.Qn/QRn and routing them by id range — one reader for the single Q key shape, range routing inside (the Heidenhain analogue of the Fanuc range-routed reader trio):

  • Q0-Q99HeidenhainQParameterTable free range (hincproj-persisted; the table is the single source of truth — no JSON mirror).
  • QRn → the same table's permanent QR store.
  • Q100-Q199 → controller-written system parameters: the write is consumed but not applied, with a HeidenhainQ--SystemReadOnly warning (no fabricated values).
  • Q200+ → volatile range: dict-merged into Vars.Volatile with canonical Q+id keys, carried block-to-block like the Fanuc VolatileVariableReadingSyntax; cleared at program end by ProgramEndCleanSyntax.

The carry of the previous block's Vars.Volatile happens on every block regardless of assignments, so the single-step traceback contract of HeidenhainVolatileQLookup holds.

Only literal numeric RHS values are consumed (Q1 = 5000 ✓; Q1 = Q1*.75 ✗). Non-literal RHS entries are left untouched in Parsing.Assignments; VariableEvaluatorSyntax resolves them to literals earlier on the same block, so by the time this syntax runs, every evaluable RHS is literal. The two syntaxes are decoupled.

HeidenhainSubProgramCallSyntax

Consumes the Parsing.CALL record captured by HeidenhainCallSyntax and executes the three klartext call mechanisms over the shared M98 inline machinery:

  • CALL LBL n / CALL LBL "name" (subprogram): the host file is re-segmented through the runner's own segmenter (SegmenterDependency), scanned for the matching LBL definition with LabelScanUtil and the LabelProbeSyntaxes, truncated at the first following LBL 0 (inclusive — its consumption pops the frame), and prepended into layers[0] with a pushed CallStack frame. A subprogram without LBL 0 is a structured safe-skip (inlining to EOF would double-execute the file tail).
  • CALL LBL n REP m (program-section repeat — TNC semantics: the section from LBL n up to, not including, the call line runs m extra times): m fresh re-segmentation passes of that slice are prepended. A loop, not a call — no new CallStack frame is pushed, but the host block's stack propagates onto the repeated pieces so nested calls inside the section still accumulate depth against the rail; the repeat count itself is a literal bound. Any LBL 0 passed inside the section is a no-op for the return syntax (null-safe pop).
  • CALL PGM name: resolved through InternalFolder with the FilePatterns chain ({0}.h{0}.H{0}) and inlined whole — the SiemensSubProgramCallSyntax mechanism verbatim, including resolve-miss safe-skip (HeidenhainCall--Skipped). The callee's END PGM pops the frame via HeidenhainSubProgramReturnSyntax; a unit switch inside the callee is not restored on return (recorded limitation).

Recursion rail: like the Siemens call path, a self-recursive CALL LBL/CALL PGM would splice forever; a call whose host block already carries MaxCallDepth CallStack frames is consumed as a safe-skip with HeidenhainCall--DepthLimitExceeded. No MacroFrame is stamped — klartext subprograms share the caller's Q scope.

Pipeline placement: head of the Heidenhain Evaluation bundle (the Fanuc discipline — call/inline ahead of all variable machinery).
HeidenhainSubProgramReturnSyntax

Consumes the two klartext return spellings and the standalone label markers:

  • LBL 0 — end-of-subprogram sentinel: stamps SubProgramReturn (Term: "LBL 0") and pops the CallStack frame (null-safe: an LBL 0 reached in the main flow — the 1.H head layout — is a no-op). Like the Fanuc M99, the "return" itself is structural: the call syntax truncated the inlined slice at this block, so the caller's tail follows naturally.
  • LBL n / LBL "name" — definition markers: consumed into a block-root HeidenhainLbl record (no motion; keeps the label visible for dumps and the P5 jump family).
  • END PGM with a non-empty CallStack — the return of a CALL PGM callee: consumes Parsing.PGM before the Logic program-header syntax can treat it as a real program end (which would clear the volatile Q store mid-stream), stamps SubProgramReturn (Term: "END PGM"), and pops the frame. The main file's END PGM (empty stack) is untouched.
Pipeline placement: directly after HeidenhainSubProgramCallSyntax at the head of the Evaluation bundle.
HeidenhainVolatileQLookup

Reads Heidenhain volatile Q parameters (Q200+, the cycle/user range that resets at program start) from Vars.Volatile. Self-gates the id range so the evaluator's RuntimeVariableLookups chain can fall through for other keys (Q0-Q99/QRn resolve on the HeidenhainQParameterTable further down the chain; Q100-Q199 stay vacant by design). Sibling of the Fanuc VolatileVariableLookup with the same single-step traceback contract: HeidenhainQParameterReadingSyntax dict-merges every block's Vars.Volatile into the next block, so the entry — if it exists — is on the current block or the immediately previous one.

Keys are stored canonically as Q + id by the reader; this lookup re-canonicalises the incoming key the same way, so lowercase raw captures still resolve. Stateless and dependency-free.