@chestnutlabs/gcode-parser 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/parse.d.ts CHANGED
@@ -34,6 +34,19 @@ export interface ParseLimits {
34
34
  maxBufferBytes: number;
35
35
  maxLineLength: number;
36
36
  maxWarnings: number;
37
+ /**
38
+ * Total loop work across an RS274NGC O-word program (DD-017 §4.5, Phase 2). Charged per loop pass and
39
+ * per statement executed inside a loop body; exceeding it stops the program with a partial IR and a
40
+ * `rs274-iteration-limit` disclosure, so a hostile `o while [1]` wastes only bounded CPU. Irrelevant to
41
+ * FDM / non-parametric input, which never enters the interpreter.
42
+ */
43
+ maxProgramIterations: number;
44
+ /**
45
+ * Subroutine call-recursion depth for an RS274NGC O-word program (DD-017 §4.5, Phase 3). A `call` that
46
+ * would exceed this depth is disclosed (`rs274-call-depth`) and skipped, so unbounded recursion cannot
47
+ * exhaust the stack. Irrelevant to FDM / non-parametric input.
48
+ */
49
+ maxCallDepth: number;
37
50
  }
38
51
  export declare const DEFAULT_LIMITS: ParseLimits;
39
52
  /**
@@ -144,6 +157,18 @@ export interface Engine {
144
157
  processLine(rawLine: string, offset: number): void;
145
158
  stopped(): boolean;
146
159
  markInputTooBig(): void;
160
+ /**
161
+ * Interpret the buffered `text` as an RS274NGC O-word program (DD-017 Phase 2). Called by the
162
+ * in-memory drivers INSTEAD of the forward-only line loop when `programUsesOWords(text)` is true;
163
+ * the interpreter re-feeds every plain line through `processLine`, so geometry stays on one path.
164
+ */
165
+ runOWordFlow(): void;
166
+ /**
167
+ * Streaming only: record that an O-word control line was seen but NOT interpreted (control flow needs a
168
+ * fully-buffered program — DD-017 §4.1). Discloses once and marks the parametric capability degraded;
169
+ * the line still runs linearly (the honest pre-Phase-2 behaviour), never silently as if flow had run.
170
+ */
171
+ noteUninterpretedOWord(srcByte: number): void;
147
172
  /** Streaming driver: record the true source byte count as it becomes known. */
148
173
  setSourceBytes(bytes: number): void;
149
174
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
package/dist/parse.js CHANGED
@@ -29,12 +29,16 @@
29
29
  */
30
30
  import { IR_SCHEMA_VERSION, MoveKind, buildSourceIndex, classifyModelBounds, computeSegmentBounds, emptyBounds } from '@chestnutlabs/toolpath-core';
31
31
  import { BudgetExceededError, SegmentWriter } from './growable.js';
32
+ import { Rs274Context, lineUsesParametric } from './rs274.js';
33
+ import { RS274_FLOW_WARN, interpretOWordProgram, programUsesOWords } from './rs274-flow.js';
32
34
  export const DEFAULT_LIMITS = {
33
35
  maxInputBytes: 512 * 1024 * 1024,
34
36
  maxSegments: 20_000_000,
35
37
  maxBufferBytes: 1536 * 1024 * 1024,
36
38
  maxLineLength: 64 * 1024,
37
- maxWarnings: 10_000
39
+ maxWarnings: 10_000,
40
+ maxProgramIterations: 1_000_000,
41
+ maxCallDepth: 50
38
42
  };
39
43
  const LAYER_TOLERANCE = 0.05;
40
44
  const UNRESOLVED_LAYER = 0xffffffff;
@@ -186,6 +190,10 @@ export function createEngine(input, opts) {
186
190
  let cannedInitialZ = 0; // Z when the cycle activated — the G98 retract plane
187
191
  let cannedRetractInitial = true; // G98 (retract to initial Z) vs G99 (retract to R); modal, default G98
188
192
  let cannedCyclesSeen = false; // any G81/G82/G83 expanded → cannedCycles capability 'known'
193
+ // RS274NGC parametric programs (DD-017 Phase 1, #189 phase 7). The store + expression evaluator resolve
194
+ // `#params`/`[expr]` on any line that uses them; non-parametric lines never touch it (byte-identical).
195
+ // The system-parameter allow-list (#5420–#5422 = current position) reads live engine position.
196
+ const rs274 = new Rs274Context(warn, () => ({ x: sx, y: sy, z: sz }));
189
197
  // Per-axis position certainty (DD-010 D4 amendment, #158). A G31 probe endpoint is reached at
190
198
  // RUNTIME (workpiece contact), not the commanded value, so it marks the probed axes uncertain. A
191
199
  // following G92 then RESYNCS the logical frame at the datum (rather than datum-shifting a stale
@@ -573,7 +581,22 @@ export function createEngine(input, opts) {
573
581
  if (ci !== -1)
574
582
  onComment(rawLine.slice(ci + 1), offset);
575
583
  }
576
- const cmd = lexLine(rawLine);
584
+ // RS274NGC parametric programs (DD-017 Phase 1): a line using `#`/`[` is resolved by the parameter +
585
+ // expression interpreter; a `#n = <expr>` assignment executes and the line ends there. Every other
586
+ // line (all FDM, all simple CNC) takes the untouched lexer below — byte-identical.
587
+ let cmd;
588
+ // Hot-path gate: a cheap `#`/`[` scan of the RAW line — no allocation. Every FDM line fails this and
589
+ // takes the untouched lexer (byte-identical). A `#`/`[` that turns out to be only inside a comment
590
+ // routes through the resolver, which strips comments and returns 'plain' → the normal lexer.
591
+ if (lineUsesParametric(rawLine)) {
592
+ const r = rs274.lexLine(rawLine, offset);
593
+ if (r.kind === 'assign' || r.kind === 'empty')
594
+ return; // assignment applied / nothing to do
595
+ cmd = r.kind === 'cmd' ? { codes: r.codes, params: r.params } : lexLine(rawLine); // 'plain' → normal
596
+ }
597
+ else {
598
+ cmd = lexLine(rawLine);
599
+ }
577
600
  // Skip only genuinely empty lines. A line with no lexable words but real content (a dialect
578
601
  // extended command like `EXCLUDE_OBJECT_START NAME=cube`, a macro) must NOT be dropped — the
579
602
  // observer below still forwards its raw line. The body split runs only when there are no words.
@@ -838,6 +861,46 @@ export function createEngine(input, opts) {
838
861
  truncatedAtByte = offset;
839
862
  }
840
863
  };
864
+ // RS274NGC O-word control flow (DD-017 Phase 2). Engaged only when the buffered program actually
865
+ // contains a control line; it buffers the lines with their byte offsets and hands them to the tree
866
+ // interpreter, which re-feeds each plain line through `processLine` (one geometry path) and evaluates
867
+ // conditions/counts against the SAME parameter store. `used`/`degraded` tier the capability.
868
+ const runOWordFlow = () => {
869
+ rs274.used = true;
870
+ const flowLines = [];
871
+ const len = text.length;
872
+ let offset = 0;
873
+ while (offset < len) {
874
+ let nl = text.indexOf('\n', offset);
875
+ if (nl === -1)
876
+ nl = len;
877
+ flowLines.push({ text: text.slice(offset, nl), offset });
878
+ offset = nl + 1;
879
+ }
880
+ const { degraded } = interpretOWordProgram(flowLines, {
881
+ processLine,
882
+ stopped: () => stopReason !== undefined,
883
+ evalExpression: (t, b) => rs274.evalExpression(t, b),
884
+ pushScope: (args) => rs274.pushScope(args),
885
+ popScope: () => rs274.popScope(),
886
+ warn,
887
+ maxProgramIterations: limits.maxProgramIterations,
888
+ maxCallDepth: limits.maxCallDepth
889
+ });
890
+ if (degraded)
891
+ rs274.degraded = true;
892
+ };
893
+ // Streaming can't interpret control flow from a partial stream (a loop needs its whole body — DD-017
894
+ // §4.1); disclose once and let the program run linearly (control lines are inert). Honest, bounded.
895
+ let streamingOWordWarned = false;
896
+ const noteUninterpretedOWord = (srcByte) => {
897
+ if (streamingOWordWarned)
898
+ return;
899
+ streamingOWordWarned = true;
900
+ rs274.used = true;
901
+ rs274.degraded = true;
902
+ warn(RS274_FLOW_WARN.streamingUnsupported, 'O-word control flow is only interpreted for fully-buffered input; this streamed program ran linearly (control flow not executed).', srcByte);
903
+ };
841
904
  const finish = (cancelledAtByte) => {
842
905
  if (stopReason !== undefined && truncatedAtByte === undefined) {
843
906
  truncatedAtByte = currentSrcByte;
@@ -891,6 +954,11 @@ export function createEngine(input, opts) {
891
954
  // Canned drilling cycles expanded to geometry (DD-012 phase 2, #189). 'known' once a G81/G82/G83
892
955
  // was seen (holes are real segments), else 'unavailable' (no cycles / FDM).
893
956
  cannedCycles: cannedCyclesSeen ? 'known' : 'unavailable',
957
+ // RS274NGC parametric programs (DD-017, #189 phase 7). 'known' once a `#param`/`[expr]`/O-word was
958
+ // executed deterministically within limits (the interpreter computes geometry — it does not infer);
959
+ // 'approximated' when Phase 2 flow was degraded (a hit iteration cap, an unbalanced/unsupported
960
+ // O-word, or a malformed condition — always disclosed); 'unavailable' for non-parametric input.
961
+ parametricProgram: rs274.parametricConfidence(),
894
962
  // Motion-model modes (DD-010 E10 phase 1). 'known' when the governing command was seen
895
963
  // (M82/M83, or a firmware-known G90/G91 for E); 'inferred' when defaulted (absolute).
896
964
  extrusionMode: eModeExplicit !== null || (extruderFollowsPositioning && positioningSeen) ? 'known' : 'inferred',
@@ -1012,6 +1080,7 @@ export function createEngine(input, opts) {
1012
1080
  seamMoves: 'unavailable',
1013
1081
  cutMoves: 'unavailable', // non-extrusion classification resolves on the final IR (DD-012)
1014
1082
  cannedCycles: 'unavailable', // canned-cycle expansion resolves on the final IR (DD-012)
1083
+ parametricProgram: 'unavailable', // parametric resolution resolves on the final IR (DD-017)
1015
1084
  extrusionMode: 'inferred', // motion modes resolve fully on the final IR (E10)
1016
1085
  positioningMode: 'inferred',
1017
1086
  arcPlanes: 'inferred',
@@ -1037,6 +1106,8 @@ export function createEngine(input, opts) {
1037
1106
  limits,
1038
1107
  processLine,
1039
1108
  stopped: () => stopReason !== undefined,
1109
+ runOWordFlow,
1110
+ noteUninterpretedOWord,
1040
1111
  snapshot,
1041
1112
  markInputTooBig: () => {
1042
1113
  stopReason = {
@@ -1062,6 +1133,13 @@ export function parseGcodeToIR(input, opts = {}) {
1062
1133
  return engine.finish();
1063
1134
  }
1064
1135
  const text = engine.text;
1136
+ // RS274NGC O-word program (DD-017 Phase 2): interpret with control flow instead of the forward-only
1137
+ // loop. The cheap detection scan fails for every FDM / non-parametric file, which takes the untouched
1138
+ // loop below (byte-identical).
1139
+ if (programUsesOWords(text)) {
1140
+ engine.runOWordFlow();
1141
+ return engine.finish();
1142
+ }
1065
1143
  const len = text.length;
1066
1144
  let offset = 0;
1067
1145
  while (offset < len && !engine.stopped()) {
@@ -1101,6 +1179,14 @@ export async function parseGcodeToIRAsync(input, opts = {}, hooks = {}) {
1101
1179
  const yieldEvery = hooks.yieldIntervalMs ?? 50;
1102
1180
  const partial = makePartialEmitter(engine, hooks);
1103
1181
  const text = engine.text;
1182
+ // RS274NGC O-word program (DD-017 Phase 2): a bounded synchronous interpretation section (loops are
1183
+ // capped by `maxProgramIterations`, and these programs are small — hand/CAM-written). Non-parametric
1184
+ // input takes the cooperative line loop below unchanged.
1185
+ if (programUsesOWords(text)) {
1186
+ engine.runOWordFlow();
1187
+ hooks.onProgress?.(text.length, byteLength, 0);
1188
+ return { ...engine.finish(), cancelled: false };
1189
+ }
1104
1190
  const len = text.length;
1105
1191
  let offset = 0;
1106
1192
  let sliceStart = Date.now();
@@ -0,0 +1,69 @@
1
+ /** Warning codes surfaced to the IR by the flow interpreter (DD-017 §4.6). */
2
+ export declare const RS274_FLOW_WARN: {
3
+ readonly iterationLimit: "rs274-iteration-limit";
4
+ readonly unbalancedOword: "rs274-unbalanced-oword";
5
+ readonly unsupportedOword: "rs274-unsupported-oword";
6
+ readonly controlNesting: "rs274-control-nesting";
7
+ readonly misplacedControl: "rs274-misplaced-control";
8
+ readonly streamingUnsupported: "rs274-streaming-flow-unsupported";
9
+ readonly callDepth: "rs274-call-depth";
10
+ readonly unknownSub: "rs274-unknown-sub";
11
+ readonly duplicateSub: "rs274-duplicate-sub";
12
+ };
13
+ /** The recognized O-word keywords. An `o<id>` line with any OTHER trailing word is NOT a control line
14
+ * (e.g. a stray `o5 g1`) — it falls through to the normal lexer, so detection never hijacks plain code. */
15
+ export declare const KNOWN_OWORD_KEYWORDS: ReadonlySet<string>;
16
+ export type WarnFn = (code: string, message: string, srcByte?: number) => void;
17
+ /** A classified O-word control line: its keyword, its (opaque, normalized) id token, and any trailing
18
+ * expression (an `if`/`while` condition or a `repeat` count). */
19
+ export interface OwInfo {
20
+ keyword: string;
21
+ /** The id token verbatim-normalized: `100`, `<name>`, or a literal `[expr]` — matched, never evaluated. */
22
+ id: string;
23
+ expr: string;
24
+ }
25
+ /**
26
+ * Classify one raw line as an O-word control line, or `null` when it is not one. `null` covers every
27
+ * plain line (all FDM, all geometry/param lines) AND an `o<id>` whose trailing word is not a recognized
28
+ * keyword (e.g. `o5 g1 x10`) — those fall through to the normal lexer, so classification never hijacks
29
+ * ordinary code. The id is read as an opaque token (numeric / `<name>` / balanced `[expr]`) and is used
30
+ * only to pair an opener with its closer — it is deliberately NOT evaluated here.
31
+ */
32
+ export declare function classifyOWord(rawLine: string): OwInfo | null;
33
+ /** One buffered source line with its byte offset (for `srcByte` on any geometry it emits). */
34
+ export interface FlowLine {
35
+ text: string;
36
+ offset: number;
37
+ }
38
+ /** The engine surface the interpreter drives — supplied by `createEngine` with its closures. */
39
+ export interface FlowHost {
40
+ /** Execute one plain (non-control) source line through the normal engine path. */
41
+ processLine(text: string, offset: number): void;
42
+ /** True once a geometry limit (segments/buffer) has stopped emission — the interpreter then halts. */
43
+ stopped(): boolean;
44
+ /** Evaluate a control expression against the shared parameter store; `null` = malformed/non-finite. */
45
+ evalExpression(text: string, srcByte: number): number | null;
46
+ /** Push a subroutine call-frame scope with `args` bound to `#1`..`#30` (Phase 3). */
47
+ pushScope(args: readonly number[]): void;
48
+ /** Pop the current call-frame scope (Phase 3). */
49
+ popScope(): void;
50
+ warn: WarnFn;
51
+ maxProgramIterations: number;
52
+ /** Subroutine recursion depth cap (Phase 3); exceeding it discloses and skips the call. */
53
+ maxCallDepth: number;
54
+ }
55
+ export interface FlowOutcome {
56
+ /** True when execution was degraded (limit hit, unbalanced/unsupported O-word, malformed condition). */
57
+ degraded: boolean;
58
+ }
59
+ /** True once the built tree used any recognized control construct (drives the capability). */
60
+ export declare function programUsesOWords(text: string): boolean;
61
+ /** True when a single line is a recognized O-word control line (used by the streaming disclosure). */
62
+ export declare function isOWordControlLine(line: string): boolean;
63
+ /**
64
+ * Interpret a fully-buffered O-word program. Builds the block tree (tolerant of unbalanced O-words —
65
+ * disclosed, never thrown out), then executes it with bounded iteration. Returns whether execution was
66
+ * degraded so the caller can tier the `parametricProgram` capability.
67
+ */
68
+ export declare function interpretOWordProgram(lines: readonly FlowLine[], host: FlowHost): FlowOutcome;
69
+ //# sourceMappingURL=rs274-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rs274-flow.d.ts","sourceRoot":"","sources":["../src/rs274-flow.ts"],"names":[],"mappings":"AAgCA,8EAA8E;AAC9E,eAAO,MAAM,eAAe;;;;;;;;;;CAUlB,CAAC;AAeX;4GAC4G;AAC5G,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAgBnD,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/E;kEACkE;AAClE,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,2GAA2G;IAC3G,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkD5D;AAED,8FAA8F;AAC9F,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gGAAgG;AAChG,MAAM,WAAW,QAAQ;IACvB,kFAAkF;IAClF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,sGAAsG;IACtG,OAAO,IAAI,OAAO,CAAC;IACnB,uGAAuG;IACvG,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC7D,qFAAqF;IACrF,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACzC,kDAAkD;IAClD,QAAQ,IAAI,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,MAAM,CAAC;IAC7B,2FAA2F;IAC3F,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,wGAAwG;IACxG,QAAQ,EAAE,OAAO,CAAC;CACnB;AAoCD,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUvD;AAED,sGAAsG;AACtG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AA2FD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,GAAG,WAAW,CAmb7F"}