shaderkit 0.1.14 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +352 -3
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ast.ts +137 -0
- package/src/generator.ts +170 -0
- package/src/index.ts +2 -0
- package/src/parser.ts +568 -0
package/src/parser.ts
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AST,
|
|
3
|
+
Type,
|
|
4
|
+
Identifier,
|
|
5
|
+
Literal,
|
|
6
|
+
CallExpression,
|
|
7
|
+
UnaryExpression,
|
|
8
|
+
MemberExpression,
|
|
9
|
+
TernaryExpression,
|
|
10
|
+
BinaryExpression,
|
|
11
|
+
BlockStatement,
|
|
12
|
+
FunctionDeclaration,
|
|
13
|
+
VariableDeclaration,
|
|
14
|
+
VariableDeclarator,
|
|
15
|
+
ContinueStatement,
|
|
16
|
+
BreakStatement,
|
|
17
|
+
DiscardStatement,
|
|
18
|
+
ReturnStatement,
|
|
19
|
+
IfStatement,
|
|
20
|
+
WhileStatement,
|
|
21
|
+
ForStatement,
|
|
22
|
+
DoWhileStatement,
|
|
23
|
+
SwitchStatement,
|
|
24
|
+
SwitchCase,
|
|
25
|
+
StructDeclaration,
|
|
26
|
+
PrecisionStatement,
|
|
27
|
+
ArrayExpression,
|
|
28
|
+
PreprocessorStatement,
|
|
29
|
+
} from './ast'
|
|
30
|
+
import { type Token, tokenize } from './tokenizer'
|
|
31
|
+
|
|
32
|
+
const UNARY_OPERATORS = ['+', '-', '~', '!', '++', '--']
|
|
33
|
+
|
|
34
|
+
const BINARY_OPERATORS = [
|
|
35
|
+
'>>=',
|
|
36
|
+
'<<=',
|
|
37
|
+
'|=',
|
|
38
|
+
'&=',
|
|
39
|
+
'^=',
|
|
40
|
+
'%=',
|
|
41
|
+
'/=',
|
|
42
|
+
'*=',
|
|
43
|
+
'-=',
|
|
44
|
+
'+=',
|
|
45
|
+
'=',
|
|
46
|
+
'?',
|
|
47
|
+
'||',
|
|
48
|
+
'^^',
|
|
49
|
+
'&&',
|
|
50
|
+
'|',
|
|
51
|
+
'^',
|
|
52
|
+
'&',
|
|
53
|
+
'!=',
|
|
54
|
+
'==',
|
|
55
|
+
'>=',
|
|
56
|
+
'<=',
|
|
57
|
+
'>',
|
|
58
|
+
'<',
|
|
59
|
+
'>>',
|
|
60
|
+
'<<',
|
|
61
|
+
'+',
|
|
62
|
+
'-',
|
|
63
|
+
'%',
|
|
64
|
+
'/',
|
|
65
|
+
'*',
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
// TODO: this is GLSL-only, separate language constants
|
|
69
|
+
const TYPE_REGEX = /^(void|bool|float|u?int|[uib]?vec\d|mat\d(x\d)?)$/
|
|
70
|
+
const QUALIFIER_REGEX = /^(const|uniform|in|out|inout|centroid|flat|smooth|invariant|lowp|mediump|highp)$/
|
|
71
|
+
const VARIABLE_REGEX = new RegExp(`${TYPE_REGEX.source}|${QUALIFIER_REGEX.source}|layout`)
|
|
72
|
+
|
|
73
|
+
const isDeclaration = RegExp.prototype.test.bind(VARIABLE_REGEX)
|
|
74
|
+
|
|
75
|
+
const isOpen = RegExp.prototype.test.bind(/^[\(\[\{]$/)
|
|
76
|
+
const isClose = RegExp.prototype.test.bind(/^[\)\]\}]$/)
|
|
77
|
+
|
|
78
|
+
function getScopeDelta(token: Token): number {
|
|
79
|
+
if (isOpen(token.value)) return 1
|
|
80
|
+
if (isClose(token.value)) return -1
|
|
81
|
+
return 0
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let tokens: Token[] = []
|
|
85
|
+
let i: number = 0
|
|
86
|
+
|
|
87
|
+
function readUntil(value: string, body: Token[], offset: number = 0): Token[] {
|
|
88
|
+
const output: Token[] = []
|
|
89
|
+
let scopeIndex = 0
|
|
90
|
+
|
|
91
|
+
while (offset < body.length) {
|
|
92
|
+
const token = body[offset++]
|
|
93
|
+
output.push(token)
|
|
94
|
+
|
|
95
|
+
scopeIndex += getScopeDelta(token)
|
|
96
|
+
if (scopeIndex === 0 && token.value === value) break
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return output
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function consumeUntil(value: string): Token[] {
|
|
103
|
+
const output = readUntil(value, tokens, i)
|
|
104
|
+
i += output.length
|
|
105
|
+
return output
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseExpression(body: Token[]): AST | null {
|
|
109
|
+
if (body.length === 0) return null
|
|
110
|
+
|
|
111
|
+
const first = body[0]
|
|
112
|
+
const last = body[body.length - 1]
|
|
113
|
+
if (UNARY_OPERATORS.includes(first.value)) {
|
|
114
|
+
const right = parseExpression(body.slice(1))!
|
|
115
|
+
return new UnaryExpression(first.value, null, right)
|
|
116
|
+
} else if (UNARY_OPERATORS.includes(last.value)) {
|
|
117
|
+
const left = parseExpression(body.slice(0, body.length - 1))!
|
|
118
|
+
return new UnaryExpression(last.value, left, null)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (first.value === '(') {
|
|
122
|
+
const leftBody = readUntil(')', body)
|
|
123
|
+
const left = parseExpression(leftBody.slice(1, leftBody.length - 1))!
|
|
124
|
+
|
|
125
|
+
const operator = body[leftBody.length]
|
|
126
|
+
if (operator) {
|
|
127
|
+
const rightBody = body.slice(leftBody.length + 1)
|
|
128
|
+
const right = parseExpression(rightBody)!
|
|
129
|
+
|
|
130
|
+
return new BinaryExpression(operator.value, left, right)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return left
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let scopeIndex = 0
|
|
137
|
+
|
|
138
|
+
for (const operator of BINARY_OPERATORS) {
|
|
139
|
+
for (let i = 0; i < body.length; i++) {
|
|
140
|
+
const token = body[i]
|
|
141
|
+
if (token.type !== 'symbol') continue
|
|
142
|
+
|
|
143
|
+
scopeIndex += getScopeDelta(token)
|
|
144
|
+
|
|
145
|
+
if (scopeIndex === 0 && token.value === operator) {
|
|
146
|
+
if (operator === '?') {
|
|
147
|
+
const testBody = body.slice(0, i)
|
|
148
|
+
const consequentBody = readUntil(':', body, i + 1).slice(0, -1)
|
|
149
|
+
const alternateBody = body.slice(i + consequentBody.length + 2)
|
|
150
|
+
|
|
151
|
+
const test = parseExpression(testBody)!
|
|
152
|
+
const consequent = parseExpression(consequentBody)!
|
|
153
|
+
const alternate = parseExpression(alternateBody)!
|
|
154
|
+
|
|
155
|
+
return new TernaryExpression(test, consequent, alternate)
|
|
156
|
+
} else {
|
|
157
|
+
const left = parseExpression(body.slice(0, i))!
|
|
158
|
+
const right = parseExpression(body.slice(i + 1, body.length))!
|
|
159
|
+
|
|
160
|
+
return new BinaryExpression(operator, left, right)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (scopeIndex < 0) {
|
|
165
|
+
return parseExpression(body.slice(0, i))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (first.type === 'bool' || first.type === 'int' || first.type === 'float') {
|
|
171
|
+
return new Literal(first.value)
|
|
172
|
+
} else if (first.type === 'identifier' || first.type === 'keyword') {
|
|
173
|
+
const second = body[1]
|
|
174
|
+
|
|
175
|
+
if (!second) {
|
|
176
|
+
return new Identifier(first.value)
|
|
177
|
+
} else if (second.value === '(') {
|
|
178
|
+
const callee = new Identifier(first.value)
|
|
179
|
+
const args: AST[] = []
|
|
180
|
+
|
|
181
|
+
const scope = readUntil(')', body, 1).slice(1, -1)
|
|
182
|
+
|
|
183
|
+
let j = 0
|
|
184
|
+
while (j < scope.length) {
|
|
185
|
+
const line = readUntil(',', scope, j)
|
|
186
|
+
j += line.length
|
|
187
|
+
if (line[line.length - 1]?.value === ',') line.pop() // skip ,
|
|
188
|
+
|
|
189
|
+
const arg = parseExpression(line)
|
|
190
|
+
if (arg) args.push(arg)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const expression = new CallExpression(callee, args)
|
|
194
|
+
|
|
195
|
+
// e.g. texture().rgb
|
|
196
|
+
let i = 3 + j
|
|
197
|
+
if (body[i]?.value === '.') {
|
|
198
|
+
const right = parseExpression([first, ...body.slice(i)])! as MemberExpression
|
|
199
|
+
right.object = expression
|
|
200
|
+
return right
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return expression
|
|
204
|
+
} else if (second.value === '.') {
|
|
205
|
+
const object = new Identifier(first.value)
|
|
206
|
+
const property = parseExpression([body[2]])!
|
|
207
|
+
const left = new MemberExpression(object, property)
|
|
208
|
+
|
|
209
|
+
// e.g. array.length()
|
|
210
|
+
if (body[3]?.value === '(' && last.value === ')') {
|
|
211
|
+
const right = parseExpression(body.slice(2))! as CallExpression
|
|
212
|
+
right.callee = left
|
|
213
|
+
return right
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return left
|
|
217
|
+
} else if (second.value === '[') {
|
|
218
|
+
let i = 2
|
|
219
|
+
|
|
220
|
+
const type = new Type(first.value, [])
|
|
221
|
+
|
|
222
|
+
if (body[i].value !== ']') type.parameters!.push(parseExpression([body[i++]]) as any)
|
|
223
|
+
i++ // skip ]
|
|
224
|
+
|
|
225
|
+
const scope = readUntil(')', body, i).slice(1, -1)
|
|
226
|
+
|
|
227
|
+
const members: AST[] = []
|
|
228
|
+
|
|
229
|
+
let j = 0
|
|
230
|
+
while (j < scope.length) {
|
|
231
|
+
const next = readUntil(',', scope, j)
|
|
232
|
+
j += next.length
|
|
233
|
+
|
|
234
|
+
if (next[next.length - 1].value === ',') next.pop()
|
|
235
|
+
|
|
236
|
+
members.push(parseExpression(next)!)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return new ArrayExpression(type, members)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function parseVariable(
|
|
247
|
+
qualifiers: string[] = [],
|
|
248
|
+
layout: Record<string, string | boolean> | null = null,
|
|
249
|
+
): VariableDeclaration {
|
|
250
|
+
i-- // TODO: remove backtrack hack
|
|
251
|
+
|
|
252
|
+
const kind = null // TODO: WGSL
|
|
253
|
+
const type = new Type(tokens[i++].value, null)
|
|
254
|
+
|
|
255
|
+
const declarations: VariableDeclarator[] = []
|
|
256
|
+
|
|
257
|
+
const body = consumeUntil(';')
|
|
258
|
+
let j = 0
|
|
259
|
+
|
|
260
|
+
while (j < body.length) {
|
|
261
|
+
const name = body[j++].value
|
|
262
|
+
|
|
263
|
+
let prefix: AST | null = null
|
|
264
|
+
if (body[j].value === '[') {
|
|
265
|
+
j++ // skip [
|
|
266
|
+
prefix = new ArrayExpression(new Type(type.name, [parseExpression([body[j++]]) as any]), [])
|
|
267
|
+
j++ // skip ]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let value: AST | null = null
|
|
271
|
+
|
|
272
|
+
const delimiter = body[j++]
|
|
273
|
+
if (delimiter?.value === '=') {
|
|
274
|
+
const right = readUntil(',', body, j)
|
|
275
|
+
j += right.length
|
|
276
|
+
|
|
277
|
+
value = parseExpression(right.slice(0, -1))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
declarations.push(new VariableDeclarator(name, value ?? prefix))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return new VariableDeclaration(layout, qualifiers, kind, type, declarations)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function parseFunction(qualifiers: string[]): FunctionDeclaration {
|
|
287
|
+
i-- // TODO: remove backtrack hack
|
|
288
|
+
|
|
289
|
+
const type = new Type(tokens[i++].value, null)
|
|
290
|
+
const name = tokens[i++].value
|
|
291
|
+
const args: VariableDeclaration[] = []
|
|
292
|
+
|
|
293
|
+
// TODO: merge with parseVariable
|
|
294
|
+
const header = consumeUntil(')').slice(1, -1)
|
|
295
|
+
let j = 0
|
|
296
|
+
while (j < header.length) {
|
|
297
|
+
const qualifiers: string[] = []
|
|
298
|
+
while (header[j] && header[j].type !== 'identifier') {
|
|
299
|
+
qualifiers.push(header[j++].value)
|
|
300
|
+
}
|
|
301
|
+
const type = new Type(qualifiers.pop()!, null)
|
|
302
|
+
|
|
303
|
+
const line = readUntil(',', header, j)
|
|
304
|
+
j += line.length
|
|
305
|
+
|
|
306
|
+
const name = line.shift()!.value
|
|
307
|
+
|
|
308
|
+
let prefix: AST | null = null
|
|
309
|
+
if (line[0]?.value === '[') {
|
|
310
|
+
line.shift() // skip [
|
|
311
|
+
prefix = new ArrayExpression(new Type(type.name, [parseExpression([line.shift()!]) as any]), [])
|
|
312
|
+
line.shift() // skip ]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (line[line.length - 1]?.value === ',') line.pop() // skip ,
|
|
316
|
+
|
|
317
|
+
const value = parseExpression(line) ?? prefix
|
|
318
|
+
|
|
319
|
+
const declarations: VariableDeclarator[] = [new VariableDeclarator(name, value)]
|
|
320
|
+
|
|
321
|
+
args.push(new VariableDeclaration(null, qualifiers, null, type, declarations))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let body = null
|
|
325
|
+
if (tokens[i].value === ';') i++ // skip ;
|
|
326
|
+
else body = parseBlock()
|
|
327
|
+
|
|
328
|
+
return new FunctionDeclaration(name, type, qualifiers, args, body)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function parseIndeterminate(): VariableDeclaration | FunctionDeclaration {
|
|
332
|
+
i-- // TODO: remove backtrack hack
|
|
333
|
+
|
|
334
|
+
let layout: Record<string, string | boolean> | null = null
|
|
335
|
+
if (tokens[i].value === 'layout') {
|
|
336
|
+
i++ // skip layout
|
|
337
|
+
|
|
338
|
+
layout = {}
|
|
339
|
+
|
|
340
|
+
let key: string | null = null
|
|
341
|
+
while (tokens[i] && tokens[i].value !== ')') {
|
|
342
|
+
const token = tokens[i++]
|
|
343
|
+
|
|
344
|
+
if (token.value === ',') key = null
|
|
345
|
+
if (token.type === 'symbol') continue
|
|
346
|
+
|
|
347
|
+
if (!key) {
|
|
348
|
+
key = token.value
|
|
349
|
+
layout[key] = true
|
|
350
|
+
} else {
|
|
351
|
+
layout[key] = token.value
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
i++ // skip )
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const qualifiers: string[] = []
|
|
359
|
+
while (tokens[i] && QUALIFIER_REGEX.test(tokens[i].value)) {
|
|
360
|
+
qualifiers.push(tokens[i++].value)
|
|
361
|
+
}
|
|
362
|
+
i++
|
|
363
|
+
|
|
364
|
+
return tokens[i + 1]?.value === '(' ? parseFunction(qualifiers) : parseVariable(qualifiers, layout)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function parseStruct(): StructDeclaration {
|
|
368
|
+
const name = tokens[i++].value
|
|
369
|
+
i++ // skip {
|
|
370
|
+
const members: VariableDeclaration[] = []
|
|
371
|
+
while (tokens[i] && tokens[i].value !== '}') {
|
|
372
|
+
i++ // TODO: remove backtrack hack
|
|
373
|
+
members.push(parseIndeterminate() as VariableDeclaration)
|
|
374
|
+
}
|
|
375
|
+
i++ // skip }
|
|
376
|
+
i++ // skip ;
|
|
377
|
+
|
|
378
|
+
return new StructDeclaration(name, members)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function parseReturn(): ReturnStatement {
|
|
382
|
+
const body = consumeUntil(';')
|
|
383
|
+
body.pop() // skip ;
|
|
384
|
+
|
|
385
|
+
const argument = parseExpression(body)
|
|
386
|
+
|
|
387
|
+
return new ReturnStatement(argument as any)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function parseIf(): IfStatement {
|
|
391
|
+
const test = parseExpression(consumeUntil(')'))!
|
|
392
|
+
const consequent = parseBlock()
|
|
393
|
+
|
|
394
|
+
let alternate = null
|
|
395
|
+
if (tokens[i].value === 'else') {
|
|
396
|
+
i++ // TODO: remove backtrack hack
|
|
397
|
+
|
|
398
|
+
if (tokens[i].value === 'if') {
|
|
399
|
+
i++
|
|
400
|
+
alternate = parseIf()
|
|
401
|
+
} else {
|
|
402
|
+
alternate = parseBlock()
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return new IfStatement(test, consequent, alternate)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function parseWhile(): WhileStatement {
|
|
410
|
+
const test = parseExpression(consumeUntil(')'))!
|
|
411
|
+
const body = parseBlock()
|
|
412
|
+
|
|
413
|
+
return new WhileStatement(test, body)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function parseFor(): ForStatement {
|
|
417
|
+
const delimiterIndex = i + (readUntil(')', tokens, i).length - 1)
|
|
418
|
+
|
|
419
|
+
i++ // skip (
|
|
420
|
+
i++ // TODO: remove backtrack hack
|
|
421
|
+
|
|
422
|
+
const init = parseVariable()
|
|
423
|
+
const test = parseExpression(consumeUntil(';').slice(0, -1))
|
|
424
|
+
const update = parseExpression(tokens.slice(i, delimiterIndex))
|
|
425
|
+
|
|
426
|
+
i = delimiterIndex
|
|
427
|
+
i++ // skip )
|
|
428
|
+
|
|
429
|
+
const body = parseBlock()
|
|
430
|
+
|
|
431
|
+
return new ForStatement(init, test, update, body)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function parseDoWhile(): DoWhileStatement {
|
|
435
|
+
const body = parseBlock()
|
|
436
|
+
i++ // skip while
|
|
437
|
+
const test = parseExpression(consumeUntil(')'))!
|
|
438
|
+
i++ // skip ;
|
|
439
|
+
|
|
440
|
+
return new DoWhileStatement(test, body)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function parseSwitch(): SwitchStatement {
|
|
444
|
+
const discriminant = parseExpression(consumeUntil(')'))
|
|
445
|
+
const delimiterIndex = i + readUntil('}', tokens, i).length - 1
|
|
446
|
+
|
|
447
|
+
const cases: SwitchCase[] = []
|
|
448
|
+
while (i < delimiterIndex) {
|
|
449
|
+
const token = tokens[i++]
|
|
450
|
+
|
|
451
|
+
if (token.value === 'case') {
|
|
452
|
+
const test = parseExpression(consumeUntil(':').slice(0, -1))
|
|
453
|
+
const consequent = parseStatements()
|
|
454
|
+
cases.push(new SwitchCase(test, consequent))
|
|
455
|
+
} else if (token.value === 'default') {
|
|
456
|
+
i++ // skip :
|
|
457
|
+
const consequent = parseStatements()
|
|
458
|
+
cases.push(new SwitchCase(null, consequent))
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return new SwitchStatement(discriminant!, cases)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function parsePrecision(): PrecisionStatement {
|
|
466
|
+
const precision = tokens[i++].value
|
|
467
|
+
const type = new Type(tokens[i++].value, null)
|
|
468
|
+
i++ // skip ;
|
|
469
|
+
return new PrecisionStatement(precision as any, type)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function parsePreprocessor(): PreprocessorStatement {
|
|
473
|
+
const name = tokens[i++].value
|
|
474
|
+
|
|
475
|
+
const body = consumeUntil('\\').slice(0, -1)
|
|
476
|
+
let value: AST[] | null = null
|
|
477
|
+
|
|
478
|
+
if (name !== 'else' && name !== 'endif') {
|
|
479
|
+
value = []
|
|
480
|
+
|
|
481
|
+
if (name === 'define') {
|
|
482
|
+
const left = parseExpression([body.shift()!])!
|
|
483
|
+
const right = parseExpression(body)!
|
|
484
|
+
value.push(left, right)
|
|
485
|
+
} else if (name === 'extension') {
|
|
486
|
+
const left = parseExpression([body.shift()!])!
|
|
487
|
+
body.shift() // skip :
|
|
488
|
+
const right = parseExpression(body)!
|
|
489
|
+
value.push(left, right)
|
|
490
|
+
} else if (name === 'include') {
|
|
491
|
+
value.push(parseExpression(body.slice(1, -1))!)
|
|
492
|
+
} else {
|
|
493
|
+
value.push(parseExpression(body)!)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return new PreprocessorStatement(name, value)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function parseStatements(): AST[] {
|
|
501
|
+
const body: AST[] = []
|
|
502
|
+
let scopeIndex = 0
|
|
503
|
+
|
|
504
|
+
while (i < tokens.length) {
|
|
505
|
+
const token = tokens[i++]
|
|
506
|
+
|
|
507
|
+
scopeIndex += getScopeDelta(token)
|
|
508
|
+
if (scopeIndex < 0) break
|
|
509
|
+
|
|
510
|
+
let statement: AST | null = null
|
|
511
|
+
|
|
512
|
+
if (token.value === '#') {
|
|
513
|
+
statement = parsePreprocessor()
|
|
514
|
+
} else if (token.type === 'keyword') {
|
|
515
|
+
if (token.value === 'case' || token.value === 'default') {
|
|
516
|
+
i--
|
|
517
|
+
break
|
|
518
|
+
} else if (token.value === 'struct') statement = parseStruct()
|
|
519
|
+
else if (token.value === 'continue') (statement = new ContinueStatement()), i++
|
|
520
|
+
else if (token.value === 'break') (statement = new BreakStatement()), i++
|
|
521
|
+
else if (token.value === 'discard') (statement = new DiscardStatement()), i++
|
|
522
|
+
else if (token.value === 'return') statement = parseReturn()
|
|
523
|
+
else if (token.value === 'if') statement = parseIf()
|
|
524
|
+
else if (token.value === 'while') statement = parseWhile()
|
|
525
|
+
else if (token.value === 'for') statement = parseFor()
|
|
526
|
+
else if (token.value === 'do') statement = parseDoWhile()
|
|
527
|
+
else if (token.value === 'switch') statement = parseSwitch()
|
|
528
|
+
else if (token.value === 'precision') statement = parsePrecision()
|
|
529
|
+
else if (isDeclaration(token.value) && tokens[i].value !== '[') statement = parseIndeterminate()
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (statement) {
|
|
533
|
+
body.push(statement)
|
|
534
|
+
} else {
|
|
535
|
+
const line = [token, ...consumeUntil(';')]
|
|
536
|
+
if (line[line.length - 1].value === ';') line.pop()
|
|
537
|
+
const expression = parseExpression(line)
|
|
538
|
+
if (expression) body.push(expression)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return body
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function parseBlock(): BlockStatement {
|
|
546
|
+
i++ // skip {
|
|
547
|
+
const body = parseStatements()
|
|
548
|
+
return new BlockStatement(body)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const DIRECTIVE_REGEX = /(^\s*#[^\\]*?)(\n|\/[\/\*])/gm
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Parses a string of GLSL (WGSL WIP) code into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
|
|
555
|
+
*/
|
|
556
|
+
export function parse(code: string): AST[] {
|
|
557
|
+
// Remove (implicit) version header
|
|
558
|
+
code = code.replace('#version 300 es', '')
|
|
559
|
+
|
|
560
|
+
// Escape newlines after directives, skip comments
|
|
561
|
+
code = code.replace(DIRECTIVE_REGEX, '$1\\$2')
|
|
562
|
+
|
|
563
|
+
// TODO: preserve
|
|
564
|
+
tokens = tokenize(code).filter((token) => token.type !== 'whitespace' && token.type !== 'comment')
|
|
565
|
+
i = 0
|
|
566
|
+
|
|
567
|
+
return parseStatements()
|
|
568
|
+
}
|