@faicad/faijs 0.19.0 → 0.22.2
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/api/brep-mirror/joinery-brep.d.ts.map +1 -1
- package/dist/api/brep-mirror/joinery-brep.js +3 -1
- package/dist/api/brep-mirror/joinery-brep.js.map +1 -1
- package/dist/api/generated/script-face-manifest.d.ts +4 -0
- package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
- package/dist/api/generated/script-face-manifest.js.map +1 -1
- package/dist/api/knurl.d.ts.map +1 -1
- package/dist/api/knurl.js +1 -0
- package/dist/api/knurl.js.map +1 -1
- package/dist/api/primitives.d.ts +4 -3
- package/dist/api/primitives.d.ts.map +1 -1
- package/dist/api/primitives.js +9 -3
- package/dist/api/primitives.js.map +1 -1
- package/dist/api/profile.d.ts +34 -5
- package/dist/api/profile.d.ts.map +1 -1
- package/dist/api/profile.js +60 -9
- package/dist/api/profile.js.map +1 -1
- package/dist/api/punch-hole.d.ts +3 -0
- package/dist/api/punch-hole.d.ts.map +1 -1
- package/dist/api/punch-hole.js.map +1 -1
- package/dist/api/sketch-on-plane.d.ts +12 -6
- package/dist/api/sketch-on-plane.d.ts.map +1 -1
- package/dist/api/sketch-on-plane.js +11 -8
- package/dist/api/sketch-on-plane.js.map +1 -1
- package/dist/api/surface/arg-spec.d.ts +8 -0
- package/dist/api/surface/arg-spec.d.ts.map +1 -1
- package/dist/api/surface/arg-spec.js.map +1 -1
- package/dist/api/transform.d.ts +5 -5
- package/dist/api/transform.d.ts.map +1 -1
- package/dist/api/transform.js +12 -10
- package/dist/api/transform.js.map +1 -1
- package/dist/api/xml-dom.d.ts +27 -0
- package/dist/api/xml-dom.d.ts.map +1 -0
- package/dist/api/xml-dom.js +45 -0
- package/dist/api/xml-dom.js.map +1 -0
- package/dist/brep/brep-ops.d.ts +4 -4
- package/dist/brep/brep-ops.d.ts.map +1 -1
- package/dist/brep/brep-ops.js +8 -6
- package/dist/brep/brep-ops.js.map +1 -1
- package/dist/brep/brep-topology.d.ts.map +1 -1
- package/dist/brep/brep-topology.js +3 -1
- package/dist/brep/brep-topology.js.map +1 -1
- package/dist/brep/export/export-model.d.ts +79 -0
- package/dist/brep/export/export-model.d.ts.map +1 -0
- package/dist/brep/export/export-model.js +335 -0
- package/dist/brep/export/export-model.js.map +1 -0
- package/dist/brep/primitives-brep.d.ts.map +1 -1
- package/dist/brep/primitives-brep.js +3 -1
- package/dist/brep/primitives-brep.js.map +1 -1
- package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
- package/dist/brepkit-kernel/brepkitKernel.js +4 -2
- package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
- package/dist/cad-runtime/direct-executor.d.ts +4 -0
- package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
- package/dist/cad-runtime/direct-executor.js +17 -1
- package/dist/cad-runtime/direct-executor.js.map +1 -1
- package/dist/cad-runtime/runtime.d.ts +24 -1
- package/dist/cad-runtime/runtime.d.ts.map +1 -1
- package/dist/cad-runtime/runtime.js +53 -3
- package/dist/cad-runtime/runtime.js.map +1 -1
- package/dist/define-op.d.ts +22 -0
- package/dist/define-op.d.ts.map +1 -1
- package/dist/define-op.js +15 -0
- package/dist/define-op.js.map +1 -1
- package/dist/env-agnostic.d.ts +7 -7
- package/dist/env-agnostic.d.ts.map +1 -1
- package/dist/env-agnostic.js +3 -3
- package/dist/env-agnostic.js.map +1 -1
- package/dist/geometry2d/bridge/lift-on-plane.d.ts.map +1 -1
- package/dist/geometry2d/bridge/lift-on-plane.js +6 -0
- package/dist/geometry2d/bridge/lift-on-plane.js.map +1 -1
- package/dist/geometry2d/pen-sketcher.d.ts +25 -0
- package/dist/geometry2d/pen-sketcher.d.ts.map +1 -1
- package/dist/geometry2d/pen-sketcher.js +43 -0
- package/dist/geometry2d/pen-sketcher.js.map +1 -1
- package/dist/lang/code-to-args.d.ts +29 -3
- package/dist/lang/code-to-args.d.ts.map +1 -1
- package/dist/lang/code-to-args.js +41 -2
- package/dist/lang/code-to-args.js.map +1 -1
- package/dist/lang/codegen.d.ts +45 -1
- package/dist/lang/codegen.d.ts.map +1 -1
- package/dist/lang/codegen.js +135 -6
- package/dist/lang/codegen.js.map +1 -1
- package/dist/lang/host-arg.d.ts +6 -0
- package/dist/lang/host-arg.d.ts.map +1 -1
- package/dist/lang/host-arg.js.map +1 -1
- package/dist/lang/metadata-extractor.d.ts +25 -0
- package/dist/lang/metadata-extractor.d.ts.map +1 -1
- package/dist/lang/metadata-extractor.js +289 -25
- package/dist/lang/metadata-extractor.js.map +1 -1
- package/dist/lang/parse-error.d.ts +7 -1
- package/dist/lang/parse-error.d.ts.map +1 -1
- package/dist/lang/parse-error.js.map +1 -1
- package/dist/lang/security-scanner.d.ts +9 -1
- package/dist/lang/security-scanner.d.ts.map +1 -1
- package/dist/lang/security-scanner.js +38 -1
- package/dist/lang/security-scanner.js.map +1 -1
- package/dist/mesh/io.d.ts +62 -9
- package/dist/mesh/io.d.ts.map +1 -1
- package/dist/mesh/io.js +124 -17
- package/dist/mesh/io.js.map +1 -1
- package/dist/mesh/threemf-loader.d.ts +15 -0
- package/dist/mesh/threemf-loader.d.ts.map +1 -0
- package/dist/mesh/threemf-loader.js +163 -0
- package/dist/mesh/threemf-loader.js.map +1 -0
- package/dist/mesh/transform.d.ts +2 -2
- package/dist/mesh/transform.d.ts.map +1 -1
- package/dist/mesh/transform.js +3 -3
- package/dist/mesh/transform.js.map +1 -1
- package/dist/mesh/types.d.ts +1 -1
- package/dist/mesh/types.d.ts.map +1 -1
- package/dist/mesh/types.js.map +1 -1
- package/dist/node-host/cli.d.ts.map +1 -1
- package/dist/node-host/cli.js +53 -26
- package/dist/node-host/cli.js.map +1 -1
- package/dist/occt-kernel/occtKernel.d.ts.map +1 -1
- package/dist/occt-kernel/occtKernel.js +4 -2
- package/dist/occt-kernel/occtKernel.js.map +1 -1
- package/dist/occt-kernel/topologyExt.d.ts.map +1 -1
- package/dist/occt-kernel/topologyExt.js +4 -1
- package/dist/occt-kernel/topologyExt.js.map +1 -1
- package/dist/runtime-state.d.ts +18 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +23 -0
- package/dist/runtime-state.js.map +1 -1
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +1 -1
- package/dist/sdk.js.map +1 -1
- package/dist/tolerance.d.ts +38 -0
- package/dist/tolerance.d.ts.map +1 -0
- package/dist/tolerance.js +40 -0
- package/dist/tolerance.js.map +1 -0
- package/dist/topology/naming/score.js +3 -3
- package/dist/topology/naming/score.js.map +1 -1
- package/dist/units.d.ts +248 -0
- package/dist/units.d.ts.map +1 -0
- package/dist/units.js +397 -0
- package/dist/units.js.map +1 -0
- package/package.json +11 -2
|
@@ -20,6 +20,7 @@ import { isHostVarRef, isHostParamRef, isHostCallRef, isHostExprRef } from './ho
|
|
|
20
20
|
import { ParseError } from './parse-error.js';
|
|
21
21
|
import { fnv1a32 } from './fnv-hash.js';
|
|
22
22
|
import { assertSecure } from './security-scanner.js';
|
|
23
|
+
import { SCRIPT_UNIT_NAMES, UNIT_DIM, UNIT_SCALE, SCRIPT_UNIT_TO_NAME } from '../units.js';
|
|
23
24
|
const CONTROL_FLOW_TYPES = new Set([
|
|
24
25
|
'IfStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',
|
|
25
26
|
'WhileStatement', 'DoWhileStatement', 'SwitchStatement', 'TryStatement',
|
|
@@ -36,8 +37,8 @@ function derivePackageName(specifier) {
|
|
|
36
37
|
}
|
|
37
38
|
return specifier.split('/')[0];
|
|
38
39
|
}
|
|
39
|
-
function numFold(v, usedParam = false) {
|
|
40
|
-
return Number.isFinite(v) ? { ok: true, value: v, usedParam } : { ok: false };
|
|
40
|
+
function numFold(v, usedParam = false, usedUnit = false) {
|
|
41
|
+
return Number.isFinite(v) ? { ok: true, value: v, usedParam, usedUnit } : { ok: false };
|
|
41
42
|
}
|
|
42
43
|
function looseEq(a, b) {
|
|
43
44
|
let x = a;
|
|
@@ -57,12 +58,23 @@ function looseEq(a, b) {
|
|
|
57
58
|
function tryFoldConstExpr(node, symbols) {
|
|
58
59
|
switch (node.type) {
|
|
59
60
|
case 'Literal':
|
|
60
|
-
return { ok: true, value: node.value, usedParam: false };
|
|
61
|
+
return { ok: true, value: node.value, usedParam: false, usedUnit: false };
|
|
61
62
|
case 'Identifier': {
|
|
63
|
+
// P7/D10.4: unit constants (MM, INCH, DEGREE, …) are recognized as
|
|
64
|
+
// foldable constants. Their values come from UNIT_SCALE (the single
|
|
65
|
+
// source of truth). The `usedUnit` flag is set so that `parseValueExpr`
|
|
66
|
+
// can mark the line as computed (host read-only downgrade), preventing
|
|
67
|
+
// the folded base value (e.g. 254) from being written back to source.
|
|
68
|
+
if (SCRIPT_UNIT_NAMES.has(node.name)) {
|
|
69
|
+
const unitName = SCRIPT_UNIT_TO_NAME[node.name];
|
|
70
|
+
if (unitName) {
|
|
71
|
+
return { ok: true, value: UNIT_SCALE[unitName], usedParam: false, usedUnit: true };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
62
74
|
if (!symbols.paramNames.has(node.name))
|
|
63
75
|
return { ok: false };
|
|
64
76
|
const v = symbols.paramValues.get(node.name);
|
|
65
|
-
return v === undefined ? { ok: false } : { ok: true, value: v, usedParam: true };
|
|
77
|
+
return v === undefined ? { ok: false } : { ok: true, value: v, usedParam: true, usedUnit: false };
|
|
66
78
|
}
|
|
67
79
|
case 'UnaryExpression': {
|
|
68
80
|
const operand = tryFoldConstExpr(node.argument, symbols);
|
|
@@ -70,11 +82,11 @@ function tryFoldConstExpr(node, symbols) {
|
|
|
70
82
|
return { ok: false };
|
|
71
83
|
const v = operand.value;
|
|
72
84
|
switch (node.operator) {
|
|
73
|
-
case '-': return typeof v === 'number' ? numFold(-v, operand.usedParam) : { ok: false };
|
|
74
|
-
case '+': return typeof v === 'number' ? numFold(+v, operand.usedParam) : { ok: false };
|
|
75
|
-
case '!': return { ok: true, value: !v, usedParam: operand.usedParam };
|
|
76
|
-
case '~': return typeof v === 'number' ? { ok: true, value: ~v, usedParam: operand.usedParam } : { ok: false };
|
|
77
|
-
case 'typeof': return { ok: true, value: typeof v, usedParam: operand.usedParam };
|
|
85
|
+
case '-': return typeof v === 'number' ? numFold(-v, operand.usedParam, operand.usedUnit) : { ok: false };
|
|
86
|
+
case '+': return typeof v === 'number' ? numFold(+v, operand.usedParam, operand.usedUnit) : { ok: false };
|
|
87
|
+
case '!': return { ok: true, value: !v, usedParam: operand.usedParam, usedUnit: operand.usedUnit };
|
|
88
|
+
case '~': return typeof v === 'number' ? { ok: true, value: ~v, usedParam: operand.usedParam, usedUnit: operand.usedUnit } : { ok: false };
|
|
89
|
+
case 'typeof': return { ok: true, value: typeof v, usedParam: operand.usedParam, usedUnit: operand.usedUnit };
|
|
78
90
|
default: return { ok: false };
|
|
79
91
|
}
|
|
80
92
|
}
|
|
@@ -88,20 +100,21 @@ function tryFoldConstExpr(node, symbols) {
|
|
|
88
100
|
const a = left.value;
|
|
89
101
|
const b = right.value;
|
|
90
102
|
const usedParam = left.usedParam || right.usedParam;
|
|
91
|
-
const
|
|
103
|
+
const usedUnit = left.usedUnit || right.usedUnit;
|
|
104
|
+
const numBin = (op) => typeof a === 'number' && typeof b === 'number' ? numFold(op(a, b), usedParam, usedUnit) : { ok: false };
|
|
92
105
|
const cmpBin = (op) => {
|
|
93
106
|
if (typeof a === 'number' && typeof b === 'number')
|
|
94
|
-
return { ok: true, value: op(a, b), usedParam };
|
|
107
|
+
return { ok: true, value: op(a, b), usedParam, usedUnit };
|
|
95
108
|
if (typeof a === 'string' && typeof b === 'string')
|
|
96
|
-
return { ok: true, value: op(a, b), usedParam };
|
|
109
|
+
return { ok: true, value: op(a, b), usedParam, usedUnit };
|
|
97
110
|
return { ok: false };
|
|
98
111
|
};
|
|
99
112
|
switch (node.operator) {
|
|
100
113
|
case '+': {
|
|
101
114
|
if (typeof a === 'number' && typeof b === 'number')
|
|
102
|
-
return numFold(a + b, usedParam);
|
|
115
|
+
return numFold(a + b, usedParam, usedUnit);
|
|
103
116
|
if (typeof a === 'string' || typeof b === 'string')
|
|
104
|
-
return { ok: true, value: String(a) + String(b), usedParam };
|
|
117
|
+
return { ok: true, value: String(a) + String(b), usedParam, usedUnit };
|
|
105
118
|
return { ok: false };
|
|
106
119
|
}
|
|
107
120
|
case '-': return numBin((x, y) => x - y);
|
|
@@ -113,10 +126,10 @@ function tryFoldConstExpr(node, symbols) {
|
|
|
113
126
|
case '<=': return cmpBin((x, y) => x <= y);
|
|
114
127
|
case '>': return cmpBin((x, y) => x > y);
|
|
115
128
|
case '>=': return cmpBin((x, y) => x >= y);
|
|
116
|
-
case '==': return { ok: true, value: looseEq(a, b), usedParam };
|
|
117
|
-
case '!=': return { ok: true, value: !looseEq(a, b), usedParam };
|
|
118
|
-
case '===': return { ok: true, value: a === b, usedParam };
|
|
119
|
-
case '!==': return { ok: true, value: a !== b, usedParam };
|
|
129
|
+
case '==': return { ok: true, value: looseEq(a, b), usedParam, usedUnit };
|
|
130
|
+
case '!=': return { ok: true, value: !looseEq(a, b), usedParam, usedUnit };
|
|
131
|
+
case '===': return { ok: true, value: a === b, usedParam, usedUnit };
|
|
132
|
+
case '!==': return { ok: true, value: a !== b, usedParam, usedUnit };
|
|
120
133
|
default: return { ok: false };
|
|
121
134
|
}
|
|
122
135
|
}
|
|
@@ -129,10 +142,11 @@ function tryFoldConstExpr(node, symbols) {
|
|
|
129
142
|
return { ok: false };
|
|
130
143
|
const a = left.value;
|
|
131
144
|
const usedParam = left.usedParam || right.usedParam;
|
|
145
|
+
const usedUnit = left.usedUnit || right.usedUnit;
|
|
132
146
|
switch (node.operator) {
|
|
133
|
-
case '&&': return { ok: true, value: a ? right.value : left.value, usedParam };
|
|
134
|
-
case '||': return { ok: true, value: a ? left.value : right.value, usedParam };
|
|
135
|
-
case '??': return { ok: true, value: a === null ? right.value : left.value, usedParam };
|
|
147
|
+
case '&&': return { ok: true, value: a ? right.value : left.value, usedParam, usedUnit };
|
|
148
|
+
case '||': return { ok: true, value: a ? left.value : right.value, usedParam, usedUnit };
|
|
149
|
+
case '??': return { ok: true, value: a === null ? right.value : left.value, usedParam, usedUnit };
|
|
136
150
|
default: return { ok: false };
|
|
137
151
|
}
|
|
138
152
|
}
|
|
@@ -141,6 +155,7 @@ function tryFoldConstExpr(node, symbols) {
|
|
|
141
155
|
const quasis = node.quasis;
|
|
142
156
|
const exprs = node.expressions;
|
|
143
157
|
let usedParam = false;
|
|
158
|
+
let usedUnit = false;
|
|
144
159
|
for (let i = 0; i < quasis.length; i++) {
|
|
145
160
|
const cooked = quasis[i].value?.cooked;
|
|
146
161
|
parts.push(cooked ?? '');
|
|
@@ -149,10 +164,11 @@ function tryFoldConstExpr(node, symbols) {
|
|
|
149
164
|
if (!r.ok)
|
|
150
165
|
return { ok: false };
|
|
151
166
|
usedParam = usedParam || r.usedParam;
|
|
167
|
+
usedUnit = usedUnit || r.usedUnit;
|
|
152
168
|
parts.push(String(r.value));
|
|
153
169
|
}
|
|
154
170
|
}
|
|
155
|
-
return { ok: true, value: parts.join(''), usedParam };
|
|
171
|
+
return { ok: true, value: parts.join(''), usedParam, usedUnit };
|
|
156
172
|
}
|
|
157
173
|
case 'ConditionalExpression': {
|
|
158
174
|
const test = tryFoldConstExpr(node.test, symbols);
|
|
@@ -161,7 +177,7 @@ function tryFoldConstExpr(node, symbols) {
|
|
|
161
177
|
const chosen = tryFoldConstExpr(test.value ? node.consequent : node.alternate, symbols);
|
|
162
178
|
if (!chosen.ok)
|
|
163
179
|
return { ok: false };
|
|
164
|
-
return { ok: true, value: chosen.value, usedParam: test.usedParam || chosen.usedParam };
|
|
180
|
+
return { ok: true, value: chosen.value, usedParam: test.usedParam || chosen.usedParam, usedUnit: test.usedUnit || chosen.usedUnit };
|
|
165
181
|
}
|
|
166
182
|
default:
|
|
167
183
|
return { ok: false };
|
|
@@ -240,6 +256,11 @@ export function collectExprIdentifiers(node, symbols, line, params, refs, opts)
|
|
|
240
256
|
else if (symbols.declared.has(name)) {
|
|
241
257
|
refs.add(name);
|
|
242
258
|
}
|
|
259
|
+
else if (SCRIPT_UNIT_NAMES.has(name)) {
|
|
260
|
+
// P6/D7: unit constants (MM, INCH, DEGREE, …) are global read-only constants.
|
|
261
|
+
// Treat as a known reference — do not throw E_REFERENCE.
|
|
262
|
+
refs.add(name);
|
|
263
|
+
}
|
|
243
264
|
else if (opts?.lenient === true) {
|
|
244
265
|
// lenient:未知标识符跳过(ArgSource 记录路径,保证提取不抛新错)
|
|
245
266
|
}
|
|
@@ -310,6 +331,130 @@ function isExpressionNode(node) {
|
|
|
310
331
|
return false;
|
|
311
332
|
return true;
|
|
312
333
|
}
|
|
334
|
+
// ── P6/D8: 静态量纲校验(dimension pass) ──
|
|
335
|
+
/**
|
|
336
|
+
* P6/D8 R4: Determine the dimension of an AST expression node.
|
|
337
|
+
*
|
|
338
|
+
* Returns the DimName if the expression can be statically determined to carry
|
|
339
|
+
* a dimension, `'bare'` for a bare number literal, or `null` if it cannot be
|
|
340
|
+
* determined (R5: pass through).
|
|
341
|
+
*/
|
|
342
|
+
function inferDim(node) {
|
|
343
|
+
if (!node)
|
|
344
|
+
return null;
|
|
345
|
+
switch (node.type) {
|
|
346
|
+
case 'Literal':
|
|
347
|
+
if (typeof node.value === 'number')
|
|
348
|
+
return 'bare';
|
|
349
|
+
return null;
|
|
350
|
+
case 'UnaryExpression':
|
|
351
|
+
if (node.operator === '-' || node.operator === '+' || node.operator === '~') {
|
|
352
|
+
return inferDim(node.argument);
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
case 'Identifier':
|
|
356
|
+
// Unit constant? → its dimension
|
|
357
|
+
if (SCRIPT_UNIT_NAMES.has(node.name)) {
|
|
358
|
+
const unitName = SCRIPT_UNIT_TO_NAME[node.name];
|
|
359
|
+
return unitName ? (UNIT_DIM[unitName] ?? null) : null;
|
|
360
|
+
}
|
|
361
|
+
// Declared variable or parameter → cannot determine (R5: pass through)
|
|
362
|
+
return null;
|
|
363
|
+
case 'BinaryExpression': {
|
|
364
|
+
const left = inferDim(node.left);
|
|
365
|
+
const right = inferDim(node.right);
|
|
366
|
+
switch (node.operator) {
|
|
367
|
+
case '*': {
|
|
368
|
+
// number * unitConst → unit's dim; unitConst * number → same
|
|
369
|
+
if (left === 'bare' && right && right !== 'bare')
|
|
370
|
+
return right;
|
|
371
|
+
if (right === 'bare' && left && left !== 'bare')
|
|
372
|
+
return left;
|
|
373
|
+
// both bare → bare (dimensionless multiply)
|
|
374
|
+
if (left === 'bare' && right === 'bare')
|
|
375
|
+
return 'bare';
|
|
376
|
+
// both have dims → compound (length * length → area); pass through
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
case '/': {
|
|
380
|
+
// same dim / same dim → dimensionless (bare)
|
|
381
|
+
if (left && right && left === right && left !== 'bare')
|
|
382
|
+
return 'bare';
|
|
383
|
+
// dim / bare → dim
|
|
384
|
+
if (left && left !== 'bare' && right === 'bare')
|
|
385
|
+
return left;
|
|
386
|
+
// bare / dim → 1/dim (not a simple dim); pass through
|
|
387
|
+
if (left === 'bare' && right && right !== 'bare')
|
|
388
|
+
return null;
|
|
389
|
+
if (left === 'bare' && right === 'bare')
|
|
390
|
+
return 'bare';
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
case '+':
|
|
394
|
+
case '-': {
|
|
395
|
+
// Both sides must have the same dimension (R3)
|
|
396
|
+
if (left === right)
|
|
397
|
+
return left;
|
|
398
|
+
// R3: if both sides are determinable but differ → mismatch.
|
|
399
|
+
// But inferDim can't report errors — it returns the dim or null.
|
|
400
|
+
// We return a special sentinel 'mismatch' to signal checkDim.
|
|
401
|
+
if (left && right && left !== 'bare' && right !== 'bare' && left !== right) {
|
|
402
|
+
return 'mismatch';
|
|
403
|
+
}
|
|
404
|
+
// If one is bare and the other has a dim → the result has that dim
|
|
405
|
+
if (left === 'bare' && right && right !== 'bare')
|
|
406
|
+
return right;
|
|
407
|
+
if (right === 'bare' && left && left !== 'bare')
|
|
408
|
+
return left;
|
|
409
|
+
// If either is null (unknown), pass through (R5)
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
default:
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
case 'ParenthesizedExpression':
|
|
417
|
+
return inferDim(node.expression);
|
|
418
|
+
case 'ArrayExpression':
|
|
419
|
+
// vec3 — check first element for dimensionality
|
|
420
|
+
if (node.elements && node.elements.length > 0 && node.elements[0]) {
|
|
421
|
+
return inferDim(node.elements[0]);
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
default:
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* P6/D8: Check if an argument expression has the required dimension.
|
|
430
|
+
*
|
|
431
|
+
* - R2: bare number literal on a dimensioned slot → E_DIM_BARE_NUMBER
|
|
432
|
+
* - R3: mismatched dimensions → E_DIM_MISMATCH
|
|
433
|
+
* - R5: cannot determine → pass (no error)
|
|
434
|
+
*
|
|
435
|
+
* Appends errors to ctx.dimErrors (does not throw — extraction continues).
|
|
436
|
+
*/
|
|
437
|
+
function checkDim(node, expectedDim, ctx) {
|
|
438
|
+
const actualDim = inferDim(node);
|
|
439
|
+
// R5: cannot determine → pass through
|
|
440
|
+
if (actualDim === null)
|
|
441
|
+
return;
|
|
442
|
+
// R3: mismatched dimensions within the expression (e.g. mm + degree)
|
|
443
|
+
if (actualDim === 'mismatch') {
|
|
444
|
+
ctx.dimErrors.push(new ParseError(`dimension mismatch in expression: "${ctx.sourceText.slice(node.start, node.end)}"`, lineOf(node), 'E_DIM_MISMATCH'));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
// R2: bare number on dimensioned slot
|
|
448
|
+
if (actualDim === 'bare') {
|
|
449
|
+
ctx.dimErrors.push(new ParseError(`bare number literal requires unit suffix (expected ${expectedDim}): "${ctx.sourceText.slice(node.start, node.end)}"`, lineOf(node), 'E_DIM_BARE_NUMBER'));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
// R3: dimension mismatch with expected
|
|
453
|
+
if (actualDim !== expectedDim) {
|
|
454
|
+
ctx.dimErrors.push(new ParseError(`dimension mismatch: expected ${expectedDim}, got ${actualDim} in "${ctx.sourceText.slice(node.start, node.end)}"`, lineOf(node), 'E_DIM_MISMATCH'));
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
313
458
|
/**
|
|
314
459
|
* 在折叠之前记录一条参数槽(ArgSource)。
|
|
315
460
|
*
|
|
@@ -358,7 +503,10 @@ function parseValueExpr(node, ctx, path) {
|
|
|
358
503
|
const r = tryFoldConstExpr(node, ctx.symbols);
|
|
359
504
|
if (r.ok) {
|
|
360
505
|
// F1-E1:仅折叠表达式引用参数才标 computed;纯字面量(负字面量)不标
|
|
361
|
-
|
|
506
|
+
// P7/D10.4: unit constants (usedUnit) also mark computed — the folded
|
|
507
|
+
// base value (e.g. 254 for `10 * INCH`) must NOT be written back to
|
|
508
|
+
// source code (would lose the unit and change geometry in non-base contexts).
|
|
509
|
+
if (r.usedParam || r.usedUnit)
|
|
362
510
|
ctx.computed.value = true;
|
|
363
511
|
return r.value;
|
|
364
512
|
}
|
|
@@ -948,6 +1096,8 @@ export function extractMetadata(code, options) {
|
|
|
948
1096
|
let terminalShapes;
|
|
949
1097
|
// P0-B:参数槽记录累积(跨语句共享,语句顺序追加)
|
|
950
1098
|
const argSources = [];
|
|
1099
|
+
// P6/D8: dimension 校验错误累积(跨语句共享)
|
|
1100
|
+
const allDimErrors = [];
|
|
951
1101
|
// ── 逐顶层语句分类 ──
|
|
952
1102
|
for (const stmtNode of bodyNodes) {
|
|
953
1103
|
const rawLine = lineOf(stmtNode);
|
|
@@ -961,6 +1111,8 @@ export function extractMetadata(code, options) {
|
|
|
961
1111
|
stmtId: asStmtId(`s${line}`),
|
|
962
1112
|
codeOffset,
|
|
963
1113
|
argSources,
|
|
1114
|
+
opDims: options?.opDims,
|
|
1115
|
+
dimErrors: allDimErrors,
|
|
964
1116
|
};
|
|
965
1117
|
switch (stmtNode.type) {
|
|
966
1118
|
case 'ImportDeclaration':
|
|
@@ -1097,16 +1249,22 @@ export function extractMetadata(code, options) {
|
|
|
1097
1249
|
}
|
|
1098
1250
|
}
|
|
1099
1251
|
}
|
|
1100
|
-
// P0-B:名称集合 = 参数 ∪ 已声明变量 ∪ 命名空间绑定 ∪
|
|
1252
|
+
// P0-B:名称集合 = 参数 ∪ 已声明变量 ∪ 命名空间绑定 ∪ 本地函数名 ∪ 单位常量(字典序去重)。
|
|
1101
1253
|
// 宿主把「参数表达式」中的未知标识符当作参数名(期望名)处理时用它做联想。
|
|
1254
|
+
// P6/D7:单位常量名(MM, INCH, DEGREE, …)加入 knownNames,使表达式实时校验通过。
|
|
1102
1255
|
const names = [
|
|
1103
1256
|
...new Set([
|
|
1104
1257
|
...symbols.paramNames,
|
|
1105
1258
|
...symbols.declared,
|
|
1106
1259
|
...symbols.nsBindings.keys(),
|
|
1107
1260
|
...symbols.localFnParams.keys(),
|
|
1261
|
+
...SCRIPT_UNIT_NAMES,
|
|
1108
1262
|
]),
|
|
1109
1263
|
].sort();
|
|
1264
|
+
// P6/D8: dimension 校验——如果有错误,抛出第一个(用 stage='dimension' 包装)
|
|
1265
|
+
if (allDimErrors.length > 0) {
|
|
1266
|
+
throw allDimErrors[0];
|
|
1267
|
+
}
|
|
1110
1268
|
return { lines, params, imports, functions, blocks, keep: symbols.keep, meta, terminalShapes, argSources, names };
|
|
1111
1269
|
}
|
|
1112
1270
|
function isLiteralValue(init) {
|
|
@@ -1206,6 +1364,14 @@ function classifyOpCall(call, line, vctx, opts) {
|
|
|
1206
1364
|
throw new ParseError('expected <ns>.<op>(...) or local function call', line, 'E_STATEMENT');
|
|
1207
1365
|
}
|
|
1208
1366
|
const positional = parsePositionalArgs(call.arguments ?? [], vctx, line);
|
|
1367
|
+
// P6/D8: dimension pass — check each argument against declared paramDims
|
|
1368
|
+
if (vctx.opDims && call.arguments) {
|
|
1369
|
+
// Build the full callee key for opDims lookup: 'cad.box' for default ns,
|
|
1370
|
+
// 'otherNs.box' for non-default ns, or just 'box' for local fn.
|
|
1371
|
+
const dimKey = namespace !== undefined ? `${namespace}.${callee}` : `${vctx.symbols.defaultNsName}.${callee}`;
|
|
1372
|
+
const localKey = local ? callee : dimKey;
|
|
1373
|
+
checkOpCallDims(call, local ? localKey : dimKey, vctx);
|
|
1374
|
+
}
|
|
1209
1375
|
const summary = buildLineSummary({
|
|
1210
1376
|
callee,
|
|
1211
1377
|
line,
|
|
@@ -1221,6 +1387,104 @@ function classifyOpCall(call, line, vctx, opts) {
|
|
|
1221
1387
|
attachAssemblySummary(summary);
|
|
1222
1388
|
return summary;
|
|
1223
1389
|
}
|
|
1390
|
+
/**
|
|
1391
|
+
* P6/D8: Check dimension constraints on op call arguments.
|
|
1392
|
+
*
|
|
1393
|
+
* Handles both object-form (`cad.box({ width: 10 * MM })`) and positional-form
|
|
1394
|
+
* (`cad.box(10 * MM, 10 * MM, 10 * MM)`). For positional form, uses slotMap
|
|
1395
|
+
* to map positional args to parameter names. For vec3 params, checks each element.
|
|
1396
|
+
*
|
|
1397
|
+
* Errors are accumulated in `ctx.dimErrors` (does not throw).
|
|
1398
|
+
*/
|
|
1399
|
+
function checkOpCallDims(call, calleeKey, ctx) {
|
|
1400
|
+
const decl = ctx.opDims?.[calleeKey];
|
|
1401
|
+
if (!decl || !decl.paramDims)
|
|
1402
|
+
return;
|
|
1403
|
+
const args = call.arguments ?? [];
|
|
1404
|
+
if (args.length === 0)
|
|
1405
|
+
return;
|
|
1406
|
+
const slotMap = decl.slotMap;
|
|
1407
|
+
const shapeArity = slotMap?.shapeArity ?? 0;
|
|
1408
|
+
// ── Object form ──
|
|
1409
|
+
// Look for an ObjectExpression arg that contains keys matching paramDims.
|
|
1410
|
+
// It could be at position `shapeArity` (with slotMap) or at any position
|
|
1411
|
+
// (without slotMap — e.g. fai_extrude(shape, { length: 5 })).
|
|
1412
|
+
if (slotMap) {
|
|
1413
|
+
const firstNonShape = args[shapeArity];
|
|
1414
|
+
if (firstNonShape?.type === 'ObjectExpression') {
|
|
1415
|
+
const hasDimKeys = firstNonShape.properties.some((p) => p?.type === 'Property' && p.key?.type === 'Identifier' && p.key.name in decl.paramDims);
|
|
1416
|
+
if (hasDimKeys) {
|
|
1417
|
+
for (const prop of firstNonShape.properties) {
|
|
1418
|
+
if (prop?.type !== 'Property')
|
|
1419
|
+
continue;
|
|
1420
|
+
const keyNode = prop.key;
|
|
1421
|
+
if (keyNode?.type !== 'Identifier')
|
|
1422
|
+
continue;
|
|
1423
|
+
const paramName = keyNode.name;
|
|
1424
|
+
const dim = decl.paramDims[paramName];
|
|
1425
|
+
if (!dim)
|
|
1426
|
+
continue;
|
|
1427
|
+
checkDim(prop.value, dim, ctx);
|
|
1428
|
+
if (slotMap.vec3Keys?.includes(paramName) && prop.value?.type === 'ArrayExpression') {
|
|
1429
|
+
for (const el of prop.value.elements ?? []) {
|
|
1430
|
+
if (el)
|
|
1431
|
+
checkDim(el, dim, ctx);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
// No slotMap — scan all args for ObjectExpression with dim keys
|
|
1441
|
+
for (const arg of args) {
|
|
1442
|
+
if (arg?.type !== 'ObjectExpression')
|
|
1443
|
+
continue;
|
|
1444
|
+
const hasDimKeys = arg.properties.some((p) => p?.type === 'Property' && p.key?.type === 'Identifier' && p.key.name in decl.paramDims);
|
|
1445
|
+
if (!hasDimKeys)
|
|
1446
|
+
continue;
|
|
1447
|
+
for (const prop of arg.properties) {
|
|
1448
|
+
if (prop?.type !== 'Property')
|
|
1449
|
+
continue;
|
|
1450
|
+
const keyNode = prop.key;
|
|
1451
|
+
if (keyNode?.type !== 'Identifier')
|
|
1452
|
+
continue;
|
|
1453
|
+
const paramName = keyNode.name;
|
|
1454
|
+
const dim = decl.paramDims[paramName];
|
|
1455
|
+
if (!dim)
|
|
1456
|
+
continue;
|
|
1457
|
+
checkDim(prop.value, dim, ctx);
|
|
1458
|
+
}
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
// ── Positional form ──
|
|
1463
|
+
if (!slotMap)
|
|
1464
|
+
return;
|
|
1465
|
+
const keys = slotMap.keys;
|
|
1466
|
+
const vec3Keys = new Set(slotMap.vec3Keys ?? []);
|
|
1467
|
+
let keyIdx = 0;
|
|
1468
|
+
for (let i = shapeArity; i < args.length && keyIdx < keys.length; i++) {
|
|
1469
|
+
const arg = args[i];
|
|
1470
|
+
if (arg?.type === 'ObjectExpression')
|
|
1471
|
+
break; // trailing options object
|
|
1472
|
+
const paramName = keys[keyIdx];
|
|
1473
|
+
const dim = decl.paramDims[paramName];
|
|
1474
|
+
keyIdx++;
|
|
1475
|
+
if (!dim)
|
|
1476
|
+
continue;
|
|
1477
|
+
if (vec3Keys.has(paramName) && arg?.type === 'ArrayExpression') {
|
|
1478
|
+
for (const el of arg.elements ?? []) {
|
|
1479
|
+
if (el)
|
|
1480
|
+
checkDim(el, dim, ctx);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
else if (arg) {
|
|
1484
|
+
checkDim(arg, dim, ctx);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1224
1488
|
/**
|
|
1225
1489
|
* P2-f4:装配语句摘要附加(cad.assembly / asmN.do_assemble / asmN.solve)。
|
|
1226
1490
|
*
|