shaderkit 0.6.4 → 0.8.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/LICENSE +21 -21
- package/README.md +781 -781
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/package.json +46 -40
- package/src/ast.ts +425 -425
- package/src/constants.ts +1005 -1005
- package/src/generator.ts +128 -128
- package/src/hoister.ts +171 -0
- package/src/index.ts +7 -7
- package/src/minifier.ts +445 -159
- package/src/parser.ts +878 -802
- package/src/tokenizer.ts +105 -89
- package/src/visitor.ts +131 -131
package/src/parser.ts
CHANGED
|
@@ -1,802 +1,878 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArraySpecifier,
|
|
3
|
-
AssignmentOperator,
|
|
4
|
-
BinaryOperator,
|
|
5
|
-
BlockStatement,
|
|
6
|
-
BreakStatement,
|
|
7
|
-
ConstantQualifier,
|
|
8
|
-
ContinueStatement,
|
|
9
|
-
DiscardStatement,
|
|
10
|
-
DoWhileStatement,
|
|
11
|
-
Expression,
|
|
12
|
-
ForStatement,
|
|
13
|
-
FunctionDeclaration,
|
|
14
|
-
FunctionParameter,
|
|
15
|
-
Identifier,
|
|
16
|
-
IfStatement,
|
|
17
|
-
InterpolationQualifier,
|
|
18
|
-
InvariantQualifierStatement,
|
|
19
|
-
LayoutQualifier,
|
|
20
|
-
Literal,
|
|
21
|
-
LogicalOperator,
|
|
22
|
-
ParameterQualifier,
|
|
23
|
-
PrecisionQualifier,
|
|
24
|
-
PrecisionQualifierStatement,
|
|
25
|
-
PreprocessorStatement,
|
|
26
|
-
Program,
|
|
27
|
-
ReturnStatement,
|
|
28
|
-
Statement,
|
|
29
|
-
StorageQualifier,
|
|
30
|
-
StructDeclaration,
|
|
31
|
-
SwitchCase,
|
|
32
|
-
SwitchStatement,
|
|
33
|
-
UnaryOperator,
|
|
34
|
-
StructuredBufferDeclaration,
|
|
35
|
-
UpdateOperator,
|
|
36
|
-
VariableDeclaration,
|
|
37
|
-
VariableDeclarator,
|
|
38
|
-
WhileStatement,
|
|
39
|
-
LayoutQualifierStatement,
|
|
40
|
-
} from './ast.js'
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// https://
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
'
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
94
|
-
'
|
|
95
|
-
'
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
'
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
'
|
|
120
|
-
'
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
'
|
|
130
|
-
'
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
'
|
|
134
|
-
'
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const leftBindingPower =
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
lhs = { type: '
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
consume(tokens, '
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (peek(tokens)?.value
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
return { type: '
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function
|
|
585
|
-
consume(tokens, '
|
|
586
|
-
consume(tokens, '
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
consume(tokens, '
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
consume(tokens, '
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
consume(tokens, '
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
consume(tokens, '
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1
|
+
import {
|
|
2
|
+
ArraySpecifier,
|
|
3
|
+
AssignmentOperator,
|
|
4
|
+
BinaryOperator,
|
|
5
|
+
BlockStatement,
|
|
6
|
+
BreakStatement,
|
|
7
|
+
ConstantQualifier,
|
|
8
|
+
ContinueStatement,
|
|
9
|
+
DiscardStatement,
|
|
10
|
+
DoWhileStatement,
|
|
11
|
+
Expression,
|
|
12
|
+
ForStatement,
|
|
13
|
+
FunctionDeclaration,
|
|
14
|
+
FunctionParameter,
|
|
15
|
+
Identifier,
|
|
16
|
+
IfStatement,
|
|
17
|
+
InterpolationQualifier,
|
|
18
|
+
InvariantQualifierStatement,
|
|
19
|
+
LayoutQualifier,
|
|
20
|
+
Literal,
|
|
21
|
+
LogicalOperator,
|
|
22
|
+
ParameterQualifier,
|
|
23
|
+
PrecisionQualifier,
|
|
24
|
+
PrecisionQualifierStatement,
|
|
25
|
+
PreprocessorStatement,
|
|
26
|
+
Program,
|
|
27
|
+
ReturnStatement,
|
|
28
|
+
Statement,
|
|
29
|
+
StorageQualifier,
|
|
30
|
+
StructDeclaration,
|
|
31
|
+
SwitchCase,
|
|
32
|
+
SwitchStatement,
|
|
33
|
+
UnaryOperator,
|
|
34
|
+
StructuredBufferDeclaration,
|
|
35
|
+
UpdateOperator,
|
|
36
|
+
VariableDeclaration,
|
|
37
|
+
VariableDeclarator,
|
|
38
|
+
WhileStatement,
|
|
39
|
+
LayoutQualifierStatement,
|
|
40
|
+
} from './ast.js'
|
|
41
|
+
import { hoistPreprocessorDirectives } from './hoister.js'
|
|
42
|
+
import { type Token, tokenize } from './tokenizer.js'
|
|
43
|
+
|
|
44
|
+
// https://engineering.desmos.com/articles/pratt-parser
|
|
45
|
+
// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
|
|
46
|
+
|
|
47
|
+
// 5.1 Operators
|
|
48
|
+
enum Precedence {
|
|
49
|
+
LOWEST,
|
|
50
|
+
COMMA,
|
|
51
|
+
ASSIGN,
|
|
52
|
+
LOGICAL_OR,
|
|
53
|
+
LOGICAL_XOR,
|
|
54
|
+
LOGICAL_AND,
|
|
55
|
+
BITWISE_OR,
|
|
56
|
+
BITWISE_XOR,
|
|
57
|
+
BITWISE_AND,
|
|
58
|
+
TERNARY,
|
|
59
|
+
COMPARE,
|
|
60
|
+
SHIFT,
|
|
61
|
+
ADD,
|
|
62
|
+
MULTIPLY,
|
|
63
|
+
UNARY_PREFIX,
|
|
64
|
+
UNARY_POSTFIX,
|
|
65
|
+
CALL,
|
|
66
|
+
MEMBER,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const PREFIX_OPERATOR_PRECEDENCE: Record<string, Precedence> = {
|
|
70
|
+
'~': Precedence.UNARY_PREFIX,
|
|
71
|
+
'!': Precedence.UNARY_PREFIX,
|
|
72
|
+
'--': Precedence.UNARY_PREFIX,
|
|
73
|
+
'++': Precedence.UNARY_PREFIX,
|
|
74
|
+
'-': Precedence.UNARY_PREFIX,
|
|
75
|
+
'+': Precedence.UNARY_PREFIX,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const POSTFIX_OPERATOR_PRECEDENCE: Record<string, Precedence> = {
|
|
79
|
+
'--': Precedence.UNARY_POSTFIX,
|
|
80
|
+
'++': Precedence.UNARY_POSTFIX,
|
|
81
|
+
'(': Precedence.CALL,
|
|
82
|
+
'[': Precedence.MEMBER,
|
|
83
|
+
'.': Precedence.MEMBER,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const INFIX_OPERATOR_PRECEDENCE_LEFT: Record<string, Precedence> = {
|
|
87
|
+
',': Precedence.COMMA,
|
|
88
|
+
'||': Precedence.LOGICAL_OR,
|
|
89
|
+
'^^': Precedence.LOGICAL_XOR,
|
|
90
|
+
'&&': Precedence.LOGICAL_AND,
|
|
91
|
+
'|': Precedence.BITWISE_OR,
|
|
92
|
+
'^': Precedence.BITWISE_XOR,
|
|
93
|
+
'&': Precedence.BITWISE_AND,
|
|
94
|
+
'==': Precedence.COMPARE,
|
|
95
|
+
'>': Precedence.COMPARE,
|
|
96
|
+
'>=': Precedence.COMPARE,
|
|
97
|
+
'<': Precedence.COMPARE,
|
|
98
|
+
'<=': Precedence.COMPARE,
|
|
99
|
+
'!=': Precedence.COMPARE,
|
|
100
|
+
'<<': Precedence.SHIFT,
|
|
101
|
+
'>>': Precedence.SHIFT,
|
|
102
|
+
'+': Precedence.ADD,
|
|
103
|
+
'-': Precedence.ADD,
|
|
104
|
+
'*': Precedence.MULTIPLY,
|
|
105
|
+
'/': Precedence.MULTIPLY,
|
|
106
|
+
'%': Precedence.MULTIPLY,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const INFIX_OPERATOR_PRECEDENCE_RIGHT: Record<string, Precedence> = {
|
|
110
|
+
'=': Precedence.ASSIGN,
|
|
111
|
+
'+=': Precedence.ASSIGN,
|
|
112
|
+
'&=': Precedence.ASSIGN,
|
|
113
|
+
'|=': Precedence.ASSIGN,
|
|
114
|
+
'^=': Precedence.ASSIGN,
|
|
115
|
+
'/=': Precedence.ASSIGN,
|
|
116
|
+
'*=': Precedence.ASSIGN,
|
|
117
|
+
'%=': Precedence.ASSIGN,
|
|
118
|
+
'<<=': Precedence.ASSIGN,
|
|
119
|
+
'>>=': Precedence.ASSIGN,
|
|
120
|
+
'-=': Precedence.ASSIGN,
|
|
121
|
+
'?': Precedence.TERNARY,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const QUALIFIER_REGEX =
|
|
125
|
+
/^(const|buffer|uniform|in|out|inout|centroid|flat|smooth|invariant|lowp|mediump|highp|coherent|volatile|restrict|readonly|writeonly|attribute|varying)$/
|
|
126
|
+
|
|
127
|
+
const SCOPE_DELTAS: Record<string, number> = {
|
|
128
|
+
// Open
|
|
129
|
+
'(': 1,
|
|
130
|
+
'[': 1,
|
|
131
|
+
'{': 1,
|
|
132
|
+
// Close
|
|
133
|
+
')': -1,
|
|
134
|
+
']': -1,
|
|
135
|
+
'}': -1,
|
|
136
|
+
}
|
|
137
|
+
function getScopeDelta(token: Token): number {
|
|
138
|
+
return SCOPE_DELTAS[token.value] ?? 0
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface Tokens {
|
|
142
|
+
list: Token[]
|
|
143
|
+
/**
|
|
144
|
+
* Starts at 0, points at the next token yet to be consumed.
|
|
145
|
+
*/
|
|
146
|
+
cursor: number
|
|
147
|
+
/**
|
|
148
|
+
* If a macro (or preprocessor statement) was encountered
|
|
149
|
+
* during parsing. Used to workaround expression-level macros
|
|
150
|
+
* and transform them into statements.
|
|
151
|
+
*/
|
|
152
|
+
encounteredMacro?: boolean | undefined
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function hasNextToken(tokens: Tokens): boolean {
|
|
156
|
+
return tokens.cursor < tokens.list.length
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function skipIrrelevant(tokens: Tokens, ignorePreprocessor: boolean = true): void {
|
|
160
|
+
let preprocessorScope = 0
|
|
161
|
+
for (; hasNextToken(tokens); tokens.cursor++) {
|
|
162
|
+
const token = tokens.list[tokens.cursor]
|
|
163
|
+
|
|
164
|
+
if (ignorePreprocessor && token.value === '#') {
|
|
165
|
+
tokens.encounteredMacro = true
|
|
166
|
+
let name = ''
|
|
167
|
+
const nameToken = tokens.list[++tokens.cursor]
|
|
168
|
+
if (nameToken.value !== '\\') {
|
|
169
|
+
name = nameToken.value
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (name === 'if' || name === 'ifdef' || name === 'ifndef') {
|
|
173
|
+
preprocessorScope++
|
|
174
|
+
} else if (name === 'endif') {
|
|
175
|
+
preprocessorScope--
|
|
176
|
+
tokens.cursor++
|
|
177
|
+
} else {
|
|
178
|
+
// Ignoring everything up until the end of the directive
|
|
179
|
+
while (hasNextToken(tokens) && tokens.list[tokens.cursor].value !== '\\') tokens.cursor++
|
|
180
|
+
tokens.cursor++
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (preprocessorScope > 0) {
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (token.type === 'whitespace' || token.type === 'comment') {
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Something relevant
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function peek(tokens: Tokens, offset: number = 0, ignorePreprocessor: boolean = true): Token | null {
|
|
200
|
+
const prevCursor = tokens.cursor
|
|
201
|
+
while (hasNextToken(tokens)) {
|
|
202
|
+
skipIrrelevant(tokens, ignorePreprocessor)
|
|
203
|
+
|
|
204
|
+
if (offset === 0) {
|
|
205
|
+
const token = tokens.list[tokens.cursor]
|
|
206
|
+
tokens.cursor = prevCursor
|
|
207
|
+
return token
|
|
208
|
+
} else {
|
|
209
|
+
offset--
|
|
210
|
+
tokens.cursor++
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
tokens.cursor = prevCursor
|
|
215
|
+
return null
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function consume(tokens: Tokens, expected?: string, ignorePreprocessor: boolean = true): Token {
|
|
219
|
+
// TODO: use store for sourcemaps
|
|
220
|
+
skipIrrelevant(tokens, ignorePreprocessor)
|
|
221
|
+
const token = tokens.list[tokens.cursor++]
|
|
222
|
+
|
|
223
|
+
if (token === undefined && expected !== undefined) {
|
|
224
|
+
throw new SyntaxError(`Expected "${expected}"`)
|
|
225
|
+
} else if (token === undefined) {
|
|
226
|
+
throw new SyntaxError('Unexpected end of input')
|
|
227
|
+
} else if (expected !== undefined && token.value !== expected) {
|
|
228
|
+
throw new SyntaxError(`Expected "${expected}" got "${token.value}"`)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return token
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseExpression(tokens: Tokens, minBindingPower: number = 0): Expression {
|
|
235
|
+
let token = consume(tokens)
|
|
236
|
+
|
|
237
|
+
let lhs: Expression
|
|
238
|
+
if (token.type === 'identifier' || token.type === 'keyword') {
|
|
239
|
+
lhs = { type: 'Identifier', name: token.value }
|
|
240
|
+
} else if (token.type === 'bool' || token.type === 'float' || token.type === 'int') {
|
|
241
|
+
lhs = { type: 'Literal', value: token.value }
|
|
242
|
+
} else if (token.type === 'symbol' && token.value === '(') {
|
|
243
|
+
lhs = parseExpression(tokens, 0)
|
|
244
|
+
consume(tokens, ')')
|
|
245
|
+
} else if (token.type === 'symbol' && token.value in PREFIX_OPERATOR_PRECEDENCE) {
|
|
246
|
+
const rightBindingPower = PREFIX_OPERATOR_PRECEDENCE[token.value]
|
|
247
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
248
|
+
if (token.value === '--' || token.value === '++') {
|
|
249
|
+
lhs = { type: 'UpdateExpression', operator: token.value, prefix: true, argument: rhs }
|
|
250
|
+
} else {
|
|
251
|
+
lhs = { type: 'UnaryExpression', operator: token.value as UnaryOperator, prefix: true, argument: rhs }
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
throw new SyntaxError(`Unexpected token: "${token.value}"`)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
while (hasNextToken(tokens)) {
|
|
258
|
+
token = peek(tokens)!
|
|
259
|
+
|
|
260
|
+
if (token.value in POSTFIX_OPERATOR_PRECEDENCE) {
|
|
261
|
+
const leftBindingPower = POSTFIX_OPERATOR_PRECEDENCE[token.value]
|
|
262
|
+
if (leftBindingPower < minBindingPower) break
|
|
263
|
+
|
|
264
|
+
consume(tokens)
|
|
265
|
+
|
|
266
|
+
if (token.value === '(') {
|
|
267
|
+
const args: Expression[] = []
|
|
268
|
+
|
|
269
|
+
while (peek(tokens)?.value !== ')') {
|
|
270
|
+
args.push(parseExpression(tokens, Precedence.COMMA))
|
|
271
|
+
if (peek(tokens)?.value !== ')') consume(tokens, ',')
|
|
272
|
+
}
|
|
273
|
+
consume(tokens, ')')
|
|
274
|
+
|
|
275
|
+
lhs = { type: 'CallExpression', callee: lhs, arguments: args }
|
|
276
|
+
} else if (token.value === '[') {
|
|
277
|
+
const rhs = peek(tokens)?.value !== ']' ? parseExpression(tokens, 0) : null
|
|
278
|
+
consume(tokens, ']')
|
|
279
|
+
|
|
280
|
+
if (peek(tokens)?.value === '(') {
|
|
281
|
+
consume(tokens, '(')
|
|
282
|
+
const elements: Expression[] = []
|
|
283
|
+
|
|
284
|
+
while (peek(tokens)?.value !== ')') {
|
|
285
|
+
elements.push(parseExpression(tokens, Precedence.COMMA))
|
|
286
|
+
if (peek(tokens)?.value !== ')') consume(tokens, ',')
|
|
287
|
+
}
|
|
288
|
+
consume(tokens, ')')
|
|
289
|
+
|
|
290
|
+
const typeSpecifier: ArraySpecifier = {
|
|
291
|
+
type: 'ArraySpecifier',
|
|
292
|
+
typeSpecifier: lhs as Identifier,
|
|
293
|
+
dimensions: [rhs as Literal | null],
|
|
294
|
+
}
|
|
295
|
+
lhs = { type: 'ArrayExpression', typeSpecifier, elements }
|
|
296
|
+
} else {
|
|
297
|
+
lhs = { type: 'MemberExpression', object: lhs, property: rhs!, computed: true }
|
|
298
|
+
}
|
|
299
|
+
} else if (token.value === '.') {
|
|
300
|
+
const rhs = parseExpression(tokens, leftBindingPower)
|
|
301
|
+
lhs = { type: 'MemberExpression', object: lhs, property: rhs, computed: false }
|
|
302
|
+
} else if (token.value === '--' || token.value === '++') {
|
|
303
|
+
lhs = { type: 'UpdateExpression', operator: token.value as UpdateOperator, prefix: false, argument: lhs }
|
|
304
|
+
} else {
|
|
305
|
+
lhs = { type: 'UnaryExpression', operator: token.value as UnaryOperator, prefix: false, argument: lhs }
|
|
306
|
+
}
|
|
307
|
+
} else if (token.value in INFIX_OPERATOR_PRECEDENCE_LEFT) {
|
|
308
|
+
const precedence = INFIX_OPERATOR_PRECEDENCE_LEFT[token.value]
|
|
309
|
+
const leftBindingPower = precedence - 1
|
|
310
|
+
const rightBindingPower = precedence
|
|
311
|
+
|
|
312
|
+
if (leftBindingPower < minBindingPower) break
|
|
313
|
+
|
|
314
|
+
consume(tokens)
|
|
315
|
+
|
|
316
|
+
if (token.value === '||' || token.value === '&&' || token.value === '^^') {
|
|
317
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
318
|
+
lhs = { type: 'LogicalExpression', operator: token.value, left: lhs, right: rhs }
|
|
319
|
+
} else {
|
|
320
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
321
|
+
lhs = { type: 'BinaryExpression', operator: token.value as BinaryOperator, left: lhs, right: rhs }
|
|
322
|
+
}
|
|
323
|
+
} else if (token.value in INFIX_OPERATOR_PRECEDENCE_RIGHT) {
|
|
324
|
+
const precedence = INFIX_OPERATOR_PRECEDENCE_RIGHT[token.value]
|
|
325
|
+
const leftBindingPower = precedence
|
|
326
|
+
const rightBindingPower = precedence - 1
|
|
327
|
+
|
|
328
|
+
if (leftBindingPower < minBindingPower) break
|
|
329
|
+
|
|
330
|
+
consume(tokens)
|
|
331
|
+
|
|
332
|
+
if (token.value === '?') {
|
|
333
|
+
const mhs = parseExpression(tokens, 0)
|
|
334
|
+
consume(tokens, ':')
|
|
335
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
336
|
+
lhs = { type: 'ConditionalExpression', test: lhs, alternate: mhs, consequent: rhs }
|
|
337
|
+
} else {
|
|
338
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
339
|
+
lhs = { type: 'AssignmentExpression', operator: token.value as AssignmentOperator, left: lhs, right: rhs }
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
break
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return lhs
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function parseTypeSpecifier(tokens: Tokens): Identifier | ArraySpecifier {
|
|
350
|
+
let typeSpecifier: Identifier | ArraySpecifier = { type: 'Identifier', name: consume(tokens).value }
|
|
351
|
+
|
|
352
|
+
if (peek(tokens)?.value === '[') {
|
|
353
|
+
const dimensions: (Literal | Identifier | null)[] = []
|
|
354
|
+
|
|
355
|
+
while (peek(tokens)?.value === '[') {
|
|
356
|
+
consume(tokens, '[')
|
|
357
|
+
|
|
358
|
+
if (peek(tokens)?.value !== ']') {
|
|
359
|
+
dimensions.push(parseExpression(tokens) as Literal | Identifier)
|
|
360
|
+
} else {
|
|
361
|
+
dimensions.push(null)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
consume(tokens, ']')
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
typeSpecifier = {
|
|
368
|
+
type: 'ArraySpecifier',
|
|
369
|
+
typeSpecifier,
|
|
370
|
+
dimensions,
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return typeSpecifier
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function parseVariableDeclarator(
|
|
378
|
+
tokens: Tokens,
|
|
379
|
+
typeSpecifier: Identifier | ArraySpecifier,
|
|
380
|
+
qualifiers: (ConstantQualifier | InterpolationQualifier | StorageQualifier | PrecisionQualifier)[],
|
|
381
|
+
layout: Record<string, string | boolean> | null,
|
|
382
|
+
): VariableDeclarator {
|
|
383
|
+
const id = parseTypeSpecifier(tokens) as Identifier
|
|
384
|
+
|
|
385
|
+
let init: Expression | null = null
|
|
386
|
+
|
|
387
|
+
if (peek(tokens)?.value === '=') {
|
|
388
|
+
consume(tokens, '=')
|
|
389
|
+
init = parseExpression(tokens, Precedence.COMMA)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return { type: 'VariableDeclarator', id, qualifiers, typeSpecifier, layout, init }
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function parseVariable(
|
|
396
|
+
tokens: Tokens,
|
|
397
|
+
typeSpecifier: Identifier | ArraySpecifier,
|
|
398
|
+
qualifiers: (ConstantQualifier | InterpolationQualifier | StorageQualifier | PrecisionQualifier)[] = [],
|
|
399
|
+
layout: Record<string, string | boolean> | null = null,
|
|
400
|
+
): VariableDeclaration {
|
|
401
|
+
const declarations: VariableDeclarator[] = []
|
|
402
|
+
|
|
403
|
+
if (peek(tokens)?.value !== ';') {
|
|
404
|
+
while (hasNextToken(tokens)) {
|
|
405
|
+
declarations.push(parseVariableDeclarator(tokens, typeSpecifier, qualifiers, layout))
|
|
406
|
+
|
|
407
|
+
if (peek(tokens)?.value === ',') {
|
|
408
|
+
consume(tokens, ',')
|
|
409
|
+
} else {
|
|
410
|
+
break
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
consume(tokens, ';')
|
|
416
|
+
|
|
417
|
+
return { type: 'VariableDeclaration', declarations }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function parseBufferInterface(
|
|
421
|
+
tokens: Tokens,
|
|
422
|
+
typeSpecifier: Identifier | ArraySpecifier,
|
|
423
|
+
qualifiers: LayoutQualifier[] = [],
|
|
424
|
+
layout: Record<string, string | boolean> | null = null,
|
|
425
|
+
): StructuredBufferDeclaration {
|
|
426
|
+
const members = parseBlock(tokens).body as VariableDeclaration[]
|
|
427
|
+
|
|
428
|
+
let id: Identifier | null = null
|
|
429
|
+
if (peek(tokens)?.value !== ';') id = parseExpression(tokens) as Identifier
|
|
430
|
+
consume(tokens, ';')
|
|
431
|
+
|
|
432
|
+
return { type: 'StructuredBufferDeclaration', id, qualifiers, typeSpecifier, layout, members }
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function parseFunction(
|
|
436
|
+
tokens: Tokens,
|
|
437
|
+
typeSpecifier: ArraySpecifier | Identifier,
|
|
438
|
+
qualifiers: PrecisionQualifier[] = [],
|
|
439
|
+
): FunctionDeclaration {
|
|
440
|
+
const id: Identifier = { type: 'Identifier', name: consume(tokens).value }
|
|
441
|
+
|
|
442
|
+
consume(tokens, '(')
|
|
443
|
+
|
|
444
|
+
const params: FunctionParameter[] = []
|
|
445
|
+
while (true) {
|
|
446
|
+
const token = peek(tokens)
|
|
447
|
+
if (!token || token.value === ')') break
|
|
448
|
+
|
|
449
|
+
const qualifiers: (ConstantQualifier | ParameterQualifier | PrecisionQualifier)[] = []
|
|
450
|
+
while (peek(tokens) && QUALIFIER_REGEX.test(peek(tokens)!.value)) {
|
|
451
|
+
qualifiers.push(consume(tokens).value as ConstantQualifier | ParameterQualifier | PrecisionQualifier)
|
|
452
|
+
}
|
|
453
|
+
const typeSpecifier = parseTypeSpecifier(tokens)
|
|
454
|
+
|
|
455
|
+
let id: Identifier | null = null
|
|
456
|
+
if (peek(tokens)?.type !== 'symbol') id = parseTypeSpecifier(tokens) as Identifier
|
|
457
|
+
|
|
458
|
+
params.push({ type: 'FunctionParameter', id, qualifiers, typeSpecifier })
|
|
459
|
+
|
|
460
|
+
if (peek(tokens)?.value === ',') consume(tokens, ',')
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
consume(tokens, ')')
|
|
464
|
+
|
|
465
|
+
let body = null
|
|
466
|
+
if (peek(tokens)?.value === ';') consume(tokens, ';')
|
|
467
|
+
else body = parseBlock(tokens)
|
|
468
|
+
|
|
469
|
+
return { type: 'FunctionDeclaration', id, qualifiers, typeSpecifier, params, body }
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function parseLayoutQualifier(tokens: Tokens, layout: Record<string, string | boolean>): LayoutQualifierStatement {
|
|
473
|
+
const qualifier = consume(tokens).value as StorageQualifier
|
|
474
|
+
consume(tokens, ';')
|
|
475
|
+
return { type: 'LayoutQualifierStatement', layout, qualifier }
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function parseInvariant(tokens: Tokens): InvariantQualifierStatement {
|
|
479
|
+
consume(tokens, 'invariant')
|
|
480
|
+
const typeSpecifier = parseExpression(tokens) as Identifier
|
|
481
|
+
consume(tokens, ';')
|
|
482
|
+
return { type: 'InvariantQualifierStatement', typeSpecifier }
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function parseIndeterminate(
|
|
486
|
+
tokens: Tokens,
|
|
487
|
+
):
|
|
488
|
+
| VariableDeclaration
|
|
489
|
+
| FunctionDeclaration
|
|
490
|
+
| StructuredBufferDeclaration
|
|
491
|
+
| LayoutQualifierStatement
|
|
492
|
+
| InvariantQualifierStatement {
|
|
493
|
+
let layout: Record<string, string | boolean> | null = null
|
|
494
|
+
if (peek(tokens)?.value === 'layout') {
|
|
495
|
+
consume(tokens, 'layout')
|
|
496
|
+
consume(tokens, '(')
|
|
497
|
+
|
|
498
|
+
layout = {}
|
|
499
|
+
|
|
500
|
+
while (peek(tokens) && peek(tokens)!.value !== ')') {
|
|
501
|
+
const expression = parseExpression(tokens, Precedence.COMMA)
|
|
502
|
+
|
|
503
|
+
if (
|
|
504
|
+
expression.type === 'AssignmentExpression' &&
|
|
505
|
+
expression.left.type === 'Identifier' &&
|
|
506
|
+
expression.right.type === 'Literal'
|
|
507
|
+
) {
|
|
508
|
+
layout[expression.left.name] = expression.right.value
|
|
509
|
+
} else if (expression.type === 'Identifier') {
|
|
510
|
+
layout[expression.name] = true
|
|
511
|
+
} else {
|
|
512
|
+
throw new TypeError('Unexpected expression')
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (peek(tokens) && peek(tokens)!.value !== ')') consume(tokens, ',')
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
consume(tokens, ')')
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Input qualifiers will suddenly terminate
|
|
522
|
+
if (layout !== null && peek(tokens, 1)?.value === ';') {
|
|
523
|
+
return parseLayoutQualifier(tokens, layout)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Invariant qualifiers will terminate with an identifier
|
|
527
|
+
if (
|
|
528
|
+
layout === null &&
|
|
529
|
+
peek(tokens)?.value === 'invariant' &&
|
|
530
|
+
(peek(tokens, 1)?.type === 'identifier' || (peek(tokens, 1)?.type === 'keyword' && peek(tokens, 2)?.value === ';'))
|
|
531
|
+
) {
|
|
532
|
+
return parseInvariant(tokens)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// TODO: only precision qualifier valid for function return type
|
|
536
|
+
const qualifiers: string[] = []
|
|
537
|
+
while (peek(tokens) && QUALIFIER_REGEX.test(peek(tokens)!.value)) {
|
|
538
|
+
qualifiers.push(consume(tokens).value)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const typeSpecifier = parseTypeSpecifier(tokens)
|
|
542
|
+
|
|
543
|
+
if (peek(tokens)?.value === '{') {
|
|
544
|
+
return parseBufferInterface(tokens, typeSpecifier, qualifiers as LayoutQualifier[], layout)
|
|
545
|
+
} else if (peek(tokens, 1)?.value === '(') {
|
|
546
|
+
return parseFunction(tokens, typeSpecifier, qualifiers as PrecisionQualifier[])
|
|
547
|
+
} else {
|
|
548
|
+
return parseVariable(
|
|
549
|
+
tokens,
|
|
550
|
+
typeSpecifier,
|
|
551
|
+
qualifiers as (ConstantQualifier | InterpolationQualifier | StorageQualifier | PrecisionQualifier)[],
|
|
552
|
+
layout,
|
|
553
|
+
)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function parseStruct(tokens: Tokens): StructDeclaration {
|
|
558
|
+
consume(tokens, 'struct')
|
|
559
|
+
const id: Identifier = { type: 'Identifier', name: consume(tokens).value }
|
|
560
|
+
consume(tokens, '{')
|
|
561
|
+
const members: VariableDeclaration[] = []
|
|
562
|
+
while (peek(tokens, 0, false) && peek(tokens, 0, false)!.value !== '}') {
|
|
563
|
+
members.push(...(parseStatements(tokens) as unknown as VariableDeclaration[]))
|
|
564
|
+
}
|
|
565
|
+
consume(tokens, '}')
|
|
566
|
+
|
|
567
|
+
// Hack to append a separate variable declaration in the next iterator
|
|
568
|
+
// `struct a {} name;` is parsed as `struct a {}; a name;`
|
|
569
|
+
if (peek(tokens)?.type === 'identifier') {
|
|
570
|
+
const type = id.name
|
|
571
|
+
const name = consume(tokens).value
|
|
572
|
+
tokens.list.push(
|
|
573
|
+
{ type: 'identifier', value: type },
|
|
574
|
+
{ type: 'identifier', value: name },
|
|
575
|
+
{ type: 'symbol', value: ';' },
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
consume(tokens, ';')
|
|
580
|
+
|
|
581
|
+
return { type: 'StructDeclaration', id, members }
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function parseContinue(tokens: Tokens): ContinueStatement {
|
|
585
|
+
consume(tokens, 'continue')
|
|
586
|
+
consume(tokens, ';')
|
|
587
|
+
|
|
588
|
+
return { type: 'ContinueStatement' }
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function parseBreak(tokens: Tokens): BreakStatement {
|
|
592
|
+
consume(tokens, 'break')
|
|
593
|
+
consume(tokens, ';')
|
|
594
|
+
|
|
595
|
+
return { type: 'BreakStatement' }
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function parseDiscard(tokens: Tokens): DiscardStatement {
|
|
599
|
+
consume(tokens, 'discard')
|
|
600
|
+
consume(tokens, ';')
|
|
601
|
+
|
|
602
|
+
return { type: 'DiscardStatement' }
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function parseReturn(tokens: Tokens): ReturnStatement {
|
|
606
|
+
consume(tokens, 'return')
|
|
607
|
+
|
|
608
|
+
let argument: Expression | null = null
|
|
609
|
+
if (peek(tokens)?.value !== ';') argument = parseExpression(tokens)
|
|
610
|
+
consume(tokens, ';')
|
|
611
|
+
|
|
612
|
+
return { type: 'ReturnStatement', argument }
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function parseIf(tokens: Tokens): IfStatement {
|
|
616
|
+
consume(tokens, 'if')
|
|
617
|
+
consume(tokens, '(')
|
|
618
|
+
const test = parseExpression(tokens)
|
|
619
|
+
consume(tokens, ')')
|
|
620
|
+
|
|
621
|
+
const consequent = parseBlockOrStatement(tokens)
|
|
622
|
+
|
|
623
|
+
let alternate = null
|
|
624
|
+
const elseToken = peek(tokens)
|
|
625
|
+
if (elseToken && elseToken.value === 'else') {
|
|
626
|
+
consume(tokens, 'else')
|
|
627
|
+
|
|
628
|
+
if (peek(tokens) && peek(tokens)!.value === 'if') {
|
|
629
|
+
alternate = parseIf(tokens)
|
|
630
|
+
} else {
|
|
631
|
+
alternate = parseBlockOrStatement(tokens)
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return { type: 'IfStatement', test, consequent, alternate }
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function parseWhile(tokens: Tokens): WhileStatement {
|
|
639
|
+
consume(tokens, 'while')
|
|
640
|
+
consume(tokens, '(')
|
|
641
|
+
const test = parseExpression(tokens)
|
|
642
|
+
consume(tokens, ')')
|
|
643
|
+
const body = parseBlockOrStatement(tokens)
|
|
644
|
+
|
|
645
|
+
return { type: 'WhileStatement', test, body }
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function parseFor(tokens: Tokens): ForStatement {
|
|
649
|
+
consume(tokens, 'for')
|
|
650
|
+
consume(tokens, '(')
|
|
651
|
+
const typeSpecifier = parseExpression(tokens) as Identifier | ArraySpecifier
|
|
652
|
+
const init = parseVariable(tokens, typeSpecifier)
|
|
653
|
+
// consume(tokens, ';')
|
|
654
|
+
const test = parseExpression(tokens)
|
|
655
|
+
consume(tokens, ';')
|
|
656
|
+
const update = parseExpression(tokens)
|
|
657
|
+
consume(tokens, ')')
|
|
658
|
+
const body = parseBlockOrStatement(tokens)
|
|
659
|
+
|
|
660
|
+
return { type: 'ForStatement', init, test, update, body }
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function parseDoWhile(tokens: Tokens): DoWhileStatement {
|
|
664
|
+
consume(tokens, 'do')
|
|
665
|
+
const body = parseBlockOrStatement(tokens)
|
|
666
|
+
consume(tokens, 'while')
|
|
667
|
+
consume(tokens, '(')
|
|
668
|
+
const test = parseExpression(tokens)
|
|
669
|
+
consume(tokens, ')')
|
|
670
|
+
consume(tokens, ';')
|
|
671
|
+
|
|
672
|
+
return { type: 'DoWhileStatement', test, body }
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function parseSwitch(tokens: Tokens): SwitchStatement {
|
|
676
|
+
consume(tokens, 'switch')
|
|
677
|
+
const discriminant = parseExpression(tokens)
|
|
678
|
+
|
|
679
|
+
const cases: SwitchCase[] = []
|
|
680
|
+
while (hasNextToken(tokens)) {
|
|
681
|
+
const token = consume(tokens)
|
|
682
|
+
if (token.value === '}') break
|
|
683
|
+
|
|
684
|
+
if (token.value === 'case') {
|
|
685
|
+
const test = parseExpression(tokens)
|
|
686
|
+
consume(tokens, ':')
|
|
687
|
+
const consequent = parseStatements(tokens)
|
|
688
|
+
cases.push({ type: 'SwitchCase', test, consequent })
|
|
689
|
+
} else if (token.value === 'default') {
|
|
690
|
+
const test = null
|
|
691
|
+
consume(tokens, ':')
|
|
692
|
+
const consequent = parseStatements(tokens)
|
|
693
|
+
cases.push({ type: 'SwitchCase', test, consequent })
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return { type: 'SwitchStatement', discriminant, cases }
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function parsePrecision(tokens: Tokens): PrecisionQualifierStatement {
|
|
701
|
+
consume(tokens, 'precision')
|
|
702
|
+
const precision = consume(tokens).value as PrecisionQualifier
|
|
703
|
+
const typeSpecifier: Identifier = { type: 'Identifier', name: consume(tokens).value }
|
|
704
|
+
consume(tokens, ';')
|
|
705
|
+
return { type: 'PrecisionQualifierStatement', precision, typeSpecifier }
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function parsePreprocessor(tokens: Tokens): PreprocessorStatement {
|
|
709
|
+
consume(tokens, '#', false)
|
|
710
|
+
|
|
711
|
+
let name = '' // name can be unset for the # directive which is ignored
|
|
712
|
+
let value: Expression[] | null = null
|
|
713
|
+
|
|
714
|
+
if (peek(tokens)?.value !== '\\') {
|
|
715
|
+
name = consume(tokens).value
|
|
716
|
+
|
|
717
|
+
if (name === 'define') {
|
|
718
|
+
const lhs = consume(tokens)
|
|
719
|
+
let left: Expression = { type: 'Identifier', name: lhs.value }
|
|
720
|
+
const next = peek(tokens)
|
|
721
|
+
|
|
722
|
+
// Macro definition: #define foo(a, b, c) ...
|
|
723
|
+
if (next && next.value === '(') {
|
|
724
|
+
consume(tokens)
|
|
725
|
+
|
|
726
|
+
const args: Expression[] = []
|
|
727
|
+
while (peek(tokens)?.value !== ')') {
|
|
728
|
+
args.push(parseExpression(tokens, Precedence.COMMA))
|
|
729
|
+
if (peek(tokens)?.value !== ')') consume(tokens, ',')
|
|
730
|
+
}
|
|
731
|
+
consume(tokens, ')')
|
|
732
|
+
|
|
733
|
+
left = { type: 'CallExpression', callee: left, arguments: args }
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (peek(tokens)?.value === '\\') value = [left]
|
|
737
|
+
else value = [left, parseExpression(tokens)]
|
|
738
|
+
} else if (name === 'extension') {
|
|
739
|
+
// TODO: extension directives must be before declarations
|
|
740
|
+
const left = parseExpression(tokens)
|
|
741
|
+
consume(tokens, ':')
|
|
742
|
+
const right = parseExpression(tokens)
|
|
743
|
+
value = [left, right]
|
|
744
|
+
} else if (name === 'include') {
|
|
745
|
+
consume(tokens, '<')
|
|
746
|
+
value = [{ type: 'Identifier', name: consume(tokens).value }]
|
|
747
|
+
consume(tokens, '>')
|
|
748
|
+
} else if (name !== 'else' && name !== 'endif') {
|
|
749
|
+
value = []
|
|
750
|
+
while (peek(tokens) && peek(tokens)!.value !== '\\') {
|
|
751
|
+
value.push(parseExpression(tokens))
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
consume(tokens, '\\')
|
|
757
|
+
|
|
758
|
+
return { type: 'PreprocessorStatement', name, value }
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function isVariable(tokens: Tokens): boolean {
|
|
762
|
+
let token = peek(tokens, 0)
|
|
763
|
+
|
|
764
|
+
// Skip first token if EOF or not type qualifier/specifier
|
|
765
|
+
if (!token || (token.type !== 'identifier' && token.type !== 'keyword')) return false
|
|
766
|
+
|
|
767
|
+
// Layout qualifiers are only valid for declarations
|
|
768
|
+
if (token.value === 'layout') return true
|
|
769
|
+
|
|
770
|
+
// Skip to end of possible expression statement (e.g. callexpr -> fndecl)
|
|
771
|
+
let i = 1
|
|
772
|
+
let scopeIndex = 0
|
|
773
|
+
while (true) {
|
|
774
|
+
token = peek(tokens, i)
|
|
775
|
+
if (!token) break
|
|
776
|
+
|
|
777
|
+
const delta = getScopeDelta(token)
|
|
778
|
+
if (scopeIndex <= 0 && delta <= 0) break
|
|
779
|
+
|
|
780
|
+
scopeIndex += delta
|
|
781
|
+
i++
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// A variable declaration must follow with an identifier or type
|
|
785
|
+
return peek(tokens, i)?.type !== 'symbol'
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function parseStatement(tokens: Tokens): Statement {
|
|
789
|
+
const token = peek(tokens, 0, false)!
|
|
790
|
+
let statement: Statement | null = null
|
|
791
|
+
|
|
792
|
+
if (token.value === '#') statement = parsePreprocessor(tokens)
|
|
793
|
+
else if (token.value === 'struct') statement = parseStruct(tokens)
|
|
794
|
+
else if (token.value === 'continue') statement = parseContinue(tokens)
|
|
795
|
+
else if (token.value === 'break') statement = parseBreak(tokens)
|
|
796
|
+
else if (token.value === 'discard') statement = parseDiscard(tokens)
|
|
797
|
+
else if (token.value === 'return') statement = parseReturn(tokens)
|
|
798
|
+
else if (token.value === 'if') statement = parseIf(tokens)
|
|
799
|
+
else if (token.value === 'while') statement = parseWhile(tokens)
|
|
800
|
+
else if (token.value === 'for') statement = parseFor(tokens)
|
|
801
|
+
else if (token.value === 'do') statement = parseDoWhile(tokens)
|
|
802
|
+
else if (token.value === 'switch') statement = parseSwitch(tokens)
|
|
803
|
+
else if (token.value === 'precision') statement = parsePrecision(tokens)
|
|
804
|
+
else if (isVariable(tokens)) statement = parseIndeterminate(tokens)
|
|
805
|
+
else if (token.value === '{') statement = parseBlock(tokens)
|
|
806
|
+
else {
|
|
807
|
+
const expression = parseExpression(tokens)
|
|
808
|
+
if (peek(tokens)?.value === ',') consume(tokens, ';')
|
|
809
|
+
else consume(tokens, ';')
|
|
810
|
+
statement = { type: 'ExpressionStatement', expression }
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return statement
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function parseStatements(tokens: Tokens): Statement[] {
|
|
817
|
+
const body: Statement[] = []
|
|
818
|
+
let scopeIndex = 0
|
|
819
|
+
|
|
820
|
+
while (true) {
|
|
821
|
+
const token = peek(tokens, 0, false)
|
|
822
|
+
if (!token) break
|
|
823
|
+
|
|
824
|
+
scopeIndex += getScopeDelta(token)
|
|
825
|
+
if (scopeIndex < 0 || token.value === '}') break
|
|
826
|
+
|
|
827
|
+
if (token.value === 'case' || token.value === 'default') break
|
|
828
|
+
|
|
829
|
+
tokens.encounteredMacro = false
|
|
830
|
+
const start = tokens.cursor
|
|
831
|
+
const statement = parseStatement(tokens)
|
|
832
|
+
if (tokens.encounteredMacro) {
|
|
833
|
+
const hoistedTokens = hoistPreprocessorDirectives(tokens.list.slice(start, tokens.cursor))
|
|
834
|
+
body.push(...parseStatements({ list: hoistedTokens, cursor: 0 }))
|
|
835
|
+
} else {
|
|
836
|
+
body.push(statement)
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return body
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function parseBlock(tokens: Tokens): BlockStatement {
|
|
844
|
+
consume(tokens, '{')
|
|
845
|
+
const body = parseStatements(tokens)
|
|
846
|
+
consume(tokens, '}')
|
|
847
|
+
return { type: 'BlockStatement', body }
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// TODO: validate block versus sub-statements for GLSL/WGSL
|
|
851
|
+
function parseBlockOrStatement(tokens: Tokens): BlockStatement | Statement {
|
|
852
|
+
if (peek(tokens)?.value === '{') {
|
|
853
|
+
return parseBlock(tokens)
|
|
854
|
+
} else {
|
|
855
|
+
return parseStatement(tokens)
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const NEWLINE_REGEX = /\\\s+/gm
|
|
860
|
+
const DIRECTIVE_REGEX = /(^\s*#[^\\]*?)(\n|\/[\/\*])/gm
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Parses a string of GLSL (WGSL WIP) code into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
|
|
864
|
+
*/
|
|
865
|
+
export function parse(code: string): Program {
|
|
866
|
+
// Fold newlines
|
|
867
|
+
code = code.replace(NEWLINE_REGEX, '')
|
|
868
|
+
|
|
869
|
+
// Escape newlines after directives, skip comments
|
|
870
|
+
code = code.replace(DIRECTIVE_REGEX, '$1\\$2')
|
|
871
|
+
|
|
872
|
+
const tokens = {
|
|
873
|
+
list: tokenize(code),
|
|
874
|
+
cursor: 0,
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return { type: 'Program', body: parseStatements(tokens) }
|
|
878
|
+
}
|