@holoscript/core 2.0.1 → 2.1.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 (137) hide show
  1. package/dist/chunk-2XXE34KS.js +344 -0
  2. package/dist/chunk-2XXE34KS.js.map +1 -0
  3. package/dist/chunk-3X2EGU7Z.cjs +52 -0
  4. package/dist/chunk-3X2EGU7Z.cjs.map +1 -0
  5. package/dist/chunk-AFFVFO4D.js +1689 -0
  6. package/dist/chunk-AFFVFO4D.js.map +1 -0
  7. package/dist/chunk-DGUM43GV.js +10 -0
  8. package/dist/chunk-DGUM43GV.js.map +1 -0
  9. package/{src/HoloScriptDebugger.ts → dist/chunk-DOY73HDH.js} +118 -257
  10. package/dist/chunk-DOY73HDH.js.map +1 -0
  11. package/dist/chunk-JEQ2X3Z6.cjs +12 -0
  12. package/dist/chunk-JEQ2X3Z6.cjs.map +1 -0
  13. package/dist/chunk-L6VLNVKP.cjs +1691 -0
  14. package/dist/chunk-L6VLNVKP.cjs.map +1 -0
  15. package/dist/chunk-MFNO57XL.cjs +347 -0
  16. package/dist/chunk-MFNO57XL.cjs.map +1 -0
  17. package/dist/chunk-R75MREOS.cjs +424 -0
  18. package/dist/chunk-R75MREOS.cjs.map +1 -0
  19. package/dist/chunk-SATNCODL.js +45 -0
  20. package/dist/chunk-SATNCODL.js.map +1 -0
  21. package/dist/chunk-T57ZL7KR.cjs +1281 -0
  22. package/dist/chunk-T57ZL7KR.cjs.map +1 -0
  23. package/dist/chunk-U72GEJZT.js +1279 -0
  24. package/dist/chunk-U72GEJZT.js.map +1 -0
  25. package/dist/debugger.cjs +20 -0
  26. package/dist/debugger.cjs.map +1 -0
  27. package/dist/debugger.d.cts +171 -0
  28. package/dist/debugger.d.ts +171 -0
  29. package/dist/debugger.js +7 -0
  30. package/dist/debugger.js.map +1 -0
  31. package/dist/index.cjs +6803 -0
  32. package/dist/index.cjs.map +1 -0
  33. package/dist/index.d.cts +4093 -0
  34. package/dist/index.d.ts +4093 -0
  35. package/dist/index.js +6715 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/parser.cjs +14 -0
  38. package/dist/parser.cjs.map +1 -0
  39. package/dist/parser.d.cts +172 -0
  40. package/dist/parser.d.ts +172 -0
  41. package/dist/parser.js +5 -0
  42. package/dist/parser.js.map +1 -0
  43. package/dist/runtime.cjs +14 -0
  44. package/dist/runtime.cjs.map +1 -0
  45. package/dist/runtime.d.cts +200 -0
  46. package/dist/runtime.d.ts +200 -0
  47. package/dist/runtime.js +5 -0
  48. package/dist/runtime.js.map +1 -0
  49. package/dist/type-checker.cjs +17 -0
  50. package/dist/type-checker.cjs.map +1 -0
  51. package/dist/type-checker.d.cts +105 -0
  52. package/dist/type-checker.d.ts +105 -0
  53. package/dist/type-checker.js +4 -0
  54. package/dist/type-checker.js.map +1 -0
  55. package/dist/types-4h8cbtF_.d.cts +329 -0
  56. package/dist/types-4h8cbtF_.d.ts +329 -0
  57. package/package.json +17 -13
  58. package/src/HoloScript2DParser.js +0 -227
  59. package/src/HoloScript2DParser.ts +0 -261
  60. package/src/HoloScriptCodeParser.js +0 -1102
  61. package/src/HoloScriptCodeParser.ts +0 -1188
  62. package/src/HoloScriptDebugger.js +0 -458
  63. package/src/HoloScriptParser.js +0 -338
  64. package/src/HoloScriptParser.ts +0 -397
  65. package/src/HoloScriptPlusParser.js +0 -371
  66. package/src/HoloScriptPlusParser.ts +0 -543
  67. package/src/HoloScriptRuntime.js +0 -1399
  68. package/src/HoloScriptRuntime.test.js +0 -351
  69. package/src/HoloScriptRuntime.test.ts +0 -436
  70. package/src/HoloScriptRuntime.ts +0 -1653
  71. package/src/HoloScriptTypeChecker.js +0 -356
  72. package/src/HoloScriptTypeChecker.ts +0 -475
  73. package/src/__tests__/GraphicsServices.test.js +0 -357
  74. package/src/__tests__/GraphicsServices.test.ts +0 -427
  75. package/src/__tests__/HoloScriptPlusParser.test.js +0 -317
  76. package/src/__tests__/HoloScriptPlusParser.test.ts +0 -392
  77. package/src/__tests__/integration.test.js +0 -336
  78. package/src/__tests__/integration.test.ts +0 -416
  79. package/src/__tests__/performance.bench.js +0 -218
  80. package/src/__tests__/performance.bench.ts +0 -262
  81. package/src/__tests__/type-checker.test.js +0 -60
  82. package/src/__tests__/type-checker.test.ts +0 -73
  83. package/src/index.js +0 -217
  84. package/src/index.ts +0 -426
  85. package/src/interop/Interoperability.js +0 -413
  86. package/src/interop/Interoperability.ts +0 -494
  87. package/src/logger.js +0 -42
  88. package/src/logger.ts +0 -57
  89. package/src/parser/EnhancedParser.js +0 -205
  90. package/src/parser/EnhancedParser.ts +0 -251
  91. package/src/parser/HoloScriptPlusParser.js +0 -928
  92. package/src/parser/HoloScriptPlusParser.ts +0 -1089
  93. package/src/runtime/HoloScriptPlusRuntime.js +0 -674
  94. package/src/runtime/HoloScriptPlusRuntime.ts +0 -861
  95. package/src/runtime/PerformanceTelemetry.js +0 -323
  96. package/src/runtime/PerformanceTelemetry.ts +0 -467
  97. package/src/runtime/RuntimeOptimization.js +0 -361
  98. package/src/runtime/RuntimeOptimization.ts +0 -416
  99. package/src/services/HololandGraphicsPipelineService.js +0 -506
  100. package/src/services/HololandGraphicsPipelineService.ts +0 -662
  101. package/src/services/PlatformPerformanceOptimizer.js +0 -356
  102. package/src/services/PlatformPerformanceOptimizer.ts +0 -503
  103. package/src/state/ReactiveState.js +0 -427
  104. package/src/state/ReactiveState.ts +0 -572
  105. package/src/tools/DeveloperExperience.js +0 -376
  106. package/src/tools/DeveloperExperience.ts +0 -438
  107. package/src/traits/AIDriverTrait.js +0 -322
  108. package/src/traits/AIDriverTrait.test.js +0 -329
  109. package/src/traits/AIDriverTrait.test.ts +0 -357
  110. package/src/traits/AIDriverTrait.ts +0 -474
  111. package/src/traits/LightingTrait.js +0 -313
  112. package/src/traits/LightingTrait.test.js +0 -410
  113. package/src/traits/LightingTrait.test.ts +0 -462
  114. package/src/traits/LightingTrait.ts +0 -505
  115. package/src/traits/MaterialTrait.js +0 -194
  116. package/src/traits/MaterialTrait.test.js +0 -286
  117. package/src/traits/MaterialTrait.test.ts +0 -329
  118. package/src/traits/MaterialTrait.ts +0 -324
  119. package/src/traits/RenderingTrait.js +0 -356
  120. package/src/traits/RenderingTrait.test.js +0 -363
  121. package/src/traits/RenderingTrait.test.ts +0 -427
  122. package/src/traits/RenderingTrait.ts +0 -555
  123. package/src/traits/VRTraitSystem.js +0 -740
  124. package/src/traits/VRTraitSystem.ts +0 -1040
  125. package/src/traits/VoiceInputTrait.js +0 -284
  126. package/src/traits/VoiceInputTrait.test.js +0 -226
  127. package/src/traits/VoiceInputTrait.test.ts +0 -252
  128. package/src/traits/VoiceInputTrait.ts +0 -401
  129. package/src/types/AdvancedTypeSystem.js +0 -226
  130. package/src/types/AdvancedTypeSystem.ts +0 -494
  131. package/src/types/HoloScriptPlus.d.ts +0 -853
  132. package/src/types.js +0 -6
  133. package/src/types.ts +0 -369
  134. package/tsconfig.json +0 -23
  135. package/tsup.config.d.ts +0 -2
  136. package/tsup.config.js +0 -18
  137. package/tsup.config.ts +0 -19
@@ -1,928 +0,0 @@
1
- /**
2
- * HoloScript+ Parser
3
- *
4
- * Parses HoloScript+ source code into an AST with support for:
5
- * - Standard HoloScript syntax (backward compatible)
6
- * - @ directive parsing for VR traits, state, control flow
7
- * - Expression interpolation with ${...}
8
- * - TypeScript companion imports
9
- *
10
- * @version 1.0.0
11
- */
12
- // =============================================================================
13
- // VR TRAITS
14
- // =============================================================================
15
- const VR_TRAITS = [
16
- 'grabbable',
17
- 'throwable',
18
- 'pointable',
19
- 'hoverable',
20
- 'scalable',
21
- 'rotatable',
22
- 'stackable',
23
- 'snappable',
24
- 'breakable',
25
- ];
26
- // =============================================================================
27
- // LIFECYCLE HOOKS
28
- // =============================================================================
29
- const LIFECYCLE_HOOKS = [
30
- // Standard lifecycle
31
- 'on_mount',
32
- 'on_unmount',
33
- 'on_update',
34
- 'on_data_update',
35
- // VR lifecycle
36
- 'on_grab',
37
- 'on_release',
38
- 'on_hover_enter',
39
- 'on_hover_exit',
40
- 'on_point_enter',
41
- 'on_point_exit',
42
- 'on_collision',
43
- 'on_trigger_enter',
44
- 'on_trigger_exit',
45
- 'on_click',
46
- 'on_double_click',
47
- // Controller hooks
48
- 'on_controller_button',
49
- 'on_trigger_hold',
50
- 'on_trigger_release',
51
- 'on_grip_hold',
52
- 'on_grip_release',
53
- ];
54
- // =============================================================================
55
- // LEXER
56
- // =============================================================================
57
- class Lexer {
58
- constructor(source) {
59
- this.pos = 0;
60
- this.line = 1;
61
- this.column = 1;
62
- this.indentStack = [0];
63
- this.tokens = [];
64
- this.pendingDedents = 0;
65
- this.source = source;
66
- }
67
- tokenize() {
68
- while (this.pos < this.source.length) {
69
- // Handle pending dedents
70
- while (this.pendingDedents > 0) {
71
- this.tokens.push(this.createToken('DEDENT', ''));
72
- this.pendingDedents--;
73
- }
74
- const char = this.source[this.pos];
75
- // Skip whitespace (but track indentation at line start)
76
- if (char === ' ' || char === '\t') {
77
- if (this.column === 1) {
78
- this.handleIndentation();
79
- }
80
- else {
81
- this.advance();
82
- }
83
- continue;
84
- }
85
- // Comments
86
- if (char === '/' && this.peek(1) === '/') {
87
- this.skipLineComment();
88
- continue;
89
- }
90
- if (char === '/' && this.peek(1) === '*') {
91
- this.skipBlockComment();
92
- continue;
93
- }
94
- if (char === '#' && this.peek(1) !== '#') {
95
- if (this.peek(1) === '#') {
96
- this.skipLineComment();
97
- continue;
98
- }
99
- }
100
- // Newlines
101
- if (char === '\n') {
102
- this.tokens.push(this.createToken('NEWLINE', '\n'));
103
- this.advance();
104
- this.line++;
105
- this.column = 1;
106
- continue;
107
- }
108
- if (char === '\r') {
109
- this.advance();
110
- if (this.peek() === '\n') {
111
- this.advance();
112
- }
113
- this.tokens.push(this.createToken('NEWLINE', '\n'));
114
- this.line++;
115
- this.column = 1;
116
- continue;
117
- }
118
- // Symbols
119
- if (char === '{') {
120
- this.tokens.push(this.createToken('LBRACE', '{'));
121
- this.advance();
122
- continue;
123
- }
124
- if (char === '}') {
125
- this.tokens.push(this.createToken('RBRACE', '}'));
126
- this.advance();
127
- continue;
128
- }
129
- if (char === '[') {
130
- this.tokens.push(this.createToken('LBRACKET', '['));
131
- this.advance();
132
- continue;
133
- }
134
- if (char === ']') {
135
- this.tokens.push(this.createToken('RBRACKET', ']'));
136
- this.advance();
137
- continue;
138
- }
139
- if (char === '(') {
140
- this.tokens.push(this.createToken('LPAREN', '('));
141
- this.advance();
142
- continue;
143
- }
144
- if (char === ')') {
145
- this.tokens.push(this.createToken('RPAREN', ')'));
146
- this.advance();
147
- continue;
148
- }
149
- if (char === ':') {
150
- this.tokens.push(this.createToken('COLON', ':'));
151
- this.advance();
152
- continue;
153
- }
154
- if (char === ',') {
155
- this.tokens.push(this.createToken('COMMA', ','));
156
- this.advance();
157
- continue;
158
- }
159
- if (char === '@') {
160
- this.tokens.push(this.createToken('AT', '@'));
161
- this.advance();
162
- continue;
163
- }
164
- if (char === '#') {
165
- this.tokens.push(this.createToken('HASH', '#'));
166
- this.advance();
167
- continue;
168
- }
169
- if (char === '.') {
170
- this.tokens.push(this.createToken('DOT', '.'));
171
- this.advance();
172
- continue;
173
- }
174
- if (char === '=') {
175
- if (this.peek(1) === '>') {
176
- this.tokens.push(this.createToken('ARROW', '=>'));
177
- this.advance();
178
- this.advance();
179
- continue;
180
- }
181
- this.tokens.push(this.createToken('EQUALS', '='));
182
- this.advance();
183
- continue;
184
- }
185
- if (char === '|') {
186
- this.tokens.push(this.createToken('PIPE', '|'));
187
- this.advance();
188
- continue;
189
- }
190
- // Strings
191
- if (char === '"' || char === "'") {
192
- this.tokens.push(this.readString(char));
193
- continue;
194
- }
195
- // Numbers
196
- if (this.isDigit(char) || (char === '-' && this.isDigit(this.peek(1)))) {
197
- this.tokens.push(this.readNumber());
198
- continue;
199
- }
200
- // Expression interpolation ${...}
201
- if (char === '$' && this.peek(1) === '{') {
202
- this.tokens.push(this.readExpression());
203
- continue;
204
- }
205
- // Identifiers and keywords
206
- if (this.isIdentifierStart(char)) {
207
- this.tokens.push(this.readIdentifier());
208
- continue;
209
- }
210
- // Unknown character - skip
211
- this.advance();
212
- }
213
- // Handle remaining dedents
214
- while (this.indentStack.length > 1) {
215
- this.tokens.push(this.createToken('DEDENT', ''));
216
- this.indentStack.pop();
217
- }
218
- this.tokens.push(this.createToken('EOF', ''));
219
- return this.tokens;
220
- }
221
- advance() {
222
- const char = this.source[this.pos];
223
- this.pos++;
224
- this.column++;
225
- return char;
226
- }
227
- peek(offset = 0) {
228
- const pos = this.pos + offset;
229
- return pos < this.source.length ? this.source[pos] : '';
230
- }
231
- createToken(type, value) {
232
- return {
233
- type,
234
- value,
235
- line: this.line,
236
- column: this.column - value.length,
237
- };
238
- }
239
- handleIndentation() {
240
- let indent = 0;
241
- while (this.peek() === ' ' || this.peek() === '\t') {
242
- indent += this.peek() === '\t' ? 4 : 1;
243
- this.advance();
244
- }
245
- if (this.peek() === '\n' || this.peek() === '\r') {
246
- return;
247
- }
248
- const currentIndent = this.indentStack[this.indentStack.length - 1];
249
- if (indent > currentIndent) {
250
- this.indentStack.push(indent);
251
- this.tokens.push(this.createToken('INDENT', ''));
252
- }
253
- else if (indent < currentIndent) {
254
- while (this.indentStack.length > 1 &&
255
- indent < this.indentStack[this.indentStack.length - 1]) {
256
- this.indentStack.pop();
257
- this.pendingDedents++;
258
- }
259
- }
260
- }
261
- skipLineComment() {
262
- while (this.peek() !== '\n' && this.pos < this.source.length) {
263
- this.advance();
264
- }
265
- }
266
- skipBlockComment() {
267
- this.advance(); // /
268
- this.advance(); // *
269
- while (this.pos < this.source.length) {
270
- if (this.peek() === '*' && this.peek(1) === '/') {
271
- this.advance();
272
- this.advance();
273
- break;
274
- }
275
- if (this.peek() === '\n') {
276
- this.line++;
277
- this.column = 0;
278
- }
279
- this.advance();
280
- }
281
- }
282
- readString(quote) {
283
- const startLine = this.line;
284
- const startColumn = this.column;
285
- this.advance(); // Opening quote
286
- let value = '';
287
- while (this.peek() !== quote && this.pos < this.source.length) {
288
- if (this.peek() === '\\') {
289
- this.advance();
290
- const escaped = this.advance();
291
- switch (escaped) {
292
- case 'n':
293
- value += '\n';
294
- break;
295
- case 't':
296
- value += '\t';
297
- break;
298
- case 'r':
299
- value += '\r';
300
- break;
301
- case '\\':
302
- value += '\\';
303
- break;
304
- case '"':
305
- value += '"';
306
- break;
307
- case "'":
308
- value += "'";
309
- break;
310
- default:
311
- value += escaped;
312
- }
313
- }
314
- else if (this.peek() === '\n') {
315
- this.line++;
316
- this.column = 0;
317
- value += this.advance();
318
- }
319
- else {
320
- value += this.advance();
321
- }
322
- }
323
- this.advance(); // Closing quote
324
- return {
325
- type: 'STRING',
326
- value,
327
- line: startLine,
328
- column: startColumn,
329
- };
330
- }
331
- readNumber() {
332
- const startColumn = this.column;
333
- let value = '';
334
- if (this.peek() === '-') {
335
- value += this.advance();
336
- }
337
- while (this.isDigit(this.peek())) {
338
- value += this.advance();
339
- }
340
- if (this.peek() === '.' && this.isDigit(this.peek(1))) {
341
- value += this.advance(); // .
342
- while (this.isDigit(this.peek())) {
343
- value += this.advance();
344
- }
345
- }
346
- // Scientific notation
347
- if (this.peek() === 'e' || this.peek() === 'E') {
348
- value += this.advance();
349
- if (this.peek() === '+' || this.peek() === '-') {
350
- value += this.advance();
351
- }
352
- while (this.isDigit(this.peek())) {
353
- value += this.advance();
354
- }
355
- }
356
- // Unit suffix
357
- while (this.isAlpha(this.peek()) || this.peek() === '%') {
358
- value += this.advance();
359
- }
360
- return {
361
- type: 'NUMBER',
362
- value,
363
- line: this.line,
364
- column: startColumn,
365
- };
366
- }
367
- readExpression() {
368
- const startLine = this.line;
369
- const startColumn = this.column;
370
- this.advance(); // $
371
- this.advance(); // {
372
- let value = '';
373
- let braceDepth = 1;
374
- while (braceDepth > 0 && this.pos < this.source.length) {
375
- if (this.peek() === '{') {
376
- braceDepth++;
377
- }
378
- else if (this.peek() === '}') {
379
- braceDepth--;
380
- if (braceDepth === 0) {
381
- break;
382
- }
383
- }
384
- if (this.peek() === '\n') {
385
- this.line++;
386
- this.column = 0;
387
- }
388
- value += this.advance();
389
- }
390
- this.advance(); // Closing }
391
- return {
392
- type: 'EXPRESSION',
393
- value: value.trim(),
394
- line: startLine,
395
- column: startColumn,
396
- };
397
- }
398
- readIdentifier() {
399
- const startColumn = this.column;
400
- let value = '';
401
- while (this.isIdentifierPart(this.peek())) {
402
- value += this.advance();
403
- }
404
- if (value === 'true' || value === 'false') {
405
- return {
406
- type: 'BOOLEAN',
407
- value,
408
- line: this.line,
409
- column: startColumn,
410
- };
411
- }
412
- if (value === 'null' || value === 'none') {
413
- return {
414
- type: 'NULL',
415
- value,
416
- line: this.line,
417
- column: startColumn,
418
- };
419
- }
420
- return {
421
- type: 'IDENTIFIER',
422
- value,
423
- line: this.line,
424
- column: startColumn,
425
- };
426
- }
427
- isDigit(char) {
428
- return char >= '0' && char <= '9';
429
- }
430
- isAlpha(char) {
431
- return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z');
432
- }
433
- isIdentifierStart(char) {
434
- return this.isAlpha(char) || char === '_';
435
- }
436
- isIdentifierPart(char) {
437
- return this.isIdentifierStart(char) || this.isDigit(char) || char === '-';
438
- }
439
- }
440
- // =============================================================================
441
- // PARSER
442
- // =============================================================================
443
- export class HoloScriptPlusParser {
444
- constructor(options = {}) {
445
- this.tokens = [];
446
- this.pos = 0;
447
- this.errors = [];
448
- this.warnings = [];
449
- this.imports = [];
450
- this.hasState = false;
451
- this.hasVRTraits = false;
452
- this.hasControlFlow = false;
453
- this.compiledExpressions = new Map();
454
- this.options = {
455
- enableVRTraits: true,
456
- enableTypeScriptImports: true,
457
- strict: false,
458
- ...options,
459
- };
460
- }
461
- parse(source) {
462
- // Reset state
463
- this.errors = [];
464
- this.warnings = [];
465
- this.imports = [];
466
- this.hasState = false;
467
- this.hasVRTraits = false;
468
- this.hasControlFlow = false;
469
- this.compiledExpressions = new Map();
470
- this.pos = 0;
471
- // Tokenize
472
- const lexer = new Lexer(source);
473
- this.tokens = lexer.tokenize();
474
- // Parse root node
475
- const root = this.parseDocument();
476
- // Build AST
477
- const ast = {
478
- version: '1.0',
479
- root,
480
- imports: this.imports,
481
- hasState: this.hasState,
482
- hasVRTraits: this.hasVRTraits,
483
- hasControlFlow: this.hasControlFlow,
484
- };
485
- return {
486
- ast,
487
- compiledExpressions: this.compiledExpressions,
488
- requiredCompanions: this.imports.map((i) => i.path),
489
- features: {
490
- state: this.hasState,
491
- vrTraits: this.hasVRTraits,
492
- loops: this.hasControlFlow,
493
- conditionals: this.hasControlFlow,
494
- lifecycleHooks: root.directives.some((d) => d.type === 'lifecycle'),
495
- },
496
- warnings: this.warnings,
497
- errors: this.errors,
498
- };
499
- }
500
- parseDocument() {
501
- this.skipNewlines();
502
- const directives = [];
503
- while (this.check('AT')) {
504
- const directive = this.parseDirective();
505
- if (directive) {
506
- directives.push(directive);
507
- }
508
- this.skipNewlines();
509
- }
510
- const root = this.parseNode();
511
- root.directives = [...directives, ...root.directives];
512
- return root;
513
- }
514
- parseNode() {
515
- const startToken = this.current();
516
- const type = this.expect('IDENTIFIER', 'Expected element type').value;
517
- let id;
518
- if (this.check('HASH')) {
519
- this.advance();
520
- id = this.expect('IDENTIFIER', 'Expected ID after #').value;
521
- }
522
- const properties = {};
523
- const directives = [];
524
- const traits = new Map();
525
- while (!this.check('LBRACE') && !this.check('NEWLINE') && !this.check('EOF')) {
526
- if (this.check('AT')) {
527
- const directive = this.parseDirective();
528
- if (directive) {
529
- if (directive.type === 'trait') {
530
- traits.set(directive.name, directive.config);
531
- this.hasVRTraits = true;
532
- }
533
- else {
534
- directives.push(directive);
535
- }
536
- }
537
- }
538
- else if (this.check('IDENTIFIER')) {
539
- const key = this.advance().value;
540
- if (this.check('COLON') || this.check('EQUALS')) {
541
- this.advance();
542
- properties[key] = this.parseValue();
543
- }
544
- else {
545
- properties[key] = true;
546
- }
547
- }
548
- else {
549
- break;
550
- }
551
- }
552
- const children = [];
553
- if (this.check('LBRACE')) {
554
- this.advance();
555
- this.skipNewlines();
556
- while (!this.check('RBRACE') && !this.check('EOF')) {
557
- if (this.check('AT')) {
558
- const directive = this.parseDirective();
559
- if (directive) {
560
- if (directive.type === 'trait') {
561
- traits.set(directive.name, directive.config);
562
- this.hasVRTraits = true;
563
- }
564
- else {
565
- directives.push(directive);
566
- }
567
- }
568
- }
569
- else if (this.check('IDENTIFIER')) {
570
- const saved = this.pos;
571
- const name = this.advance().value;
572
- if (this.check('COLON') || this.check('EQUALS')) {
573
- this.advance();
574
- properties[name] = this.parseValue();
575
- }
576
- else {
577
- this.pos = saved;
578
- children.push(this.parseNode());
579
- }
580
- }
581
- else {
582
- this.skipNewlines();
583
- if (this.check('RBRACE') || this.check('EOF'))
584
- break;
585
- this.advance();
586
- }
587
- this.skipNewlines();
588
- }
589
- this.expect('RBRACE', 'Expected }');
590
- }
591
- return {
592
- type,
593
- id,
594
- properties,
595
- directives,
596
- children,
597
- traits,
598
- loc: {
599
- start: { line: startToken.line, column: startToken.column },
600
- end: { line: this.current().line, column: this.current().column },
601
- },
602
- };
603
- }
604
- parseDirective() {
605
- this.expect('AT', 'Expected @');
606
- const name = this.expect('IDENTIFIER', 'Expected directive name').value;
607
- if (VR_TRAITS.includes(name)) {
608
- if (!this.options.enableVRTraits) {
609
- this.warn(`VR trait @${name} is disabled`);
610
- return null;
611
- }
612
- const config = this.parseTraitConfig();
613
- return { type: 'trait', name: name, config };
614
- }
615
- if (LIFECYCLE_HOOKS.includes(name)) {
616
- const params = [];
617
- if (this.check('LPAREN')) {
618
- this.advance();
619
- while (!this.check('RPAREN') && !this.check('EOF')) {
620
- params.push(this.expect('IDENTIFIER', 'Expected parameter name').value);
621
- if (this.check('COMMA'))
622
- this.advance();
623
- }
624
- this.expect('RPAREN', 'Expected )');
625
- }
626
- let body = '';
627
- if (this.check('ARROW')) {
628
- this.advance();
629
- body = this.parseInlineExpression();
630
- }
631
- else if (this.check('LBRACE')) {
632
- body = this.parseCodeBlock();
633
- }
634
- return {
635
- type: 'lifecycle',
636
- hook: name,
637
- params,
638
- body,
639
- };
640
- }
641
- if (name === 'state') {
642
- this.hasState = true;
643
- const body = this.parseStateBlock();
644
- return { type: 'state', body };
645
- }
646
- if (name === 'for') {
647
- this.hasControlFlow = true;
648
- const variable = this.expect('IDENTIFIER', 'Expected variable name').value;
649
- this.expect('IDENTIFIER', 'Expected "in"');
650
- const iterable = this.parseInlineExpression();
651
- const body = this.parseControlFlowBody();
652
- return { type: 'for', variable, iterable, body };
653
- }
654
- if (name === 'if') {
655
- this.hasControlFlow = true;
656
- const condition = this.parseInlineExpression();
657
- const body = this.parseControlFlowBody();
658
- let elseBody;
659
- this.skipNewlines();
660
- if (this.check('AT')) {
661
- const saved = this.pos;
662
- this.advance();
663
- if (this.check('IDENTIFIER') && this.current().value === 'else') {
664
- this.advance();
665
- elseBody = this.parseControlFlowBody();
666
- }
667
- else {
668
- this.pos = saved;
669
- }
670
- }
671
- return { type: 'if', condition, body, else: elseBody };
672
- }
673
- if (name === 'import') {
674
- if (!this.options.enableTypeScriptImports) {
675
- this.warn('@import is disabled');
676
- return null;
677
- }
678
- const path = this.expect('STRING', 'Expected import path').value;
679
- let alias = path.split('/').pop()?.replace(/\.[^.]+$/, '') || 'import';
680
- if (this.check('IDENTIFIER') && this.current().value === 'as') {
681
- this.advance();
682
- alias = this.expect('IDENTIFIER', 'Expected alias').value;
683
- }
684
- this.imports.push({ path, alias });
685
- return { type: 'import', path, alias };
686
- }
687
- if (this.options.strict) {
688
- this.error(`Unknown directive @${name}`);
689
- }
690
- else {
691
- this.warn(`Unknown directive @${name}`);
692
- }
693
- return null;
694
- }
695
- parseTraitConfig() {
696
- const config = {};
697
- if (this.check('LPAREN')) {
698
- this.advance();
699
- while (!this.check('RPAREN') && !this.check('EOF')) {
700
- const key = this.expect('IDENTIFIER', 'Expected property name').value;
701
- if (this.check('COLON') || this.check('EQUALS')) {
702
- this.advance();
703
- config[key] = this.parseValue();
704
- }
705
- else {
706
- config[key] = true;
707
- }
708
- if (this.check('COMMA'))
709
- this.advance();
710
- }
711
- this.expect('RPAREN', 'Expected )');
712
- }
713
- return config;
714
- }
715
- parseStateBlock() {
716
- const state = {};
717
- if (this.check('LBRACE')) {
718
- this.advance();
719
- this.skipNewlines();
720
- while (!this.check('RBRACE') && !this.check('EOF')) {
721
- const key = this.expect('IDENTIFIER', 'Expected state variable name').value;
722
- if (this.check('COLON') || this.check('EQUALS')) {
723
- this.advance();
724
- state[key] = this.parseValue();
725
- }
726
- else {
727
- state[key] = null;
728
- }
729
- this.skipNewlines();
730
- }
731
- this.expect('RBRACE', 'Expected }');
732
- }
733
- return state;
734
- }
735
- parseControlFlowBody() {
736
- const nodes = [];
737
- if (this.check('LBRACE')) {
738
- this.advance();
739
- this.skipNewlines();
740
- while (!this.check('RBRACE') && !this.check('EOF')) {
741
- if (this.check('AT')) {
742
- const directive = this.parseDirective();
743
- if (directive && directive.type === 'for') {
744
- nodes.push({
745
- type: 'fragment',
746
- properties: {},
747
- directives: [directive],
748
- children: [],
749
- traits: new Map(),
750
- });
751
- }
752
- }
753
- else if (this.check('IDENTIFIER')) {
754
- nodes.push(this.parseNode());
755
- }
756
- this.skipNewlines();
757
- }
758
- this.expect('RBRACE', 'Expected }');
759
- }
760
- return nodes;
761
- }
762
- parseCodeBlock() {
763
- let code = '';
764
- let braceDepth = 0;
765
- if (this.check('LBRACE')) {
766
- this.advance();
767
- braceDepth = 1;
768
- while (braceDepth > 0 && !this.check('EOF')) {
769
- const token = this.advance();
770
- if (token.type === 'LBRACE') {
771
- braceDepth++;
772
- code += '{';
773
- }
774
- else if (token.type === 'RBRACE') {
775
- braceDepth--;
776
- if (braceDepth > 0) {
777
- code += '}';
778
- }
779
- }
780
- else {
781
- code += token.value;
782
- if (token.type === 'NEWLINE') {
783
- code += '\n';
784
- }
785
- else {
786
- code += ' ';
787
- }
788
- }
789
- }
790
- }
791
- return code.trim();
792
- }
793
- parseInlineExpression() {
794
- let expr = '';
795
- while (!this.check('NEWLINE') &&
796
- !this.check('LBRACE') &&
797
- !this.check('EOF')) {
798
- const token = this.advance();
799
- expr += token.value + ' ';
800
- }
801
- return expr.trim();
802
- }
803
- parseValue() {
804
- const token = this.current();
805
- if (token.type === 'STRING') {
806
- this.advance();
807
- return token.value;
808
- }
809
- if (token.type === 'NUMBER') {
810
- this.advance();
811
- const match = token.value.match(/^(-?\d+(?:\.\d+)?(?:e[+-]?\d+)?)(.*)?$/i);
812
- if (match) {
813
- const num = parseFloat(match[1]);
814
- const unit = match[2];
815
- if (unit) {
816
- return `${num}${unit}`;
817
- }
818
- return num;
819
- }
820
- return parseFloat(token.value);
821
- }
822
- if (token.type === 'BOOLEAN') {
823
- this.advance();
824
- return token.value === 'true';
825
- }
826
- if (token.type === 'NULL') {
827
- this.advance();
828
- return null;
829
- }
830
- if (token.type === 'EXPRESSION') {
831
- this.advance();
832
- const exprId = `expr_${this.compiledExpressions.size}`;
833
- this.compiledExpressions.set(exprId, token.value);
834
- return { __expr: exprId, __raw: token.value };
835
- }
836
- if (token.type === 'LBRACKET') {
837
- return this.parseArray();
838
- }
839
- if (token.type === 'LBRACE') {
840
- return this.parseObject();
841
- }
842
- if (token.type === 'IDENTIFIER') {
843
- this.advance();
844
- return { __ref: token.value };
845
- }
846
- return null;
847
- }
848
- parseArray() {
849
- const arr = [];
850
- this.expect('LBRACKET', 'Expected [');
851
- while (!this.check('RBRACKET') && !this.check('EOF')) {
852
- arr.push(this.parseValue());
853
- if (this.check('COMMA'))
854
- this.advance();
855
- this.skipNewlines();
856
- }
857
- this.expect('RBRACKET', 'Expected ]');
858
- return arr;
859
- }
860
- parseObject() {
861
- const obj = {};
862
- this.expect('LBRACE', 'Expected {');
863
- this.skipNewlines();
864
- while (!this.check('RBRACE') && !this.check('EOF')) {
865
- const key = this.expect('IDENTIFIER', 'Expected property name').value;
866
- if (this.check('COLON') || this.check('EQUALS')) {
867
- this.advance();
868
- obj[key] = this.parseValue();
869
- }
870
- else {
871
- obj[key] = true;
872
- }
873
- if (this.check('COMMA'))
874
- this.advance();
875
- this.skipNewlines();
876
- }
877
- this.expect('RBRACE', 'Expected }');
878
- return obj;
879
- }
880
- current() {
881
- return this.tokens[this.pos] || { type: 'EOF', value: '', line: 0, column: 0 };
882
- }
883
- check(type) {
884
- return this.current().type === type;
885
- }
886
- advance() {
887
- const token = this.current();
888
- if (this.pos < this.tokens.length) {
889
- this.pos++;
890
- }
891
- return token;
892
- }
893
- expect(type, message) {
894
- if (!this.check(type)) {
895
- this.error(`${message}. Got ${this.current().type} "${this.current().value}"`);
896
- return { type, value: '', line: this.current().line, column: this.current().column };
897
- }
898
- return this.advance();
899
- }
900
- skipNewlines() {
901
- while (this.check('NEWLINE') || this.check('INDENT') || this.check('DEDENT')) {
902
- this.advance();
903
- }
904
- }
905
- error(message) {
906
- const token = this.current();
907
- this.errors.push({
908
- message,
909
- line: token.line,
910
- column: token.column,
911
- });
912
- }
913
- warn(message) {
914
- const token = this.current();
915
- this.warnings.push({
916
- message,
917
- line: token.line,
918
- column: token.column,
919
- });
920
- }
921
- }
922
- export function createParser(options) {
923
- return new HoloScriptPlusParser(options);
924
- }
925
- export function parse(source, options) {
926
- const parser = createParser(options);
927
- return parser.parse(source);
928
- }