shaderkit 0.2.1 → 0.3.1
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 +1 -1
- package/README.md +350 -153
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -14
- package/src/ast.ts +343 -77
- package/src/constants.ts +4 -4
- package/src/generator.ts +102 -150
- package/src/index.ts +6 -5
- package/src/minifier.ts +27 -8
- package/src/parser.ts +497 -405
- package/src/tokenizer.ts +24 -13
- package/dist/index.cjs +0 -5
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1
package/src/parser.ts
CHANGED
|
@@ -1,568 +1,660 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Literal,
|
|
6
|
-
CallExpression,
|
|
7
|
-
UnaryExpression,
|
|
8
|
-
MemberExpression,
|
|
9
|
-
TernaryExpression,
|
|
10
|
-
BinaryExpression,
|
|
2
|
+
ArraySpecifier,
|
|
3
|
+
AssignmentOperator,
|
|
4
|
+
BinaryOperator,
|
|
11
5
|
BlockStatement,
|
|
12
|
-
FunctionDeclaration,
|
|
13
|
-
VariableDeclaration,
|
|
14
|
-
VariableDeclarator,
|
|
15
|
-
ContinueStatement,
|
|
16
6
|
BreakStatement,
|
|
7
|
+
ConstantQualifier,
|
|
8
|
+
ContinueStatement,
|
|
17
9
|
DiscardStatement,
|
|
18
|
-
ReturnStatement,
|
|
19
|
-
IfStatement,
|
|
20
|
-
WhileStatement,
|
|
21
|
-
ForStatement,
|
|
22
10
|
DoWhileStatement,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
Expression,
|
|
12
|
+
ForStatement,
|
|
13
|
+
FunctionDeclaration,
|
|
14
|
+
FunctionParameter,
|
|
15
|
+
Identifier,
|
|
16
|
+
IfStatement,
|
|
17
|
+
InterpolationQualifier,
|
|
18
|
+
LayoutQualifier,
|
|
19
|
+
Literal,
|
|
20
|
+
LogicalOperator,
|
|
21
|
+
ParameterQualifier,
|
|
22
|
+
PrecisionQualifier,
|
|
26
23
|
PrecisionStatement,
|
|
27
|
-
ArrayExpression,
|
|
28
24
|
PreprocessorStatement,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
25
|
+
Program,
|
|
26
|
+
ReturnStatement,
|
|
27
|
+
Statement,
|
|
28
|
+
StorageQualifier,
|
|
29
|
+
StructDeclaration,
|
|
30
|
+
SwitchCase,
|
|
31
|
+
SwitchStatement,
|
|
32
|
+
UnaryOperator,
|
|
33
|
+
UniformDeclarationBlock,
|
|
34
|
+
UpdateOperator,
|
|
35
|
+
VariableDeclaration,
|
|
36
|
+
VariableDeclarator,
|
|
37
|
+
WhileStatement,
|
|
38
|
+
} from './ast.js'
|
|
39
|
+
import { type Token, tokenize } from './tokenizer.js'
|
|
40
|
+
|
|
41
|
+
// https://engineering.desmos.com/articles/pratt-parser
|
|
42
|
+
// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
|
|
43
|
+
|
|
44
|
+
// 5.1 Operators
|
|
45
|
+
enum Precedence {
|
|
46
|
+
LOWEST,
|
|
47
|
+
COMMA,
|
|
48
|
+
ASSIGN,
|
|
49
|
+
LOGICAL_OR,
|
|
50
|
+
LOGICAL_XOR,
|
|
51
|
+
LOGICAL_AND,
|
|
52
|
+
BITWISE_OR,
|
|
53
|
+
BITWISE_XOR,
|
|
54
|
+
BITWISE_AND,
|
|
55
|
+
TERNARY,
|
|
56
|
+
COMPARE,
|
|
57
|
+
SHIFT,
|
|
58
|
+
ADD,
|
|
59
|
+
MULTIPLY,
|
|
60
|
+
UNARY_PREFIX,
|
|
61
|
+
UNARY_POSTFIX,
|
|
62
|
+
MEMBER,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const PREFIX_OPERATOR_PRECEDENCE: Record<string, Precedence> = {
|
|
66
|
+
'~': Precedence.UNARY_PREFIX,
|
|
67
|
+
'!': Precedence.UNARY_PREFIX,
|
|
68
|
+
'--': Precedence.UNARY_PREFIX,
|
|
69
|
+
'++': Precedence.UNARY_PREFIX,
|
|
70
|
+
'-': Precedence.UNARY_PREFIX,
|
|
71
|
+
'+': Precedence.UNARY_PREFIX,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const POSTFIX_OPERATOR_PRECEDENCE: Record<string, Precedence> = {
|
|
75
|
+
'--': Precedence.UNARY_POSTFIX,
|
|
76
|
+
'++': Precedence.UNARY_POSTFIX,
|
|
77
|
+
'(': Precedence.LOWEST,
|
|
78
|
+
'[': Precedence.MEMBER,
|
|
79
|
+
'.': Precedence.MEMBER,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const INFIX_OPERATOR_PRECEDENCE_LEFT: Record<string, Precedence> = {
|
|
83
|
+
'||': Precedence.LOGICAL_OR,
|
|
84
|
+
'^^': Precedence.LOGICAL_XOR,
|
|
85
|
+
'&&': Precedence.LOGICAL_AND,
|
|
86
|
+
'|': Precedence.BITWISE_OR,
|
|
87
|
+
'^': Precedence.BITWISE_XOR,
|
|
88
|
+
'&': Precedence.BITWISE_AND,
|
|
89
|
+
'==': Precedence.COMPARE,
|
|
90
|
+
'>': Precedence.COMPARE,
|
|
91
|
+
'>=': Precedence.COMPARE,
|
|
92
|
+
'<': Precedence.COMPARE,
|
|
93
|
+
'<=': Precedence.COMPARE,
|
|
94
|
+
'!=': Precedence.COMPARE,
|
|
95
|
+
'<<': Precedence.SHIFT,
|
|
96
|
+
'>>': Precedence.SHIFT,
|
|
97
|
+
'+': Precedence.ADD,
|
|
98
|
+
'-': Precedence.ADD,
|
|
99
|
+
'*': Precedence.MULTIPLY,
|
|
100
|
+
'/': Precedence.MULTIPLY,
|
|
101
|
+
'%': Precedence.MULTIPLY,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const INFIX_OPERATOR_PRECEDENCE_RIGHT: Record<string, Precedence> = {
|
|
105
|
+
'=': Precedence.ASSIGN,
|
|
106
|
+
'+=': Precedence.ASSIGN,
|
|
107
|
+
'&=': Precedence.ASSIGN,
|
|
108
|
+
'|=': Precedence.ASSIGN,
|
|
109
|
+
'^=': Precedence.ASSIGN,
|
|
110
|
+
'/=': Precedence.ASSIGN,
|
|
111
|
+
'*=': Precedence.ASSIGN,
|
|
112
|
+
'%=': Precedence.ASSIGN,
|
|
113
|
+
'<<=': Precedence.ASSIGN,
|
|
114
|
+
'>>=': Precedence.ASSIGN,
|
|
115
|
+
'-=': Precedence.ASSIGN,
|
|
116
|
+
'?': Precedence.TERNARY,
|
|
117
|
+
}
|
|
67
118
|
|
|
68
119
|
// TODO: this is GLSL-only, separate language constants
|
|
69
120
|
const TYPE_REGEX = /^(void|bool|float|u?int|[uib]?vec\d|mat\d(x\d)?)$/
|
|
121
|
+
// TODO: must be ordered: invariant interpolation storage precision storage parameter precision
|
|
122
|
+
// const cannot be used with storage or parameter qualifiers
|
|
70
123
|
const QUALIFIER_REGEX = /^(const|uniform|in|out|inout|centroid|flat|smooth|invariant|lowp|mediump|highp)$/
|
|
71
124
|
const VARIABLE_REGEX = new RegExp(`${TYPE_REGEX.source}|${QUALIFIER_REGEX.source}|layout`)
|
|
72
125
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
126
|
+
const SCOPE_DELTAS: Record<string, number> = {
|
|
127
|
+
// Open
|
|
128
|
+
'(': 1,
|
|
129
|
+
'[': 1,
|
|
130
|
+
'{': 1,
|
|
131
|
+
// Close
|
|
132
|
+
')': -1,
|
|
133
|
+
']': -1,
|
|
134
|
+
'}': -1,
|
|
135
|
+
}
|
|
78
136
|
function getScopeDelta(token: Token): number {
|
|
79
|
-
|
|
80
|
-
if (isClose(token.value)) return -1
|
|
81
|
-
return 0
|
|
137
|
+
return SCOPE_DELTAS[token.value] ?? 0
|
|
82
138
|
}
|
|
83
139
|
|
|
84
|
-
|
|
85
|
-
|
|
140
|
+
function consume(tokens: Token[], expected?: string): Token {
|
|
141
|
+
const token = tokens.shift()
|
|
86
142
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
output.push(token)
|
|
94
|
-
|
|
95
|
-
scopeIndex += getScopeDelta(token)
|
|
96
|
-
if (scopeIndex === 0 && token.value === value) break
|
|
143
|
+
if (token === undefined && expected !== undefined) {
|
|
144
|
+
throw new SyntaxError(`Expected "${expected}"`)
|
|
145
|
+
} else if (token === undefined) {
|
|
146
|
+
throw new SyntaxError('Unexpected end of input')
|
|
147
|
+
} else if (expected !== undefined && token.value !== expected) {
|
|
148
|
+
throw new SyntaxError(`Expected "${expected}" got "${token.value}"`)
|
|
97
149
|
}
|
|
98
150
|
|
|
99
|
-
return
|
|
151
|
+
return token
|
|
100
152
|
}
|
|
101
153
|
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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)
|
|
154
|
+
function parseExpression(tokens: Token[], minBindingPower: number = 0): Expression {
|
|
155
|
+
let token = consume(tokens)
|
|
156
|
+
|
|
157
|
+
let lhs: Expression
|
|
158
|
+
if (token.type === 'identifier' || token.type === 'keyword') {
|
|
159
|
+
lhs = { type: 'Identifier', name: token.value }
|
|
160
|
+
} else if (token.type === 'bool' || token.type === 'float' || token.type === 'int') {
|
|
161
|
+
lhs = { type: 'Literal', value: token.value }
|
|
162
|
+
} else if (token.type === 'symbol' && token.value === '(') {
|
|
163
|
+
lhs = parseExpression(tokens, 0)
|
|
164
|
+
consume(tokens, ')')
|
|
165
|
+
} else if (token.type === 'symbol' && token.value in PREFIX_OPERATOR_PRECEDENCE) {
|
|
166
|
+
const rightBindingPower = PREFIX_OPERATOR_PRECEDENCE[token.value]
|
|
167
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
168
|
+
if (token.value === '--' || token.value === '++') {
|
|
169
|
+
lhs = { type: 'UpdateExpression', operator: token.value, prefix: true, argument: rhs }
|
|
170
|
+
} else {
|
|
171
|
+
lhs = { type: 'UnaryExpression', operator: token.value as UnaryOperator, prefix: true, argument: rhs }
|
|
131
172
|
}
|
|
132
|
-
|
|
133
|
-
|
|
173
|
+
} else {
|
|
174
|
+
throw new SyntaxError(`Unexpected token: "${token.value}"`)
|
|
134
175
|
}
|
|
135
176
|
|
|
136
|
-
|
|
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
|
|
177
|
+
while (tokens.length) {
|
|
178
|
+
token = tokens[0]
|
|
142
179
|
|
|
143
|
-
|
|
180
|
+
if (token.value in POSTFIX_OPERATOR_PRECEDENCE) {
|
|
181
|
+
const leftBindingPower = POSTFIX_OPERATOR_PRECEDENCE[token.value]
|
|
182
|
+
if (leftBindingPower < minBindingPower) break
|
|
144
183
|
|
|
145
|
-
|
|
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)
|
|
184
|
+
consume(tokens)
|
|
150
185
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const alternate = parseExpression(alternateBody)!
|
|
186
|
+
if (token.value === '(') {
|
|
187
|
+
const args: Expression[] = []
|
|
154
188
|
|
|
155
|
-
|
|
189
|
+
while (tokens[0]?.value !== ')') {
|
|
190
|
+
args.push(parseExpression(tokens, 0))
|
|
191
|
+
if (tokens[0]?.value !== ')') consume(tokens, ',')
|
|
192
|
+
}
|
|
193
|
+
consume(tokens, ')')
|
|
194
|
+
|
|
195
|
+
lhs = { type: 'CallExpression', callee: lhs, arguments: args }
|
|
196
|
+
} else if (token.value === '[') {
|
|
197
|
+
const rhs = tokens[0]?.value !== ']' ? parseExpression(tokens, 0) : null
|
|
198
|
+
consume(tokens, ']')
|
|
199
|
+
|
|
200
|
+
if (tokens[0]?.value === '(') {
|
|
201
|
+
consume(tokens, '(')
|
|
202
|
+
const elements: Expression[] = []
|
|
203
|
+
|
|
204
|
+
while ((tokens[0] as Token | undefined)?.value !== ')') {
|
|
205
|
+
elements.push(parseExpression(tokens, 0))
|
|
206
|
+
if ((tokens[0] as Token | undefined)?.value !== ')') consume(tokens, ',')
|
|
207
|
+
}
|
|
208
|
+
consume(tokens, ')')
|
|
209
|
+
|
|
210
|
+
const typeSpecifier: ArraySpecifier = {
|
|
211
|
+
type: 'ArraySpecifier',
|
|
212
|
+
typeSpecifier: lhs as Identifier,
|
|
213
|
+
dimensions: [rhs as Literal | null],
|
|
214
|
+
}
|
|
215
|
+
lhs = { type: 'ArrayExpression', typeSpecifier, elements }
|
|
156
216
|
} else {
|
|
157
|
-
|
|
158
|
-
const right = parseExpression(body.slice(i + 1, body.length))!
|
|
159
|
-
|
|
160
|
-
return new BinaryExpression(operator, left, right)
|
|
217
|
+
lhs = { type: 'MemberExpression', object: lhs, property: rhs!, computed: true }
|
|
161
218
|
}
|
|
219
|
+
} else if (token.value === '.') {
|
|
220
|
+
const rhs = parseExpression(tokens, 0)
|
|
221
|
+
lhs = { type: 'MemberExpression', object: lhs, property: rhs, computed: false }
|
|
222
|
+
} else if (token.value === '--' || token.value === '++') {
|
|
223
|
+
lhs = { type: 'UpdateExpression', operator: token.value as UpdateOperator, prefix: false, argument: lhs }
|
|
224
|
+
} else {
|
|
225
|
+
lhs = { type: 'UnaryExpression', operator: token.value as UnaryOperator, prefix: false, argument: lhs }
|
|
162
226
|
}
|
|
227
|
+
} else if (token.value in INFIX_OPERATOR_PRECEDENCE_LEFT) {
|
|
228
|
+
const precedence = INFIX_OPERATOR_PRECEDENCE_LEFT[token.value]
|
|
229
|
+
const leftBindingPower = precedence - 1
|
|
230
|
+
const rightBindingPower = precedence
|
|
163
231
|
|
|
164
|
-
if (
|
|
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[] = []
|
|
232
|
+
if (leftBindingPower < minBindingPower) break
|
|
180
233
|
|
|
181
|
-
|
|
234
|
+
consume(tokens)
|
|
182
235
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const arg = parseExpression(line)
|
|
190
|
-
if (arg) args.push(arg)
|
|
236
|
+
if (token.value === '||' || token.value === '&&' || token.value === '^^') {
|
|
237
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
238
|
+
lhs = { type: 'LogicalExpression', operator: token.value, left: lhs, right: rhs }
|
|
239
|
+
} else {
|
|
240
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
241
|
+
lhs = { type: 'BinaryExpression', operator: token.value as BinaryOperator, left: lhs, right: rhs }
|
|
191
242
|
}
|
|
243
|
+
} else if (token.value in INFIX_OPERATOR_PRECEDENCE_RIGHT) {
|
|
244
|
+
const precedence = INFIX_OPERATOR_PRECEDENCE_RIGHT[token.value]
|
|
245
|
+
const leftBindingPower = precedence
|
|
246
|
+
const rightBindingPower = precedence - 1
|
|
192
247
|
|
|
193
|
-
|
|
248
|
+
if (leftBindingPower < minBindingPower) break
|
|
194
249
|
|
|
195
|
-
|
|
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
|
-
}
|
|
250
|
+
consume(tokens)
|
|
202
251
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const right = parseExpression(body.slice(2))! as CallExpression
|
|
212
|
-
right.callee = left
|
|
213
|
-
return right
|
|
252
|
+
if (token.value === '?') {
|
|
253
|
+
const mhs = parseExpression(tokens, 0)
|
|
254
|
+
consume(tokens, ':')
|
|
255
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
256
|
+
lhs = { type: 'ConditionalExpression', test: lhs, alternate: mhs, consequent: rhs }
|
|
257
|
+
} else {
|
|
258
|
+
const rhs = parseExpression(tokens, rightBindingPower)
|
|
259
|
+
lhs = { type: 'AssignmentExpression', operator: token.value as AssignmentOperator, left: lhs, right: rhs }
|
|
214
260
|
}
|
|
261
|
+
} else {
|
|
262
|
+
break
|
|
263
|
+
}
|
|
264
|
+
}
|
|
215
265
|
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
266
|
+
return lhs
|
|
267
|
+
}
|
|
233
268
|
|
|
234
|
-
|
|
269
|
+
function parseVariableDeclarator(
|
|
270
|
+
tokens: Token[],
|
|
271
|
+
typeSpecifier: Identifier | ArraySpecifier,
|
|
272
|
+
qualifiers: (ConstantQualifier | InterpolationQualifier | StorageQualifier | PrecisionQualifier)[],
|
|
273
|
+
layout: Record<string, string | boolean> | null,
|
|
274
|
+
): VariableDeclarator {
|
|
275
|
+
let id: Identifier = { type: 'Identifier', name: consume(tokens).value }
|
|
276
|
+
|
|
277
|
+
if (tokens[0]?.value === '[') {
|
|
278
|
+
consume(tokens, '[')
|
|
279
|
+
id = {
|
|
280
|
+
type: 'ArraySpecifier',
|
|
281
|
+
typeSpecifier: id,
|
|
282
|
+
dimensions: [parseExpression(tokens) as Literal | Identifier],
|
|
283
|
+
} satisfies ArraySpecifier as unknown as Identifier
|
|
284
|
+
consume(tokens, ']')
|
|
285
|
+
}
|
|
235
286
|
|
|
236
|
-
|
|
237
|
-
}
|
|
287
|
+
let init: Expression | null = null
|
|
238
288
|
|
|
239
|
-
|
|
240
|
-
|
|
289
|
+
if (tokens[0]?.value === '=') {
|
|
290
|
+
consume(tokens, '=')
|
|
291
|
+
init = parseExpression(tokens)
|
|
241
292
|
}
|
|
242
293
|
|
|
243
|
-
return
|
|
294
|
+
return { type: 'VariableDeclarator', id, qualifiers, typeSpecifier, layout, init }
|
|
244
295
|
}
|
|
245
296
|
|
|
246
297
|
function parseVariable(
|
|
247
|
-
|
|
298
|
+
tokens: Token[],
|
|
299
|
+
typeSpecifier: Identifier | ArraySpecifier,
|
|
300
|
+
qualifiers: (ConstantQualifier | InterpolationQualifier | StorageQualifier | PrecisionQualifier)[] = [],
|
|
248
301
|
layout: Record<string, string | boolean> | null = null,
|
|
249
302
|
): 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
303
|
const declarations: VariableDeclarator[] = []
|
|
256
304
|
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
305
|
+
if (tokens[0]?.value !== ';') {
|
|
306
|
+
while (tokens.length) {
|
|
307
|
+
declarations.push(parseVariableDeclarator(tokens, typeSpecifier, qualifiers, layout))
|
|
276
308
|
|
|
277
|
-
value
|
|
309
|
+
if (tokens[0]?.value === ',') {
|
|
310
|
+
consume(tokens, ',')
|
|
311
|
+
} else {
|
|
312
|
+
break
|
|
313
|
+
}
|
|
278
314
|
}
|
|
279
|
-
|
|
280
|
-
declarations.push(new VariableDeclarator(name, value ?? prefix))
|
|
281
315
|
}
|
|
282
316
|
|
|
283
|
-
|
|
317
|
+
consume(tokens, ';')
|
|
318
|
+
|
|
319
|
+
return { type: 'VariableDeclaration', declarations }
|
|
284
320
|
}
|
|
285
321
|
|
|
286
|
-
function
|
|
287
|
-
|
|
322
|
+
function parseUniformBlock(
|
|
323
|
+
tokens: Token[],
|
|
324
|
+
typeSpecifier: Identifier | ArraySpecifier,
|
|
325
|
+
qualifiers: LayoutQualifier[] = [],
|
|
326
|
+
layout: Record<string, string | boolean> | null = null,
|
|
327
|
+
): UniformDeclarationBlock {
|
|
328
|
+
const members = parseBlock(tokens).body as VariableDeclaration[]
|
|
288
329
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
330
|
+
let id: Identifier | null = null
|
|
331
|
+
if (tokens[0]?.value !== ';') id = parseExpression(tokens) as Identifier
|
|
332
|
+
consume(tokens, ';')
|
|
292
333
|
|
|
293
|
-
|
|
294
|
-
|
|
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)
|
|
334
|
+
return { type: 'UniformDeclarationBlock', id, qualifiers, typeSpecifier, layout, members }
|
|
335
|
+
}
|
|
302
336
|
|
|
303
|
-
|
|
304
|
-
|
|
337
|
+
function parseFunction(
|
|
338
|
+
tokens: Token[],
|
|
339
|
+
typeSpecifier: ArraySpecifier | Identifier,
|
|
340
|
+
qualifiers: PrecisionQualifier[] = [],
|
|
341
|
+
): FunctionDeclaration {
|
|
342
|
+
const id: Identifier = { type: 'Identifier', name: consume(tokens).value }
|
|
305
343
|
|
|
306
|
-
|
|
344
|
+
consume(tokens, '(')
|
|
307
345
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
346
|
+
const params: FunctionParameter[] = []
|
|
347
|
+
while (tokens[0] && tokens[0].value !== ')') {
|
|
348
|
+
const qualifiers: (ConstantQualifier | ParameterQualifier | PrecisionQualifier)[] = []
|
|
349
|
+
while (tokens[0] && QUALIFIER_REGEX.test(tokens[0].value)) {
|
|
350
|
+
qualifiers.push(consume(tokens).value as ConstantQualifier | ParameterQualifier | PrecisionQualifier)
|
|
313
351
|
}
|
|
352
|
+
const typeSpecifier = parseExpression(tokens) as ArraySpecifier | Identifier
|
|
353
|
+
const id: Identifier = { type: 'Identifier', name: consume(tokens).value }
|
|
314
354
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const value = parseExpression(line) ?? prefix
|
|
355
|
+
params.push({ type: 'FunctionParameter', id, qualifiers, typeSpecifier })
|
|
318
356
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
args.push(new VariableDeclaration(null, qualifiers, null, type, declarations))
|
|
357
|
+
if (tokens[0]?.value === ',') consume(tokens, ',')
|
|
322
358
|
}
|
|
323
359
|
|
|
360
|
+
consume(tokens, ')')
|
|
361
|
+
|
|
324
362
|
let body = null
|
|
325
|
-
if (tokens[
|
|
326
|
-
else body = parseBlock()
|
|
363
|
+
if (tokens[0].value === ';') consume(tokens, ';')
|
|
364
|
+
else body = parseBlock(tokens)
|
|
327
365
|
|
|
328
|
-
return
|
|
366
|
+
return { type: 'FunctionDeclaration', id, qualifiers, typeSpecifier, params, body }
|
|
329
367
|
}
|
|
330
368
|
|
|
331
|
-
function parseIndeterminate(): VariableDeclaration | FunctionDeclaration {
|
|
332
|
-
i-- // TODO: remove backtrack hack
|
|
333
|
-
|
|
369
|
+
function parseIndeterminate(tokens: Token[]): VariableDeclaration | FunctionDeclaration | UniformDeclarationBlock {
|
|
334
370
|
let layout: Record<string, string | boolean> | null = null
|
|
335
|
-
if (tokens[
|
|
336
|
-
|
|
371
|
+
if (tokens[0].value === 'layout') {
|
|
372
|
+
consume(tokens, 'layout')
|
|
373
|
+
consume(tokens, '(')
|
|
337
374
|
|
|
338
375
|
layout = {}
|
|
339
376
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
377
|
+
while (tokens[0] && (tokens[0] as Token).value !== ')') {
|
|
378
|
+
const expression = parseExpression(tokens)
|
|
379
|
+
|
|
380
|
+
if (
|
|
381
|
+
expression.type === 'AssignmentExpression' &&
|
|
382
|
+
expression.left.type === 'Identifier' &&
|
|
383
|
+
expression.right.type === 'Literal'
|
|
384
|
+
) {
|
|
385
|
+
layout[expression.left.name] = expression.right.value
|
|
386
|
+
} else if (expression.type === 'Identifier') {
|
|
387
|
+
layout[expression.name] = true
|
|
350
388
|
} else {
|
|
351
|
-
|
|
389
|
+
throw new TypeError('Unexpected expression')
|
|
352
390
|
}
|
|
391
|
+
|
|
392
|
+
if (tokens[0] && (tokens[0] as Token).value !== ')') consume(tokens, ',')
|
|
353
393
|
}
|
|
354
394
|
|
|
355
|
-
|
|
395
|
+
consume(tokens, ')')
|
|
356
396
|
}
|
|
357
397
|
|
|
398
|
+
// TODO: only precision qualifier valid for function return type
|
|
358
399
|
const qualifiers: string[] = []
|
|
359
|
-
while (tokens[
|
|
360
|
-
qualifiers.push(tokens
|
|
400
|
+
while (tokens[0] && QUALIFIER_REGEX.test(tokens[0].value)) {
|
|
401
|
+
qualifiers.push(consume(tokens).value)
|
|
361
402
|
}
|
|
362
|
-
i++
|
|
363
403
|
|
|
364
|
-
|
|
404
|
+
// TODO: cleanup handling of typeSpecifier
|
|
405
|
+
if (tokens[2]?.value === '(')
|
|
406
|
+
return parseFunction(
|
|
407
|
+
tokens,
|
|
408
|
+
parseExpression(tokens) as Identifier | ArraySpecifier,
|
|
409
|
+
qualifiers as PrecisionQualifier[],
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
const typeSpecifier = parseExpression(tokens) as Identifier | ArraySpecifier
|
|
413
|
+
|
|
414
|
+
if (tokens[0]?.value === '{') return parseUniformBlock(tokens, typeSpecifier, qualifiers as LayoutQualifier[], layout)
|
|
415
|
+
else
|
|
416
|
+
return parseVariable(
|
|
417
|
+
tokens,
|
|
418
|
+
typeSpecifier,
|
|
419
|
+
qualifiers as (ConstantQualifier | InterpolationQualifier | StorageQualifier | PrecisionQualifier)[],
|
|
420
|
+
layout,
|
|
421
|
+
)
|
|
365
422
|
}
|
|
366
423
|
|
|
367
|
-
function parseStruct(): StructDeclaration {
|
|
368
|
-
|
|
369
|
-
|
|
424
|
+
function parseStruct(tokens: Token[]): StructDeclaration {
|
|
425
|
+
consume(tokens, 'struct')
|
|
426
|
+
const id: Identifier = { type: 'Identifier', name: consume(tokens).value }
|
|
427
|
+
consume(tokens, '{')
|
|
370
428
|
const members: VariableDeclaration[] = []
|
|
371
|
-
while (tokens[
|
|
372
|
-
|
|
373
|
-
members.push(parseIndeterminate() as VariableDeclaration)
|
|
429
|
+
while (tokens[0] && tokens[0].value !== '}') {
|
|
430
|
+
members.push(...(parseStatements(tokens) as unknown as VariableDeclaration[]))
|
|
374
431
|
}
|
|
375
|
-
|
|
376
|
-
|
|
432
|
+
consume(tokens, '}')
|
|
433
|
+
consume(tokens, ';')
|
|
377
434
|
|
|
378
|
-
return
|
|
435
|
+
return { type: 'StructDeclaration', id, members }
|
|
379
436
|
}
|
|
380
437
|
|
|
381
|
-
function
|
|
382
|
-
|
|
383
|
-
|
|
438
|
+
function parseContinue(tokens: Token[]): ContinueStatement {
|
|
439
|
+
consume(tokens, 'continue')
|
|
440
|
+
consume(tokens, ';')
|
|
384
441
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return new ReturnStatement(argument as any)
|
|
442
|
+
return { type: 'ContinueStatement' }
|
|
388
443
|
}
|
|
389
444
|
|
|
390
|
-
function
|
|
391
|
-
|
|
392
|
-
|
|
445
|
+
function parseBreak(tokens: Token[]): BreakStatement {
|
|
446
|
+
consume(tokens, 'break')
|
|
447
|
+
consume(tokens, ';')
|
|
393
448
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
i++ // TODO: remove backtrack hack
|
|
449
|
+
return { type: 'BreakStatement' }
|
|
450
|
+
}
|
|
397
451
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
} else {
|
|
402
|
-
alternate = parseBlock()
|
|
403
|
-
}
|
|
404
|
-
}
|
|
452
|
+
function parseDiscard(tokens: Token[]): DiscardStatement {
|
|
453
|
+
consume(tokens, 'discard')
|
|
454
|
+
consume(tokens, ';')
|
|
405
455
|
|
|
406
|
-
return
|
|
456
|
+
return { type: 'DiscardStatement' }
|
|
407
457
|
}
|
|
408
458
|
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
|
|
459
|
+
function parseReturn(tokens: Token[]): ReturnStatement {
|
|
460
|
+
consume(tokens, 'return')
|
|
461
|
+
|
|
462
|
+
let argument: Expression | null = null
|
|
463
|
+
if (tokens[0]?.value !== ';') argument = parseExpression(tokens)
|
|
464
|
+
consume(tokens, ';')
|
|
412
465
|
|
|
413
|
-
return
|
|
466
|
+
return { type: 'ReturnStatement', argument }
|
|
414
467
|
}
|
|
415
468
|
|
|
416
|
-
function
|
|
417
|
-
|
|
469
|
+
function parseIf(tokens: Token[]): IfStatement {
|
|
470
|
+
consume(tokens, 'if')
|
|
471
|
+
consume(tokens, '(')
|
|
472
|
+
const test = parseExpression(tokens)
|
|
473
|
+
consume(tokens, ')')
|
|
418
474
|
|
|
419
|
-
|
|
420
|
-
i++ // TODO: remove backtrack hack
|
|
475
|
+
const consequent = parseBlock(tokens)
|
|
421
476
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
477
|
+
let alternate = null
|
|
478
|
+
if (tokens[0] && tokens[0].value === 'else') {
|
|
479
|
+
consume(tokens, 'else')
|
|
480
|
+
|
|
481
|
+
if (tokens[0] && (tokens[0] as Token).value === 'if') {
|
|
482
|
+
alternate = parseIf(tokens)
|
|
483
|
+
} else {
|
|
484
|
+
alternate = parseBlock(tokens)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return { type: 'IfStatement', test, consequent, alternate }
|
|
489
|
+
}
|
|
425
490
|
|
|
426
|
-
|
|
427
|
-
|
|
491
|
+
function parseWhile(tokens: Token[]): WhileStatement {
|
|
492
|
+
consume(tokens, 'while')
|
|
493
|
+
consume(tokens, '(')
|
|
494
|
+
const test = parseExpression(tokens)
|
|
495
|
+
consume(tokens, ')')
|
|
496
|
+
const body = parseBlock(tokens)
|
|
428
497
|
|
|
429
|
-
|
|
498
|
+
return { type: 'WhileStatement', test, body }
|
|
499
|
+
}
|
|
430
500
|
|
|
431
|
-
|
|
501
|
+
function parseFor(tokens: Token[]): ForStatement {
|
|
502
|
+
consume(tokens, 'for')
|
|
503
|
+
consume(tokens, '(')
|
|
504
|
+
const typeSpecifier = parseExpression(tokens) as Identifier | ArraySpecifier
|
|
505
|
+
const init = parseVariable(tokens, typeSpecifier)
|
|
506
|
+
// consume(tokens, ';')
|
|
507
|
+
const test = parseExpression(tokens)
|
|
508
|
+
consume(tokens, ';')
|
|
509
|
+
const update = parseExpression(tokens)
|
|
510
|
+
consume(tokens, ')')
|
|
511
|
+
const body = parseBlock(tokens)
|
|
512
|
+
|
|
513
|
+
return { type: 'ForStatement', init, test, update, body }
|
|
432
514
|
}
|
|
433
515
|
|
|
434
|
-
function parseDoWhile(): DoWhileStatement {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
516
|
+
function parseDoWhile(tokens: Token[]): DoWhileStatement {
|
|
517
|
+
consume(tokens, 'do')
|
|
518
|
+
const body = parseBlock(tokens)
|
|
519
|
+
consume(tokens, 'while')
|
|
520
|
+
consume(tokens, '(')
|
|
521
|
+
const test = parseExpression(tokens)
|
|
522
|
+
consume(tokens, ')')
|
|
523
|
+
consume(tokens, ';')
|
|
439
524
|
|
|
440
|
-
return
|
|
525
|
+
return { type: 'DoWhileStatement', test, body }
|
|
441
526
|
}
|
|
442
527
|
|
|
443
|
-
function parseSwitch(): SwitchStatement {
|
|
444
|
-
|
|
445
|
-
const
|
|
528
|
+
function parseSwitch(tokens: Token[]): SwitchStatement {
|
|
529
|
+
consume(tokens, 'switch')
|
|
530
|
+
const discriminant = parseExpression(tokens)
|
|
446
531
|
|
|
447
532
|
const cases: SwitchCase[] = []
|
|
448
|
-
while (
|
|
449
|
-
const token = tokens
|
|
533
|
+
while (tokens.length) {
|
|
534
|
+
const token = consume(tokens)
|
|
535
|
+
if (token.value === '}') break
|
|
450
536
|
|
|
451
537
|
if (token.value === 'case') {
|
|
452
|
-
const test = parseExpression(
|
|
453
|
-
|
|
454
|
-
|
|
538
|
+
const test = parseExpression(tokens)
|
|
539
|
+
consume(tokens, ':')
|
|
540
|
+
const consequent = parseStatements(tokens)
|
|
541
|
+
cases.push({ type: 'SwitchCase', test, consequent })
|
|
455
542
|
} else if (token.value === 'default') {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
543
|
+
const test = null
|
|
544
|
+
consume(tokens, ':')
|
|
545
|
+
const consequent = parseStatements(tokens)
|
|
546
|
+
cases.push({ type: 'SwitchCase', test, consequent })
|
|
459
547
|
}
|
|
460
548
|
}
|
|
461
549
|
|
|
462
|
-
return
|
|
550
|
+
return { type: 'SwitchStatement', discriminant, cases }
|
|
463
551
|
}
|
|
464
552
|
|
|
465
|
-
function parsePrecision(): PrecisionStatement {
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
553
|
+
function parsePrecision(tokens: Token[]): PrecisionStatement {
|
|
554
|
+
consume(tokens, 'precision')
|
|
555
|
+
const precision = consume(tokens).value as PrecisionQualifier
|
|
556
|
+
const typeSpecifier: Identifier = { type: 'Identifier', name: consume(tokens).value }
|
|
557
|
+
consume(tokens, ';')
|
|
558
|
+
return { type: 'PrecisionStatement', precision, typeSpecifier }
|
|
470
559
|
}
|
|
471
560
|
|
|
472
|
-
function parsePreprocessor(): PreprocessorStatement {
|
|
473
|
-
|
|
561
|
+
function parsePreprocessor(tokens: Token[]): PreprocessorStatement {
|
|
562
|
+
consume(tokens, '#')
|
|
474
563
|
|
|
475
|
-
|
|
476
|
-
let value:
|
|
564
|
+
let name = '' // name can be unset for the # directive which is ignored
|
|
565
|
+
let value: Expression[] | null = null
|
|
477
566
|
|
|
478
|
-
if (
|
|
479
|
-
|
|
567
|
+
if (tokens[0]?.value !== '\\') {
|
|
568
|
+
name = consume(tokens).value
|
|
480
569
|
|
|
481
570
|
if (name === 'define') {
|
|
482
|
-
const left = parseExpression(
|
|
483
|
-
|
|
484
|
-
value
|
|
571
|
+
const left = parseExpression(tokens)
|
|
572
|
+
if (tokens[0]?.value === '\\') value = [left]
|
|
573
|
+
else value = [left, parseExpression(tokens)]
|
|
485
574
|
} else if (name === 'extension') {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
575
|
+
// TODO: extension directives must be before declarations
|
|
576
|
+
const left = parseExpression(tokens)
|
|
577
|
+
consume(tokens, ':')
|
|
578
|
+
const right = parseExpression(tokens)
|
|
579
|
+
value = [left, right]
|
|
490
580
|
} else if (name === 'include') {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
581
|
+
consume(tokens, '<')
|
|
582
|
+
value = [{ type: 'Identifier', name: consume(tokens).value }]
|
|
583
|
+
consume(tokens, '>')
|
|
584
|
+
} else if (name !== 'else' && name !== 'endif') {
|
|
585
|
+
value = []
|
|
586
|
+
while (tokens.length && tokens[0].value !== '\\') {
|
|
587
|
+
value.push(parseExpression(tokens))
|
|
588
|
+
}
|
|
494
589
|
}
|
|
495
590
|
}
|
|
496
591
|
|
|
497
|
-
|
|
592
|
+
consume(tokens, '\\')
|
|
593
|
+
|
|
594
|
+
return { type: 'PreprocessorStatement', name, value }
|
|
498
595
|
}
|
|
499
596
|
|
|
500
|
-
function parseStatements():
|
|
501
|
-
const body:
|
|
597
|
+
function parseStatements(tokens: Token[]): Statement[] {
|
|
598
|
+
const body: Statement[] = []
|
|
502
599
|
let scopeIndex = 0
|
|
503
600
|
|
|
504
|
-
while (
|
|
505
|
-
const token = tokens[
|
|
601
|
+
while (tokens.length) {
|
|
602
|
+
const token = tokens[0]
|
|
506
603
|
|
|
507
604
|
scopeIndex += getScopeDelta(token)
|
|
508
605
|
if (scopeIndex < 0) break
|
|
509
606
|
|
|
510
|
-
let statement:
|
|
511
|
-
|
|
512
|
-
if (token.value === '
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
607
|
+
let statement: Statement | null = null
|
|
608
|
+
|
|
609
|
+
if (token.value === 'case' || token.value === 'default') break
|
|
610
|
+
else if (token.value === '#') statement = parsePreprocessor(tokens)
|
|
611
|
+
else if (token.value === 'struct') statement = parseStruct(tokens)
|
|
612
|
+
else if (token.value === 'continue') statement = parseContinue(tokens)
|
|
613
|
+
else if (token.value === 'break') statement = parseBreak(tokens)
|
|
614
|
+
else if (token.value === 'discard') statement = parseDiscard(tokens)
|
|
615
|
+
else if (token.value === 'return') statement = parseReturn(tokens)
|
|
616
|
+
else if (token.value === 'if') statement = parseIf(tokens)
|
|
617
|
+
else if (token.value === 'while') statement = parseWhile(tokens)
|
|
618
|
+
else if (token.value === 'for') statement = parseFor(tokens)
|
|
619
|
+
else if (token.value === 'do') statement = parseDoWhile(tokens)
|
|
620
|
+
else if (token.value === 'switch') statement = parseSwitch(tokens)
|
|
621
|
+
else if (token.value === 'precision') statement = parsePrecision(tokens)
|
|
622
|
+
else if (VARIABLE_REGEX.test(token.value) && tokens[1].value !== '[') statement = parseIndeterminate(tokens)
|
|
623
|
+
else {
|
|
624
|
+
const expression = parseExpression(tokens)
|
|
625
|
+
consume(tokens, ';')
|
|
626
|
+
statement = { type: 'ExpressionStatement', expression }
|
|
530
627
|
}
|
|
531
628
|
|
|
532
|
-
|
|
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
|
-
}
|
|
629
|
+
body.push(statement)
|
|
540
630
|
}
|
|
541
631
|
|
|
542
632
|
return body
|
|
543
633
|
}
|
|
544
634
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
635
|
+
// TODO: allow block versus sub-statements for GLSL/WGSL
|
|
636
|
+
function parseBlock(tokens: Token[]): BlockStatement {
|
|
637
|
+
consume(tokens, '{')
|
|
638
|
+
const body = parseStatements(tokens)
|
|
639
|
+
consume(tokens, '}')
|
|
640
|
+
return { type: 'BlockStatement', body }
|
|
549
641
|
}
|
|
550
642
|
|
|
643
|
+
const NEWLINE_REGEX = /\\\n/gm
|
|
551
644
|
const DIRECTIVE_REGEX = /(^\s*#[^\\]*?)(\n|\/[\/\*])/gm
|
|
552
645
|
|
|
553
646
|
/**
|
|
554
647
|
* Parses a string of GLSL (WGSL WIP) code into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
|
|
555
648
|
*/
|
|
556
|
-
export function parse(code: string):
|
|
557
|
-
//
|
|
558
|
-
code = code.replace(
|
|
649
|
+
export function parse(code: string): Program {
|
|
650
|
+
// Fold newlines
|
|
651
|
+
code = code.replace(NEWLINE_REGEX, '')
|
|
559
652
|
|
|
560
653
|
// Escape newlines after directives, skip comments
|
|
561
654
|
code = code.replace(DIRECTIVE_REGEX, '$1\\$2')
|
|
562
655
|
|
|
563
656
|
// TODO: preserve
|
|
564
|
-
tokens = tokenize(code).filter((token) => token.type !== 'whitespace' && token.type !== 'comment')
|
|
565
|
-
i = 0
|
|
657
|
+
const tokens = tokenize(code).filter((token) => token.type !== 'whitespace' && token.type !== 'comment')
|
|
566
658
|
|
|
567
|
-
return parseStatements()
|
|
659
|
+
return { type: 'Program', body: parseStatements(tokens) }
|
|
568
660
|
}
|