@holoscript/core 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/holoscript.cjs +3 -0
- package/dist/GLTFPipeline-276GE64O.js +16 -0
- package/dist/GLTFPipeline-M7WSSJED.cjs +37 -0
- package/dist/HoloScriptPlusParser-7FBIFUFC.js +693 -0
- package/dist/HoloScriptPlusParser-A2W7OCN2.cjs +698 -0
- package/dist/HoloScriptPlusParser-F2Z56FJR.js +10 -0
- package/dist/HoloScriptPlusParser-WKHRIH6T.cjs +27 -0
- package/dist/HoloScriptRuntime-3URVTKPJ.js +10 -0
- package/dist/HoloScriptRuntime-SXEAEU2N.cjs +19 -0
- package/dist/USDZExporter-3FWJ25JI.cjs +1037 -0
- package/dist/USDZExporter-D2HQLQYK.js +1035 -0
- package/dist/agents/index.cjs +19 -0
- package/dist/agents/index.d.ts +100 -0
- package/dist/agents/index.js +17 -0
- package/dist/chunk-23J755AR.js +1046 -0
- package/dist/chunk-253GBQZY.cjs +627 -0
- package/dist/chunk-25IQCHTO.cjs +781 -0
- package/dist/chunk-2BSMSUCE.js +1941 -0
- package/dist/chunk-2M2DPHKB.js +762 -0
- package/dist/chunk-2YSNA2F2.cjs +1274 -0
- package/dist/chunk-3AO425MZ.cjs +886 -0
- package/dist/chunk-3FOKASKO.cjs +348 -0
- package/dist/chunk-3HYYMEXI.cjs +56578 -0
- package/dist/chunk-3NFFOWGG.js +4287 -0
- package/dist/chunk-3WZEJHUQ.cjs +116 -0
- package/dist/chunk-4C57PR42.js +468 -0
- package/dist/chunk-4N4SXXWF.js +769 -0
- package/dist/chunk-4PDEPEQW.cjs +41 -0
- package/dist/chunk-4R6SBXDB.js +45 -0
- package/dist/chunk-4V2E7U7Q.cjs +2015 -0
- package/dist/chunk-5FSKKMR7.cjs +2092 -0
- package/dist/chunk-5GLCTO44.js +161 -0
- package/dist/chunk-5JEU52DW.js +382 -0
- package/dist/chunk-5S7RKFGR.cjs +446 -0
- package/dist/chunk-5WUNN6ZN.cjs +428 -0
- package/dist/chunk-62FBK3B7.js +70 -0
- package/dist/chunk-6CTRH7HT.cjs +1985 -0
- package/dist/chunk-6DPSFNJB.cjs +847 -0
- package/dist/chunk-6EX3I5P4.cjs +130 -0
- package/dist/chunk-73FELZVJ.cjs +611 -0
- package/dist/chunk-7CUEMLDZ.cjs +6445 -0
- package/dist/chunk-7HIASF7S.cjs +814 -0
- package/dist/chunk-A6KINLZJ.js +236 -0
- package/dist/chunk-AIA52I7T.cjs +543 -0
- package/dist/chunk-ANXZFVY2.js +113 -0
- package/dist/chunk-AYBXB26F.js +2176 -0
- package/dist/chunk-B4TFTDG4.js +449 -0
- package/dist/chunk-BE4UJCQT.js +623 -0
- package/dist/chunk-BRQJKJGT.cjs +20 -0
- package/dist/chunk-C2A5DUMF.cjs +689 -0
- package/dist/chunk-C37BHB7N.cjs +636 -0
- package/dist/chunk-CBJY23VL.js +126 -0
- package/dist/chunk-CIWWXYHZ.cjs +50 -0
- package/dist/chunk-CXFDRBQU.cjs +672 -0
- package/dist/chunk-D54CNIAJ.cjs +413 -0
- package/dist/chunk-DAJXW4KX.js +725 -0
- package/dist/chunk-DTGBOSEM.cjs +319 -0
- package/dist/chunk-E7JP3BS3.cjs +61 -0
- package/dist/chunk-EKBKLK7P.js +1169 -0
- package/dist/chunk-EKYL4PE3.cjs +404 -0
- package/dist/chunk-EUDQTK7N.cjs +4310 -0
- package/dist/chunk-EXKSFGSX.cjs +274 -0
- package/dist/chunk-EXUP2BUM.cjs +1173 -0
- package/dist/chunk-F43YAA34.js +623 -0
- package/dist/chunk-FDSGHPAK.js +130 -0
- package/dist/chunk-FEWUHQNM.cjs +144 -0
- package/dist/chunk-FGGJQRLZ.js +270 -0
- package/dist/chunk-FHP6LXJS.js +415 -0
- package/dist/chunk-FJPADH77.cjs +666 -0
- package/dist/chunk-FYSPZT4V.cjs +169 -0
- package/dist/chunk-GIOYPNTH.js +5575 -0
- package/dist/chunk-GJNCFRJU.cjs +125 -0
- package/dist/chunk-GMP623FG.cjs +1508 -0
- package/dist/chunk-GQHQOPXZ.cjs +5580 -0
- package/dist/chunk-GQT4TYKY.cjs +4381 -0
- package/dist/chunk-GTU2Z2WW.cjs +262 -0
- package/dist/chunk-GYZE55MO.js +2007 -0
- package/dist/chunk-GZG4KA4E.js +529 -0
- package/dist/chunk-H652NHX5.js +600 -0
- package/dist/chunk-HCCQLAA4.cjs +74 -0
- package/dist/chunk-HINXWAOD.cjs +1049 -0
- package/dist/chunk-HYNTS5CP.js +422 -0
- package/dist/chunk-HZSPDCXV.cjs +457 -0
- package/dist/chunk-HZTDET3N.js +1399 -0
- package/dist/chunk-I6XZ5DZF.cjs +488 -0
- package/dist/chunk-ILRBGFVD.cjs +72 -0
- package/dist/chunk-IPWSXCCN.js +1183 -0
- package/dist/chunk-IUTY5WQ2.cjs +424 -0
- package/dist/chunk-IVSZDECV.js +882 -0
- package/dist/chunk-IVY74O6C.js +247 -0
- package/dist/chunk-IZM5VQZD.js +714 -0
- package/dist/chunk-J32ZJZJH.js +554 -0
- package/dist/chunk-J6M4FPK5.js +421 -0
- package/dist/chunk-JC5YBPSF.cjs +984 -0
- package/dist/chunk-JCLBKPMX.js +3813 -0
- package/dist/chunk-JD7CIZ33.js +1949 -0
- package/dist/chunk-JIYN45TV.cjs +2525 -0
- package/dist/chunk-JJBE3TIH.cjs +1189 -0
- package/dist/chunk-JX7YL6ZE.js +2252 -0
- package/dist/chunk-K4VVR7CN.cjs +2433 -0
- package/dist/chunk-KEFNV2WM.cjs +2257 -0
- package/dist/chunk-KH4CNH66.cjs +424 -0
- package/dist/chunk-KJLB4OPV.js +5731 -0
- package/dist/chunk-KKFZ3B6J.cjs +557 -0
- package/dist/chunk-KKP7JKN4.cjs +6002 -0
- package/dist/chunk-KPJL34Y7.cjs +186 -0
- package/dist/chunk-KQCN6AKJ.cjs +3042 -0
- package/dist/chunk-KVRSTFNH.cjs +72 -0
- package/dist/chunk-LICPB5HZ.cjs +1409 -0
- package/dist/chunk-LM2VU56C.cjs +37 -0
- package/dist/chunk-M56JSP4A.js +400 -0
- package/dist/chunk-MBOOR7X4.js +18 -0
- package/dist/chunk-MGE5Z4GS.js +608 -0
- package/dist/chunk-MXFUTKCO.js +56 -0
- package/dist/chunk-MYY4PMWX.cjs +264 -0
- package/dist/chunk-N75IH2HY.js +258 -0
- package/dist/chunk-NBMHU6SW.js +669 -0
- package/dist/chunk-NG77ATEW.cjs +778 -0
- package/dist/chunk-NGKREZ36.js +6384 -0
- package/dist/chunk-NHWSN2SD.cjs +718 -0
- package/dist/chunk-NV4VMVXU.js +421 -0
- package/dist/chunk-NW5G5FBQ.js +215 -0
- package/dist/chunk-NXZFOPVD.cjs +5737 -0
- package/dist/chunk-OBIYPYYS.cjs +1993 -0
- package/dist/chunk-OIQYIIO3.cjs +473 -0
- package/dist/chunk-OL46QLBJ.js +61 -0
- package/dist/chunk-OMXII44L.js +5386 -0
- package/dist/chunk-OR4Q2BGL.cjs +759 -0
- package/dist/chunk-OSYNRVOV.cjs +243 -0
- package/dist/chunk-PGANYCAG.cjs +220 -0
- package/dist/chunk-PHENW4MQ.js +65 -0
- package/dist/chunk-PHVRG4I2.js +844 -0
- package/dist/chunk-Q56TT7IL.js +483 -0
- package/dist/chunk-QPOQ7VPI.js +1491 -0
- package/dist/chunk-QPSKHBJP.js +5963 -0
- package/dist/chunk-QXH7QDSZ.js +1989 -0
- package/dist/chunk-R75BSDH7.cjs +50 -0
- package/dist/chunk-RARB3MBR.js +207 -0
- package/dist/chunk-RVBHAXFG.js +56160 -0
- package/dist/chunk-S3MDHNKB.cjs +490 -0
- package/dist/chunk-S4W6Z2MN.js +2426 -0
- package/dist/chunk-S6YWHCKV.cjs +410 -0
- package/dist/chunk-SIEXCILW.js +229 -0
- package/dist/chunk-SJBGLS25.cjs +2196 -0
- package/dist/chunk-SLU6UPJI.cjs +410 -0
- package/dist/chunk-SMIWS6DE.cjs +212 -0
- package/dist/chunk-T2QCTZMO.js +811 -0
- package/dist/chunk-T5J7ZKP3.cjs +4885 -0
- package/dist/chunk-TDCNJHWO.js +341 -0
- package/dist/chunk-TG7O4WBC.js +406 -0
- package/dist/chunk-THCK6ECW.js +2085 -0
- package/dist/chunk-TJHVSBP2.js +439 -0
- package/dist/chunk-TVHSPJ4N.cjs +606 -0
- package/dist/chunk-U44IKGG7.cjs +5390 -0
- package/dist/chunk-UJXISJBN.js +487 -0
- package/dist/chunk-UM6C3CGK.js +4375 -0
- package/dist/chunk-UMABK6SI.js +686 -0
- package/dist/chunk-UOIGFQXC.cjs +9750 -0
- package/dist/chunk-URG2SJLK.js +2521 -0
- package/dist/chunk-V6MKC2FZ.js +979 -0
- package/dist/chunk-VECQYRXL.js +474 -0
- package/dist/chunk-VJ3L7CHT.cjs +169 -0
- package/dist/chunk-VMLKSV26.js +1216 -0
- package/dist/chunk-VVKVQZI6.js +55 -0
- package/dist/chunk-W6RW64BB.cjs +1219 -0
- package/dist/chunk-W7VDOASB.cjs +639 -0
- package/dist/chunk-WDT3JTGM.cjs +483 -0
- package/dist/chunk-WJIHFLMJ.js +404 -0
- package/dist/chunk-WKX5AMOY.js +309 -0
- package/dist/chunk-WNNE3PO3.js +34 -0
- package/dist/chunk-WRKUDAZJ.js +3038 -0
- package/dist/chunk-WRLUBRKS.cjs +1943 -0
- package/dist/chunk-WRNEH5MO.js +3169 -0
- package/dist/chunk-WRWEX5ZF.js +658 -0
- package/dist/chunk-X3GJPRVK.cjs +3246 -0
- package/dist/chunk-XHIV6326.cjs +793 -0
- package/dist/chunk-XIVIE43A.cjs +729 -0
- package/dist/chunk-XJAN7CZG.cjs +432 -0
- package/dist/chunk-XKTPFXGA.js +164 -0
- package/dist/chunk-XL6INWKT.js +121 -0
- package/dist/chunk-XSFNHP2E.js +632 -0
- package/dist/chunk-XT6JY5W5.js +9684 -0
- package/dist/chunk-XUILI4KB.cjs +430 -0
- package/dist/chunk-XUUVDHIF.cjs +3962 -0
- package/dist/chunk-YF2I3MV3.js +46 -0
- package/dist/chunk-YIMN575O.js +4847 -0
- package/dist/chunk-YIVFJNYV.js +176 -0
- package/dist/chunk-YKGPCZEE.cjs +237 -0
- package/dist/chunk-YN6V7R3L.cjs +327 -0
- package/dist/chunk-YQELFNJV.js +39 -0
- package/dist/chunk-YYXH45E2.js +426 -0
- package/dist/chunk-ZBQXLH2L.js +756 -0
- package/dist/chunk-ZDSYWBIE.cjs +69 -0
- package/dist/chunk-ZU7NIRT6.js +325 -0
- package/dist/chunk-ZZ7RO2UQ.js +1243 -0
- package/dist/chunk-ZZEI7MV4.js +778 -0
- package/dist/cli/holoscript-runner.cjs +2255 -0
- package/dist/cli/holoscript-runner.js +2230 -0
- package/dist/codebase/index.cjs +336 -0
- package/dist/codebase/index.js +331 -0
- package/dist/compiler/agent-inference.cjs +31 -0
- package/dist/compiler/agent-inference.js +8 -0
- package/dist/compiler/android-xr.cjs +24 -0
- package/dist/compiler/android-xr.js +14 -0
- package/dist/compiler/android.cjs +24 -0
- package/dist/compiler/android.js +14 -0
- package/dist/compiler/ar.cjs +23 -0
- package/dist/compiler/ar.js +11 -0
- package/dist/compiler/babylon.cjs +21 -0
- package/dist/compiler/babylon.js +15 -0
- package/dist/compiler/business-quest.cjs +43 -0
- package/dist/compiler/business-quest.js +4 -0
- package/dist/compiler/coco.cjs +27 -0
- package/dist/compiler/coco.js +4 -0
- package/dist/compiler/context.cjs +25 -0
- package/dist/compiler/context.d.ts +28 -0
- package/dist/compiler/context.js +8 -0
- package/dist/compiler/domain-block-utils.cjs +605 -0
- package/dist/compiler/domain-block-utils.js +11 -0
- package/dist/compiler/dtdl.cjs +27 -0
- package/dist/compiler/dtdl.js +11 -0
- package/dist/compiler/gltf-pipeline.cjs +39 -0
- package/dist/compiler/gltf-pipeline.js +4 -0
- package/dist/compiler/godot.cjs +18 -0
- package/dist/compiler/godot.js +12 -0
- package/dist/compiler/incremental.cjs +34 -0
- package/dist/compiler/incremental.js +9 -0
- package/dist/compiler/index.cjs +5860 -0
- package/dist/compiler/index.d.ts +322 -21
- package/dist/compiler/index.js +5525 -0
- package/dist/compiler/ios.cjs +23 -0
- package/dist/compiler/ios.js +13 -0
- package/dist/compiler/llm-provider-capabilities.cjs +25 -0
- package/dist/compiler/llm-provider-capabilities.d.ts +43 -0
- package/dist/compiler/llm-provider-capabilities.js +8 -0
- package/dist/compiler/multi-layer.cjs +24 -0
- package/dist/compiler/multi-layer.js +15 -0
- package/dist/compiler/nodetoy.cjs +23 -0
- package/dist/compiler/nodetoy.js +4 -0
- package/dist/compiler/openxr.cjs +19 -0
- package/dist/compiler/openxr.js +13 -0
- package/dist/compiler/playcanvas.cjs +19 -0
- package/dist/compiler/playcanvas.js +13 -0
- package/dist/compiler/r3f.cjs +38 -0
- package/dist/compiler/r3f.d.ts +22 -0
- package/dist/compiler/r3f.js +16 -0
- package/dist/compiler/remotion.cjs +23 -0
- package/dist/compiler/remotion.js +4 -0
- package/dist/compiler/reproducibility.cjs +39 -0
- package/dist/compiler/reproducibility.js +4 -0
- package/dist/compiler/sdf.cjs +24 -0
- package/dist/compiler/sdf.js +12 -0
- package/dist/compiler/semantic-scene.cjs +31 -0
- package/dist/compiler/semantic-scene.js +4 -0
- package/dist/compiler/state.cjs +21 -0
- package/dist/compiler/state.js +11 -0
- package/dist/compiler/trait-composition.cjs +30 -0
- package/dist/compiler/trait-composition.js +9 -0
- package/dist/compiler/unity.cjs +19 -0
- package/dist/compiler/unity.js +13 -0
- package/dist/compiler/unreal.cjs +23 -0
- package/dist/compiler/unreal.js +13 -0
- package/dist/compiler/urdf.cjs +52 -0
- package/dist/compiler/urdf.js +12 -0
- package/dist/compiler/usd-physics.cjs +26 -0
- package/dist/compiler/usd-physics.js +9 -0
- package/dist/compiler/visionos.cjs +19 -0
- package/dist/compiler/visionos.js +13 -0
- package/dist/compiler/vrchat.cjs +22 -0
- package/dist/compiler/vrchat.js +12 -0
- package/dist/compiler/vrr.cjs +24 -0
- package/dist/compiler/vrr.js +12 -0
- package/dist/compiler/wasm.cjs +36 -0
- package/dist/compiler/wasm.js +12 -0
- package/dist/compiler/webgpu.cjs +18 -0
- package/dist/compiler/webgpu.js +12 -0
- package/dist/coordinators/index.cjs +1266 -0
- package/dist/coordinators/index.d.ts +72 -1
- package/dist/coordinators/index.js +1259 -0
- package/dist/debugger.cjs +24 -0
- package/dist/debugger.js +11 -0
- package/dist/entries/interop.cjs +64 -0
- package/dist/entries/interop.js +7 -0
- package/dist/entries/scripting.cjs +55 -0
- package/dist/entries/scripting.js +10 -0
- package/dist/hololand/index.cjs +146 -0
- package/dist/hololand/index.d.ts +70 -0
- package/dist/hololand/index.js +5 -0
- package/dist/index.cjs +43233 -0
- package/dist/index.d.ts +976 -21
- package/dist/index.js +39995 -0
- package/dist/math/tropical-spmv.cjs +45 -0
- package/dist/math/tropical-spmv.js +4 -0
- package/dist/math/vec3.cjs +57 -0
- package/dist/math/vec3.js +4 -0
- package/dist/ml-dsa-FSKBTE3W.cjs +1265 -0
- package/dist/ml-dsa-Z7JDKW4L.js +1265 -0
- package/dist/paper-0c-spike/index.cjs +150 -0
- package/dist/paper-0c-spike/index.js +141 -0
- package/dist/parser.cjs +61 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.js +12 -0
- package/dist/playwright-3AT3AKTV.cjs +98673 -0
- package/dist/playwright-VZPDVUEQ.js +98660 -0
- package/dist/reconstruction/index.cjs +153 -0
- package/dist/reconstruction/index.d.ts +29 -2
- package/dist/reconstruction/index.js +8 -0
- package/dist/runtime.cjs +316 -0
- package/dist/runtime.d.ts +61 -0
- package/dist/runtime.js +31 -0
- package/dist/self-improvement/index.cjs +563 -0
- package/dist/self-improvement/index.js +545 -0
- package/dist/storage/index.cjs +38 -0
- package/dist/storage/index.js +5 -0
- package/dist/testing.cjs +25 -0
- package/dist/testing.d.ts +70 -0
- package/dist/testing.js +4 -0
- package/dist/traitDocs/traitDocs.cjs +33 -0
- package/dist/traitDocs/traitDocs.js +7 -0
- package/dist/traits/botanical-lotus.cjs +41 -0
- package/dist/traits/botanical-lotus.d.ts +172 -0
- package/dist/traits/botanical-lotus.js +4 -0
- package/dist/traits/engines/index.cjs +520 -0
- package/dist/traits/engines/index.js +380 -0
- package/dist/traits/index.cjs +24409 -0
- package/dist/traits/index.d.ts +171 -35
- package/dist/traits/index.js +21634 -0
- package/dist/traits/simulation-solver-factory.cjs +13 -0
- package/dist/traits/simulation-solver-factory.d.ts +23 -0
- package/dist/traits/simulation-solver-factory.js +4 -0
- package/dist/traits/webcam-gaze.cjs +35 -0
- package/dist/traits/webcam-gaze.d.ts +17 -0
- package/dist/traits/webcam-gaze.js +4 -0
- package/dist/type-checker.cjs +19 -0
- package/dist/type-checker.js +6 -0
- package/dist/world/index.cjs +947 -0
- package/dist/world/index.d.ts +147 -0
- package/dist/world/index.js +939 -0
- package/dist/world-model/index.cjs +1446 -0
- package/dist/world-model/index.d.ts +119 -0
- package/dist/world-model/index.js +1414 -0
- package/dist/wot/index.cjs +29 -0
- package/dist/wot/index.js +4 -0
- package/package.json +93 -49
|
@@ -0,0 +1,1216 @@
|
|
|
1
|
+
import { loadConstraintsFromConfig, BUILTIN_CONSTRAINTS } from './chunk-XSFNHP2E.js';
|
|
2
|
+
|
|
3
|
+
// src/types/AdvancedTypeSystem.ts
|
|
4
|
+
var ExhaustivenessChecker = class {
|
|
5
|
+
/**
|
|
6
|
+
* Check if all union members are covered in match statement
|
|
7
|
+
*/
|
|
8
|
+
checkMatch(unionType, casePatterns) {
|
|
9
|
+
const patterns = new Set(casePatterns.map((p) => p.toLowerCase()));
|
|
10
|
+
const uncovered = [];
|
|
11
|
+
for (const member of unionType.members) {
|
|
12
|
+
const caseName = this.getCaseName(member);
|
|
13
|
+
if (!patterns.has(caseName.toLowerCase()) && caseName !== "_") {
|
|
14
|
+
uncovered.push(caseName);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
isExhaustive: uncovered.length === 0 || patterns.has("_"),
|
|
19
|
+
uncoveredCases: uncovered
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
getCaseName(type) {
|
|
23
|
+
if (type.kind === "literal") {
|
|
24
|
+
return String(type.value);
|
|
25
|
+
}
|
|
26
|
+
if (type.kind === "custom") {
|
|
27
|
+
return type.name;
|
|
28
|
+
}
|
|
29
|
+
return type.kind;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/types/TypeAliasRegistry.ts
|
|
34
|
+
var TypeAliasRegistry = class {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.aliases = /* @__PURE__ */ new Map();
|
|
37
|
+
/** Tracks which aliases are currently being expanded (cycle detection) */
|
|
38
|
+
this.expanding = /* @__PURE__ */ new Set();
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Registration
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
register(decl) {
|
|
44
|
+
this.aliases.set(decl.name, decl);
|
|
45
|
+
}
|
|
46
|
+
has(name) {
|
|
47
|
+
return this.aliases.has(name);
|
|
48
|
+
}
|
|
49
|
+
get(name) {
|
|
50
|
+
return this.aliases.get(name);
|
|
51
|
+
}
|
|
52
|
+
all() {
|
|
53
|
+
return Array.from(this.aliases.values());
|
|
54
|
+
}
|
|
55
|
+
clear() {
|
|
56
|
+
this.aliases.clear();
|
|
57
|
+
this.expanding.clear();
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Parsing helper (parses a `type Foo = ...` source line)
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* Parse a type alias declaration from source text.
|
|
64
|
+
* Recognises:
|
|
65
|
+
* type Name = definition
|
|
66
|
+
* type Name<T, U> = definition
|
|
67
|
+
*/
|
|
68
|
+
static parse(source, line) {
|
|
69
|
+
const match = source.match(/^\s*type\s+([A-Z][A-Za-z0-9_]*)(?:<([^>]+)>)?\s*=\s*(.+)$/);
|
|
70
|
+
if (!match) return null;
|
|
71
|
+
const name = match[1];
|
|
72
|
+
const rawParams = match[2];
|
|
73
|
+
const definition = match[3].trim();
|
|
74
|
+
const typeParams = rawParams ? rawParams.split(",").map((p) => p.trim()).filter(Boolean) : [];
|
|
75
|
+
const isUnion = definition.includes("|");
|
|
76
|
+
const kind = typeParams.length > 0 ? "generic" : isUnion ? "union" : "simple";
|
|
77
|
+
return { name, kind, definition, typeParams, line };
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Resolution
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* Resolve an alias by name, substituting type params if provided.
|
|
84
|
+
* Returns null if unknown or recursive.
|
|
85
|
+
*
|
|
86
|
+
* Example:
|
|
87
|
+
* registry.resolve('Optional', ['string'])
|
|
88
|
+
* → 'string | null'
|
|
89
|
+
*/
|
|
90
|
+
resolve(name, typeArgs = []) {
|
|
91
|
+
if (this.expanding.has(name)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const decl = this.aliases.get(name);
|
|
95
|
+
if (!decl) return null;
|
|
96
|
+
if (this.containsAlias(decl.definition, name)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
this.expanding.add(name);
|
|
100
|
+
try {
|
|
101
|
+
let expanded = decl.definition;
|
|
102
|
+
if (decl.typeParams && decl.typeParams.length > 0) {
|
|
103
|
+
for (let i = 0; i < decl.typeParams.length; i++) {
|
|
104
|
+
const param = decl.typeParams[i];
|
|
105
|
+
const arg = typeArgs[i] ?? "any";
|
|
106
|
+
expanded = expanded.replace(new RegExp(`\\b${param}\\b`, "g"), arg);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
expanded = this.expandReferences(expanded);
|
|
110
|
+
return expanded;
|
|
111
|
+
} finally {
|
|
112
|
+
this.expanding.delete(name);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Detect recursive/self-referential type aliases.
|
|
117
|
+
*/
|
|
118
|
+
isRecursive(name) {
|
|
119
|
+
const decl = this.aliases.get(name);
|
|
120
|
+
if (!decl) return false;
|
|
121
|
+
this.expanding.add(name);
|
|
122
|
+
const result = this.containsAlias(decl.definition, name);
|
|
123
|
+
this.expanding.delete(name);
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Private helpers
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
expandReferences(definition) {
|
|
130
|
+
let result = definition;
|
|
131
|
+
for (const [aliasName, decl] of this.aliases) {
|
|
132
|
+
if (this.expanding.has(aliasName)) continue;
|
|
133
|
+
const pattern = new RegExp(`\\b${aliasName}\\b`, "g");
|
|
134
|
+
if (pattern.test(result)) {
|
|
135
|
+
const resolved = this.resolve(aliasName);
|
|
136
|
+
if (resolved) {
|
|
137
|
+
result = result.replace(new RegExp(`\\b${aliasName}\\b`, "g"), `(${resolved})`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
containsAlias(definition, targetName) {
|
|
144
|
+
const pattern = new RegExp(`\\b${targetName}\\b`);
|
|
145
|
+
return pattern.test(definition);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/HoloScriptTypeChecker.ts
|
|
150
|
+
var BUILTIN_FUNCTIONS = /* @__PURE__ */ new Map([
|
|
151
|
+
[
|
|
152
|
+
"add",
|
|
153
|
+
{
|
|
154
|
+
type: "function",
|
|
155
|
+
parameters: [
|
|
156
|
+
{ name: "a", type: "number" },
|
|
157
|
+
{ name: "b", type: "number" }
|
|
158
|
+
],
|
|
159
|
+
returnType: "number"
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
"subtract",
|
|
164
|
+
{
|
|
165
|
+
type: "function",
|
|
166
|
+
parameters: [
|
|
167
|
+
{ name: "a", type: "number" },
|
|
168
|
+
{ name: "b", type: "number" }
|
|
169
|
+
],
|
|
170
|
+
returnType: "number"
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
"multiply",
|
|
175
|
+
{
|
|
176
|
+
type: "function",
|
|
177
|
+
parameters: [
|
|
178
|
+
{ name: "a", type: "number" },
|
|
179
|
+
{ name: "b", type: "number" }
|
|
180
|
+
],
|
|
181
|
+
returnType: "number"
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
"divide",
|
|
186
|
+
{
|
|
187
|
+
type: "function",
|
|
188
|
+
parameters: [
|
|
189
|
+
{ name: "a", type: "number" },
|
|
190
|
+
{ name: "b", type: "number" }
|
|
191
|
+
],
|
|
192
|
+
returnType: "number"
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
[
|
|
196
|
+
"concat",
|
|
197
|
+
{
|
|
198
|
+
type: "function",
|
|
199
|
+
parameters: [
|
|
200
|
+
{ name: "a", type: "string" },
|
|
201
|
+
{ name: "b", type: "string" }
|
|
202
|
+
],
|
|
203
|
+
returnType: "string"
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
[
|
|
207
|
+
"length",
|
|
208
|
+
{ type: "function", parameters: [{ name: "value", type: "any" }], returnType: "number" }
|
|
209
|
+
],
|
|
210
|
+
[
|
|
211
|
+
"push",
|
|
212
|
+
{
|
|
213
|
+
type: "function",
|
|
214
|
+
parameters: [
|
|
215
|
+
{ name: "arr", type: "array" },
|
|
216
|
+
{ name: "item", type: "any" }
|
|
217
|
+
],
|
|
218
|
+
returnType: "array"
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
["pop", { type: "function", parameters: [{ name: "arr", type: "array" }], returnType: "any" }],
|
|
222
|
+
["log", { type: "function", parameters: [{ name: "message", type: "any" }], returnType: "void" }],
|
|
223
|
+
[
|
|
224
|
+
"print",
|
|
225
|
+
{ type: "function", parameters: [{ name: "message", type: "any" }], returnType: "void" }
|
|
226
|
+
],
|
|
227
|
+
["show", { type: "function", parameters: [{ name: "target", type: "orb" }], returnType: "void" }],
|
|
228
|
+
["hide", { type: "function", parameters: [{ name: "target", type: "orb" }], returnType: "void" }],
|
|
229
|
+
[
|
|
230
|
+
"pulse",
|
|
231
|
+
{ type: "function", parameters: [{ name: "target", type: "orb" }], returnType: "void" }
|
|
232
|
+
],
|
|
233
|
+
[
|
|
234
|
+
"animate",
|
|
235
|
+
{
|
|
236
|
+
type: "function",
|
|
237
|
+
parameters: [
|
|
238
|
+
{ name: "target", type: "orb" },
|
|
239
|
+
{ name: "config", type: "object" }
|
|
240
|
+
],
|
|
241
|
+
returnType: "void"
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
[
|
|
245
|
+
"spawn",
|
|
246
|
+
{
|
|
247
|
+
type: "function",
|
|
248
|
+
parameters: [
|
|
249
|
+
{ name: "type", type: "string" },
|
|
250
|
+
{ name: "position", type: "object" }
|
|
251
|
+
],
|
|
252
|
+
returnType: "orb"
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
"isNumber",
|
|
257
|
+
{ type: "function", parameters: [{ name: "value", type: "any" }], returnType: "boolean" }
|
|
258
|
+
],
|
|
259
|
+
[
|
|
260
|
+
"isString",
|
|
261
|
+
{ type: "function", parameters: [{ name: "value", type: "any" }], returnType: "boolean" }
|
|
262
|
+
],
|
|
263
|
+
[
|
|
264
|
+
"isArray",
|
|
265
|
+
{ type: "function", parameters: [{ name: "value", type: "any" }], returnType: "boolean" }
|
|
266
|
+
],
|
|
267
|
+
// Simulation built-in functions (v4.2)
|
|
268
|
+
[
|
|
269
|
+
"createCollider",
|
|
270
|
+
{
|
|
271
|
+
type: "function",
|
|
272
|
+
parameters: [
|
|
273
|
+
{ name: "shape", type: "string" },
|
|
274
|
+
{ name: "config", type: "object" }
|
|
275
|
+
],
|
|
276
|
+
returnType: "object"
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
[
|
|
280
|
+
"createRigidbody",
|
|
281
|
+
{
|
|
282
|
+
type: "function",
|
|
283
|
+
parameters: [
|
|
284
|
+
{ name: "mass", type: "number" },
|
|
285
|
+
{ name: "config", type: "object" }
|
|
286
|
+
],
|
|
287
|
+
returnType: "object"
|
|
288
|
+
}
|
|
289
|
+
],
|
|
290
|
+
[
|
|
291
|
+
"createMaterial",
|
|
292
|
+
{
|
|
293
|
+
type: "function",
|
|
294
|
+
parameters: [
|
|
295
|
+
{ name: "name", type: "string" },
|
|
296
|
+
{ name: "preset", type: "string" }
|
|
297
|
+
],
|
|
298
|
+
returnType: "object"
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
[
|
|
302
|
+
"createParticleSystem",
|
|
303
|
+
{ type: "function", parameters: [{ name: "config", type: "object" }], returnType: "object" }
|
|
304
|
+
],
|
|
305
|
+
[
|
|
306
|
+
"applyForce",
|
|
307
|
+
{
|
|
308
|
+
type: "function",
|
|
309
|
+
parameters: [
|
|
310
|
+
{ name: "target", type: "orb" },
|
|
311
|
+
{ name: "force", type: "vec3" }
|
|
312
|
+
],
|
|
313
|
+
returnType: "void"
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
[
|
|
317
|
+
"raycast",
|
|
318
|
+
{
|
|
319
|
+
type: "function",
|
|
320
|
+
parameters: [
|
|
321
|
+
{ name: "origin", type: "vec3" },
|
|
322
|
+
{ name: "direction", type: "vec3" },
|
|
323
|
+
{ name: "maxDistance", type: "number" }
|
|
324
|
+
],
|
|
325
|
+
returnType: "object"
|
|
326
|
+
}
|
|
327
|
+
],
|
|
328
|
+
[
|
|
329
|
+
"playAudio",
|
|
330
|
+
{
|
|
331
|
+
type: "function",
|
|
332
|
+
parameters: [
|
|
333
|
+
{ name: "clip", type: "string" },
|
|
334
|
+
{ name: "config", type: "object" }
|
|
335
|
+
],
|
|
336
|
+
returnType: "void"
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
[
|
|
340
|
+
"setWeather",
|
|
341
|
+
{ type: "function", parameters: [{ name: "preset", type: "string" }], returnType: "void" }
|
|
342
|
+
],
|
|
343
|
+
[
|
|
344
|
+
"generateTerrain",
|
|
345
|
+
{ type: "function", parameters: [{ name: "config", type: "object" }], returnType: "object" }
|
|
346
|
+
],
|
|
347
|
+
[
|
|
348
|
+
"navigate",
|
|
349
|
+
{
|
|
350
|
+
type: "function",
|
|
351
|
+
parameters: [
|
|
352
|
+
{ name: "agent", type: "orb" },
|
|
353
|
+
{ name: "target", type: "vec3" }
|
|
354
|
+
],
|
|
355
|
+
returnType: "void"
|
|
356
|
+
}
|
|
357
|
+
],
|
|
358
|
+
[
|
|
359
|
+
"distance",
|
|
360
|
+
{
|
|
361
|
+
type: "function",
|
|
362
|
+
parameters: [
|
|
363
|
+
{ name: "a", type: "vec3" },
|
|
364
|
+
{ name: "b", type: "vec3" }
|
|
365
|
+
],
|
|
366
|
+
returnType: "number"
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
]);
|
|
370
|
+
var HoloScriptTypeChecker = class {
|
|
371
|
+
constructor() {
|
|
372
|
+
this.typeMap = /* @__PURE__ */ new Map();
|
|
373
|
+
this.diagnostics = [];
|
|
374
|
+
this.currentLine = 0;
|
|
375
|
+
this.currentColumn = 0;
|
|
376
|
+
/** Registry for user-defined union types (e.g., type State = "idle" | "loading" | "error") */
|
|
377
|
+
this.unionTypes = /* @__PURE__ */ new Map();
|
|
378
|
+
/** Type alias registry (type Color = string | number[], type List<T> = T[]) */
|
|
379
|
+
this.typeAliasRegistry = new TypeAliasRegistry();
|
|
380
|
+
/** Custom constraints merged from holoscript.config.json */
|
|
381
|
+
this.customConstraints = [];
|
|
382
|
+
/** Diagnostics accumulated before check() so they survive the diagnostics reset */
|
|
383
|
+
this.preCheckDiagnostics = [];
|
|
384
|
+
BUILTIN_FUNCTIONS.forEach((type, name) => {
|
|
385
|
+
this.typeMap.set(name, type);
|
|
386
|
+
});
|
|
387
|
+
this.exhaustivenessChecker = new ExhaustivenessChecker();
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Merge custom constraints from a parsed config object.
|
|
391
|
+
* Call before check() when you have a holoscript.config.json.
|
|
392
|
+
*/
|
|
393
|
+
loadConfig(config) {
|
|
394
|
+
this.customConstraints = loadConstraintsFromConfig(config);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Register a type alias (e.g. from a `type Foo = ...` declaration in source).
|
|
398
|
+
*/
|
|
399
|
+
registerTypeAlias(decl) {
|
|
400
|
+
this.typeAliasRegistry.register(decl);
|
|
401
|
+
if (this.typeAliasRegistry.isRecursive(decl.name)) {
|
|
402
|
+
this.preCheckDiagnostics.push({
|
|
403
|
+
severity: "error",
|
|
404
|
+
message: `Type alias '${decl.name}' is recursive and cannot be expanded.`,
|
|
405
|
+
line: decl.line ?? 0,
|
|
406
|
+
column: 0,
|
|
407
|
+
code: "HSP020",
|
|
408
|
+
suggestions: [`Remove the self-reference in '${decl.name}' definition.`]
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Type check an AST
|
|
414
|
+
*/
|
|
415
|
+
check(ast) {
|
|
416
|
+
this.diagnostics = [...this.preCheckDiagnostics];
|
|
417
|
+
for (const node of ast) {
|
|
418
|
+
this.collectDeclaration(node);
|
|
419
|
+
}
|
|
420
|
+
this.checkBlock(ast);
|
|
421
|
+
return {
|
|
422
|
+
valid: this.diagnostics.filter((d) => d.severity === "error").length === 0,
|
|
423
|
+
diagnostics: this.diagnostics,
|
|
424
|
+
typeMap: new Map(this.typeMap)
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Collect type declarations
|
|
429
|
+
*/
|
|
430
|
+
collectDeclaration(node) {
|
|
431
|
+
switch (node.type) {
|
|
432
|
+
case "orb":
|
|
433
|
+
this.collectOrbDeclaration(node);
|
|
434
|
+
break;
|
|
435
|
+
case "method":
|
|
436
|
+
this.collectMethodDeclaration(node);
|
|
437
|
+
break;
|
|
438
|
+
case "variable-declaration":
|
|
439
|
+
this.collectVariableDeclaration(node);
|
|
440
|
+
break;
|
|
441
|
+
case "stream":
|
|
442
|
+
this.collectStreamDeclaration(node);
|
|
443
|
+
break;
|
|
444
|
+
case "template":
|
|
445
|
+
this.collectTemplateDeclaration(node);
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
collectTemplateDeclaration(node) {
|
|
450
|
+
this.typeMap.set(node.name, {
|
|
451
|
+
type: "object"
|
|
452
|
+
// We could try to infer properties from body if structure allows,
|
|
453
|
+
// but for now simple registration is enough to pass "Unknown variable" checks
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
collectOrbDeclaration(node) {
|
|
457
|
+
const properties = /* @__PURE__ */ new Map();
|
|
458
|
+
properties.set("position", { type: "vec3" });
|
|
459
|
+
properties.set("rotation", { type: "euler" });
|
|
460
|
+
properties.set("scale", { type: "vec3" });
|
|
461
|
+
properties.set("color", { type: "color" });
|
|
462
|
+
properties.set("glow", { type: "boolean" });
|
|
463
|
+
properties.set("interactive", { type: "boolean" });
|
|
464
|
+
properties.set("visible", { type: "boolean" });
|
|
465
|
+
for (const [key, value] of Object.entries(node.properties)) {
|
|
466
|
+
const expectedType = properties.get(key);
|
|
467
|
+
properties.set(key, this.inferTypeWithContext(value, expectedType));
|
|
468
|
+
}
|
|
469
|
+
this.typeMap.set(node.name, {
|
|
470
|
+
type: "orb",
|
|
471
|
+
properties
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
collectMethodDeclaration(node) {
|
|
475
|
+
const parameters = node.parameters.map((p) => ({
|
|
476
|
+
name: p.name,
|
|
477
|
+
type: this.parseTypeString(p.dataType),
|
|
478
|
+
optional: p.defaultValue !== void 0,
|
|
479
|
+
defaultValue: p.defaultValue
|
|
480
|
+
}));
|
|
481
|
+
this.typeMap.set(node.name, {
|
|
482
|
+
type: "function",
|
|
483
|
+
parameters,
|
|
484
|
+
returnType: node.returnType ? this.parseTypeString(node.returnType) : "void"
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
collectVariableDeclaration(node) {
|
|
488
|
+
let typeInfo = { type: "any" };
|
|
489
|
+
if (node.dataType) {
|
|
490
|
+
typeInfo = { type: this.parseTypeString(node.dataType) };
|
|
491
|
+
if (node.value !== void 0) {
|
|
492
|
+
this.inferTypeWithContext(node.value, typeInfo);
|
|
493
|
+
}
|
|
494
|
+
} else if (node.value !== void 0) {
|
|
495
|
+
typeInfo = this.inferType(node.value);
|
|
496
|
+
}
|
|
497
|
+
this.typeMap.set(node.name, typeInfo);
|
|
498
|
+
}
|
|
499
|
+
collectStreamDeclaration(node) {
|
|
500
|
+
this.typeMap.set(node.name, {
|
|
501
|
+
type: "stream",
|
|
502
|
+
properties: /* @__PURE__ */ new Map([
|
|
503
|
+
["source", { type: "string" }],
|
|
504
|
+
["result", { type: "any" }]
|
|
505
|
+
])
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Check a node for type errors
|
|
510
|
+
*/
|
|
511
|
+
checkNode(node) {
|
|
512
|
+
this.currentLine = node.position?.[0] ?? 0;
|
|
513
|
+
this.currentColumn = node.position?.[1] ?? 0;
|
|
514
|
+
switch (node.type) {
|
|
515
|
+
case "connection":
|
|
516
|
+
this.checkConnection(node);
|
|
517
|
+
break;
|
|
518
|
+
case "gate":
|
|
519
|
+
this.checkGate(node);
|
|
520
|
+
break;
|
|
521
|
+
case "for-loop":
|
|
522
|
+
this.checkForLoop(node);
|
|
523
|
+
break;
|
|
524
|
+
case "while-loop":
|
|
525
|
+
this.checkWhileLoop(node);
|
|
526
|
+
break;
|
|
527
|
+
case "foreach-loop":
|
|
528
|
+
this.checkForEachLoop(node);
|
|
529
|
+
break;
|
|
530
|
+
case "import":
|
|
531
|
+
this.checkImport(node);
|
|
532
|
+
break;
|
|
533
|
+
case "export":
|
|
534
|
+
this.checkExport(node);
|
|
535
|
+
break;
|
|
536
|
+
case "type-guard":
|
|
537
|
+
this.checkTypeGuard(node);
|
|
538
|
+
break;
|
|
539
|
+
case "variable-declaration":
|
|
540
|
+
this.checkVariableDeclaration(node);
|
|
541
|
+
break;
|
|
542
|
+
case "method":
|
|
543
|
+
this.checkBlock(node.body);
|
|
544
|
+
break;
|
|
545
|
+
case "orb":
|
|
546
|
+
this.validateTraitConstraints(node);
|
|
547
|
+
this.checkBlock(node.children || []);
|
|
548
|
+
break;
|
|
549
|
+
case "template":
|
|
550
|
+
this.validateTraitConstraints(node);
|
|
551
|
+
this.checkBlock(node.children || []);
|
|
552
|
+
break;
|
|
553
|
+
case "spread":
|
|
554
|
+
this.checkSpread(node);
|
|
555
|
+
break;
|
|
556
|
+
case "nullCoalescingAssignment":
|
|
557
|
+
this.checkNullCoalescingAssignment(node);
|
|
558
|
+
break;
|
|
559
|
+
case "match":
|
|
560
|
+
this.checkMatchExpression(node);
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
checkNullCoalescingAssignment(node) {
|
|
565
|
+
if (!node.target) {
|
|
566
|
+
this.addDiagnostic("error", `Null coalescing assignment missing target`, "E104");
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const targetStr = typeof node.target === "string" ? node.target : node.target?.__ref || node.target?.name;
|
|
570
|
+
if (!targetStr) {
|
|
571
|
+
this.addDiagnostic(
|
|
572
|
+
"error",
|
|
573
|
+
`Null coalescing assignment to non-assignable expression`,
|
|
574
|
+
"E105"
|
|
575
|
+
);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const targetType = this.typeMap.get(targetStr);
|
|
579
|
+
const valueType = node.value ? this.inferType(node.value) : "unknown";
|
|
580
|
+
this.typeMap.set(
|
|
581
|
+
targetStr,
|
|
582
|
+
targetType || { type: valueType, nullable: false }
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
checkSpread(node) {
|
|
586
|
+
if (!node.target) {
|
|
587
|
+
this.addDiagnostic("error", `Spread expression missing target`, "E102");
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (!this.typeMap.has(node.target)) {
|
|
591
|
+
this.addDiagnostic("error", `Unknown template or object '${node.target}' in spread`, "E102");
|
|
592
|
+
} else {
|
|
593
|
+
const type = this.typeMap.get(node.target);
|
|
594
|
+
if (type && type.type !== "object" && type.type !== "orb" && type.type !== "any") {
|
|
595
|
+
this.addDiagnostic(
|
|
596
|
+
"error",
|
|
597
|
+
`Cannot spread '${node.target}' type '${type.type}', must be object or template`,
|
|
598
|
+
"E103"
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Check a match expression for:
|
|
605
|
+
* 1. Valid subject expression
|
|
606
|
+
* 2. Exhaustiveness - all union cases are covered
|
|
607
|
+
* 3. Unreachable patterns (after wildcard)
|
|
608
|
+
* 4. Type consistency in case bodies
|
|
609
|
+
*/
|
|
610
|
+
checkMatchExpression(node) {
|
|
611
|
+
if (!node.subject) {
|
|
612
|
+
this.addDiagnostic("error", "Match expression missing subject", "E200");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (!node.cases || node.cases.length === 0) {
|
|
616
|
+
this.addDiagnostic("error", "Match expression has no cases", "E201");
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const subjectId = typeof node.subject === "string" ? node.subject : node.subject?.__ref || node.subject?.name;
|
|
620
|
+
let unionType;
|
|
621
|
+
if (subjectId && this.unionTypes.has(subjectId)) {
|
|
622
|
+
unionType = this.unionTypes.get(subjectId);
|
|
623
|
+
}
|
|
624
|
+
const casePatterns = [];
|
|
625
|
+
let hasWildcard = false;
|
|
626
|
+
let wildcardIndex = -1;
|
|
627
|
+
for (let i = 0; i < node.cases.length; i++) {
|
|
628
|
+
const matchCase = node.cases[i];
|
|
629
|
+
const pattern = matchCase.pattern;
|
|
630
|
+
if (!pattern) {
|
|
631
|
+
this.addDiagnostic("error", `Match case ${i + 1} has no pattern`, "E202");
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (pattern.type === "wildcard-pattern") {
|
|
635
|
+
if (hasWildcard) {
|
|
636
|
+
this.addDiagnostic(
|
|
637
|
+
"warning",
|
|
638
|
+
`Duplicate wildcard pattern at case ${i + 1} - only the first wildcard will be reached`,
|
|
639
|
+
"W200"
|
|
640
|
+
);
|
|
641
|
+
} else {
|
|
642
|
+
hasWildcard = true;
|
|
643
|
+
wildcardIndex = i;
|
|
644
|
+
}
|
|
645
|
+
casePatterns.push("_");
|
|
646
|
+
} else if (pattern.type === "literal-pattern") {
|
|
647
|
+
const value = String(pattern.value);
|
|
648
|
+
if (casePatterns.includes(value)) {
|
|
649
|
+
this.addDiagnostic(
|
|
650
|
+
"warning",
|
|
651
|
+
`Duplicate pattern "${value}" at case ${i + 1} - this case will never be reached`,
|
|
652
|
+
"W201"
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
casePatterns.push(value);
|
|
656
|
+
} else if (pattern.type === "binding-pattern") {
|
|
657
|
+
casePatterns.push(pattern.name);
|
|
658
|
+
}
|
|
659
|
+
if (hasWildcard && i > wildcardIndex) {
|
|
660
|
+
this.addDiagnostic(
|
|
661
|
+
"warning",
|
|
662
|
+
`Unreachable pattern at case ${i + 1} - previous wildcard (_) matches all remaining cases`,
|
|
663
|
+
"W202",
|
|
664
|
+
["Remove this case or reorder patterns"]
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
if (Array.isArray(matchCase.body)) {
|
|
668
|
+
this.checkBlock(matchCase.body);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (unionType) {
|
|
672
|
+
const result = this.exhaustivenessChecker.checkMatch(unionType, casePatterns);
|
|
673
|
+
if (!result.isExhaustive && !hasWildcard) {
|
|
674
|
+
const missingCases = result.uncoveredCases.map((c) => `"${c}"`).join(", ");
|
|
675
|
+
this.addDiagnostic(
|
|
676
|
+
"error",
|
|
677
|
+
`Non-exhaustive match. Missing cases: ${missingCases}`,
|
|
678
|
+
"E203",
|
|
679
|
+
result.uncoveredCases.map((c) => `Add case: "${c}" => ...`)
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
} else if (!hasWildcard && subjectId) {
|
|
683
|
+
this.addDiagnostic(
|
|
684
|
+
"info",
|
|
685
|
+
`Match expression on '${subjectId}' may be non-exhaustive. Consider adding a wildcard (_) case.`,
|
|
686
|
+
"I200"
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Register a union type for exhaustiveness checking
|
|
692
|
+
* Example: type State = "idle" | "loading" | "success" | "error"
|
|
693
|
+
*/
|
|
694
|
+
registerUnionType(name, members) {
|
|
695
|
+
const unionMembers = members.map((m) => ({
|
|
696
|
+
kind: "literal",
|
|
697
|
+
value: m
|
|
698
|
+
}));
|
|
699
|
+
this.unionTypes.set(name, {
|
|
700
|
+
kind: "union",
|
|
701
|
+
members: unionMembers
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get the union type for a registered name
|
|
706
|
+
*/
|
|
707
|
+
getUnionType(name) {
|
|
708
|
+
return this.unionTypes.get(name);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Check if a match expression is exhaustive given the union type.
|
|
712
|
+
* Returns HSP021 diagnostics for missing cases.
|
|
713
|
+
*/
|
|
714
|
+
checkExhaustiveMatch(matchExpr) {
|
|
715
|
+
const decl = this.typeAliasRegistry.get(matchExpr.typeName);
|
|
716
|
+
if (!decl) return [];
|
|
717
|
+
if (decl.kind !== "union" && decl.kind !== "simple") return [];
|
|
718
|
+
if (matchExpr.coveredPatterns.includes("_")) return [];
|
|
719
|
+
const members = this.extractUnionMembers(decl.definition);
|
|
720
|
+
if (members.length === 0) return [];
|
|
721
|
+
const coveredSet = new Set(matchExpr.coveredPatterns);
|
|
722
|
+
const missing = members.filter((m) => !coveredSet.has(m));
|
|
723
|
+
if (missing.length === 0) return [];
|
|
724
|
+
const missingStr = missing.map((m) => '"' + m + '"').join(", ");
|
|
725
|
+
return [
|
|
726
|
+
{
|
|
727
|
+
code: "HSP021",
|
|
728
|
+
message: "Non-exhaustive match on '" + matchExpr.typeName + "'. Missing: " + missingStr,
|
|
729
|
+
severity: "error",
|
|
730
|
+
line: matchExpr.line ?? 0,
|
|
731
|
+
column: matchExpr.column ?? 0,
|
|
732
|
+
suggestions: missing.map((m) => "Add case: " + m + " => ...")
|
|
733
|
+
}
|
|
734
|
+
];
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Extract individual members from a union type definition string.
|
|
738
|
+
* Handles string literals, number literals, and identifiers.
|
|
739
|
+
*/
|
|
740
|
+
extractUnionMembers(definition) {
|
|
741
|
+
const parts = definition.split("|").map((p) => p.trim()).filter(Boolean);
|
|
742
|
+
const members = [];
|
|
743
|
+
for (const part of parts) {
|
|
744
|
+
const strMatch = part.match(/^["'](.+?)["']$/);
|
|
745
|
+
if (strMatch) {
|
|
746
|
+
members.push(strMatch[1]);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (/^-?\d+(\.\d+)?$/.test(part)) {
|
|
750
|
+
members.push(part);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return members;
|
|
755
|
+
}
|
|
756
|
+
checkBlock(nodes) {
|
|
757
|
+
if (!nodes) return;
|
|
758
|
+
for (const node of nodes) {
|
|
759
|
+
this.checkNode(node);
|
|
760
|
+
if (node.type === "expression-statement" && typeof node.expression === "string") {
|
|
761
|
+
const varName = node.expression;
|
|
762
|
+
const typeInfo = this.typeMap.get(varName);
|
|
763
|
+
if (typeInfo) {
|
|
764
|
+
this.addDiagnostic("info", `Type of '${varName}' is ${typeInfo.type}`, "DEBUG");
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Check if two types are compatible
|
|
771
|
+
*/
|
|
772
|
+
isCompatible(source, target) {
|
|
773
|
+
if (source.type === "any" || target.type === "any") return true;
|
|
774
|
+
if (source.type === target.type) return true;
|
|
775
|
+
if (target.type === "vec3" && source.type === "euler") return true;
|
|
776
|
+
if (target.type === "euler" && source.type === "vec3") return true;
|
|
777
|
+
if (target.type === "vec4" && source.type === "quat") return true;
|
|
778
|
+
if (target.type === "quat" && source.type === "vec4") return true;
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
checkConnection(node) {
|
|
782
|
+
const fromType = this.typeMap.get(node.from);
|
|
783
|
+
const toType = this.typeMap.get(node.to);
|
|
784
|
+
if (!fromType) {
|
|
785
|
+
this.addDiagnostic("error", `Unknown source '${node.from}' in connection`, "E001");
|
|
786
|
+
}
|
|
787
|
+
if (!toType) {
|
|
788
|
+
this.addDiagnostic("error", `Unknown target '${node.to}' in connection`, "E002");
|
|
789
|
+
}
|
|
790
|
+
if (fromType && toType && node.dataType !== "any") {
|
|
791
|
+
if (!this.isCompatible(fromType, toType)) {
|
|
792
|
+
this.addDiagnostic(
|
|
793
|
+
"warning",
|
|
794
|
+
`Connection from '${node.from}' (${fromType.type}) to '${node.to}' (${toType.type}) may be incompatible`,
|
|
795
|
+
"W001",
|
|
796
|
+
[`Consider using 'as "any"' to bypass type checking`]
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
checkGate(node) {
|
|
802
|
+
let narrowedVar;
|
|
803
|
+
let narrowedType;
|
|
804
|
+
if (typeof node.condition === "string") {
|
|
805
|
+
const conditionVars = this.extractVariables(node.condition);
|
|
806
|
+
for (const varName of conditionVars) {
|
|
807
|
+
if (!this.typeMap.has(varName) && !this.isLiteral(varName)) {
|
|
808
|
+
this.addDiagnostic("error", `Unknown variable '${varName}' in gate condition`, "E003");
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
} else if (node.condition.type === "type-guard") {
|
|
812
|
+
narrowedVar = node.condition.subject;
|
|
813
|
+
narrowedType = { type: this.parseTypeString(node.condition.guardType) };
|
|
814
|
+
this.checkTypeGuard(node.condition);
|
|
815
|
+
}
|
|
816
|
+
if (narrowedVar && narrowedType) {
|
|
817
|
+
const originalType = this.typeMap.get(narrowedVar);
|
|
818
|
+
this.typeMap.set(narrowedVar, narrowedType);
|
|
819
|
+
this.addDiagnostic("info", `Type of '${narrowedVar}' is ${narrowedType.type}`, "DEBUG");
|
|
820
|
+
this.checkBlock(node.truePath);
|
|
821
|
+
if (originalType) this.typeMap.set(narrowedVar, originalType);
|
|
822
|
+
else this.typeMap.delete(narrowedVar);
|
|
823
|
+
} else {
|
|
824
|
+
this.checkBlock(node.truePath);
|
|
825
|
+
}
|
|
826
|
+
if (node.falsePath && node.falsePath.length > 0) {
|
|
827
|
+
this.checkBlock(node.falsePath);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
checkTypeGuard(node) {
|
|
831
|
+
if (!this.typeMap.has(node.subject)) {
|
|
832
|
+
this.addDiagnostic("error", `Unknown variable '${node.subject}' in type guard`, "E101");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
checkVariableDeclaration(node) {
|
|
836
|
+
const typeInfo = this.typeMap.get(node.name);
|
|
837
|
+
if (typeInfo && node.value !== void 0 && !node.isExpression) {
|
|
838
|
+
const inferredValueType = this.inferTypeWithContext(node.value, typeInfo);
|
|
839
|
+
if (!this.isCompatible(inferredValueType, typeInfo)) {
|
|
840
|
+
this.addDiagnostic(
|
|
841
|
+
"error",
|
|
842
|
+
`Type mismatch: cannot assign ${inferredValueType.type} to ${typeInfo.type}`,
|
|
843
|
+
"E102"
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (node.isExpression && typeof node.value === "string") {
|
|
848
|
+
const vars = this.extractVariables(node.value);
|
|
849
|
+
for (const v of vars) {
|
|
850
|
+
if (!this.typeMap.has(v) && !this.isLiteral(v) && !BUILTIN_FUNCTIONS.has(v)) {
|
|
851
|
+
this.addDiagnostic(
|
|
852
|
+
"warning",
|
|
853
|
+
`Reference to potentially undeclared variable '${v}'`,
|
|
854
|
+
"W101"
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
checkForLoop(node) {
|
|
861
|
+
const initVars = this.extractVariables(node.init);
|
|
862
|
+
for (const varName of initVars) {
|
|
863
|
+
if (!this.typeMap.has(varName)) {
|
|
864
|
+
this.typeMap.set(varName, { type: "number" });
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (typeof node.condition === "string") {
|
|
868
|
+
const condVars = this.extractVariables(node.condition);
|
|
869
|
+
for (const varName of condVars) {
|
|
870
|
+
if (!this.typeMap.has(varName) && !this.isLiteral(varName)) {
|
|
871
|
+
this.addDiagnostic(
|
|
872
|
+
"error",
|
|
873
|
+
`Unknown variable '${varName}' in for loop condition`,
|
|
874
|
+
"E004"
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
} else {
|
|
879
|
+
this.checkTypeGuard(node.condition);
|
|
880
|
+
}
|
|
881
|
+
this.checkBlock(node.body);
|
|
882
|
+
}
|
|
883
|
+
checkWhileLoop(node) {
|
|
884
|
+
if (typeof node.condition === "string") {
|
|
885
|
+
const condVars = this.extractVariables(node.condition);
|
|
886
|
+
for (const varName of condVars) {
|
|
887
|
+
if (!this.typeMap.has(varName) && !this.isLiteral(varName)) {
|
|
888
|
+
this.addDiagnostic(
|
|
889
|
+
"error",
|
|
890
|
+
`Unknown variable '${varName}' in while loop condition`,
|
|
891
|
+
"E005"
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
} else {
|
|
896
|
+
this.checkTypeGuard(node.condition);
|
|
897
|
+
}
|
|
898
|
+
this.checkBlock(node.body);
|
|
899
|
+
}
|
|
900
|
+
checkForEachLoop(node) {
|
|
901
|
+
if (!this.typeMap.has(node.collection)) {
|
|
902
|
+
this.addDiagnostic(
|
|
903
|
+
"error",
|
|
904
|
+
`Unknown collection '${node.collection}' in forEach loop`,
|
|
905
|
+
"E006"
|
|
906
|
+
);
|
|
907
|
+
} else {
|
|
908
|
+
const collectionType2 = this.typeMap.get(node.collection);
|
|
909
|
+
if (collectionType2 && collectionType2.type !== "array" && collectionType2.type !== "any") {
|
|
910
|
+
this.addDiagnostic(
|
|
911
|
+
"error",
|
|
912
|
+
`'${node.collection}' is not iterable (type: ${collectionType2.type})`,
|
|
913
|
+
"E007"
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const collectionType = this.typeMap.get(node.collection);
|
|
918
|
+
const itemType = collectionType?.type === "array" && collectionType.elementType ? collectionType.elementType : "any";
|
|
919
|
+
const originalType = this.typeMap.get(node.variable);
|
|
920
|
+
this.typeMap.set(node.variable, { type: itemType });
|
|
921
|
+
this.checkBlock(node.body);
|
|
922
|
+
if (originalType) this.typeMap.set(node.variable, originalType);
|
|
923
|
+
else this.typeMap.delete(node.variable);
|
|
924
|
+
}
|
|
925
|
+
checkImport(node) {
|
|
926
|
+
if (!node.modulePath) {
|
|
927
|
+
this.addDiagnostic("error", "Import statement missing module path", "E008");
|
|
928
|
+
}
|
|
929
|
+
for (const name of node.imports) {
|
|
930
|
+
this.typeMap.set(name, { type: "any" });
|
|
931
|
+
}
|
|
932
|
+
if (node.defaultImport) {
|
|
933
|
+
this.typeMap.set(node.defaultImport, { type: "any" });
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
checkExport(node) {
|
|
937
|
+
if (node.exports) {
|
|
938
|
+
for (const name of node.exports) {
|
|
939
|
+
if (!this.typeMap.has(name)) {
|
|
940
|
+
this.addDiagnostic("error", `Cannot export unknown identifier '${name}'`, "E009");
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Infer type from a value
|
|
947
|
+
*/
|
|
948
|
+
inferType(value) {
|
|
949
|
+
if (value === null || value === void 0) {
|
|
950
|
+
return { type: "any", nullable: true };
|
|
951
|
+
}
|
|
952
|
+
if (typeof value === "function") {
|
|
953
|
+
const fn = value;
|
|
954
|
+
return {
|
|
955
|
+
type: "function",
|
|
956
|
+
parameters: Array.from({ length: fn.length }, (_, i) => ({
|
|
957
|
+
name: `arg${i}`,
|
|
958
|
+
type: "any"
|
|
959
|
+
})),
|
|
960
|
+
returnType: "void"
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
if (typeof value === "number") {
|
|
964
|
+
return { type: "number" };
|
|
965
|
+
}
|
|
966
|
+
if (typeof value === "string") {
|
|
967
|
+
if (/^\(.*\)\s*=>/.test(value) || /^\w+\s*=>/.test(value)) {
|
|
968
|
+
const arrowMatch = value.match(/=>\s*(.+)$/s);
|
|
969
|
+
let returnType = "void";
|
|
970
|
+
if (arrowMatch) {
|
|
971
|
+
const body = arrowMatch[1].trim();
|
|
972
|
+
if (/^["']/.test(body)) returnType = "string";
|
|
973
|
+
else if (/^-?\d/.test(body)) returnType = "number";
|
|
974
|
+
else if (body === "true" || body === "false") returnType = "boolean";
|
|
975
|
+
else if (/^\[/.test(body)) returnType = "array";
|
|
976
|
+
else if (/^\{/.test(body) && !body.startsWith("{\n")) returnType = "object";
|
|
977
|
+
else if (body !== "" && !body.startsWith("{")) returnType = "any";
|
|
978
|
+
}
|
|
979
|
+
return { type: "function", parameters: [], returnType };
|
|
980
|
+
}
|
|
981
|
+
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(value)) return { type: "color" };
|
|
982
|
+
if (/^rgba?\s*\(/.test(value)) return { type: "color" };
|
|
983
|
+
return { type: "string" };
|
|
984
|
+
}
|
|
985
|
+
if (typeof value === "boolean") {
|
|
986
|
+
return { type: "boolean" };
|
|
987
|
+
}
|
|
988
|
+
if (Array.isArray(value)) {
|
|
989
|
+
if (value.length === 2 && value.every((v) => typeof v === "number")) return { type: "vec2" };
|
|
990
|
+
if (value.length === 3 && value.every((v) => typeof v === "number")) return { type: "vec3" };
|
|
991
|
+
if (value.length === 4 && value.every((v) => typeof v === "number")) return { type: "vec4" };
|
|
992
|
+
const elementType = value.length > 0 ? this.inferType(value[0]).type : "any";
|
|
993
|
+
return { type: "array", elementType };
|
|
994
|
+
}
|
|
995
|
+
if (typeof value === "object") {
|
|
996
|
+
if ("x" in value && "y" in value) {
|
|
997
|
+
if ("z" in value) return { type: "vec3" };
|
|
998
|
+
return { type: "vec2" };
|
|
999
|
+
}
|
|
1000
|
+
const properties = /* @__PURE__ */ new Map();
|
|
1001
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1002
|
+
properties.set(key, this.inferType(val));
|
|
1003
|
+
}
|
|
1004
|
+
return { type: "object", properties };
|
|
1005
|
+
}
|
|
1006
|
+
return { type: "any" };
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Infer type with context
|
|
1010
|
+
*/
|
|
1011
|
+
inferTypeWithContext(value, expectedType) {
|
|
1012
|
+
if (!expectedType) return this.inferType(value);
|
|
1013
|
+
const baseInferred = this.inferType(value);
|
|
1014
|
+
if (expectedType.type === "color" && typeof value === "string") {
|
|
1015
|
+
return { type: "color" };
|
|
1016
|
+
}
|
|
1017
|
+
if (expectedType.type === "euler" && Array.isArray(value) && value.length === 3) {
|
|
1018
|
+
return { type: "euler" };
|
|
1019
|
+
}
|
|
1020
|
+
if (expectedType.type === "quat" && Array.isArray(value) && value.length === 4) {
|
|
1021
|
+
return { type: "quat" };
|
|
1022
|
+
}
|
|
1023
|
+
if (expectedType.type === "vec3" && Array.isArray(value) && value.length === 3) {
|
|
1024
|
+
return { type: "vec3" };
|
|
1025
|
+
}
|
|
1026
|
+
return baseInferred;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Parse type string to HoloScriptType
|
|
1030
|
+
*/
|
|
1031
|
+
parseTypeString(typeStr) {
|
|
1032
|
+
const normalized = typeStr.toLowerCase().trim();
|
|
1033
|
+
const validTypes = [
|
|
1034
|
+
"number",
|
|
1035
|
+
"string",
|
|
1036
|
+
"boolean",
|
|
1037
|
+
"array",
|
|
1038
|
+
"object",
|
|
1039
|
+
"function",
|
|
1040
|
+
"void",
|
|
1041
|
+
"any",
|
|
1042
|
+
"unknown",
|
|
1043
|
+
"never",
|
|
1044
|
+
"orb",
|
|
1045
|
+
"stream",
|
|
1046
|
+
"connection",
|
|
1047
|
+
"gate",
|
|
1048
|
+
"vec2",
|
|
1049
|
+
"vec3",
|
|
1050
|
+
"vec4",
|
|
1051
|
+
"color",
|
|
1052
|
+
"euler",
|
|
1053
|
+
"quat",
|
|
1054
|
+
"mat4"
|
|
1055
|
+
];
|
|
1056
|
+
if (validTypes.includes(normalized)) {
|
|
1057
|
+
return normalized;
|
|
1058
|
+
}
|
|
1059
|
+
return "any";
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Extract variable names from an expression string
|
|
1063
|
+
*/
|
|
1064
|
+
extractVariables(expr) {
|
|
1065
|
+
const varPattern = /\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
|
|
1066
|
+
const matches = expr.match(varPattern) || [];
|
|
1067
|
+
const keywords = ["true", "false", "null", "undefined", "if", "else", "for", "while", "return"];
|
|
1068
|
+
return matches.filter((m) => !keywords.includes(m));
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Check if string is a literal
|
|
1072
|
+
*/
|
|
1073
|
+
isLiteral(str) {
|
|
1074
|
+
if (/^-?\d+(\.\d+)?$/.test(str)) return true;
|
|
1075
|
+
if (str === "true" || str === "false") return true;
|
|
1076
|
+
if (/^["'].*["']$/.test(str)) return true;
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
validateTraitConstraints(node) {
|
|
1080
|
+
const traitsMap = node.traits;
|
|
1081
|
+
if (!traitsMap || traitsMap.size === 0) {
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
const traitNames = Array.from(traitsMap.keys());
|
|
1085
|
+
const allConstraints = [...BUILTIN_CONSTRAINTS, ...this.customConstraints];
|
|
1086
|
+
for (const constraint of allConstraints) {
|
|
1087
|
+
if (constraint.type === "requires") {
|
|
1088
|
+
if (traitNames.includes(constraint.source)) {
|
|
1089
|
+
for (const target of constraint.targets) {
|
|
1090
|
+
if (!traitNames.includes(target)) {
|
|
1091
|
+
const suggestions = constraint.suggestion ? [constraint.suggestion] : [`Add @${target} to the same orb as @${constraint.source}.`];
|
|
1092
|
+
this.addDiagnostic(
|
|
1093
|
+
"error",
|
|
1094
|
+
constraint.message || `Trait @${constraint.source} requires @${target}.`,
|
|
1095
|
+
"HSP014",
|
|
1096
|
+
suggestions
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
} else if (constraint.type === "conflicts") {
|
|
1102
|
+
if (traitNames.includes(constraint.source)) {
|
|
1103
|
+
for (const target of constraint.targets) {
|
|
1104
|
+
if (traitNames.includes(target)) {
|
|
1105
|
+
const suggestions = constraint.suggestion ? [constraint.suggestion] : [`Remove either @${constraint.source} or @${target} from the orb.`];
|
|
1106
|
+
this.addDiagnostic(
|
|
1107
|
+
"error",
|
|
1108
|
+
constraint.message || `Trait @${constraint.source} conflicts with @${target}.`,
|
|
1109
|
+
"HSP014",
|
|
1110
|
+
suggestions
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
} else if (constraint.type === "oneof") {
|
|
1116
|
+
const matches = traitNames.filter((t) => constraint.targets.includes(t));
|
|
1117
|
+
if (matches.length > 1) {
|
|
1118
|
+
const suggestions = constraint.suggestion ? [constraint.suggestion] : [`Keep only one of: ${matches.map((t) => "@" + t).join(", ")}. Remove the others.`];
|
|
1119
|
+
this.addDiagnostic(
|
|
1120
|
+
"error",
|
|
1121
|
+
constraint.message || `Only one of the following traits can be used at a time: ${constraint.targets.map((t) => "@" + t).join(", ")}.`,
|
|
1122
|
+
"HSP014",
|
|
1123
|
+
suggestions
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Add a diagnostic
|
|
1131
|
+
*/
|
|
1132
|
+
addDiagnostic(severity, message, code, suggestions) {
|
|
1133
|
+
this.diagnostics.push({
|
|
1134
|
+
severity,
|
|
1135
|
+
message,
|
|
1136
|
+
line: this.currentLine,
|
|
1137
|
+
column: this.currentColumn,
|
|
1138
|
+
code,
|
|
1139
|
+
suggestions
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Get type info for a name
|
|
1144
|
+
*/
|
|
1145
|
+
getType(name) {
|
|
1146
|
+
return this.typeMap.get(name);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Get all registered types
|
|
1150
|
+
*/
|
|
1151
|
+
getAllTypes() {
|
|
1152
|
+
return new Map(this.typeMap);
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Reset the type checker
|
|
1156
|
+
*/
|
|
1157
|
+
reset() {
|
|
1158
|
+
this.typeMap.clear();
|
|
1159
|
+
this.diagnostics = [];
|
|
1160
|
+
this.unionTypes.clear();
|
|
1161
|
+
BUILTIN_FUNCTIONS.forEach((type, name) => {
|
|
1162
|
+
this.typeMap.set(name, type);
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Get all registered union types
|
|
1167
|
+
*/
|
|
1168
|
+
getAllUnionTypes() {
|
|
1169
|
+
return new Map(this.unionTypes);
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Sprint 5 — String-based type inference.
|
|
1173
|
+
*
|
|
1174
|
+
* Infers a simplified HoloScript type name from an expression string.
|
|
1175
|
+
* This is a lightweight syntactic analysis that does NOT require the AST:
|
|
1176
|
+
*
|
|
1177
|
+
* | Expression | Inferred type |
|
|
1178
|
+
* |---------------------------|---------------|
|
|
1179
|
+
* | `[0, 1, 0]` | `'vec3'` |
|
|
1180
|
+
* | `[0, 1]` | `'vec2'` |
|
|
1181
|
+
* | `3.14` | `'float'` |
|
|
1182
|
+
* | `42`, `0` | `'int'` |
|
|
1183
|
+
* | `true` / `false` | `'bool'` |
|
|
1184
|
+
* | `"hello"` / `'world'` | `'string'` |
|
|
1185
|
+
* | anything else | `'any'` |
|
|
1186
|
+
*
|
|
1187
|
+
* @param expr Raw expression string (trimmed)
|
|
1188
|
+
*/
|
|
1189
|
+
inferTypeExpression(expr) {
|
|
1190
|
+
const s = expr.trim();
|
|
1191
|
+
if (!s) return "any";
|
|
1192
|
+
if (s === "true" || s === "false") return "bool";
|
|
1193
|
+
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'"))
|
|
1194
|
+
return "string";
|
|
1195
|
+
if (/^-?\d+$/.test(s)) return "int";
|
|
1196
|
+
if (/^-?\d+\.\d+$/.test(s)) return "float";
|
|
1197
|
+
if (s.startsWith("[") && s.endsWith("]")) {
|
|
1198
|
+
const inner = s.slice(1, -1).trim();
|
|
1199
|
+
if (!inner) return "any";
|
|
1200
|
+
const parts = inner.split(",").map((p) => p.trim());
|
|
1201
|
+
const allNumeric = parts.every((p) => /^-?\d+(\.\d+)?$/.test(p));
|
|
1202
|
+
if (allNumeric) {
|
|
1203
|
+
if (parts.length === 2) return "vec2";
|
|
1204
|
+
if (parts.length === 3) return "vec3";
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return "any";
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
function createTypeChecker() {
|
|
1211
|
+
return new HoloScriptTypeChecker();
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
export { HoloScriptTypeChecker, createTypeChecker };
|
|
1215
|
+
//# sourceMappingURL=chunk-VMLKSV26.js.map
|
|
1216
|
+
//# sourceMappingURL=chunk-VMLKSV26.js.map
|