@faicad/faijs 0.14.1 → 0.16.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.
Files changed (338) hide show
  1. package/dist/api/api-namespace.d.ts +7 -0
  2. package/dist/api/api-namespace.d.ts.map +1 -1
  3. package/dist/api/api-namespace.js +23 -22
  4. package/dist/api/api-namespace.js.map +1 -1
  5. package/dist/api/boolean.d.ts.map +1 -1
  6. package/dist/api/boolean.js +26 -8
  7. package/dist/api/boolean.js.map +1 -1
  8. package/dist/api/chamfer.d.ts.map +1 -1
  9. package/dist/api/chamfer.js +4 -1
  10. package/dist/api/chamfer.js.map +1 -1
  11. package/dist/api/generated/cad/script-face.d.ts +1 -1
  12. package/dist/api/generated/cad/script-face.d.ts.map +1 -1
  13. package/dist/api/generated/cad/script-face.js +1 -2
  14. package/dist/api/generated/cad/script-face.js.map +1 -1
  15. package/dist/api/generated/operations.js +16 -16
  16. package/dist/api/generated/operations.js.map +1 -1
  17. package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
  18. package/dist/api/generated/script-face-manifest.js +0 -1
  19. package/dist/api/generated/script-face-manifest.js.map +1 -1
  20. package/dist/api/generated/script-face.d.ts +1 -4
  21. package/dist/api/generated/script-face.d.ts.map +1 -1
  22. package/dist/api/generated/script-face.js +2 -3
  23. package/dist/api/generated/script-face.js.map +1 -1
  24. package/dist/api/generated/sketching.js +1 -1
  25. package/dist/api/generated/sketching.js.map +1 -1
  26. package/dist/api/generated/topology.d.ts +0 -8
  27. package/dist/api/generated/topology.d.ts.map +1 -1
  28. package/dist/api/generated/topology.js +19 -26
  29. package/dist/api/generated/topology.js.map +1 -1
  30. package/dist/api/index.d.ts +1 -10
  31. package/dist/api/index.d.ts.map +1 -1
  32. package/dist/api/index.js +10 -18
  33. package/dist/api/index.js.map +1 -1
  34. package/dist/api/internal/compat-projection.d.ts +3 -1
  35. package/dist/api/internal/compat-projection.d.ts.map +1 -1
  36. package/dist/api/internal/compat-projection.js +8 -5
  37. package/dist/api/internal/compat-projection.js.map +1 -1
  38. package/dist/api/internal/replica-role-table.d.ts +36 -0
  39. package/dist/api/internal/replica-role-table.d.ts.map +1 -0
  40. package/dist/api/internal/replica-role-table.js +73 -0
  41. package/dist/api/internal/replica-role-table.js.map +1 -0
  42. package/dist/api/internal/result-unwrap.d.ts +3 -1
  43. package/dist/api/internal/result-unwrap.d.ts.map +1 -1
  44. package/dist/api/internal/result-unwrap.js +2 -2
  45. package/dist/api/internal/result-unwrap.js.map +1 -1
  46. package/dist/api/occt-kernel-bridge.d.ts +105 -27
  47. package/dist/api/occt-kernel-bridge.d.ts.map +1 -1
  48. package/dist/api/occt-kernel-bridge.js +255 -41
  49. package/dist/api/occt-kernel-bridge.js.map +1 -1
  50. package/dist/api/pattern.d.ts.map +1 -1
  51. package/dist/api/pattern.js +25 -61
  52. package/dist/api/pattern.js.map +1 -1
  53. package/dist/api/replicate.d.ts +145 -0
  54. package/dist/api/replicate.d.ts.map +1 -0
  55. package/dist/api/replicate.js +351 -0
  56. package/dist/api/replicate.js.map +1 -0
  57. package/dist/api/revolve.d.ts +36 -0
  58. package/dist/api/revolve.d.ts.map +1 -0
  59. package/dist/api/revolve.js +165 -0
  60. package/dist/api/revolve.js.map +1 -0
  61. package/dist/api/surface/arg-spec.d.ts +8 -0
  62. package/dist/api/surface/arg-spec.d.ts.map +1 -1
  63. package/dist/api/surface/arg-spec.js +37 -36
  64. package/dist/api/surface/arg-spec.js.map +1 -1
  65. package/dist/api/surface/projection-overrides.d.ts.map +1 -1
  66. package/dist/api/surface/projection-overrides.js +6 -0
  67. package/dist/api/surface/projection-overrides.js.map +1 -1
  68. package/dist/boolean/deriveNormals.d.ts +5 -4
  69. package/dist/boolean/deriveNormals.d.ts.map +1 -1
  70. package/dist/boolean/deriveNormals.js +24 -7
  71. package/dist/boolean/deriveNormals.js.map +1 -1
  72. package/dist/brep/engine/adapters/brep-mock.d.ts.map +1 -1
  73. package/dist/brep/engine/adapters/brep-mock.js +58 -0
  74. package/dist/brep/engine/adapters/brep-mock.js.map +1 -1
  75. package/dist/brep/engine/adapters/brepkit.d.ts.map +1 -1
  76. package/dist/brep/engine/adapters/brepkit.js +39 -1
  77. package/dist/brep/engine/adapters/brepkit.js.map +1 -1
  78. package/dist/brep/engine/adapters/occt.d.ts +1 -5
  79. package/dist/brep/engine/adapters/occt.d.ts.map +1 -1
  80. package/dist/brep/engine/adapters/occt.js +232 -18
  81. package/dist/brep/engine/adapters/occt.js.map +1 -1
  82. package/dist/brep/engine/primitives.d.ts +79 -0
  83. package/dist/brep/engine/primitives.d.ts.map +1 -1
  84. package/dist/brep/engine/primitives.js.map +1 -1
  85. package/dist/brep/engine/types.d.ts +18 -1
  86. package/dist/brep/engine/types.d.ts.map +1 -1
  87. package/dist/brep/engine/types.js.map +1 -1
  88. package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
  89. package/dist/brepkit-kernel/brepkitKernel.js +81 -11
  90. package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
  91. package/dist/browser.d.ts +6 -28
  92. package/dist/browser.d.ts.map +1 -1
  93. package/dist/browser.js +6 -17
  94. package/dist/browser.js.map +1 -1
  95. package/dist/cad-runtime/admit-compat-lib.d.ts +13 -18
  96. package/dist/cad-runtime/admit-compat-lib.d.ts.map +1 -1
  97. package/dist/cad-runtime/admit-compat-lib.js +25 -18
  98. package/dist/cad-runtime/admit-compat-lib.js.map +1 -1
  99. package/dist/cad-runtime/backend-dispatch.d.ts +4 -2
  100. package/dist/cad-runtime/backend-dispatch.d.ts.map +1 -1
  101. package/dist/cad-runtime/backend-dispatch.js +2 -0
  102. package/dist/cad-runtime/backend-dispatch.js.map +1 -1
  103. package/dist/cad-runtime/browser-lib-loader.d.ts +4 -6
  104. package/dist/cad-runtime/browser-lib-loader.d.ts.map +1 -1
  105. package/dist/cad-runtime/browser-lib-loader.js +4 -9
  106. package/dist/cad-runtime/browser-lib-loader.js.map +1 -1
  107. package/dist/cad-runtime/ports.d.ts +0 -7
  108. package/dist/cad-runtime/ports.d.ts.map +1 -1
  109. package/dist/cad-runtime/ports.js.map +1 -1
  110. package/dist/cad-runtime/runtime.d.ts +1 -3
  111. package/dist/cad-runtime/runtime.d.ts.map +1 -1
  112. package/dist/cad-runtime/runtime.js +13 -18
  113. package/dist/cad-runtime/runtime.js.map +1 -1
  114. package/dist/env-agnostic.d.ts +38 -0
  115. package/dist/env-agnostic.d.ts.map +1 -0
  116. package/dist/env-agnostic.js +29 -0
  117. package/dist/env-agnostic.js.map +1 -0
  118. package/dist/fcstd/codegen.d.ts.map +1 -1
  119. package/dist/fcstd/codegen.js +8 -1
  120. package/dist/fcstd/codegen.js.map +1 -1
  121. package/dist/fcstd/expressions.d.ts +24 -0
  122. package/dist/fcstd/expressions.d.ts.map +1 -1
  123. package/dist/fcstd/expressions.js +91 -0
  124. package/dist/fcstd/expressions.js.map +1 -1
  125. package/dist/fcstd/feature-translate.d.ts.map +1 -1
  126. package/dist/fcstd/feature-translate.js +16 -2
  127. package/dist/fcstd/feature-translate.js.map +1 -1
  128. package/dist/fcstd/structural-types.d.ts.map +1 -1
  129. package/dist/fcstd/structural-types.js +5 -0
  130. package/dist/fcstd/structural-types.js.map +1 -1
  131. package/dist/index.d.ts +7 -24
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +10 -16
  134. package/dist/index.js.map +1 -1
  135. package/dist/lang/determinism-scanner.d.ts +9 -0
  136. package/dist/lang/determinism-scanner.d.ts.map +1 -1
  137. package/dist/lang/determinism-scanner.js +7 -1
  138. package/dist/lang/determinism-scanner.js.map +1 -1
  139. package/dist/lang/symbol-table.d.ts +31 -0
  140. package/dist/lang/symbol-table.d.ts.map +1 -1
  141. package/dist/lang/symbol-table.generated.d.ts +6 -16
  142. package/dist/lang/symbol-table.generated.d.ts.map +1 -1
  143. package/dist/lang/symbol-table.generated.js +6 -16
  144. package/dist/lang/symbol-table.generated.js.map +1 -1
  145. package/dist/lang/symbol-table.js +50 -1
  146. package/dist/lang/symbol-table.js.map +1 -1
  147. package/dist/mesh/creased-normals.d.ts +50 -0
  148. package/dist/mesh/creased-normals.d.ts.map +1 -0
  149. package/dist/mesh/creased-normals.js +114 -0
  150. package/dist/mesh/creased-normals.js.map +1 -0
  151. package/dist/mesh/decoration-provider.d.ts +53 -0
  152. package/dist/mesh/decoration-provider.d.ts.map +1 -0
  153. package/dist/mesh/decoration-provider.js +21 -0
  154. package/dist/mesh/decoration-provider.js.map +1 -0
  155. package/dist/mesh/engrave.d.ts +4 -0
  156. package/dist/mesh/engrave.d.ts.map +1 -1
  157. package/dist/mesh/engrave.js +22 -31
  158. package/dist/mesh/engrave.js.map +1 -1
  159. package/dist/mesh/index.d.ts +5 -14
  160. package/dist/mesh/index.d.ts.map +1 -1
  161. package/dist/mesh/index.js +7 -19
  162. package/dist/mesh/index.js.map +1 -1
  163. package/dist/mesh/io.d.ts +2 -1
  164. package/dist/mesh/io.d.ts.map +1 -1
  165. package/dist/mesh/io.js +5 -7
  166. package/dist/mesh/io.js.map +1 -1
  167. package/dist/mesh/primitives.d.ts +5 -18
  168. package/dist/mesh/primitives.d.ts.map +1 -1
  169. package/dist/mesh/primitives.js +4 -48
  170. package/dist/mesh/primitives.js.map +1 -1
  171. package/dist/mesh/stl-loader.d.ts +23 -0
  172. package/dist/mesh/stl-loader.d.ts.map +1 -0
  173. package/dist/mesh/stl-loader.js +139 -0
  174. package/dist/mesh/stl-loader.js.map +1 -0
  175. package/dist/module-resolver/parse-imports.d.ts +37 -0
  176. package/dist/module-resolver/parse-imports.d.ts.map +1 -0
  177. package/dist/module-resolver/parse-imports.js +39 -0
  178. package/dist/module-resolver/parse-imports.js.map +1 -0
  179. package/dist/module-resolver/resolver.d.ts +1 -1
  180. package/dist/module-resolver/resolver.js +2 -2
  181. package/dist/module-resolver/resolver.js.map +1 -1
  182. package/dist/node-host/cli.d.ts.map +1 -1
  183. package/dist/node-host/cli.js +1 -22
  184. package/dist/node-host/cli.js.map +1 -1
  185. package/dist/primitives/text/cjk-font.d.ts +42 -0
  186. package/dist/primitives/text/cjk-font.d.ts.map +1 -0
  187. package/dist/primitives/text/cjk-font.js +86 -0
  188. package/dist/primitives/text/cjk-font.js.map +1 -0
  189. package/dist/sdk.d.ts +2 -2
  190. package/dist/sdk.js +2 -2
  191. package/dist/sdk.js.map +1 -1
  192. package/dist/symbol-table.d.ts +15 -0
  193. package/dist/symbol-table.d.ts.map +1 -0
  194. package/dist/symbol-table.js +14 -0
  195. package/dist/symbol-table.js.map +1 -0
  196. package/dist/topology/naming/lineage-resolve.js +1 -1
  197. package/dist/topology/naming/lineage-resolve.js.map +1 -1
  198. package/dist/vendored/brepjs/kernel/index.d.ts +5 -0
  199. package/dist/vendored/brepjs/kernel/index.d.ts.map +1 -1
  200. package/dist/vendored/brepjs/kernel/index.js +11 -0
  201. package/dist/vendored/brepjs/kernel/index.js.map +1 -1
  202. package/dist/weapp.d.ts +3 -3
  203. package/dist/weapp.d.ts.map +1 -1
  204. package/dist/weapp.js +6 -3
  205. package/dist/weapp.js.map +1 -1
  206. package/package.json +16 -14
  207. package/dist/api/compat/index.d.ts +0 -173
  208. package/dist/api/compat/index.d.ts.map +0 -1
  209. package/dist/api/compat/index.js +0 -222
  210. package/dist/api/compat/index.js.map +0 -1
  211. package/dist/api/compound.d.ts +0 -110
  212. package/dist/api/compound.d.ts.map +0 -1
  213. package/dist/api/compound.js +0 -164
  214. package/dist/api/compound.js.map +0 -1
  215. package/dist/api/copy.d.ts +0 -40
  216. package/dist/api/copy.d.ts.map +0 -1
  217. package/dist/api/copy.js +0 -83
  218. package/dist/api/copy.js.map +0 -1
  219. package/dist/api/fai_drill.d.ts +0 -46
  220. package/dist/api/fai_drill.d.ts.map +0 -1
  221. package/dist/api/fai_drill.js +0 -243
  222. package/dist/api/fai_drill.js.map +0 -1
  223. package/dist/api/fai_extrude.d.ts +0 -36
  224. package/dist/api/fai_extrude.d.ts.map +0 -1
  225. package/dist/api/fai_extrude.js +0 -79
  226. package/dist/api/fai_extrude.js.map +0 -1
  227. package/dist/api/fai_split.d.ts +0 -46
  228. package/dist/api/fai_split.d.ts.map +0 -1
  229. package/dist/api/fai_split.js +0 -254
  230. package/dist/api/fai_split.js.map +0 -1
  231. package/dist/api/load.d.ts +0 -41
  232. package/dist/api/load.d.ts.map +0 -1
  233. package/dist/api/load.js +0 -84
  234. package/dist/api/load.js.map +0 -1
  235. package/dist/api/svgExtrude.d.ts +0 -32
  236. package/dist/api/svgExtrude.d.ts.map +0 -1
  237. package/dist/api/svgExtrude.js +0 -74
  238. package/dist/api/svgExtrude.js.map +0 -1
  239. package/dist/api/text.d.ts +0 -34
  240. package/dist/api/text.d.ts.map +0 -1
  241. package/dist/api/text.js +0 -101
  242. package/dist/api/text.js.map +0 -1
  243. package/dist/brep/text/fontTestHelper.d.ts +0 -30
  244. package/dist/brep/text/fontTestHelper.d.ts.map +0 -1
  245. package/dist/brep/text/fontTestHelper.js +0 -57
  246. package/dist/brep/text/fontTestHelper.js.map +0 -1
  247. package/dist/cad-runtime/module-executor.d.ts +0 -271
  248. package/dist/cad-runtime/module-executor.d.ts.map +0 -1
  249. package/dist/cad-runtime/module-executor.js +0 -630
  250. package/dist/cad-runtime/module-executor.js.map +0 -1
  251. package/dist/cad-runtime/terminal-dag.d.ts +0 -65
  252. package/dist/cad-runtime/terminal-dag.d.ts.map +0 -1
  253. package/dist/cad-runtime/terminal-dag.js +0 -167
  254. package/dist/cad-runtime/terminal-dag.js.map +0 -1
  255. package/dist/faqts/browser.d.ts +0 -16
  256. package/dist/faqts/browser.d.ts.map +0 -1
  257. package/dist/faqts/browser.js +0 -13
  258. package/dist/faqts/browser.js.map +0 -1
  259. package/dist/faqts/exec-blob.d.ts +0 -14
  260. package/dist/faqts/exec-blob.d.ts.map +0 -1
  261. package/dist/faqts/exec-blob.js +0 -22
  262. package/dist/faqts/exec-blob.js.map +0 -1
  263. package/dist/faqts/exec-node.d.ts +0 -15
  264. package/dist/faqts/exec-node.d.ts.map +0 -1
  265. package/dist/faqts/exec-node.js +0 -29
  266. package/dist/faqts/exec-node.js.map +0 -1
  267. package/dist/faqts/imports.d.ts +0 -74
  268. package/dist/faqts/imports.d.ts.map +0 -1
  269. package/dist/faqts/imports.js +0 -95
  270. package/dist/faqts/imports.js.map +0 -1
  271. package/dist/faqts/index.d.ts +0 -22
  272. package/dist/faqts/index.d.ts.map +0 -1
  273. package/dist/faqts/index.js +0 -21
  274. package/dist/faqts/index.js.map +0 -1
  275. package/dist/faqts/node.d.ts +0 -23
  276. package/dist/faqts/node.d.ts.map +0 -1
  277. package/dist/faqts/node.js +0 -23
  278. package/dist/faqts/node.js.map +0 -1
  279. package/dist/faqts/run.d.ts +0 -62
  280. package/dist/faqts/run.d.ts.map +0 -1
  281. package/dist/faqts/run.js +0 -62
  282. package/dist/faqts/run.js.map +0 -1
  283. package/dist/faqts/transform.d.ts +0 -43
  284. package/dist/faqts/transform.d.ts.map +0 -1
  285. package/dist/faqts/transform.js +0 -35
  286. package/dist/faqts/transform.js.map +0 -1
  287. package/dist/lang/compile.d.ts +0 -50
  288. package/dist/lang/compile.d.ts.map +0 -1
  289. package/dist/lang/compile.js +0 -368
  290. package/dist/lang/compile.js.map +0 -1
  291. package/dist/lang/keep.d.ts +0 -109
  292. package/dist/lang/keep.d.ts.map +0 -1
  293. package/dist/lang/keep.js +0 -226
  294. package/dist/lang/keep.js.map +0 -1
  295. package/dist/lang/parser.d.ts +0 -91
  296. package/dist/lang/parser.d.ts.map +0 -1
  297. package/dist/lang/parser.js +0 -1594
  298. package/dist/lang/parser.js.map +0 -1
  299. package/dist/mesh/drill-hole/DrillHoleCore.d.ts +0 -90
  300. package/dist/mesh/drill-hole/DrillHoleCore.d.ts.map +0 -1
  301. package/dist/mesh/drill-hole/DrillHoleCore.js +0 -287
  302. package/dist/mesh/drill-hole/DrillHoleCore.js.map +0 -1
  303. package/dist/mesh/drill-hole/drill-types.d.ts +0 -34
  304. package/dist/mesh/drill-hole/drill-types.d.ts.map +0 -1
  305. package/dist/mesh/drill-hole/drill-types.js +0 -7
  306. package/dist/mesh/drill-hole/drill-types.js.map +0 -1
  307. package/dist/mesh/fai_drill.d.ts +0 -21
  308. package/dist/mesh/fai_drill.d.ts.map +0 -1
  309. package/dist/mesh/fai_drill.js +0 -92
  310. package/dist/mesh/fai_drill.js.map +0 -1
  311. package/dist/mesh/fai_extrude.d.ts +0 -24
  312. package/dist/mesh/fai_extrude.d.ts.map +0 -1
  313. package/dist/mesh/fai_extrude.js +0 -42
  314. package/dist/mesh/fai_extrude.js.map +0 -1
  315. package/dist/mesh/fai_split.d.ts +0 -164
  316. package/dist/mesh/fai_split.d.ts.map +0 -1
  317. package/dist/mesh/fai_split.js +0 -269
  318. package/dist/mesh/fai_split.js.map +0 -1
  319. package/dist/primitives/svg-extrude.d.ts +0 -43
  320. package/dist/primitives/svg-extrude.d.ts.map +0 -1
  321. package/dist/primitives/svg-extrude.js +0 -78
  322. package/dist/primitives/svg-extrude.js.map +0 -1
  323. package/dist/primitives/text/cjk.d.ts +0 -61
  324. package/dist/primitives/text/cjk.d.ts.map +0 -1
  325. package/dist/primitives/text/cjk.js +0 -215
  326. package/dist/primitives/text/cjk.js.map +0 -1
  327. package/dist/primitives/text-geometry.d.ts +0 -47
  328. package/dist/primitives/text-geometry.d.ts.map +0 -1
  329. package/dist/primitives/text-geometry.js +0 -127
  330. package/dist/primitives/text-geometry.js.map +0 -1
  331. package/dist/test-helpers.d.ts +0 -31
  332. package/dist/test-helpers.d.ts.map +0 -1
  333. package/dist/test-helpers.js +0 -55
  334. package/dist/test-helpers.js.map +0 -1
  335. package/dist/vendored/brepjs/core/kernelReentrancy.d.ts +0 -41
  336. package/dist/vendored/brepjs/core/kernelReentrancy.d.ts.map +0 -1
  337. package/dist/vendored/brepjs/core/kernelReentrancy.js +0 -68
  338. package/dist/vendored/brepjs/core/kernelReentrancy.js.map +0 -1
@@ -1,1594 +0,0 @@
1
- /**
2
- * parser — 文本 → ScriptIR(合法 JS 子集,设计文档 §2)
3
- *
4
- * 执行模型:`.fai.js` 文本先用 acorn 解析(合法性证明 J-1),再 walk AST 还原为 ScriptIR。
5
- * 绝不 eval / new Function / import() 真执行。
6
- *
7
- * 支持的语法(合法 JS 子集):
8
- * - `// apiVersion: N` — 版本声明(可选,默认 1)
9
- * - `export default async (cad) => { ... }` — 唯一合法容器
10
- * - `const <name> = <literal>` → ParamDef(参数声明,右侧仅字面量)
11
- * - `const part<N>_v<M> = [await] cad.<op>(<inputVar>?, { ...args })` → StatementIR
12
- * - `cad.union/subtract/intersect(inputVar, inputVar)` → boolean op
13
- * - `cad.faceNormal(var)` / `cad.bboxCenter(var)` / ... → GeomRef
14
- * - `return { shape: part<N>_v<M>, name, color, metalness, roughness }` → ScriptIR.meta
15
- *
16
- * 禁止(acorn 抛错或 AST walk 拒绝即 ParseError):
17
- * - `param` / `with` 关键字、对象字面量用 `=`
18
- * - 循环 / 条件 / IIFE / try-catch / 模板字符串
19
- * - 多 default export、函数定义(除顶层箭头外)
20
- * - eval / new Function / 动态 import()
21
- */
22
- import { parse as acornParse } from 'acorn';
23
- import { isParamRef, isVarRef, isCallRef, isExprRef, splitPositionalOptions } from './types.js';
24
- import { asStmtId, asPartName, } from '../identity.js';
25
- // ── 解析错误 ──
26
- // ParseError / ParseErrorCode 的唯一家在 parse-error.ts(无 IR 方案下 MetadataExtractor
27
- // / SyntaxGate 复用同一类;此处重导出保持历史 import 面兼容)。
28
- import { ParseError } from './parse-error.js';
29
- import { fnv1a32 } from './fnv-hash.js';
30
- export { ParseError } from './parse-error.js';
31
- function getLine(node) {
32
- return node?.loc?.start?.line ?? 0;
33
- }
34
- /** 数值折叠:非有限值(NaN/Infinity)不是 JSON 安全字面量 → 不可折叠。 */
35
- function numFold(v, usedParam = false) {
36
- return Number.isFinite(v) ? { ok: true, value: v, usedParam } : { ok: false };
37
- }
38
- function numBin(a, b, op, usedParam = false) {
39
- return typeof a === 'number' && typeof b === 'number' ? numFold(op(a, b), usedParam) : { ok: false };
40
- }
41
- function cmpBin(a, b, op, usedParam = false) {
42
- if (typeof a === 'number' && typeof b === 'number')
43
- return { ok: true, value: op(a, b), usedParam };
44
- if (typeof a === 'string' && typeof b === 'string')
45
- return { ok: true, value: op(a, b), usedParam };
46
- return { ok: false };
47
- }
48
- /** JS `==` 的 JSON 值域近似(null/boolean/number/string,无对象)。 */
49
- function looseEq(a, b) {
50
- let x = a;
51
- let y = b;
52
- if (typeof x === 'boolean')
53
- x = x ? 1 : 0;
54
- if (typeof y === 'boolean')
55
- y = y ? 1 : 0;
56
- if (typeof x === 'number' && typeof y === 'string')
57
- return x === Number(y);
58
- if (typeof x === 'string' && typeof y === 'number')
59
- return Number(x) === y;
60
- if (x === null || y === null)
61
- return x === null && y === null;
62
- return x === y;
63
- }
64
- /**
65
- * 静态折叠求值:仅接受可由「字面量 + 参数值」算出的表达式。
66
- * 引用语句变量 / 嵌套调用 / 成员访问 → 不可折叠(返回 ok:false,调用方报 E_VALUE)。
67
- */
68
- function tryFoldConstExpr(node, paramNames, paramValues) {
69
- switch (node.type) {
70
- case 'Literal':
71
- return { ok: true, value: node.value, usedParam: false };
72
- case 'Identifier': {
73
- if (!paramNames.has(node.name))
74
- return { ok: false };
75
- const v = paramValues.get(node.name);
76
- return v === undefined ? { ok: false } : { ok: true, value: v, usedParam: true };
77
- }
78
- case 'UnaryExpression': {
79
- const operand = tryFoldConstExpr(node.argument, paramNames, paramValues);
80
- if (!operand.ok)
81
- return { ok: false };
82
- const v = operand.value;
83
- switch (node.operator) {
84
- case '-': return typeof v === 'number' ? numFold(-v, operand.usedParam) : { ok: false };
85
- case '+': return typeof v === 'number' ? numFold(+v, operand.usedParam) : { ok: false };
86
- case '!': return { ok: true, value: !v, usedParam: operand.usedParam };
87
- case '~': return typeof v === 'number' ? { ok: true, value: ~v, usedParam: operand.usedParam } : { ok: false };
88
- case 'typeof': return { ok: true, value: typeof v, usedParam: operand.usedParam };
89
- default: return { ok: false };
90
- }
91
- }
92
- case 'BinaryExpression': {
93
- const left = tryFoldConstExpr(node.left, paramNames, paramValues);
94
- if (!left.ok)
95
- return { ok: false };
96
- const right = tryFoldConstExpr(node.right, paramNames, paramValues);
97
- if (!right.ok)
98
- return { ok: false };
99
- const a = left.value;
100
- const b = right.value;
101
- const usedParam = left.usedParam || right.usedParam;
102
- switch (node.operator) {
103
- case '+': {
104
- if (typeof a === 'number' && typeof b === 'number')
105
- return numFold(a + b, usedParam);
106
- if (typeof a === 'string' || typeof b === 'string')
107
- return { ok: true, value: String(a) + String(b), usedParam };
108
- return { ok: false };
109
- }
110
- case '-': return numBin(a, b, (x, y) => x - y, usedParam);
111
- case '*': return numBin(a, b, (x, y) => x * y, usedParam);
112
- case '/': return numBin(a, b, (x, y) => x / y, usedParam);
113
- case '%': return numBin(a, b, (x, y) => x % y, usedParam);
114
- case '**': return numBin(a, b, (x, y) => x ** y, usedParam);
115
- case '<': return cmpBin(a, b, (x, y) => x < y, usedParam);
116
- case '<=': return cmpBin(a, b, (x, y) => x <= y, usedParam);
117
- case '>': return cmpBin(a, b, (x, y) => x > y, usedParam);
118
- case '>=': return cmpBin(a, b, (x, y) => x >= y, usedParam);
119
- case '==': return { ok: true, value: looseEq(a, b), usedParam };
120
- case '!=': return { ok: true, value: !looseEq(a, b), usedParam };
121
- case '===': return { ok: true, value: a === b, usedParam };
122
- case '!==': return { ok: true, value: a !== b, usedParam };
123
- default: return { ok: false };
124
- }
125
- }
126
- // acorn 把 && / || / ?? 解析为 LogicalExpression(ESTree 规范)
127
- case 'LogicalExpression': {
128
- const left = tryFoldConstExpr(node.left, paramNames, paramValues);
129
- if (!left.ok)
130
- return { ok: false };
131
- const right = tryFoldConstExpr(node.right, paramNames, paramValues);
132
- if (!right.ok)
133
- return { ok: false };
134
- const a = left.value;
135
- const usedParam = left.usedParam || right.usedParam;
136
- switch (node.operator) {
137
- case '&&': return { ok: true, value: a ? right.value : left.value, usedParam };
138
- case '||': return { ok: true, value: a ? left.value : right.value, usedParam };
139
- case '??': return { ok: true, value: a === null ? right.value : left.value, usedParam };
140
- default: return { ok: false };
141
- }
142
- }
143
- case 'TemplateLiteral': {
144
- const parts = [];
145
- const quasis = node.quasis;
146
- const exprs = node.expressions;
147
- let usedParam = false;
148
- for (let i = 0; i < quasis.length; i++) {
149
- const cooked = quasis[i].value?.cooked;
150
- parts.push(cooked ?? '');
151
- if (i < exprs.length) {
152
- const r = tryFoldConstExpr(exprs[i], paramNames, paramValues);
153
- if (!r.ok)
154
- return { ok: false };
155
- usedParam = usedParam || r.usedParam;
156
- parts.push(String(r.value));
157
- }
158
- }
159
- return { ok: true, value: parts.join(''), usedParam };
160
- }
161
- case 'ConditionalExpression': {
162
- const test = tryFoldConstExpr(node.test, paramNames, paramValues);
163
- if (!test.ok)
164
- return { ok: false };
165
- const chosen = tryFoldConstExpr(test.value ? node.consequent : node.alternate, paramNames, paramValues);
166
- if (!chosen.ok)
167
- return { ok: false };
168
- return { ok: true, value: chosen.value, usedParam: test.usedParam || chosen.usedParam };
169
- }
170
- default:
171
- return { ok: false };
172
- }
173
- }
174
- // ── ExprIR(运行时表达式,控制流放松方案 §3.3 / §4.3) ──
175
- // 折叠失败(引用了语句变量等)且节点 ∈ 白名单文法时,把表达式原文 + 引用名
176
- // 集合降级为 ExprIR,由编译期箭头包装发射、JS 引擎运行时求值(§5.4)。
177
- /**
178
- * ExprIR 白名单文法(§3.3,true-JS-subset 方案 D2 扩展):Literal / Identifier /
179
- * Unary / Binary / Logical / Conditional / Array(元素递归走白名单)/
180
- * **MemberExpression**(非可选链的全形态,computed 与否均收)/
181
- * **CallExpression**(方法调用 `a.b(c)` 归入 ExprIR;命名空间根调用在调用方
182
- * 先被截为 CallRefIR,不会到达此处)。
183
- *
184
- * ObjectExpression 不整体降级:对象属性值引用变量时本就是 VarRefIR(现状路径),
185
- * 属性值为折叠失败的表达式时由属性值递归各自降级为 ExprIR——整体降级语义等价
186
- * 且会误伤 args 顶层容器(`cad.op({ size: ... })` 的整个 args 对象不能被降级)。
187
- *
188
- * 命名空间限制(§4.2.2):ExprIR 文本由箭头包装在语句 fn 体内求值,命名空间
189
- * 绑定名(cad / import 绑定名)在该作用域不可见——含命名空间根调用的复合表达式
190
- * 由调用方(containsNsRootedCall 守卫)显式报 E_VALUE,不会进入 ExprIR。
191
- */
192
- function isExprWhitelist(node) {
193
- if (!node || typeof node !== 'object')
194
- return false;
195
- switch (node.type) {
196
- case 'Literal':
197
- case 'Identifier':
198
- return true;
199
- case 'UnaryExpression':
200
- return isExprWhitelist(node.argument);
201
- case 'BinaryExpression':
202
- case 'LogicalExpression':
203
- return isExprWhitelist(node.left) && isExprWhitelist(node.right);
204
- case 'ConditionalExpression':
205
- return (isExprWhitelist(node.test) &&
206
- isExprWhitelist(node.consequent) &&
207
- isExprWhitelist(node.alternate));
208
- case 'ArrayExpression':
209
- return node.elements.every((el) => el !== null && el !== undefined && isExprWhitelist(el));
210
- case 'MemberExpression':
211
- // 可选链(a?.b / a?.[b])不在子集内
212
- if (node.optional)
213
- return false;
214
- if (!isExprWhitelist(node.object))
215
- return false;
216
- return node.computed ? isExprWhitelist(node.property) : true;
217
- case 'CallExpression': {
218
- if (node.optional)
219
- return false;
220
- if (!isExprWhitelist(node.callee))
221
- return false;
222
- return node.arguments.every((a) => a.type !== 'SpreadElement' && isExprWhitelist(a));
223
- }
224
- default:
225
- return false;
226
- }
227
- }
228
- /**
229
- * 守卫:表达式树中是否含「命名空间根调用」(callee 链根标识符 ∈ nsNames 的
230
- * CallExpression)。这类子表达式无法在语句 fn 体的箭头包装作用域内解析
231
- * (命名空间经 ns.<name> 访问,非词法绑定)→ 调用方显式报 E_VALUE;
232
- * 整体作为单个实参的命名空间调用仍走 CallRefIR(现状路径,不受影响)。
233
- */
234
- function containsNsRootedCall(node, nsNames) {
235
- if (!node || typeof node !== 'object')
236
- return false;
237
- switch (node.type) {
238
- case 'CallExpression': {
239
- const root = rootIdentifierOfCallee(node.callee);
240
- if (root !== null && nsNames.has(root))
241
- return true;
242
- if (containsNsRootedCall(node.callee, nsNames))
243
- return true;
244
- return node.arguments.some((a) => containsNsRootedCall(a, nsNames));
245
- }
246
- case 'MemberExpression':
247
- return containsNsRootedCall(node.object, nsNames) ||
248
- (node.computed ? containsNsRootedCall(node.property, nsNames) : false);
249
- case 'UnaryExpression':
250
- return containsNsRootedCall(node.argument, nsNames);
251
- case 'BinaryExpression':
252
- case 'LogicalExpression':
253
- return containsNsRootedCall(node.left, nsNames) || containsNsRootedCall(node.right, nsNames);
254
- case 'ConditionalExpression':
255
- return (containsNsRootedCall(node.test, nsNames) ||
256
- containsNsRootedCall(node.consequent, nsNames) ||
257
- containsNsRootedCall(node.alternate, nsNames));
258
- case 'ArrayExpression':
259
- return node.elements.some((el) => containsNsRootedCall(el, nsNames));
260
- default:
261
- return false;
262
- }
263
- }
264
- /** 取 callee 链(MemberExpression.object 逐层下钻)的根标识符名;非链形态返回 null。 */
265
- function rootIdentifierOfCallee(callee) {
266
- let cur = callee;
267
- while (cur && typeof cur === 'object') {
268
- if (cur.type === 'Identifier')
269
- return cur.name;
270
- if (cur.type === 'MemberExpression') {
271
- cur = cur.object;
272
- continue;
273
- }
274
- return null;
275
- }
276
- return null;
277
- }
278
- /**
279
- * 预检:节点是否「需整体降级为 ExprIR」——数组内出现折叠失败(引用变量)且自身
280
- * ∈ 白名单的表达式元素。折叠成功 / 纯字面量 / 参数 / 变量引用 / 嵌套调用均返回
281
- * false(维持现状路径);未知标识符等非法形态也返回 false(由现状路径报错)。
282
- * 对象不整体降级(属性值各自递归降级,见 isExprWhitelist 注释)。
283
- */
284
- function needsExprFallback(node, paramNames, paramValues) {
285
- if (!node || typeof node !== 'object')
286
- return false;
287
- switch (node.type) {
288
- case 'Literal':
289
- case 'Identifier':
290
- case 'ObjectExpression':
291
- return false;
292
- case 'UnaryExpression':
293
- case 'BinaryExpression':
294
- case 'LogicalExpression':
295
- case 'TemplateLiteral':
296
- case 'ConditionalExpression': {
297
- if (tryFoldConstExpr(node, paramNames, paramValues).ok)
298
- return false;
299
- return isExprWhitelist(node);
300
- }
301
- case 'ArrayExpression':
302
- return node.elements.some((el) => el !== null && el !== undefined && needsExprFallback(el, paramNames, paramValues));
303
- default:
304
- return false;
305
- }
306
- }
307
- /**
308
- * 递归收集 ExprIR 引用的参数名 / 变量名:Identifier → 参数进 params(ctx 键)、
309
- * 已声明变量进 refs(VarRef 语义)、未知标识符 → E_REFERENCE(loose 模式透传为 partName)。
310
- */
311
- function collectExprIdentifiers(node, paramNames, varToId, line, params, refs, looseVars) {
312
- if (!node || typeof node !== 'object')
313
- return;
314
- switch (node.type) {
315
- case 'Identifier': {
316
- const name = node.name;
317
- if (paramNames.has(name)) {
318
- params.add(name);
319
- }
320
- else if (varToId.has(name)) {
321
- refs.add(String(varToId.get(name)));
322
- }
323
- else if (looseVars) {
324
- // Loose mode: external variable passes through as its physical PartName
325
- // (the append prefix is validated by the caller against the persistent ctx).
326
- refs.add(String(asPartName(name)));
327
- varToId.set(name, asPartName(name));
328
- }
329
- else {
330
- throw new ParseError(`unknown identifier "${name}" in expression`, getLine(node), 'E_REFERENCE');
331
- }
332
- return;
333
- }
334
- case 'UnaryExpression':
335
- collectExprIdentifiers(node.argument, paramNames, varToId, line, params, refs, looseVars);
336
- return;
337
- case 'BinaryExpression':
338
- case 'LogicalExpression':
339
- collectExprIdentifiers(node.left, paramNames, varToId, line, params, refs, looseVars);
340
- collectExprIdentifiers(node.right, paramNames, varToId, line, params, refs, looseVars);
341
- return;
342
- case 'ConditionalExpression':
343
- collectExprIdentifiers(node.test, paramNames, varToId, line, params, refs, looseVars);
344
- collectExprIdentifiers(node.consequent, paramNames, varToId, line, params, refs, looseVars);
345
- collectExprIdentifiers(node.alternate, paramNames, varToId, line, params, refs, looseVars);
346
- return;
347
- case 'ArrayExpression':
348
- for (const el of node.elements) {
349
- collectExprIdentifiers(el, paramNames, varToId, line, params, refs, looseVars);
350
- }
351
- return;
352
- case 'ObjectExpression':
353
- for (const prop of node.properties) {
354
- if (prop?.type !== 'Property')
355
- continue;
356
- // shorthand { w } 的 value 即 Identifier(w),直接收集即可
357
- collectExprIdentifiers(prop.value, paramNames, varToId, line, params, refs, looseVars);
358
- }
359
- return;
360
- case 'MemberExpression': {
361
- // 成员链:沿 .object 走到根标识符收为 ref(p0.solid → refs=['p0'];
362
- // p0.holes[0].center → refs=['p0']);computed 属性表达式递归收集。
363
- collectExprIdentifiers(node.object, paramNames, varToId, line, params, refs, looseVars);
364
- if (node.computed) {
365
- collectExprIdentifiers(node.property, paramNames, varToId, line, params, refs, looseVars);
366
- }
367
- return;
368
- }
369
- case 'CallExpression': {
370
- // 方法调用 a.b(c):callee 链与实参递归收集(命名空间根调用已被 buildExprIR 守卫拦截)
371
- collectExprIdentifiers(node.callee, paramNames, varToId, line, params, refs, looseVars);
372
- for (const a of node.arguments) {
373
- collectExprIdentifiers(a.type === 'SpreadElement' ? a.argument : a, paramNames, varToId, line, params, refs, looseVars);
374
- }
375
- return;
376
- }
377
- default:
378
- return;
379
- }
380
- }
381
- /** 构建 ExprIR:原文切片(acorn 坐标相对 sourceText)+ 引用名集合;置 hasComputedArgs。
382
- * 守卫:含命名空间根调用的表达式显式 E_VALUE(命名空间绑定在语句 fn 体箭头包装
383
- * 作用域内不可见,整体单实参的命名空间调用应走 CallRefIR)。 */
384
- function buildExprIR(node, paramNames, varToId, line, flags, looseVars, sourceText, nsNames) {
385
- if (containsNsRootedCall(node, nsNames)) {
386
- throw new ParseError('namespace calls are only supported as a whole argument value (CallRefIR), not inside a larger expression', line, 'E_VALUE');
387
- }
388
- const params = new Set();
389
- const refs = new Set();
390
- collectExprIdentifiers(node, paramNames, varToId, line, params, refs, looseVars);
391
- if (flags)
392
- flags.computed = true;
393
- return {
394
- $expr: {
395
- text: sourceText.slice(node.start, node.end),
396
- refs: [...refs],
397
- params: [...params],
398
- },
399
- };
400
- }
401
- /** 解析字面量 / 数组 / 对象 / ParamRefIR / GeomRef(F1:支持可静态折叠的表达式) */
402
- function parseValueExpr(node, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars = false, sourceText = '') {
403
- if (!node)
404
- throw new ParseError('missing value expression', line, 'E_VALUE');
405
- switch (node.type) {
406
- case 'Literal':
407
- return node.value;
408
- case 'Identifier': {
409
- // 裸标识符:参数变量 → ParamRefIR;已声明变量 → VarRefIR(A7 消灭后通用)
410
- const name = node.name;
411
- if (paramNames.has(name)) {
412
- const ref = { $param: name };
413
- return ref;
414
- }
415
- const varId = varToId.get(name);
416
- if (varId) {
417
- return { $ref: varId };
418
- }
419
- // Loose mode (append passes only the newest statements): an external
420
- // variable passes through as its physical PartName; the caller validates
421
- // it against the persistent ctx (AppendPrefixError when missing).
422
- if (looseVars) {
423
- const external = asPartName(name);
424
- varToId.set(name, external);
425
- return { $ref: external };
426
- }
427
- throw new ParseError(`unknown identifier "${name}" in args value (not a declared param or variable)`, line, 'E_REFERENCE');
428
- }
429
- case 'ArrayExpression': {
430
- // ExprIR 降级:数组含折叠失败(引用变量)的白名单表达式元素 → 整体作为运行时表达式
431
- if (needsExprFallback(node, paramNames, paramValues)) {
432
- return buildExprIR(node, paramNames, varToId, line, flags, looseVars, sourceText, nsNames);
433
- }
434
- const out = [];
435
- for (const el of node.elements) {
436
- if (el === null || el === undefined) {
437
- throw new ParseError('array holes are not supported in args', line, 'E_VALUE');
438
- }
439
- if (el.type === 'SpreadElement') {
440
- // [...parts]:parts 须为可折叠的数组参数/字面量
441
- const r = tryFoldConstExpr(el.argument, paramNames, paramValues);
442
- if (!r.ok || !Array.isArray(r.value)) {
443
- throw new ParseError('cannot statically evaluate spread in args (must reference an array param or literal)', line, 'E_VALUE');
444
- }
445
- if (flags)
446
- flags.computed = true;
447
- out.push(...r.value);
448
- }
449
- else {
450
- out.push(parseValueExpr(el, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, sourceText));
451
- }
452
- }
453
- return out;
454
- }
455
- case 'ObjectExpression': {
456
- // ExprIR 降级:对象含折叠失败(引用变量)的白名单表达式属性值 → 整体作为运行时表达式
457
- if (needsExprFallback(node, paramNames, paramValues)) {
458
- return buildExprIR(node, paramNames, varToId, line, flags, looseVars, sourceText, nsNames);
459
- }
460
- const obj = {};
461
- for (const prop of node.properties) {
462
- if (prop.type === 'SpreadElement') {
463
- // {...opts}:opts 须为可折叠的对象参数/字面量
464
- const r = tryFoldConstExpr(prop.argument, paramNames, paramValues);
465
- if (!r.ok || typeof r.value !== 'object' || r.value === null || Array.isArray(r.value)) {
466
- throw new ParseError('cannot statically evaluate object spread in args', line, 'E_VALUE');
467
- }
468
- if (flags)
469
- flags.computed = true;
470
- Object.assign(obj, r.value);
471
- continue;
472
- }
473
- // shorthand property: { size } → { size: size }
474
- const key = prop.key.type === 'Identifier' ? prop.key.name
475
- : prop.key.type === 'Literal' ? String(prop.key.value)
476
- : null;
477
- if (key === null) {
478
- throw new ParseError(`invalid object key`, line, 'E_VALUE');
479
- }
480
- // shorthand: { size } → value is the same identifier
481
- if (prop.shorthand) {
482
- if (paramNames.has(key)) {
483
- obj[key] = { $param: key };
484
- }
485
- else {
486
- throw new ParseError(`unknown shorthand identifier "${key}" (not a declared param)`, line, 'E_REFERENCE');
487
- }
488
- }
489
- else {
490
- obj[key] = parseValueExpr(prop.value, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, sourceText);
491
- }
492
- }
493
- return obj;
494
- }
495
- case 'CallExpression': {
496
- // 嵌套调用 → CallRefIR(A7 消灭后任意 <ns>.<ident>(...) 都合法;F2 放开命名空间)
497
- const callee = node.callee;
498
- if (callee?.type === 'MemberExpression' &&
499
- callee.object?.type === 'Identifier' &&
500
- nsNames.has(callee.object.name) &&
501
- callee.property?.type === 'Identifier') {
502
- const nsName = callee.object.name;
503
- const innerCallee = callee.property.name;
504
- const innerArgs = node.arguments.map((a) => parseValueExpr(a, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, sourceText));
505
- return {
506
- $call: {
507
- callee: innerCallee,
508
- args: innerArgs,
509
- ...(nsName !== defaultNsName ? { namespace: nsName } : {}),
510
- },
511
- };
512
- }
513
- // D2 扩展:非命名空间调用(方法调用 a.b(c) / 裸调用)∈ 白名单 → ExprIR;
514
- // 白名单外(new/箭头/可选链等红线或语义空白)→ E_VALUE(显式禁止而非默认拒绝)
515
- if (isExprWhitelist(node)) {
516
- return buildExprIR(node, paramNames, varToId, line, flags, looseVars, sourceText, nsNames);
517
- }
518
- throw new ParseError('nested calls in args must be <ns>.<ident>(...) or a whitelisted expression', line, 'E_VALUE');
519
- }
520
- // D2 扩展:成员访问(p0.solid / p0.holes[0].center)∈ 白名单 → ExprIR(原文切片运行时求值)
521
- case 'MemberExpression': {
522
- if (isExprWhitelist(node)) {
523
- return buildExprIR(node, paramNames, varToId, line, flags, looseVars, sourceText, nsNames);
524
- }
525
- throw new ParseError(`unsupported member expression in args: optional chaining is not allowed`, line, 'E_VALUE');
526
- }
527
- // F1 扩展:可静态折叠的表达式(无控制流 → 参数值已知,编译期求值)
528
- case 'UnaryExpression':
529
- case 'BinaryExpression':
530
- case 'LogicalExpression':
531
- case 'TemplateLiteral':
532
- case 'ConditionalExpression': {
533
- const r = tryFoldConstExpr(node, paramNames, paramValues);
534
- if (!r.ok) {
535
- // ExprIR 降级:折叠失败(引用了语句变量等)且节点 ∈ 白名单文法
536
- // → 运行时求值(§3.3 / §4.3);白名单外 → E_VALUE
537
- if (isExprWhitelist(node)) {
538
- return buildExprIR(node, paramNames, varToId, line, flags, looseVars, sourceText, nsNames);
539
- }
540
- throw new ParseError(`cannot statically evaluate ${node.type} in args (must reference declared params or literals)`, line, 'E_VALUE');
541
- }
542
- // F1-E1:仅当折叠表达式引用了已声明的参数才标 computed。
543
- // 纯字面量(含负数字面量,acorn 用 UnaryExpression 表示 -10)不标 computed,
544
- // 避免宿主把普通字面量参数误判为「计算参数」而降级为只读(时间线无法回填编辑)。
545
- if (flags && r.usedParam)
546
- flags.computed = true;
547
- return r.value;
548
- }
549
- default:
550
- throw new ParseError(`unsupported value expression: ${node.type}`, line, 'E_VALUE');
551
- }
552
- }
553
- // ── 位置实参解析(true-JS-subset 方案 §4.2.1:五种顶层调用形态统一入口) ──
554
- /**
555
- * 解析一条顶层调用语句的全部位置实参(true-JS-subset 方案 §4.2.1)。
556
- *
557
- * 位置实参槽接受任意合法 JS 表达式:变量引用(Identifier → VarRefIR,参数也在
558
- * varToId 中)、字面量、数组、对象(尾随对象同时投影为 args 选项槽)、
559
- * `<ns>.<fn>(...)` 嵌套调用(CallRefIR)、可折叠运算、白名单表达式(ExprIR,
560
- * 含 MemberExpression / 方法调用)。SpreadElement 仅接受可静态折叠的数组参数/
561
- * 字面量(展开为多个位置实参)。红线形态(new / 箭头函数 / await 等)由
562
- * parseValueExpr 的 default 分支显式 E_VALUE。
563
- *
564
- * @param argNodes - the raw AST argument nodes of the call.
565
- * @param label - error-message context (e.g. 'inputs', 'destructuring inputs', 'myFn() call').
566
- * @returns the positional ArgIR list in call order.
567
- */
568
- function parsePositionalArgs(argNodes, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, sourceText, label) {
569
- const out = [];
570
- for (const argNode of argNodes) {
571
- if (argNode.type === 'Identifier') {
572
- // 位置变量引用:varToId 统一解析(参数声明也在表中,与原 inputs 语义一致)
573
- const inputId = varToId.get(argNode.name);
574
- if (inputId) {
575
- out.push({ $ref: inputId });
576
- continue;
577
- }
578
- // Loose mode: external variable passes through as its physical PartName
579
- // (the append prefix is validated by the caller against the persistent ctx).
580
- if (looseVars) {
581
- const external = asPartName(argNode.name);
582
- varToId.set(argNode.name, external);
583
- out.push({ $ref: external });
584
- continue;
585
- }
586
- throw new ParseError(`unknown variable "${argNode.name}" in ${label}`, getLine(argNode), 'E_REFERENCE');
587
- }
588
- if (argNode.type === 'SpreadElement') {
589
- // [...parts]:parts 须为可折叠的数组参数/字面量(展开为多个位置实参)
590
- const r = tryFoldConstExpr(argNode.argument, paramNames, paramValues);
591
- if (!r.ok || !Array.isArray(r.value)) {
592
- throw new ParseError('cannot statically evaluate spread in arguments (must reference an array param or literal)', line, 'E_VALUE');
593
- }
594
- flags.computed = true;
595
- out.push(...r.value);
596
- continue;
597
- }
598
- out.push(parseValueExpr(argNode, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, sourceText));
599
- }
600
- return out;
601
- }
602
- /**
603
- * 解析一条 `const varName = [await] <ns>.<op>(...)` 语句。
604
- */
605
- /**
606
- * ABI 绑定校验(§3.6 / D11 / true-JS-subset §4.2.4):本机函数调用 `myFn(a1..aM, { key1: v1, ... })`,
607
- * 设形参表 [p1..pk]:
608
- * 1. 位置实参 a1..aM 按序绑定 p1..pM(M ≤ k,超出 → E_ARG);位置实参可为任意
609
- * 合法 JS 表达式形态(只数个数,不再要求 Identifier——true-JS-subset 放开);
610
- * 2. 尾部对象键绑定剩余形参 p_{M+1}..p_k(按名);未知键 / 与位置占用冲突 → E_ARG;
611
- * 3. 未被绑定的形参 → undefined(JS 语义,不报错);
612
- * 4. keep/keepHidden 键先剥离,不参与形参校验(§3.6 第 4 条)。
613
- * @param callee - 函数名(错误消息用)。
614
- * @param params - 函数形参表(来自 parseScript 预扫描的 localFnParams)。
615
- * @param positionalCount - 位置实参个数(M)。
616
- * @param args - 尾部对象键值(keep/keepHidden 可能仍在其中,校验时跳过)。
617
- * @param line - 报错行号。
618
- */
619
- function validateLocalAbi(callee, params, positionalCount, args, line) {
620
- const M = positionalCount;
621
- if (M > params.length) {
622
- throw new ParseError(`function "${callee}" takes at most ${params.length} positional argument(s), got ${M}`, line, 'E_ARG');
623
- }
624
- for (const key of Object.keys(args)) {
625
- if (key === 'keep' || key === 'keepHidden')
626
- continue;
627
- const idx = params.indexOf(key);
628
- if (idx === -1) {
629
- throw new ParseError(`function "${callee}" has no parameter named "${key}"`, line, 'E_ARG');
630
- }
631
- if (idx < M) {
632
- throw new ParseError(`function "${callee}" parameter "${key}" is already bound by a positional argument`, line, 'E_ARG');
633
- }
634
- }
635
- }
636
- function parseCadStatement(declNode, paramNames, paramValues, varToId, nsNames, defaultNsName, looseVars = false, sourceText = '', localFnParams, looseLocalCalls = false) {
637
- const line = getLine(declNode);
638
- // 提取变量名
639
- if (declNode.id?.type !== 'Identifier') {
640
- throw new ParseError('expected identifier on left side of const declaration', line, 'E_STATEMENT');
641
- }
642
- const varName = declNode.id.name;
643
- // 提取 init(可能包在 AwaitExpression 里)
644
- let init = declNode.init;
645
- if (init?.type === 'AwaitExpression') {
646
- init = init.argument;
647
- }
648
- // init 必须是 CallExpression
649
- if (init?.type !== 'CallExpression') {
650
- throw new ParseError(`expected <ns>.<op>(...) or local function call, got ${init?.type ?? 'null'}`, line, 'E_STATEMENT');
651
- }
652
- const callee = init.callee;
653
- // 命名空间调用:callee 是 MemberExpression <ns>.<op>(cad 或顶层 import 绑定名,F2 消灭硬编码)
654
- const isNsCall = callee?.type === 'MemberExpression' &&
655
- callee.object?.type === 'Identifier' &&
656
- nsNames.has(callee.object.name) &&
657
- callee.property?.type === 'Identifier';
658
- // 本机函数调用:callee 是裸标识符且 ∈ 脚本函数集(§3.4 / §4.2)
659
- const isLocalCall = callee?.type === 'Identifier' && (localFnParams?.has(callee.name) ?? false);
660
- // 宽松本机调用(单行提取 codeToArgs):裸 callee 不在函数集时也放行,
661
- // 视为 local 调用候选(ABI 形态未知 → 跳过绑定校验),由调用方在完整上下文校验。
662
- const looseLocalCall = !isLocalCall && looseLocalCalls && callee?.type === 'Identifier';
663
- if (!isNsCall && !isLocalCall && !looseLocalCall) {
664
- // 裸 callee 且不在函数集 → 「函数不存在」(E_REFERENCE,§3.4 / D15);其余形态 → E_STATEMENT
665
- if (callee?.type === 'Identifier') {
666
- throw new ParseError(`function "${callee.name}" does not exist in this script`, line, 'E_REFERENCE');
667
- }
668
- throw new ParseError('expected <ns>.<op>(...) or local function call', line, 'E_STATEMENT');
669
- }
670
- const nsName = isNsCall ? callee.object.name : undefined;
671
- const opName = isNsCall ? callee.property.name : callee.name;
672
- const local = isLocalCall || looseLocalCall;
673
- // 位置实参统一解析(true-JS-subset §4.2.1):变量/字面量/对象/嵌套调用/表达式任意混排;
674
- // 尾随纯对象同时投影为 args 选项槽。不再有"args = parsed 覆盖赋值"静默坑(D5:多对象按位置如实传递)。
675
- const flags = { computed: false };
676
- const positional = parsePositionalArgs(init.arguments, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, sourceText, 'inputs');
677
- const args = splitPositionalOptions(positional).named;
678
- // 本机调用:ABI 绑定校验(§3.6,parse 期拦截 E_ARG)
679
- // 位置实参个数校验(形态校验已删——任意合法 JS 表达式均可占位,§4.2.4)。
680
- // 宽松本机调用(codeToArgs 单行提取)形参未知 → 跳过绑定校验,由调用方在完整上下文校验。
681
- if (local && localFnParams && !looseLocalCall) {
682
- const { values, named } = splitPositionalOptions(positional);
683
- validateLocalAbi(opName, localFnParams.get(opName) ?? [], values.length, named, line);
684
- }
685
- // Phase 3:id 不再赋变量名,由最终遍历赋 sN;变量名记录到 declaredOutputs
686
- const stmt = {
687
- id: asStmtId('__pending__'),
688
- callee: opName,
689
- args,
690
- positional,
691
- outputs: [],
692
- hasAssignment: true,
693
- ...(local ? { local: true } : nsName !== defaultNsName ? { namespace: nsName } : {}),
694
- ...(flags.computed ? { hasComputedArgs: true } : {}),
695
- };
696
- return { stmt, varName, declaredOutputs: [varName] };
697
- }
698
- /**
699
- * 解析 `const { k1: v1, k2: v2 } = [await] <ns>.<any>(...)` 或本机函数调用(§3.4)。
700
- *
701
- * 任意键数(1..N)、任意 callee(不再限 split/front/back);本机函数调用走 local 分支。
702
- */
703
- function parseDestructuring(declNode, paramNames, paramValues, varToId, nsNames, defaultNsName, looseVars = false, sourceText = '', localFnParams, looseLocalCalls = false) {
704
- const line = getLine(declNode);
705
- // id 必须是 ObjectPattern
706
- if (declNode.id?.type !== 'ObjectPattern') {
707
- throw new ParseError('expected object pattern for destructuring', line, 'E_STATEMENT');
708
- }
709
- const props = declNode.id.properties;
710
- if (props.length === 0) {
711
- throw new ParseError('destructuring must have at least one property', line, 'E_STATEMENT');
712
- }
713
- const keys = [];
714
- const valueNames = [];
715
- for (const prop of props) {
716
- if (prop.type !== 'Property' || prop.key?.type !== 'Identifier') {
717
- throw new ParseError('destructuring properties must be identifiers', line, 'E_STATEMENT');
718
- }
719
- if (prop.value?.type !== 'Identifier') {
720
- throw new ParseError(`destructuring value for "${prop.key.name}" must be an identifier`, line, 'E_STATEMENT');
721
- }
722
- keys.push(prop.key.name);
723
- valueNames.push(prop.value.name);
724
- }
725
- // 提取 init(可包 AwaitExpression),必须是调用(命名空间或本机函数)
726
- let init = declNode.init;
727
- if (init?.type === 'AwaitExpression') {
728
- init = init.argument;
729
- }
730
- if (init?.type !== 'CallExpression') {
731
- throw new ParseError(`expected <ns>.<op>(...) or local function call in destructuring, got ${init?.type ?? 'null'}`, line, 'E_STATEMENT');
732
- }
733
- const callee = init.callee;
734
- const isNsCall = callee?.type === 'MemberExpression' &&
735
- callee.object?.type === 'Identifier' &&
736
- nsNames.has(callee.object.name) &&
737
- callee.property?.type === 'Identifier';
738
- const isLocalCall = callee?.type === 'Identifier' && (localFnParams?.has(callee.name) ?? false);
739
- // 宽松本机调用(单行提取 codeToArgs):裸 callee 不在函数集时也放行,
740
- // 视为 local 调用候选(ABI 形态未知 → 跳过绑定校验),由调用方在完整上下文校验。
741
- const looseLocalCall = !isLocalCall && looseLocalCalls && callee?.type === 'Identifier';
742
- if (!isNsCall && !isLocalCall && !looseLocalCall) {
743
- if (callee?.type === 'Identifier') {
744
- throw new ParseError(`function "${callee.name}" does not exist in this script`, line, 'E_REFERENCE');
745
- }
746
- throw new ParseError('destructuring is only allowed for <ns>.<op>(...) or local function calls', line, 'E_STATEMENT');
747
- }
748
- const nsName = isNsCall ? callee.object.name : undefined;
749
- const opName = isNsCall ? callee.property.name : callee.name;
750
- const local = isLocalCall || looseLocalCall;
751
- // 位置实参统一解析(true-JS-subset §4.2.1);尾随纯对象同时投影为 args 选项槽。
752
- // 旧 Object.assign 合并已删(D5:多对象按位置如实传递,不合并不覆盖)。
753
- const flags = { computed: false };
754
- const positional = parsePositionalArgs(init.arguments, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, sourceText, 'destructuring inputs');
755
- const args = splitPositionalOptions(positional).named;
756
- // 本机调用:ABI 绑定校验(§3.6,parse 期拦截 E_ARG)
757
- // 宽松本机调用(codeToArgs 单行提取)形参未知 → 跳过绑定校验,由调用方在完整上下文校验。
758
- if (local && localFnParams && !looseLocalCall) {
759
- const { values, named } = splitPositionalOptions(positional);
760
- validateLocalAbi(opName, localFnParams.get(opName) ?? [], values.length, named, line);
761
- }
762
- // Phase 3:id 不再赋变量名;outputs 在最终遍历经 varToId 解析后写入
763
- const stmt = {
764
- id: asStmtId('__pending__'),
765
- callee: opName,
766
- args,
767
- positional,
768
- outputs: [],
769
- outputKeys: keys,
770
- hasAssignment: true,
771
- ...(local ? { local: true } : nsName !== defaultNsName ? { namespace: nsName } : {}),
772
- ...(flags.computed ? { hasComputedArgs: true } : {}),
773
- };
774
- return { stmt, valueNames, declaredOutputs: valueNames };
775
- }
776
- // ── meta 解析 ──
777
- /**
778
- * 解析 return 语句,提取 terminal shape(s) 和 meta。
779
- *
780
- * 支持三种形式:
781
- * - `return part0`(裸标识符)→ 单终端,无 meta
782
- * - `return { shape: part0, name, color, ... }`(单终端 + meta)
783
- * - `return [ { shape: part1, name, ... }, { shape: part2, name, ... } ]`(多终端)
784
- */
785
- function parseReturnStatement(node, varToId) {
786
- const line = getLine(node);
787
- const arg = node.argument;
788
- if (!arg) {
789
- return { terminalShapeId: null, meta: undefined, terminalShapes: undefined };
790
- }
791
- // return part0(裸标识符)
792
- if (arg.type === 'Identifier') {
793
- const ref = varToId.get(arg.name);
794
- if (!ref) {
795
- throw new ParseError(`unknown variable "${arg.name}" in return`, line);
796
- }
797
- return { terminalShapeId: ref, meta: undefined, terminalShapes: undefined };
798
- }
799
- // return { shape: part0, name, color, ... }
800
- if (arg.type === 'ObjectExpression') {
801
- const result = parseReturnObject(arg, varToId, line);
802
- return { terminalShapeId: result.id, meta: result.meta, terminalShapes: undefined };
803
- }
804
- // return [ { shape: ... }, ... ](多终端)
805
- if (arg.type === 'ArrayExpression') {
806
- const terminalShapes = [];
807
- for (const elem of arg.elements) {
808
- if (elem?.type !== 'ObjectExpression') {
809
- throw new ParseError('return array elements must be objects { shape, ... }', line);
810
- }
811
- const result = parseReturnObject(elem, varToId, line);
812
- if (result.id) {
813
- terminalShapes.push({ id: result.id, meta: result.meta });
814
- }
815
- }
816
- if (terminalShapes.length === 0) {
817
- throw new ParseError('return array must have at least one element', line);
818
- }
819
- // 单元素数组等价为单终端
820
- if (terminalShapes.length === 1) {
821
- return { terminalShapeId: terminalShapes[0].id, meta: terminalShapes[0].meta, terminalShapes: undefined };
822
- }
823
- return { terminalShapeId: null, meta: undefined, terminalShapes };
824
- }
825
- throw new ParseError(`unsupported return expression: ${arg.type}`, line);
826
- }
827
- /** 解析单个 return 元素 { shape: varName, name?, color?, metalness?, roughness? } */
828
- function parseReturnObject(objNode, varToId, line) {
829
- let id = null;
830
- const meta = {};
831
- for (const prop of objNode.properties) {
832
- const key = prop.key?.type === 'Identifier' ? prop.key.name
833
- : prop.key?.type === 'Literal' ? String(prop.key.value)
834
- : null;
835
- if (!key)
836
- continue;
837
- if (key === 'shape') {
838
- if (prop.value?.type === 'Identifier') {
839
- const ref = varToId.get(prop.value.name);
840
- if (!ref) {
841
- throw new ParseError(`unknown variable "${prop.value.name}" in return shape`, line);
842
- }
843
- id = ref;
844
- }
845
- }
846
- else if (key === 'name') {
847
- if (prop.value?.type === 'Literal') {
848
- meta.name = prop.value.value;
849
- }
850
- }
851
- else if (key === 'color') {
852
- if (prop.value?.type === 'Literal') {
853
- if (!meta.appearance)
854
- meta.appearance = {};
855
- meta.appearance.color = prop.value.value;
856
- }
857
- }
858
- else if (key === 'metalness') {
859
- if (prop.value?.type === 'Literal') {
860
- if (!meta.appearance)
861
- meta.appearance = {};
862
- meta.appearance.metalness = prop.value.value;
863
- }
864
- }
865
- else if (key === 'roughness') {
866
- if (prop.value?.type === 'Literal') {
867
- if (!meta.appearance)
868
- meta.appearance = {};
869
- meta.appearance.roughness = prop.value.value;
870
- }
871
- }
872
- }
873
- return { id, meta: Object.keys(meta).length > 0 ? meta : undefined };
874
- }
875
- // ── 引用收集(Phase 1: VM 执行 deps 计算) ──
876
- /** 递归收集 ArgIR 中的变量引用:$param → 参数名;$ref → 变量名;$call → 递归收集内部 args;$expr → refs/params 并入(§4.3)。 */
877
- function collectRefsFromArg(value, out) {
878
- if (value === null || value === undefined)
879
- return;
880
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
881
- return;
882
- if (isParamRef(value)) {
883
- out.add(value.$param);
884
- return;
885
- }
886
- if (isVarRef(value)) {
887
- out.add(value.$ref);
888
- return;
889
- }
890
- if (isCallRef(value)) {
891
- for (const a of value.$call.args)
892
- collectRefsFromArg(a, out);
893
- return;
894
- }
895
- if (isExprRef(value)) {
896
- for (const p of value.$expr.params)
897
- out.add(p);
898
- for (const r of value.$expr.refs)
899
- out.add(r);
900
- return;
901
- }
902
- if (Array.isArray(value)) {
903
- for (const v of value)
904
- collectRefsFromArg(v, out);
905
- return;
906
- }
907
- if (typeof value === 'object') {
908
- for (const v of Object.values(value))
909
- collectRefsFromArg(v, out);
910
- }
911
- }
912
- /**
913
- * 收集单条语句引用的全部变量名(positional + args 中 $param / $ref / 嵌套调用 /
914
- * ExprIR refs/params + receiver)。positional 与 args 都扫:args 是尾随对象投影
915
- * (真源 positional 已含它,重复扫描幂等);手工构造 IR 可能只填 args。
916
- * 存入 stmt.refs,编译期据此翻译为 deps(定义这些变量的语句 id)。
917
- */
918
- function collectStatementRefs(stmt) {
919
- const refs = new Set();
920
- for (const arg of stmt.positional)
921
- collectRefsFromArg(arg, refs);
922
- for (const arg of Object.values(stmt.args))
923
- collectRefsFromArg(arg, refs);
924
- // receiver:成员方法调用(add_constraint/do_assemble 等)依赖其 receiver 变量,
925
- // 不加入则成员调用对 compound 的依赖边缺失(连带 bug,见命名分层修复文档 §2.4)。
926
- if (stmt.receiver)
927
- refs.add(stmt.receiver);
928
- return [...refs];
929
- }
930
- // ── 语句黑名单(F1:白名单 → 黑名单) ──
931
- /** 控制流语句(faijs 唯一禁令,V1.5 专用错误码)。 */
932
- const CONTROL_FLOW_TYPES = new Set([
933
- 'IfStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement',
934
- 'WhileStatement', 'DoWhileStatement', 'SwitchStatement', 'TryStatement',
935
- 'ThrowStatement', 'BreakStatement', 'ContinueStatement', 'LabeledStatement',
936
- 'WithStatement',
937
- ]);
938
- /** 文本仅含注释与空白(import 头部连续段约束用)。 */
939
- function onlyWhitespaceAndComments(text) {
940
- return text
941
- .replace(/\/\*[\s\S]*?\*\//g, '')
942
- .replace(/\/\/[^\n]*/g, '')
943
- .trim() === '';
944
- }
945
- /** 文本行数(空文本 = 0;含 n 个换行的文本 = n+1 行)。 */
946
- function countLines(text) {
947
- if (text === '')
948
- return 0;
949
- return (text.match(/\n/g) ?? []).length + 1;
950
- }
951
- /** 由 specifier 推导包名:@scope/pkg/sub → @scope/pkg;gear-lib-demo → gear-lib-demo。 */
952
- function derivePackageName(specifier) {
953
- if (specifier.startsWith('@')) {
954
- const parts = specifier.split('/');
955
- return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
956
- }
957
- return specifier.split('/')[0];
958
- }
959
- /** ImportDeclaration → ImportIR(F2)。 */
960
- function importDeclToIR(decl, line) {
961
- const specifier = decl.source?.value;
962
- if (typeof specifier !== 'string' || specifier === '') {
963
- throw new ParseError('import must have a string specifier', line, 'E_IMPORT');
964
- }
965
- const packageName = derivePackageName(specifier);
966
- const specifiers = decl.specifiers ?? [];
967
- if (specifiers.length === 0) {
968
- throw new ParseError('side-effect imports are not supported (use import * as ns from "pkg")', line, 'E_IMPORT');
969
- }
970
- const first = specifiers[0];
971
- if (first.type === 'ImportNamespaceSpecifier') {
972
- const localName = first.local?.name;
973
- if (typeof localName !== 'string')
974
- throw new ParseError('invalid namespace import binding', line, 'E_IMPORT');
975
- return { specifier, kind: 'namespace', localName, packageName };
976
- }
977
- if (first.type === 'ImportDefaultSpecifier') {
978
- const localName = first.local?.name;
979
- if (typeof localName !== 'string')
980
- throw new ParseError('invalid default import binding', line, 'E_IMPORT');
981
- return { specifier, kind: 'default', localName, packageName };
982
- }
983
- // ImportSpecifier(named)
984
- const bindings = specifiers.map((s) => s.local?.name);
985
- if (bindings.some((b) => typeof b !== 'string')) {
986
- throw new ParseError('invalid named import bindings', line, 'E_IMPORT');
987
- }
988
- return { specifier, kind: 'named', localName: bindings[0], bindings, packageName };
989
- }
990
- /**
991
- * 默认分支:黑名单式拒绝(normal-js-subset P1)。
992
- * 除控制流(专用 E_CONTROL_FLOW 码)与安全红线(eval/new/export)外,
993
- * 未识别的语句形态一律 E_STATEMENT。
994
- */
995
- function throwUnsupportedStatement(node, line) {
996
- if (CONTROL_FLOW_TYPES.has(node.type)) {
997
- throw new ParseError(`control flow statement "${node.type}" is not allowed in faijs`, line, 'E_CONTROL_FLOW');
998
- }
999
- if (node.type === 'ImportDeclaration') {
1000
- throw new ParseError('import statements must be at the top level, before any statements', line, 'E_IMPORT');
1001
- }
1002
- if (node.type === 'ExportNamedDeclaration' || node.type === 'ExportAllDeclaration') {
1003
- throw new ParseError('export statements are not allowed (faijs auto-exports the default module)', line, 'E_STATEMENT');
1004
- }
1005
- if (node.type === 'ClassDeclaration') {
1006
- throw new ParseError('class declarations are not allowed in faijs', line, 'E_STATEMENT');
1007
- }
1008
- if (node.type === 'NewExpression') {
1009
- throw new ParseError('"new" expressions are not allowed in faijs (new Function is a safety red line)', line, 'E_STATEMENT');
1010
- }
1011
- throw new ParseError(`unsupported statement: ${node.type}`, line, 'E_STATEMENT');
1012
- }
1013
- // ── 顶层函数定义(A1 / 控制流放松方案 Phase 2) ──
1014
- // bodyHash 哈希的唯一家在 fnv-hash.ts(无 IR 方案下 metadata-extractor 复用)。
1015
- /**
1016
- * 解析顶层 `function name(params) { ... }` 为 FunctionDefIR(A1 / Phase 2)。
1017
- *
1018
- * 函数定义**不是几何语句**:不进 statements、不参与 DAG 终端判定;
1019
- * 函数体原文按 acorn 坐标从 parseCode 切片保留,codegen 原样打印回文件(往返保真)。
1020
- *
1021
- * 函数体校验(Phase 2 放行控制流,§3.1 / §3.2):
1022
- * - 控制流(if/for/while/switch/try/throw/break/continue/labeled)放行;
1023
- * - eval / new / export / class / import / with / 动态 import() 仍拒(P5 安全红线);
1024
- * - 体内禁止调用本机函数(裸 callee 调用 → E_STATEMENT,D10);
1025
- * - var 允许(D12,不新增收紧)。
1026
- *
1027
- * @param codeOffset parseCode 相对原始 code 的字符偏移(扁平封装前缀;容器格式为 0)。
1028
- * 函数体坐标经此换算为相对原始文本,供宿主 bodyRange 使用。
1029
- */
1030
- function parseFunctionDeclaration(node, line, functions, parseCode, codeOffset = 0) {
1031
- const name = node.id?.name;
1032
- if (typeof name !== 'string' || name === '') {
1033
- throw new ParseError('function declaration must have a name', line, 'E_STATEMENT');
1034
- }
1035
- // 查重(D14):localFns 发射与 bodyHash 按名查表要求函数名唯一
1036
- if (functions.some((f) => f.name === name)) {
1037
- throw new ParseError(`function "${name}" is already defined (duplicate function names are not allowed)`, line, 'E_STATEMENT');
1038
- }
1039
- // 形参:只允许简单 Identifier(解构/默认值/rest 参数不在子集内)
1040
- const params = [];
1041
- for (const p of node.params ?? []) {
1042
- if (p?.type !== 'Identifier') {
1043
- throw new ParseError('function params must be plain identifiers', line, 'E_STATEMENT');
1044
- }
1045
- params.push(p.name);
1046
- }
1047
- // 函数体必须是 BlockStatement(acorn 对合法函数声明必然如此,防御性校验)
1048
- if (node.body?.type !== 'BlockStatement') {
1049
- throw new ParseError('function body must be a block statement', line, 'E_STATEMENT');
1050
- }
1051
- // 函数体校验(Phase 2:放行控制流,保留安全红线,禁体内本机调用)
1052
- validateFunctionBody(node.body, line);
1053
- // 函数体原文(花括号内的完整文本,含换行/缩进)——坐标相对 parseCode
1054
- const body = parseCode.slice(node.body.start + 1, node.body.end - 1);
1055
- functions.push({
1056
- name,
1057
- params,
1058
- body,
1059
- bodyHash: fnv1a32(body),
1060
- bodyRange: { start: node.body.start + 1 - codeOffset, end: node.body.end - 1 - codeOffset },
1061
- });
1062
- }
1063
- /**
1064
- * 校验函数体(Phase 2,§3.1 / §3.2 / D10 / D12):
1065
- * - **放行**控制流(if/for/while/switch/try/throw/break/continue/labeled)与 var;
1066
- * - **仍拒**(安全红线 P5):import 声明 / export / class / eval / new / with / 动态 import();
1067
- * - **体内禁止本机函数调用**(裸标识符 callee,D10)——递归遍历全部节点(含嵌套函数体)。
1068
- */
1069
- function validateFunctionBody(body, line) {
1070
- const stack = [body];
1071
- while (stack.length > 0) {
1072
- const n = stack.pop();
1073
- if (n.type === 'ImportDeclaration') {
1074
- throw new ParseError('import statements are not allowed inside function bodies', line, 'E_IMPORT');
1075
- }
1076
- if (n.type === 'ImportExpression') {
1077
- throw new ParseError('dynamic import() is not allowed in faijs', line, 'E_CONTROL_FLOW');
1078
- }
1079
- if (n.type === 'ExportNamedDeclaration' || n.type === 'ExportAllDeclaration') {
1080
- throw new ParseError('export statements are not allowed in faijs (faijs auto-exports the default module)', line, 'E_STATEMENT');
1081
- }
1082
- if (n.type === 'ClassDeclaration') {
1083
- throw new ParseError('class declarations are not allowed in faijs', line, 'E_STATEMENT');
1084
- }
1085
- if (n.type === 'WithStatement') {
1086
- throw new ParseError('"with" statements are not allowed in faijs (strict mode)', line, 'E_CONTROL_FLOW');
1087
- }
1088
- if (n.type === 'NewExpression') {
1089
- throw new ParseError('"new" expressions are not allowed in faijs (new Function is a safety red line)', line, 'E_STATEMENT');
1090
- }
1091
- if (n.type === 'CallExpression') {
1092
- // eval 安全红线(与顶层一致)
1093
- if (n.callee?.type === 'Identifier' && n.callee.name === 'eval') {
1094
- throw new ParseError('eval is not allowed in faijs', line, 'E_STATEMENT');
1095
- }
1096
- // D10:体内禁止调用本机函数(裸标识符 callee)——顶层 ABI 与 verbatim 函数体语义冲突
1097
- if (n.callee?.type === 'Identifier') {
1098
- throw new ParseError(`calling local function "${n.callee.name}" inside a function body is not allowed (D10): local functions are callable from the top level only`, line, 'E_STATEMENT');
1099
- }
1100
- }
1101
- // 子节点入栈(跳过元数据字段)
1102
- for (const key of Object.keys(n)) {
1103
- if (key === 'loc' || key === 'start' || key === 'end' || key === 'range' || key === 'parent')
1104
- continue;
1105
- const v = n[key];
1106
- if (Array.isArray(v)) {
1107
- for (const item of v) {
1108
- if (item && typeof item === 'object' && typeof item.type === 'string')
1109
- stack.push(item);
1110
- }
1111
- }
1112
- else if (v && typeof v === 'object' && typeof v.type === 'string') {
1113
- stack.push(v);
1114
- }
1115
- }
1116
- }
1117
- }
1118
- /**
1119
- * Parse CAD script code text into a ScriptIR.
1120
- *
1121
- * @param code - the script text to parse.
1122
- * @param options - optional parse options (looseVars enables loose variable
1123
- * resolution for partial append text).
1124
- * @returns the parse result (script IR + parse diagnostics).
1125
- */
1126
- export function parseScript(code, options) {
1127
- const looseVars = options?.looseVars === true;
1128
- const looseLocalCalls = options?.looseLocalCalls === true;
1129
- // 默认注入命名空间名(U10/R2):缺省 'cad';宿主换默认绑定名时经 options.defaultNs 声明。
1130
- const defaultNsName = options?.defaultNs ?? 'cad';
1131
- // ── 0. 顶层 import/export 预扫描(F1 黑名单化 + F2 import 提升) ──
1132
- // 用 module 模式解析原始文本:
1133
- // - 顶层 export(非默认导出容器)→ 明确 E_STATEMENT(扁平包装会先被 acorn 以
1134
- // SyntaxError 拒绝:'import' and 'export' may appear only at top level)。
1135
- // - 顶层 import → 收集声明,校验「文件头部连续段」约束(F2),供扁平代码包装提升。
1136
- let importBlock = null;
1137
- const prescanImports = [];
1138
- {
1139
- const topLevel = [];
1140
- try {
1141
- topLevel.push(...acornParse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true, ranges: true }).body);
1142
- }
1143
- catch {
1144
- // 原始文本非合法 module(如含 return)→ 交给主解析报错
1145
- }
1146
- for (const n of topLevel) {
1147
- if (n.type === 'ExportNamedDeclaration' || n.type === 'ExportAllDeclaration') {
1148
- throw new ParseError(`export statements are not allowed (faijs auto-exports the default module)`, getLine(n) ?? 1, 'E_STATEMENT');
1149
- }
1150
- if (n.type === 'ImportDeclaration')
1151
- prescanImports.push(n);
1152
- }
1153
- if (prescanImports.length > 0) {
1154
- const first = prescanImports[0];
1155
- const last = prescanImports[prescanImports.length - 1];
1156
- // 约束 1:import 之前只能有注释/空白(头部连续段)
1157
- if (!onlyWhitespaceAndComments(code.slice(0, first.start))) {
1158
- throw new ParseError('import statements must appear as a contiguous block at the top of the file', getLine(first) ?? 1, 'E_IMPORT');
1159
- }
1160
- // 约束 2:import 之间只能有注释/空白
1161
- for (let i = 1; i < prescanImports.length; i++) {
1162
- const gap = code.slice(prescanImports[i - 1].end, prescanImports[i].start);
1163
- if (!onlyWhitespaceAndComments(gap)) {
1164
- throw new ParseError('import statements must be contiguous (no statements between imports)', getLine(prescanImports[i]) ?? 1, 'E_IMPORT');
1165
- }
1166
- }
1167
- importBlock = { start: first.start, end: last.end };
1168
- }
1169
- }
1170
- // ── 0.5 扁平代码检测与封装 ──
1171
- // 如果代码不含 `export default`,则自动封装为合法容器。
1172
- // 含顶层 import 时把 import 段提到 `export default async (cad) => {...}` 之外
1173
- // (import 不能出现在函数体内,否则 acorn SyntaxError —— D4)。
1174
- let parseCode = code;
1175
- // 扁平封装后首行代码在 parseCode 中位于第 2 行;statementLines 须扣掉封装偏移,
1176
- // 使行号始终相对宿主原始文本(analyzeCode 契约)。
1177
- // codeOffset:parseCode 坐标 → 原始 code 坐标的字符偏移(函数体 bodyRange 用;容器格式为 0)。
1178
- let lineOffset = code.includes('export default') ? 0 : 1;
1179
- let codeOffset = 0;
1180
- if (lineOffset) {
1181
- if (importBlock) {
1182
- const prefix = code.slice(0, importBlock.start);
1183
- const importText = code.slice(importBlock.start, importBlock.end);
1184
- const rest = code.slice(importBlock.end);
1185
- parseCode = `${prefix}${importText}\nexport default async (${defaultNsName}) => {\n${rest}\n}`;
1186
- lineOffset = countLines(prefix + importText) + 1;
1187
- // import 段之后的代码(函数定义所在)偏移 = 封装前缀长度(`\nexport default async (<ns>) => {\n` = 29 + len)
1188
- codeOffset = 29 + defaultNsName.length;
1189
- }
1190
- else {
1191
- parseCode = `export default async (${defaultNsName}) => {\n${code}\n}`;
1192
- lineOffset = 1;
1193
- codeOffset = `export default async (${defaultNsName}) => {\n`.length;
1194
- }
1195
- }
1196
- // ── 1. acorn 解析(合法性闸门) ──
1197
- let ast;
1198
- try {
1199
- ast = acornParse(parseCode, {
1200
- ecmaVersion: 'latest',
1201
- sourceType: 'module',
1202
- locations: true,
1203
- });
1204
- }
1205
- catch (err) {
1206
- const syntaxErr = err;
1207
- const line = syntaxErr.loc?.line ?? 1;
1208
- const msg = syntaxErr.message ?? String(err);
1209
- throw new ParseError(`SyntaxError: ${msg}`, line, 'E_SYNTAX');
1210
- }
1211
- // ── 2. 验证 AST 结构 ──
1212
- // 顶层必须是 Program,且仅含一个 ExportDefaultDeclaration
1213
- if (ast.type !== 'Program') {
1214
- throw new ParseError('expected a module', 1);
1215
- }
1216
- const exportDecl = ast.body.find((n) => n.type === 'ExportDefaultDeclaration');
1217
- if (!exportDecl) {
1218
- throw new ParseError('missing `export default`', 1);
1219
- }
1220
- // export default 必须是 async 箭头函数。
1221
- // 容器形参名即脚本内对「默认注入命名空间」的引用标识符(U10/R2:缺省约定 'cad',
1222
- // 宿主换默认绑定名 g 时经 parseScript options.defaultNs 传入,容器形参须与之一致)。
1223
- const arrowFn = exportDecl.declaration;
1224
- if (arrowFn?.type !== 'ArrowFunctionExpression' ||
1225
- !arrowFn.async ||
1226
- arrowFn.params.length !== 1 ||
1227
- arrowFn.params[0]?.type !== 'Identifier' ||
1228
- arrowFn.params[0].name !== defaultNsName) {
1229
- throw new ParseError(`expected \`export default async (${defaultNsName}) => { ... }\``, getLine(exportDecl));
1230
- }
1231
- // 箭头函数体必须是 BlockStatement
1232
- const body = arrowFn.body;
1233
- if (body?.type !== 'BlockStatement') {
1234
- throw new ParseError('expected arrow function body to be a block statement { ... }', getLine(arrowFn));
1235
- }
1236
- // ── 3. walk body ──
1237
- const params = [];
1238
- const statements = [];
1239
- const statementLines = [];
1240
- /** 顶层函数定义(A1;无函数时保持空数组,ScriptIR 构建时省略) */
1241
- const functions = [];
1242
- const paramNames = new Set();
1243
- /** 参数名 → 字面量值(F1 表达式折叠依据;仅声明在前面的参数可折叠) */
1244
- const paramValues = new Map();
1245
- const varToId = new Map();
1246
- let meta;
1247
- let terminalShapes;
1248
- // ── 3.1 顶层 import → ScriptIR.imports + 命名空间绑定表(F2) ──
1249
- // import 已提升到模块顶层(扁平包装之外);主解析的 ast.body 顶层直接可取。
1250
- const scriptImports = [];
1251
- /** 命名空间绑定名 → 包名(`import * as mech from 'gear-lib-demo'` → mech → gear-lib-demo)。
1252
- * 只有 namespace 形态的 import 构成语句命名空间(cad 是缺省命名空间)。 */
1253
- const importBindings = new Map();
1254
- for (const node of ast.body) {
1255
- if (node.type !== 'ImportDeclaration')
1256
- continue;
1257
- const imp = importDeclToIR(node, getLine(node));
1258
- scriptImports.push(imp);
1259
- if (imp.kind === 'namespace')
1260
- importBindings.set(imp.localName, imp.packageName);
1261
- }
1262
- /** 命名空间判定:'cad'(缺省)或顶层 import 绑定名(F2 消灭硬编码)。 */
1263
- const isNamespaceName = (name) => name === defaultNsName || importBindings.has(name);
1264
- /** 合法命名空间名集合(parseValueExpr/parseCadStatement/parseDestructuring 共用)。 */
1265
- const nsNames = new Set([defaultNsName, ...importBindings.keys()]);
1266
- // ── 3.2 本机函数集预扫描(Phase 2 / §3.4) ──
1267
- // 函数声明**提升**(可被其后语句调用),且未知函数名 parse 期报 E_REFERENCE——
1268
- // 故解析语句前预扫描全部顶层 FunctionDeclaration,收集函数名 → 形参表(ABI 校验用)。
1269
- // 仅顶层函数构成「本机函数集」;函数体内嵌套函数不参与(D10 禁体内调用,体内容器不透明)。
1270
- const localFnParams = new Map();
1271
- for (const topNode of body.body) {
1272
- if (topNode.type !== 'FunctionDeclaration' || topNode.id?.type !== 'Identifier')
1273
- continue;
1274
- const params = [];
1275
- for (const p of topNode.params ?? []) {
1276
- if (p?.type === 'Identifier')
1277
- params.push(p.name);
1278
- }
1279
- localFnParams.set(topNode.id.name, params);
1280
- }
1281
- for (const stmtNode of body.body) {
1282
- const line = getLine(stmtNode);
1283
- switch (stmtNode.type) {
1284
- case 'FunctionDeclaration': {
1285
- // A1/Phase 2:顶层函数定义。函数定义不是几何语句——不进 statements、
1286
- // 不参与 DAG 终端判定(不污染终端集),codegen 原样打印回文件(往返保真)。
1287
- // Phase 2:函数体放行控制流(§3.1),保留安全红线,禁体内本机调用(D10)。
1288
- parseFunctionDeclaration(stmtNode, line, functions, parseCode, codeOffset);
1289
- break;
1290
- }
1291
- case 'VariableDeclaration': {
1292
- // 允许 const 和 let(let 仅用于装配变量);var 保持拒绝(O2 排后)
1293
- if (stmtNode.kind !== 'const' && stmtNode.kind !== 'let') {
1294
- throw new ParseError(`only 'const' or 'let' declarations allowed, got '${stmtNode.kind}'`, line, 'E_STATEMENT');
1295
- }
1296
- // F1 黑名单化:多声明器逐个处理(const a = 1, b = 2)
1297
- for (const decl of stmtNode.declarations) {
1298
- const dline = getLine(decl);
1299
- // 通用解构:const { k1: v1, k2: v2 } = [await] <ns>.<any>(...)(A2 消灭:任意 callee、任意键)
1300
- if (decl.id?.type === 'ObjectPattern') {
1301
- if (stmtNode.kind !== 'const') {
1302
- throw new ParseError('destructuring requires const', line, 'E_STATEMENT');
1303
- }
1304
- const { stmt, valueNames } = parseDestructuring(decl, paramNames, paramValues, varToId, nsNames, defaultNsName, looseVars, parseCode, localFnParams, looseLocalCalls);
1305
- // 命名服务不再由 parser 调用:parser 只做语法分析,保留词法变量名(设计 §5.1)
1306
- stmt.outputs = valueNames.map((n) => asPartName(n));
1307
- statements.push(stmt);
1308
- statementLines.push(dline);
1309
- // varToId 仅用于作用域校验,恒等映射(词法名 → 词法名)
1310
- valueNames.forEach((n) => varToId.set(n, asPartName(n)));
1311
- continue;
1312
- }
1313
- // 判断是参数还是语句
1314
- let init = decl.init;
1315
- const isAwait = init?.type === 'AwaitExpression';
1316
- if (isAwait)
1317
- init = init.argument;
1318
- // 动态 import() → 控制流专用错误码(V1.5;`await import("mod")` 走声明路径)
1319
- if (init?.type === 'ImportExpression') {
1320
- throw new ParseError('dynamic import() is not allowed in faijs (no control flow)', line, 'E_CONTROL_FLOW');
1321
- }
1322
- // Phase 3: let 允许用于普通 cad.op() 语句(单入单出复用名时 codegen 产生 let 重赋值)
1323
- if (init?.type === 'CallExpression' &&
1324
- (
1325
- // 命名空间调用:<ns>.<op>(...)(F2:cad 或顶层 import 绑定名)
1326
- (init.callee?.type === 'MemberExpression' &&
1327
- init.callee.object?.type === 'Identifier' &&
1328
- isNamespaceName(init.callee.object.name)) ||
1329
- // 裸标识符 callee(本机函数或未知函数名——parseCadStatement 内判 local / E_REFERENCE,§3.4)
1330
- init.callee?.type === 'Identifier')) {
1331
- // 语句:const partN = [await] <ns>.op(...) 或 const partN = [await] localFn(...)
1332
- const { stmt, varName } = parseCadStatement(decl, paramNames, paramValues, varToId, nsNames, defaultNsName, looseVars, parseCode, localFnParams, looseLocalCalls);
1333
- // 命名服务不再由 parser 调用:parser 只做语法分析,保留词法变量名(设计 §5.1)
1334
- stmt.outputs = [asPartName(varName)];
1335
- statements.push(stmt);
1336
- statementLines.push(dline);
1337
- // varToId 仅用于作用域校验,恒等映射(词法名 → 词法名)
1338
- varToId.set(varName, asPartName(varName));
1339
- }
1340
- else if (init?.type === 'Literal' ||
1341
- init?.type === 'ArrayExpression' ||
1342
- init?.type === 'ObjectExpression') {
1343
- // 参数:const name = literal
1344
- if (decl.id?.type !== 'Identifier') {
1345
- throw new ParseError('param declaration must have identifier name', line, 'E_STATEMENT');
1346
- }
1347
- const name = decl.id.name;
1348
- // 解析字面量值(不允许 ParamRefIR / GeomRef / 标识符)
1349
- const value = parseLiteralOnly(init, dline);
1350
- params.push({
1351
- name,
1352
- type: typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'bool' : 'vec3',
1353
- value: value,
1354
- default: value,
1355
- });
1356
- paramNames.add(name);
1357
- paramValues.set(name, value);
1358
- // F1:参数也可作为 input(cad.fai_drill(p, {...})),加入 varToId 供输入/接收者解析
1359
- varToId.set(name, asPartName(name));
1360
- }
1361
- else {
1362
- throw new ParseError(`unsupported const declaration: init type = ${init?.type ?? 'null'}`, line, 'E_STATEMENT');
1363
- }
1364
- }
1365
- break;
1366
- }
1367
- case 'ReturnStatement': {
1368
- const result = parseReturnStatement(stmtNode, varToId);
1369
- meta = result.meta;
1370
- terminalShapes = result.terminalShapes;
1371
- break;
1372
- }
1373
- case 'ExpressionStatement': {
1374
- const expr = stmtNode.expression;
1375
- // 动态 import() → 控制流专用错误码(V1.5)
1376
- if (expr?.type === 'ImportExpression') {
1377
- throw new ParseError('dynamic import() is not allowed in faijs (no control flow)', line, 'E_CONTROL_FLOW');
1378
- }
1379
- // eval / new Function 安全红线
1380
- if (expr?.type === 'CallExpression' && expr.callee?.type === 'Identifier' && expr.callee.name === 'eval') {
1381
- throw new ParseError('eval is not allowed in faijs', line, 'E_STATEMENT');
1382
- }
1383
- if (expr?.type === 'NewExpression') {
1384
- throw new ParseError('"new" expressions are not allowed in faijs (new Function is a safety red line)', line, 'E_STATEMENT');
1385
- }
1386
- // ── Phase 3: 裸重赋值 part0 = [await] cad.op(...) ──
1387
- // codegen 对已声明变量的重赋值不使用 let/const,产生裸赋值表达式
1388
- if (expr?.type === 'AssignmentExpression' &&
1389
- expr.operator === '=' &&
1390
- expr.left?.type === 'Identifier') {
1391
- const varName = expr.left.name;
1392
- // 验证变量已声明(loose 模式下外部变量透传,由 append 前缀校验兜底)
1393
- if (!varToId.has(varName)) {
1394
- if (looseVars) {
1395
- varToId.set(varName, asPartName(varName));
1396
- }
1397
- else {
1398
- throw new ParseError(`unknown variable "${varName}" in re-assignment`, line, 'E_REFERENCE');
1399
- }
1400
- }
1401
- let init = expr.right;
1402
- if (init?.type === 'AwaitExpression')
1403
- init = init.argument;
1404
- if (init?.type === 'CallExpression' &&
1405
- (
1406
- // 命名空间调用:<ns>.<op>(...)
1407
- (init.callee?.type === 'MemberExpression' &&
1408
- init.callee.object?.type === 'Identifier' &&
1409
- isNamespaceName(init.callee.object.name) &&
1410
- init.callee.property?.type === 'Identifier') ||
1411
- // 裸标识符 callee(本机函数或未知函数名——parseCadStatement 内判 local / E_REFERENCE,§3.4)
1412
- init.callee?.type === 'Identifier')) {
1413
- // 复用 parseCadStatement 的内部逻辑
1414
- const fakeDecl = {
1415
- id: { type: 'Identifier', name: varName },
1416
- init: expr.right,
1417
- loc: stmtNode.loc,
1418
- };
1419
- const { stmt, varName: parsedVar } = parseCadStatement(fakeDecl, paramNames, paramValues, varToId, nsNames, defaultNsName, looseVars, parseCode, localFnParams, looseLocalCalls);
1420
- // 命名服务不再由 parser 调用:裸重赋值保留词法变量名(设计 §5.1)
1421
- stmt.outputs = [asPartName(parsedVar)];
1422
- statements.push(stmt);
1423
- statementLines.push(line);
1424
- // varToId 仅用于作用域校验,恒等映射(词法名 → 词法名)
1425
- varToId.set(parsedVar, asPartName(parsedVar));
1426
- break;
1427
- }
1428
- throw new ParseError(`re-assignment must be a cad.op() call`, line, 'E_STATEMENT');
1429
- }
1430
- // ── 本机函数无赋值调用(§3.4 四形态之四:副作用调用) ──
1431
- // myFn(part0, { ... }) —— callee 是裸标识符且 ∈ 函数集;未知函数名 → E_REFERENCE
1432
- // 宽松本机调用(codeToArgs 单行提取):裸 callee 不在函数集时也放行,
1433
- // 视为 local 调用候选(ABI 形态未知 → 跳过绑定校验),由调用方在完整上下文校验。
1434
- if (expr?.type === 'CallExpression' && expr.callee?.type === 'Identifier') {
1435
- const fnName = expr.callee.name;
1436
- const looseLocal = looseLocalCalls && !localFnParams.has(fnName);
1437
- if (localFnParams.has(fnName) || looseLocal) {
1438
- const flags = { computed: false };
1439
- const positional = parsePositionalArgs(expr.arguments, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, parseCode, `${fnName}() call`);
1440
- const args = splitPositionalOptions(positional).named;
1441
- // 宽松本机调用(codeToArgs 单行提取)形参未知 → 跳过绑定校验,由调用方在完整上下文校验。
1442
- if (!looseLocal) {
1443
- const { values, named } = splitPositionalOptions(positional);
1444
- validateLocalAbi(fnName, localFnParams.get(fnName) ?? [], values.length, named, line);
1445
- }
1446
- const localStmt = {
1447
- id: asStmtId('__pending__'),
1448
- callee: fnName,
1449
- args,
1450
- positional,
1451
- outputs: [],
1452
- local: true,
1453
- hasAssignment: false,
1454
- ...(flags.computed ? { hasComputedArgs: true } : {}),
1455
- };
1456
- statements.push(localStmt);
1457
- statementLines.push(line);
1458
- break;
1459
- }
1460
- // 裸 callee 不在函数集 → 「函数不存在」(E_REFERENCE,§3.4 / D15)
1461
- throw new ParseError(`function "${fnName}" does not exist in this script`, line, 'E_REFERENCE');
1462
- }
1463
- // ── 成员方法调用(A6 消灭:任意方法名;receiver 须已声明) ──
1464
- // assem1.add_constraint({ ... }) / assem1.do_assemble() / assem1.myMethod()
1465
- if (expr?.type === 'CallExpression' &&
1466
- expr.callee?.type === 'MemberExpression' &&
1467
- expr.callee.object?.type === 'Identifier' &&
1468
- expr.callee.property?.type === 'Identifier') {
1469
- const objName = expr.callee.object.name;
1470
- const methodName = expr.callee.property.name;
1471
- // F1 黑名单化 + F2:命名空间调用(<ns>.<op>() 裸调用,无赋值)——对象是 'cad'
1472
- // 或顶层 import 绑定名(第三方命名空间调用)
1473
- if (isNamespaceName(objName)) {
1474
- const nsName = objName;
1475
- const flags = { computed: false };
1476
- const positional = parsePositionalArgs(expr.arguments, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, parseCode, `${nsName}.${methodName}() call`);
1477
- const args = splitPositionalOptions(positional).named;
1478
- const nsStmt = {
1479
- id: asStmtId('__pending__'),
1480
- callee: methodName,
1481
- args,
1482
- positional,
1483
- outputs: [],
1484
- hasAssignment: false,
1485
- ...(nsName !== defaultNsName ? { namespace: nsName } : {}),
1486
- ...(flags.computed ? { hasComputedArgs: true } : {}),
1487
- };
1488
- statements.push(nsStmt);
1489
- statementLines.push(line);
1490
- break;
1491
- }
1492
- // 成员方法调用:receiver 须已声明
1493
- const targetVar = objName;
1494
- // 验证 targetVar 已声明(变量作用域检查,非 op 知识)
1495
- if (!varToId.has(targetVar)) {
1496
- // Loose mode: external receiver passes through (append text may call
1497
- // do_assemble() on an assembly created by an earlier statement).
1498
- if (looseVars) {
1499
- varToId.set(targetVar, asPartName(targetVar));
1500
- }
1501
- else {
1502
- throw new ParseError(`unknown variable "${targetVar}" in .${methodName}() call`, line, 'E_REFERENCE');
1503
- }
1504
- }
1505
- const flags = { computed: false };
1506
- const positional = parsePositionalArgs(expr.arguments, paramNames, paramValues, varToId, nsNames, defaultNsName, line, flags, looseVars, parseCode, `.${methodName}() call`);
1507
- const args = splitPositionalOptions(positional).named;
1508
- const memberStmt = {
1509
- id: asStmtId('__pending__'),
1510
- callee: methodName,
1511
- args,
1512
- positional,
1513
- outputs: [],
1514
- receiver: targetVar,
1515
- hasAssignment: false,
1516
- ...(flags.computed ? { hasComputedArgs: true } : {}),
1517
- };
1518
- statements.push(memberStmt);
1519
- statementLines.push(line);
1520
- break;
1521
- }
1522
- // 其余裸表达式语句:黑名单化后仍拒绝(无意义的表达式)
1523
- throw new ParseError(`bare expression statements not allowed; use 'const part0 = cad.op(...)' instead`, line, 'E_STATEMENT');
1524
- }
1525
- default:
1526
- throwUnsupportedStatement(stmtNode, line);
1527
- }
1528
- }
1529
- // ── 3.5 独立 StmtId 分配 + 引用收集(Phase 3: id = sN) ──
1530
- // StmtId 按语句顺序分配 s(K+1)..s(K+N),K = params.length——参数在编译产物中占 s1..sK,
1531
- // 使 parser 分配的 id 与 compileToModule 生成的模块语句 id 一致。
1532
- const stmtIdBase = params.length;
1533
- let stmtIdx = 0;
1534
- for (const stmt of statements) {
1535
- const sN = asStmtId(`s${stmtIdBase + (++stmtIdx)}`);
1536
- stmt.id = sN;
1537
- stmt.refs = collectStatementRefs(stmt);
1538
- }
1539
- const sourceLines = lineOffset ? statementLines.map((l) => l - lineOffset) : statementLines;
1540
- // ── 4. terminal shapes ──
1541
- // Phase 3:终端判定移入执行收尾(runtime.collectResult),parser 不再计算。
1542
- // 显式 return [...] 的 terminalShapes 优先;否则运行期从 result.outputs 过滤。
1543
- const finalTerminalShapes = terminalShapes;
1544
- // ── 5. 构建 ScriptIR ──
1545
- const script = {
1546
- params,
1547
- statements,
1548
- ...(scriptImports.length > 0 ? { imports: scriptImports } : {}),
1549
- ...(functions.length > 0 ? { functions } : {}),
1550
- meta,
1551
- terminalShapes: finalTerminalShapes,
1552
- };
1553
- return { script, varToId, statementLines: sourceLines };
1554
- }
1555
- // ── 字面量解析(仅允许字面量,不允许标识符/调用) ──
1556
- function parseLiteralOnly(node, line) {
1557
- switch (node.type) {
1558
- case 'Literal':
1559
- return node.value;
1560
- case 'ArrayExpression':
1561
- return node.elements.map((el) => parseLiteralOnly(el, line));
1562
- case 'ObjectExpression': {
1563
- const obj = {};
1564
- for (const prop of node.properties) {
1565
- const key = prop.key?.type === 'Identifier' ? prop.key.name
1566
- : prop.key?.type === 'Literal' ? String(prop.key.value)
1567
- : null;
1568
- if (!key)
1569
- throw new ParseError('invalid object key in param literal', line);
1570
- obj[key] = parseLiteralOnly(prop.value, line);
1571
- }
1572
- return obj;
1573
- }
1574
- case 'UnaryExpression':
1575
- if (node.operator === '-' && node.argument?.type === 'Literal') {
1576
- return -node.argument.value;
1577
- }
1578
- throw new ParseError(`unsupported unary in param literal`, line);
1579
- default:
1580
- throw new ParseError(`param value must be a literal (number/string/boolean/null/array/object), got ${node.type}`, line);
1581
- }
1582
- }
1583
- // ── 版本迁移 ──
1584
- /**
1585
- * Read the `// apiVersion: N` declaration from the top of a faijs source text,
1586
- * defaulting to 1 when absent.
1587
- * @param code - the faijs source text to inspect.
1588
- * @returns the declared API version number, or 1 when none is present.
1589
- */
1590
- export function getApiVersion(code) {
1591
- const match = code.match(/^\/\/\s*apiVersion:\s*(\d+)/m);
1592
- return match ? parseInt(match[1], 10) : 1;
1593
- }
1594
- //# sourceMappingURL=parser.js.map