Table of Contents

Namespace Hi.NcParsers.EvaluationSyntaxs.Siemens

Classes

SiemensExpressionParser

Recursive-descent parser for Sinumerik 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     := or-expr
or-expr  := and-expr (('OR' | 'XOR') and-expr)*
and-expr := cmp-expr ('AND' cmp-expr)*
cmp-expr := add-expr (('==' | '<>' | '>=' | '<=' | '>' | '<') add-expr)*
add-expr := term (('+' | '-') term)*
term     := factor (('*' | '/' | 'MOD') factor)*
factor   := ('+' | '-')? primary
primary  := number
         | '(' expr ')'
         | 'R' digits                      → variable "R63" (canonical uppercase)
         | '$' name ('[' indexlist ']')?   → variable "$P_UIFR[1,X,TR]" (canonical)
         | name '(' arglist ')'            → function call
         | name '[' indexlist ']'          → variable "_RENC[35]" (canonical)
         | name                            → variable "_X_HOME" (case preserved)
indexlist := (integer | name) (',' (integer | name))*
arglist   := expr (',' expr)*

Dialect differences from the Fanuc parser: parentheses group and call (Fanuc uses brackets); comparisons are spelled == <> >= <= > < (Fanuc uses EQ NE .. words); bare identifiers are named-variable references (Fanuc rejects an identifier without [); R63 is canonicalised to uppercase so table lookups and Parsing.Assignments keys agree. Indexed accesses ($P_UIFR[1,X,TR], _RENC[35]) require literal index elements (integers, bare letters, or keywords like TR) and are emitted as a single NcVariableExpr whose key is the canonical uppercase-indexed token — the whole token routes through Get(string), keeping the AST brand-agnostic. Function-name aliases are normalised where Sinumerik and the shared evaluator spell the same math differently: ATAN2ATAN (both are two-arg atan2 in degrees), TRUNCFIX (truncate toward zero). Unknown functions (e.g. the rotary positioning form DC(...)) parse fine and fail later in the evaluator, which callers treat as fail-soft.

SiemensGotoSyntax

Resolves Siemens GOTOF/GOTOB control flow — the FanucGotoSyntax pattern with named-label targets and explicit direction. Triggered by Parsing.SiemensGoto (written by SiemensGotoParsingSyntax); 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 — a Siemens label may prefix code).

Direction comes from the mnemonic and drives the anchored LabelScanUtil overload: GOTOF takes the first match strictly below the host line, GOTOB the nearest match strictly above it — never the whole-file first match, which would silently mis-target duplicate label texts. Targets may be a named label (LBL1: line, matched Ordinal-exact on the block-root SiemensLabel record) or an N<num> block number (matched on Number).

The conditional forms (IF <cond> GOTOF <label>) lean on VariableEvaluatorSyntax's pass-2 tree walk; ReadCondition(JsonNode) (a dialect-neutral numeric-JSON reader despite its home) reads the resolved node. Truthy fires; zero falls through silently; unresolved warns SiemensGoto--ConditionNotEvaluated and falls through. Because the label field is an ordinary Parsing string, a label name that collides with a set named variable may have been substituted to a numeric by the evaluator — the original text is then recovered from the Formula.SiemensGoto.Label mirror.

Pipeline placement: Evaluation bundle, in the control-flow group after VariableEvaluatorSyntax. The SiemensGotoIterationDependency watchdog caps fired jumps per (file, label); a missing watchdog disables the cap (Fanuc parity).

SiemensIfSyntax

Resolves the Siemens IF ... [ELSE ...] ENDIF block conditional. Three phrases dispatched by Term, none of which needs a frame stack:

  • IF — reads the resolved condition via FanucConditionReader (dialect-neutral numeric-JSON reader). True falls through into the then-branch; false forward-jumps to just after the matching ELSE (else-branch executes) or after the matching ENDIF when no else exists; unresolved warns SiemensIf--ConditionNotEvaluated and falls through (no redirect on unresolved input — the then-branch executes, and the ELSE rule below then skips the else-branch, which keeps the two branches mutually exclusive even on the fail-soft path).
  • ELSE — reached in the normal stream only when the then-branch just executed (a false IF jumps past the ELSE line directly), so it unconditionally forward-jumps past the matching ENDIF.
  • ENDIF — consumed no-op (stamped for cache dumps).

Both forward scans run on the anchored LabelScanUtil overload from the host line, with a nesting-depth predicate: each nested block-IF increments the depth, each ENDIF at depth > 0 decrements it, and the match fires only at depth 0. The probe stack replays the Parsing statement owners the depth counter depends on — including SiemensGotoParsingSyntax ahead of SiemensIfParsingSyntax, so a nested single-line IF cond GOTOF lbl is claimed by the GOTO owner and never miscounted as a block-IF.

Pipeline placement: Evaluation bundle control-flow group, after VariableEvaluatorSyntax (condition substituted) and before the variable readers.

SiemensInlineContextUtil

Detects whether a block executes inside a P4 inlined body — an L/name-call subprogram splice or a REPEAT label section repetition. Both are PrependSource inlines whose “return to the caller / to the line after REPEAT” is the natural pipeline tail; the P5 control-flow redirects (ReplaceSource with a re-segmented host-file slice) would silently discard that pending tail — and, for a section repeat, re-execute the REPEAT statement itself, which has no watchdog (its count is eagerly known) and therefore no bound. The P5 jump syntaxes consult this guard and degrade to a structured warning + fall-through instead: control flow inside an inlined body is recognized but not simulated (corpus count zero; true support needs return-frame machinery, a future work item).

  • Subprogram context: the call syntax stamps a pushed CallStack on every inlined piece — any frame on the stack marks callee context.
  • Section-repeat context: SiemensRepeatSyntax stamps every inlined piece with a SiemensRepeat clone carrying a 1-based Iteration; the REPEAT host block itself never carries that field.
SiemensLoopSyntax

Resolves the four Siemens loop constructs against one shared frame stack — WHILE ... ENDWHILE, FOR ... ENDFOR, REPEAT ... UNTIL (the label-less post-test loop) and LOOP ... ENDLOOP — the FanucWhileDoSyntax template (frames-in-JSON + ModalCarry tracked key + forward scan to the terminator + back-jump to the recorded entry line), adapted to constructs that carry no LoopId: frames stack in nesting order on the block-root SiemensLoopFrames section, and every terminator validates that the innermost frame carries its own construct Kind and file. A single shared stack is what makes mixed-construct nesting (a FOR inside a WHILE inside a LOOP) pair correctly.

  • WHILE — pre-test: truthy pushes a frame (first arrival) and falls through; falsy/unresolved pops its own frame and forward-jumps past the matching ENDWHILE (depth-counted). ENDWHILE back-jumps unconditionally; the WHILE line re-evaluates.
  • FOR — counting: first arrival resolves the bounds once (Sinumerik semantics), assigns the loop variable by lifting into Parsing.Assignments (the reader syntaxes downstream persist it exactly as a written assignment), and pushes a frame carrying Var/End/Value; each re-arrival increments Value until it exceeds End, then pops and jumps past ENDFOR.
  • REPEAT/UNTIL — post-test: REPEAT pushes and always falls through; UNTIL exits on truthy (pop), warns and exits on unresolved, back-jumps to the REPEAT line on falsy.
  • LOOP/ENDLOOP — endless: ENDLOOP back-jumps while the SiemensLoopIterationDependency watchdog allows; the watchdog is a hard requirement here (no exit condition exists), so a missing dependency suppresses the jump with a configuration error instead of hanging the pipeline.

Back-jump counting happens at the back-jump step only (a loop whose condition is false from the outset consumes zero iterations), keyed (FileName, BeginLineNo) on the watchdog. All scans use the anchored LabelScanUtil overload from the host line — never the whole-file first match, which would pair a terminator with an earlier sibling construct of the same kind.

Pipeline placement: Evaluation bundle control-flow group, after VariableEvaluatorSyntax (conditions and bounds substituted) and before the variable readers (the FOR lift must reach them on the same block).

Known limitation — stack pairing vs lexical pairing. Real Sinumerik pairs loop constructs lexically at block preparation; this syntax pairs them dynamically on the carried frame stack. A GOTOF/GOTOB that leaves a loop body strands that loop's frame (only the FOR head detects and drops a stale frame, via AdvancePending), so a jump from an inner loop into an enclosing loop's body can mispair the enclosing terminator with the stale inner frame. Sinumerik itself forbids jumping into control structures; all such shapes stay bounded here by the watchdog and surface loud diagnostics. Scan probes also mirror the Fanuc convention of not replaying BlockSkipSyntax — a /-prefixed terminator is invisible to the exit scans.

SiemensNamedVariableLookup

Reads Sinumerik named program variables (_X_HOME) from Vars.Named. Self-gates on the named-identifier key shape so the evaluator's RuntimeVariableLookups chain can fall through for other keys. Sibling of the Fanuc VolatileVariableLookup with the same single-step traceback: SiemensNamedVariableReadingSyntax dict-merges every block's Vars.Named into the next block, so the entry — if it exists — is on the current block or the immediately previous one.

Stateless and dependency-free — instances are interchangeable.

SiemensNamedVariableReadingSyntax

Obtains values for Sinumerik named program variables (GUD/LUD identifiers such as _X_HOME, declared via DEF REAL or assigned directly). Reads literal numeric assignments from Parsing.Assignments.<ident>, dict-merges them with the previous block's state, and writes the resulting per-block dictionary into Vars.Named — the same carry-forward pattern as the Fanuc VolatileVariableReadingSyntax.

Lifetime is bounded by MachiningSession: within one session the dictionary carries forward block-by-block; session restart abandons the SyntaxPiece JSON dataflow and starts fresh. This matches LUD scoping well (program-local) and under-persists real GUD (globally retentive) — acceptable until a GUD definition-file feature exists.

Only literal numeric RHS values are consumed (_X_HOME = 155.5 ✓; _A = R1+5 ✗ — the evaluator resolves those to literals earlier on the same block). String-valued RHS (quoted) never reaches Assignments — the quote-guard syntaxes quarantine it in the Parsing bundle.

SiemensRParameterReadingSyntax

Obtains values for Sinumerik R parameters (R0-R999) by consuming literal numeric assignments from Parsing.Assignments.Rn and writing them straight to a registered SiemensRParameterTable. Sibling of the Fanuc RetainedCommonVariableReadingSyntax.

No SyntaxPiece JSON mirror is created — the table is the single source of truth for R values, and VariableEvaluatorSyntax reads from the table directly (the table implements IVariableLookup). The hincproj round-trip preserves writes across project sessions.

Only literal numeric RHS values are consumed by this syntax (R63 = 100.5 ✓; R26 = R64-14/2 ✗). 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.

If no SiemensRParameterTable is registered on the runner's effective NcDependencyList, this syntax is a no-op.

SiemensRepeatSyntax

Executes the Siemens REPEAT StartLabel EndLabel [P=n] section repeat: the host file is re-segmented from the top, the [StartLabel: … EndLabel:) slice is cut out (start-label line included — it may carry trailing code; end-label line excluded), and the slice is prepended into layers[0] once per repetition via PrependSource(IEnumerable<T>). “Return to the line after REPEAT” is the natural pipeline tail — the M98-inline pattern — so no return frame or rewind is needed (Siemens end labels are passive, unlike Fanuc's END m which actively fires a syntax).

Label scanning reuses LabelScanUtil's predicate overload with a Siemens probe stack (TailCommentSyntax + HeadIndexSyntax + SiemensLabelSyntax) and matches on the block-root SiemensLabel record. Each repetition is its own segmentation pass with a fresh fileIndex — downstream syntaxes mutate block JSON in place, so repetitions must not share piece instances. Every inlined block is stamped with a SiemensRepeat clone carrying its 1-based Iteration.

Fail-soft paths (consume + structured warning + no motion): missing end label in the statement (single-label form — corpus count zero, not simulated), start/end label not found in the file, a slice that contains the REPEAT line itself (would re-fire forever — real Sinumerik programs place REPEAT after the end label), and missing runtime dependencies. The repetition count is known eagerly, so no iteration watchdog is needed.

SiemensSubProgramCallSyntax

Consumes the Parsing.SiemensCall sub-object captured by SiemensCallStatementSyntax and either inlines the called subprogram or safe-skips the call:

  • Resolved — the callee file (looked up through InternalFolder with the FilePatterns chain, default {name}.SPF{name}.MPF{name}) is segmented and prepended into layers[0] — the SubProgramCallSyntax (M98) mechanism verbatim, including the P-times repetition loop, per-repetition file indices, and a pushed CallStack frame that SiemensSubProgramReturnSyntax pops on M17/RET. Like M98 (and unlike G65), no MacroFrame is stamped — callee blocks share the caller's variable scope; DEF-local isolation is a later work item.
  • Unresolved — the corpus norm: OEM / measuring cycles (HQ_FC, Renishaw L9810, L_ZYM91) whose definition files ship with the machine, not the NC program. The call is consumed whole with a block-root SiemensCall record (Skipped: true) and a single structured SiemensCall--Skipped warning — motion state untouched, replacing the raw UnparsedText--Remaining noise.

Pipeline placement: head of the Siemens Evaluation bundle (the Fanuc discipline — call/inline ahead of all variable and motion machinery). Argument binding to PROC parameters is not implemented: a resolved call carrying arguments emits SiemensCall--ArgsNotBound and inlines without bindings.

SiemensSubProgramReturnSyntax

Consumes Siemens subprogram-end words — M17 (subprogram end) and RET (return without function output) — and pops one CallStack frame. Like the plain-M99 half of SubProgramReturnSyntax, the actual “return” is the natural pipeline tail: the inlined body's last block is followed in layers[0] by the caller's next block, so this syntax only consumes the trigger (keeping UnconsumedCheckSyntax quiet), stamps a SubProgramReturn section, and writes the popped stack.

M17 in the main frame (no caller) is the corpus norm — UJoin posts end main .MPF files with M17 — and is consumed silently with no pop and no warning, mirroring how a plain M99 tolerates the main frame. Sinumerik's M17 has no P-jump form, so there is no redirect path here.

Triggers: M17 arrives in Parsing.Flags via NumberedFlagSyntax; RET via the FlagSyntax word table.

Known limitation — early return. Because the return is the natural pipeline tail, an M17/RET in the middle of a body (e.g. inside an IF branch) pops the frame but does not truncate the remaining inlined blocks — they still execute. Honoring a mid-body return needs body-boundary bookkeeping (a future work item alongside PROC parameter binding); the corpus places every return at the body end.

SiemensSystemVariableSyntax

Consumes writes to Sinumerik $ system variables the pipeline does not simulate ($TC_DP… handled elsewhere aside, e.g. $AC_TIME, $SC_…, non-TR $P_UIFR components). Siemens counterpart of the Fanuc record-only FanucSystemControlVariableSyntax pattern:

SiemensUifrVariableLookup

Reads Sinumerik settable-frame translations (R100=$P_UIFR[1,X,TR]) from the SiemensFrameTable on the runner's effective dependency list. Self-gates on the $P_UIFR[n,axis,TR] key shape so the evaluator's RuntimeVariableLookups chain can fall through for other keys (non-TR components fall to the record-only SiemensSystemVariableSyntax path as unevaluable reads).

State-from-deps flavour: the table is resolved from dependencies on every call — never held as a field — so the lookup survives XML round-trip without a rebind path (the wrapper anti-pattern ban on IRuntimeVariableLookup).

SiemensUifrWritingSyntax

Consumes Sinumerik settable-frame translation writes ($P_UIFR[5,Z,TR]=R102-281.48, probing programs' workpiece re-referencing idiom) by routing literal numeric assignments from Parsing.Assignments into the SiemensFrameTable on the runner's effective dependency list — the write is thereby fully simulated: a subsequent G54/G505+ selection reads the updated offset through the existing CoordinateOffset path (X/Y/Z), and $P_UIFR reads see it via SiemensUifrVariableLookup.

Only literal numeric RHS values are consumed; VariableEvaluatorSyntax normalizes expression RHS to literals earlier on the same block. Only the TR component is bridged — other components (RT, FI) stay in Assignments and fall to the record-only SiemensSystemVariableSyntax, which must run after this syntax. Writes to $P_UIFR[0,..] (G500) are consumed but ignored, matching SetAxisOffset(string, string, double).

If no SiemensFrameTable is registered, this syntax is a no-op (entries stay visible as unconsumed residue).