@holoscript/core 2.0.1 → 2.0.2

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