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 (
q1→Q1) soParsing.Assignmentskeys, 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.)DIVis 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);INTnormalises to the evaluator'sFIX(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
LBLtargets and a structural condition. Triggered byParsing.HeidenhainGoto(written by HeidenhainGotoParsingSyntax); decides whether to fire, and on fire calls ReplaceSource(IEnumerable<T>) onlayers[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--ConditionNotEvaluatedand 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 0targets the end-of-subprogram sentinel and is refused (HeidenhainGoto--Lbl0Target, the HeidenhainSubProgramCallSyntax precedent). Jumps hosted inside a P4 inlined body (CALL LBL/CALL PGMsplice or aREPsection 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/QRnand routing them by id range — one reader for the singleQkey shape, range routing inside (the Heidenhain analogue of the Fanuc range-routed reader trio):Q0-Q99→ HeidenhainQParameterTable 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 aHeidenhainQ--SystemReadOnlywarning (no fabricated values).Q200+→ volatile range: dict-merged intoVars.Volatilewith canonicalQ+id keys, carried block-to-block like the Fanuc VolatileVariableReadingSyntax; cleared at program end by ProgramEndCleanSyntax.
The carry of the previous block's
Vars.Volatilehappens 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 inParsing.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.CALLrecord 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 matchingLBLdefinition with LabelScanUtil and the LabelProbeSyntaxes, truncated at the first followingLBL 0(inclusive — its consumption pops the frame), and prepended intolayers[0]with a pushed CallStack frame. A subprogram withoutLBL 0is 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 fromLBL nup 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. AnyLBL 0passed 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'sEND PGMpops 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
Pipeline placement: head of the HeidenhainCALL LBL/CALL PGMwould splice forever; a call whose host block already carries MaxCallDepth CallStack frames is consumed as a safe-skip withHeidenhainCall--DepthLimitExceeded. No MacroFrame is stamped — klartext subprograms share the caller's Q scope.Evaluationbundle (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: anLBL 0reached 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 PGMwith a non-empty CallStack — the return of aCALL PGMcallee: consumesParsing.PGMbefore 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'sEND PGM(empty stack) is untouched.
- HeidenhainVolatileQLookup
Reads Heidenhain volatile Q parameters (
Q200+, the cycle/user range that resets at program start) fromVars.Volatile. Self-gates the id range so the evaluator's RuntimeVariableLookups chain can fall through for other keys (Q0-Q99/QRnresolve on the HeidenhainQParameterTable further down the chain;Q100-Q199stay vacant by design). Sibling of the Fanuc VolatileVariableLookup with the same single-step traceback contract: HeidenhainQParameterReadingSyntax dict-merges every block'sVars.Volatileinto 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.