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 usesEQ NE ..words); bare identifiers are named-variable references (Fanuc rejects an identifier without[);R63is canonicalised to uppercase so table lookups andParsing.Assignmentskeys agree. Indexed accesses ($P_UIFR[1,X,TR],_RENC[35]) require literal index elements (integers, bare letters, or keywords likeTR) 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:ATAN2→ATAN(both are two-arg atan2 in degrees),TRUNC→FIX(truncate toward zero). Unknown functions (e.g. the rotary positioning formDC(...)) parse fine and fail later in the evaluator, which callers treat as fail-soft.
- SiemensGotoSyntax
Resolves Siemens
GOTOF/GOTOBcontrol flow — the FanucGotoSyntax pattern with named-label targets and explicit direction. Triggered byParsing.SiemensGoto(written by SiemensGotoParsingSyntax); 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 — a Siemens label may prefix code).Direction comes from the mnemonic and drives the anchored LabelScanUtil overload:
GOTOFtakes the first match strictly below the host line,GOTOBthe 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 anN<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 warnsSiemensGoto--ConditionNotEvaluatedand 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 theFormula.SiemensGoto.Labelmirror.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 ...] ENDIFblock 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 matchingELSE(else-branch executes) or after the matchingENDIFwhen no else exists; unresolved warnsSiemensIf--ConditionNotEvaluatedand 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 matchingENDIF.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-
IFincrements the depth, eachENDIFat 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-lineIF cond GOTOF lblis 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 labelsection repetition. Both arePrependSourceinlines whose “return to the caller / to the line after REPEAT” is the natural pipeline tail; the P5 control-flow redirects (ReplaceSourcewith a re-segmented host-file slice) would silently discard that pending tail — and, for a section repeat, re-execute theREPEATstatement 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) andLOOP ... 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).ENDWHILEback-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 carryingVar/End/Value; each re-arrival incrementsValueuntil it exceedsEnd, then pops and jumps pastENDFOR. - REPEAT/UNTIL — post-test:
REPEATpushes and always falls through;UNTILexits on truthy (pop), warns and exits on unresolved, back-jumps to theREPEATline on falsy. - LOOP/ENDLOOP — endless:
ENDLOOPback-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/GOTOBthat 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 replayingBlockSkipSyntax— a/-prefixed terminator is invisible to the exit scans.- WHILE — pre-test: truthy pushes a frame (first
arrival) and falls through; falsy/unresolved pops its own frame
and forward-jumps past the matching
- SiemensNamedVariableLookup
Reads Sinumerik named program variables (
_X_HOME) fromVars.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'sVars.Namedinto 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 viaDEF REALor assigned directly). Reads literal numeric assignments fromParsing.Assignments.<ident>, dict-merges them with the previous block's state, and writes the resulting per-block dictionary intoVars.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 fromParsing.Assignments.Rnand 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 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.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 intolayers[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'sEND mwhich 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.SiemensCallsub-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 intolayers[0]— the SubProgramCallSyntax (M98) mechanism verbatim, including theP-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, RenishawL9810,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 structuredSiemensCall--Skippedwarning — motion state untouched, replacing the rawUnparsedText--Remainingnoise.
Pipeline placement: head of the Siemens
Evaluationbundle (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 emitsSiemensCall--ArgsNotBoundand inlines without bindings.- Resolved — the callee file (looked up through
InternalFolder with the
FilePatterns chain, default
- SiemensSubProgramReturnSyntax
Consumes Siemens subprogram-end words —
M17(subprogram end) andRET(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 inlayers[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.M17in the main frame (no caller) is the corpus norm — UJoin posts end main.MPFfiles 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 noP-jump form, so there is no redirect path here.Triggers:
M17arrives inParsing.Flagsvia NumberedFlagSyntax;RETvia the FlagSyntax word table.Known limitation — early return. Because the return is the natural pipeline tail, an
M17/RETin the middle of a body (e.g. inside anIFbranch) 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_UIFRcomponents). 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
dependencieson 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 fromParsing.Assignmentsinto the SiemensFrameTable on the runner's effective dependency list — the write is thereby fully simulated: a subsequentG54/G505+selection reads the updated offset through the existingCoordinateOffsetpath (X/Y/Z), and$P_UIFRreads see it via SiemensUifrVariableLookup.Only literal numeric RHS values are consumed; VariableEvaluatorSyntax normalizes expression RHS to literals earlier on the same block. Only the
TRcomponent 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).