Namespace Hi.NcParsers.ParsingSyntaxs.Siemens
Classes
- SiemensCallStatementSyntax
Captures a whole-line Siemens subprogram-call statement into
Parsing.SiemensCallfor SiemensSubProgramCallSyntax to resolve (inline the file) or safe-skip (structured warning, zero motion). Two shapes are owned:- Parenthesized call, any identifier —
HQ_FC(1,50,75,160),L_39(0.000,0.000),HH_ROBOT(2)→{ "Name": "HQ_FC", "Args": ["1","50",…] }. The argument grammar (complete quoted runs admitted, quote-aware comma split) is SiemensCycleCallSyntax's, reused verbatim. - Bare-word call —
L9810,L_ZYM91,HH_ROBOTOFF, with an optional repeat countL123 P3→{ "Name": "L123", "P": 3 }. Restricted toL-leading or underscore-bearing identifiers on purpose: this syntax runs ahead of FlagSyntax and a generic bare-identifier capture would steal TRAORI-class words before their consumers ever saw them — the same underscore heuristic NamedIdentPattern blessed for assignments (corpus named entities all carry an underscore; controller vocabulary never does). On real Sinumerik any bare identifier can be a by-name call; widening further is a corpus-driven follow-up.
Placement: last of the dedicated statement owners and still before SiemensQuotedStatementSyntax — quoted arguments must reach this capture (paired quotes ride inside the args body; a line whose quotes do not pair falls through whole to the quarantine). ExcludedNames keeps this catch-all from re-owning statements that belong elsewhere (default:
WRITEstays with the quoted-statement quarantine; control-flow words are reserved for their future syntaxes). Whole-line anchored: a call mixed with other tokens (L9810 M8) does not match and stays visible in the unparsed warning.- Parenthesized call, any identifier —
- SiemensCycleCallSyntax
Captures whitelisted Siemens cycle calls of the shape
NAME(arg1,arg2,…)(or the bareNAMEform) from the unparsed line text into aParsing.NAMEsub-object holding the verbatim positional argument strings:CYCLE832(0.05,_ORI_FINISH,0.8)→Parsing.CYCLE832 = { “Args”: [“0.05”, “_ORI_FINISH”, “0.8”] }. Empty slots (,,) stay as empty strings;NAME()yields an emptyArgsarray; a bareNAMEyields{ “Bare”: true }(a marker — empty sub-objects would be swept by intermediateCleanupParsingcalls before the consumer runs). A brand Logic syntax consumes the sub-object (e.g. SiemensPathSmoothingSyntax for CYCLE832, SiemensCycle800TiltSyntax for CYCLE800).Strictly whitelist-based (CycleNames, default
CYCLE832+CYCLE800) — a genericWORD(args)capture would eat OEM/unknown calls (HQ_FC(...),L_39(...)) whose safe-skip handling is a separate work item, and every unlisted call deliberately stays whole in UnparsedText for the visibleUnparsedText--Remainingwarning.Must be placed early in the Parsing bundle — before the assignment and tag-value syntaxes: argument tokens standing after
(or,(_ORI_FINISH, or axis-letter shapes likeV1) would otherwise be shredded by NamedVarAssignmentSyntax / tag-value captures. Quoted arguments are supported for CYCLE800's swivel-data-record name (CYCLE800(1,"TC1",…)): the args body admits complete"…"runs (which may hide)or,) and the comma split is quote-aware; quotes are kept verbatim in the captured arg string so the consumer distinguishes"0"(name) from0(number). A line whose quotes do not pair falls through whole to SiemensQuotedStatementSyntax's quarantine.
- SiemensDefStatementSyntax
Consumes Sinumerik variable declarations (
DEF REAL _X_HOME=155.5, _SAFE_Z/DEF INT N_ST=3) and lowers each declared name intoParsing.Assignments: initialised names carry their initialiser expression text (the Evaluation bundle resolves and routes it intoVars.Namedlike any named assignment), bare names carry“0”— Sinumerik default-initialises numeric types to zero, so a laterG0 Z=_SAFE_Zreads 0 instead of failing vacant.Must run before NamedVarAssignmentSyntax in the Parsing bundle: on a DEF line that syntax would capture
_X_HOME=155.5but strandDEF REAL(and the comma list) as unparsed residue. Initialiser expressions are delimited by the SiemensExpressionParser longest-valid-prefix rule, so parenthesised arithmetic and whitespace survive; a non-numeric type or unparsable initialiser leaves the whole statement untouched (visible as residue) rather than half-consuming it.BOOL/CHARinitialise to 0 like INT;STRINGdeclarations are not lowered (string values have no numeric store) — the statement is consumed whole so it cannot shed ghost tokens. The declaration itself leaves no Parsing record: the lowered Assignments entries carry the entire semantic payload, and an inert record would only re-surface as an Unconsumed residue.
- SiemensFrameStatementSyntax
Captures a whole Siemens frame statement (
TRANS/ATRANS/ROT/AROT/ ROTS/AROTS/CROTS/SCALE/ASCALE/MIRROR/AMIRROR, bare or with arguments) into aParsing.<keyword>sub-object. Sinumerik requires frame instructions in a block of their own, so the whole remaining line belongs to the statement.The consumed keywords (
TRANS/ATRANS/ROT/AROT/ROTS/AROTS— see SiemensProgrammableFrameSyntax) are captured structured: each argument token becomes a sub-key — glued axis words (X0,Y90.) and equals forms (RPL=45,Z=R5) both supported. Literal values are stored as JSON numbers; non-literal right-hand sides stay strings so VariableEvaluatorSyntax resolves them in the Evaluation stage (AROT Z=R5→Parsing.AROT = { "Z": "R5" }). A token outside the modeled grammar makes the whole statement fall back to the verbatim StatementKey capture — never a half-consumed line.The remaining keywords (
CROTS/SCALE/ASCALE/MIRROR/AMIRROR— zero corpus occurrences, no simulation) keep the verbatim{ "Statement": "<args>" }quarantine; the Logic consumer reports them as recognized-but-not-simulated. Either way the axis words are kept out of reach of the quote/context-blind tag-value syntaxes below, which would otherwise mint them intoParsing.X/Y/Zand let ProgramXyzSyntax turn a frame statement into a very real ghost rapid. Deferred-but-unmodeled must mean "no transform yet", never "extra motion".Must be placed early in the Parsing bundle — after BlockSkipSyntax, before every assignment / tag-value syntax (the
ROT RPL=45shape would otherwise feed NamedVarAssignmentSyntax).
- SiemensGotoParsingSyntax
Captures Siemens
GOTOF/GOTOBjump statements whole intoParsing.SiemensGoto. Four forms are recognised, mirroring the Fanuc phrase-level split (IF ... GOTOis one phrase, not an IF composed with a GOTO):GOTOF <label>/GOTOB <label>— unconditional forward / backward jump.IF <cond> GOTOF <label>/IF <cond> GOTOB <label>— the Sinumerik single-line conditional jump (no brackets around the condition).
Placement: inside the whole-statement owner group, after SiemensRepeatParsingSyntax and before SiemensIfParsingSyntax — the IF-jump forms carry a strictly longer prefix than the block
IF <cond>phrase, so this owner must win first (the block-IF owner also refuses GOTOF/GOTOB tails defensively). Must run before the assignment syntaxes:IF R1==1 GOTOF LBL1would otherwise be shredded by the assignment capture (its LHS grammar matchesR1and the first=of==).The label is captured as written (
LBL1,MARKE_A, or anN<num>block number — both are legal Sinumerik jump targets). The condition string is left for VariableEvaluatorSyntax's pass-2 tree walk; SiemensGotoSyntax consumes the section.
- SiemensIfParsingSyntax
Captures the Siemens block-conditional phrases whole into
Parsing.SiemensIf:IF <cond>(no brackets on Sinumerik),ELSE,ENDIF. The single-line jump form (IF <cond> GOTOF <label>) belongs to SiemensGotoParsingSyntax, which runs immediately before this owner; a GOTOF/GOTOB tail is additionally refused here so an ordering drift degrades to a visible unparsed warning instead of a silently mis-captured block-IF (the Fanuc(?!GOTO\b)precedent).Placement: whole-statement owner group, before the assignment syntaxes — a condition like
R1==1would otherwise be shredded by the assignment capture (LHS grammar matchesR1plus the first=). The condition string is left for VariableEvaluatorSyntax's pass-2 walk; SiemensIfSyntax consumes the section and decides the branch.
- SiemensLabelSyntax
Consumes a Siemens label at block start (
LBL1:— identifier followed by a colon, optionally after theNhead index) into a block-root SiemensLabel record. Labels are passive jump targets: in the normal flow the record is pure bookkeeping (no motion, no state), while SiemensRepeatSyntax uses this same syntax as its LabelScanUtil probe and matches candidates onSiemensLabel.Name. Writing the record at the block root (not underParsing) keeps the passive token out of UnconsumedCheckSyntax's audit without needing a Logic-layer consumer — mirroring how SiemensDefStatementSyntax leaves noParsing.Defentry behind.Anchored at the start of the remaining unparsed text, so a label buried mid-line is not captured (Sinumerik allows one label per block, at block start). Text after the colon stays in UnparsedText for the rest of the pipeline —
LBL1: G1 X0executes its trailing motion normally. The probe usage requires this syntax to stay self-sufficient: it reads only UnparsedText and never touches the dependency list.
- SiemensLoopParsingSyntax
Captures the Siemens loop-construct phrases whole, one Parsing section per construct family:
WHILE <cond>/ENDWHILE→Parsing.SiemensWhile.FOR <var> = <start> TO <end>/ENDFOR→Parsing.SiemensFor. The start expression is stored as a single-entryInitmap keyed by the variable name — the evaluator's pass-2 tree walk rewrites values only, never keys, so the loop variable's name survives even when it collides with a set named variable. Literal start/end bounds are written as numeric JSON values directly (writer decides the node type); expressions stay strings for the evaluator.- bare
REPEAT/UNTIL <cond>→Parsing.SiemensRepeatUntil. Only the label-less form is taken —REPEAT LBL1 ...is the P4 section repeat owned by SiemensRepeatParsingSyntax, which runs earlier in the statement group. LOOP/ENDLOOP→Parsing.SiemensLoop.
Placement: whole-statement owner group, after SiemensGotoParsingSyntax / SiemensIfParsingSyntax and before the assignment syntaxes —
FOR R1=0 TO 10would otherwise be shredded by the assignment capture (R1=0is a legal assignment shape and theTO 10tail strands). SiemensLoopSyntax consumes all four sections against its shared loop-frame stack.
- SiemensMcallSyntax
Captures a Siemens
MCALLmodal-call statement whole into aParsing.MCALLsub-object:MCALL CYCLE81(500.,327.9137,0.5,324.564,,,0,1,0)→{ “CycleName”: “CYCLE81”, “Args”: ["500.", …] }; a bareMCALL(modal cancel) →{ “Bare”: true }; a parenthesis-less callee (MCALL L123) keepsCycleNamewithoutArgs. The Logic consumer (SiemensModalCycleSyntax) maps the supportedCYCLE8xfamily onto the shared canned-cycle modal machinery and warns on everything else.Whole-statement owner: MCALL lines are standalone in the corpus, and owning the full statement keeps the cycle's positional arguments away from the assignment / tag-value syntaxes (same shredding hazard as SiemensCycleCallSyntax, whose argument grammar — including complete quoted runs — is reused verbatim). Must run before SiemensCycleCallSyntax: a whitelisted cycle name after
MCALLwould otherwise be captured bare-of-context and strand theMCALLLword. A line mixing MCALL with other tokens does not match and falls through whole to the visible unparsed warning.
- SiemensMsgSyntax
Consumes a Siemens
MSG(“text”)operator display call (or the bareMSG()clear form) from the unparsed line text into a block-root Msg section, and surfaces the text as aMsg–Displaysystem message.Must be placed before every assignment / tag-value / flag syntax in the Parsing bundle (right after TailCommentSyntax / BlockSkipSyntax): the quoted argument routinely contains NC-shaped tokens (
MSG("TOOL=5"),MSG("G54 OK"),MSG("T20 R8.000 CR:2.500")) which the quote-blind consumers downstream would otherwise shred into ghost assignments, flags, or axis words. Consuming the whole call here removes that hazard structurally — the planned fix recorded on NcSyntaxUtil's assignment RHS-boundary notes.Only the two literal forms are consumed:
MSG("...")andMSG(). Non-literal arguments (string concatenationMSG("A"<<R1), variable interpolation) are deliberately left whole in UnparsedText for the visibleUnparsedText--Remainingwarning — half-eating them would silently lose content.
- SiemensOrientationVectorSyntax
Captures the Siemens TRAORI orientation-programming words out of the block text into the block-root SiemensOrientation record and says, once per program, that they are recognized but not simulated (
Orientation-Vector–NotSimulated): the tool-axis vectorA3=/B3=/C3=, the Euler / RPY anglesA2=/B2=/C2=(ORIEULER / ORIRPY), the surface normalsA4=/A5=, the cone and intermediate-orientation vectorsA6=…A8=— the whole[ABC][2-8]=address family — and theORI*interpolation / reference-system words (OrientationWords, with a balanced parenthesized argument list such asORIRESET(0,0,0)). The rotary axes keep their previously programmed positions.Why a dedicated capture: NamedIdentPattern's one-letter+digits alternative (there for
H1=21) matches these addresses too, and NamedVarAssignmentSyntax runs ahead of the axis-word syntaxes — captured there,A3=0.5becameVars.Named.A3without any diagnostic, the posture silently frozen (the tool-vector plan's "partially eaten" hazard, found on the Siemens preset 2026-09-29). This syntax therefore sits in the Parsing bundle before NamedVarAssignmentSyntax, next to theCR=/TURN=captures that exist for the same reason, and after SiemensQuotedStatementSyntax so quoted text is never shredded.H1=21is outside the family and still lands inVars.Named.Right-hand sides follow the assignment captures' rule: the lexical boundary (the next
word=or end of text) bounds the RHS, then the Siemens expression grammar shrinks it to the longest valid prefix and puts the tail back (C3=1 F1000keepsF1000for the feed capture; gluedA3=0B3=1splits). A literal value is recorded as a number, anything else verbatim as a string — the record sits outsideParsing, so the variable evaluator does not rewrite it and the unconsumed-Parsing check does not report it. An address written twice on a block keeps its first value; the repeat goes toParsing.RepeatedWords(RepeatedWordsKey, the pipeline's first-wins rule) and surfaces asParsing--Unconsumed.The family is owned whole. A word of it that cannot be recorded is not left for NamedVarAssignmentSyntax — there it would draw the words after it into a failed right-hand side, or land in
Vars.Named— but set aside underParsing.SiemensOrientation, which nothing consumes: the unconsumed check names it (Parsing--Unconsumed,SiemensOrientation.A3) and its neighbours stay where they are. That covers a right-hand side no prefix of which parses (A3=1.2.3,A3=(R1+1; set aside up to the next blank) and an address without a value, recognized by what follows the=: nothing, another assignment (A3= B3=1), an NC address word (A3= X10,A3= X-10.5— one letter is never a Sinumerik identifier, whose first two characters are letters or underscores;R1is the R parameter and is a value), or a bare word standing apart from the=(A3= TRAFOOF). The last rule errs on the visible side: a named variable written after a blank (A3= MYVAR) is refused as well and shows up as residue, whileA3=MYVAR,A3= _MYVAR,A3= MYVAR*2andA3= SIN(30)are values. Not of the family, and left in the text: an address orORI*word that is the tail of an identifier or follows$(_P1A3=5is the variable_P1A3; a glued address chainG1X10A3=1is not an identifier), and anORI*word whose parenthesis never closes.Once per program: the notice is gated by a flag on this instance, set when the notice fires and re-armed at a program boundary — the first block of a stream (no block before it: a fresh session pipeline, also on a runner reused across sessions), a TapeBoundary block (a
%line starts a new program) or a change of NC file (a session may play several files in a row). The latter two are judged on the program's own blocks only. Inlined pieces never count: the pieces of a called subprogram (stamped SiemensCall and a pushed CallStack by SiemensSubProgramCallSyntax at splice time, the Sinumerik archive header%_N_L6_SPFa callee file may start with included) and the pieces of aREPEATsection (IsInlinedContext(JsonObject); re-segmented inside a callee they carry the callee's file and a clone of the host's CallStack, propagated by SiemensRepeatSyntax, but no SiemensCall record). A program that calls such a subprogram a hundred times still gets one notice. A program is told from the next by those three marks only: the same file played again within one session and without a tape boundary — or two inline plays under the same placeholder path — is one program to this rule, and its notice is the one already among the session's diagnostics. The syntax re-arms itself rather than through ISessionResettable: the runner's session-reset sweep covers the top-level syntax list, and this syntax sits inside the Parsing BundleSyntax. A flag instead of a walk back through the block list: frozen pieces re-parse their JSON on every read, so a walk across a long three-axis section would have cost one parse per block at the first vector block after it.The plan's P4 reads Words to write the brand-agnostic
ToolOrientation/SurfaceNormalsections; that step extends this class rather than adding a second one. A project saved before this syntax existed carries its own serialized syntax list without it (no legacy back-fill, by the policy stated onSoftNcRunner.ApplyLegacyVersionPatches): it keeps the old behaviour until its controller is set again from the Siemens preset.
- SiemensProcSyntax
Consumes a Siemens
PROCsubprogram declaration header (PROC MY_SUB,PROC CONTOUR(REAL X, INT N) SAVE DISPLOF) whole into a block-root SiemensProc record. The header carries no executable state for the simulation — parameter binding to caller arguments is a later work item — but leaving it unparsed would shred the parameter list into ghost axis words the moment the tag-value syntaxes run, so the whole statement is owned here. The verbatim remainder after the name is preserved on Statement for cache-dump readers. Writes at the block root (noParsingentry) following the SiemensDefStatementSyntax precedent — a record with no Logic consumer must not land in the UnconsumedCheckSyntax audit tree.Anchored at statement start: Sinumerik requires
PROCto be the first statement of a subprogram file, so a mid-linePROCis left alone (visible unparsed warning).
- SiemensQuotedStatementSyntax
Quote guard: any line text still carrying a double quote after the literal-form consumers (SiemensMsgSyntax, SiemensToolCallSyntax, SiemensCycleCallSyntax) have run is quarantined whole into
Parsing.QuotedStatement = { “Statement”: “<verbatim>” }—WRITE(ERROR,“CUTTIME”,“T5 X100”), string-concatenationMSG("..."<<R1), quoted-argument cycles not yet whitelisted.Every quote-blind syntax below (flags, numbered flags, tag values, assignments) opens a token boundary at the quote→letter transition, so without this guard the string contents get minted into REAL parser state:
"G54"inside a WRITE argument silently switches the active work offset,"T5 X100"mints a ghost tool call plus a ghost motion. Quarantining keeps the whole statement visible as oneParsing--Unconsumedentry (QuotedStatement.Statement) with zero state changes — the same deferred-not-corrupted contract as SiemensFrameStatementSyntax. Proper consumers (WRITE, CYCLE800 quoted args) replace this guard per statement family later.Trade-off, deliberate: on an exotic mixed block the guard also takes the co-resident plain words (a motion word sharing the block with an unconsumed quoted call goes into the quarantine too). Losing them is visible in the warning text; minting false state would be silent — never trade the first for the second. Corpus quoted statements (WRITE, non-literal MSG) are standalone blocks.
- SiemensRepeatParsingSyntax
Captures a Siemens
REPEATstatement whole intoParsing.SiemensRepeat:REPEAT LBL1 LBL0→{ “Start”: “LBL1”, “End”: “LBL0” }, with an optional trailing repetition count (P=3orP3). The Evaluation consumer (SiemensRepeatSyntax) re-runs the label-bounded section. The single-label form (REPEAT LBL1— repeat from the label back to the REPEAT line) is captured withEndabsent; the consumer currently warns it unsupported (corpus count: zero).Whole-statement owner, anchored at statement start — mixing REPEAT with other tokens on one block does not match and falls through whole to the visible unparsed warning. The end-label alternative refuses a
P+digits word soREPEAT LBL1 P3reads as start-label + count, not as an end label namedP3.
- SiemensToolCallSyntax
Consumes the Siemens string tool call
T=“name”from the unparsed line text intoParsing.Tas a string value (quotes stripped). Numeric concatenated calls (T5) stay with the preset's IntegerTagValueSyntax; ToolChangeSyntax accepts either shape.Must be placed before every tag-value syntax in the Parsing bundle: tool names routinely contain NC-shaped tokens (
T="D16R3Z6"— quote-blind consumers would mint ghostParsing.D/Parsing.Zentries out of the name;(?<=[0-9])and quote→letter\bboundaries in RegexFlagPrefix both open inside the string). Same structural-consumption rationale as SiemensMsgSyntax.A second
T="..."behind a;comment (T="M6LWXD-1301";T="M6LWX", real corpus shape) never reaches this syntax — the quote-aware TailCommentSyntax runs earlier and strips it. Tool names are matched verbatim, including non-ASCII bytes from legacy code pages.