Skip to content

Commit 511b394

Browse files
authored
fix: Call should have more precedence than assignment (#32)
1 parent 3c3476c commit 511b394

File tree

3 files changed

+153
-1
lines changed

3 files changed

+153
-1
lines changed

src/parser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ enum Precedence {
6161
MULTIPLY,
6262
UNARY_PREFIX,
6363
UNARY_POSTFIX,
64+
CALL,
6465
MEMBER,
6566
}
6667

@@ -76,7 +77,7 @@ const PREFIX_OPERATOR_PRECEDENCE: Record<string, Precedence> = {
7677
const POSTFIX_OPERATOR_PRECEDENCE: Record<string, Precedence> = {
7778
'--': Precedence.UNARY_POSTFIX,
7879
'++': Precedence.UNARY_POSTFIX,
79-
'(': Precedence.LOWEST,
80+
'(': Precedence.CALL,
8081
'[': Precedence.MEMBER,
8182
'.': Precedence.MEMBER,
8283
}

tests/parser.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,49 @@ describe('parser', () => {
466466
])
467467
})
468468

469+
it('parses variable assignments', () => {
470+
expect(parse('foo = vec4(0, 0, 0, 0);').body).toMatchInlineSnapshot(`
471+
[
472+
{
473+
"expression": {
474+
"left": {
475+
"name": "foo",
476+
"type": "Identifier",
477+
},
478+
"operator": "=",
479+
"right": {
480+
"arguments": [
481+
{
482+
"type": "Literal",
483+
"value": "0",
484+
},
485+
{
486+
"type": "Literal",
487+
"value": "0",
488+
},
489+
{
490+
"type": "Literal",
491+
"value": "0",
492+
},
493+
{
494+
"type": "Literal",
495+
"value": "0",
496+
},
497+
],
498+
"callee": {
499+
"name": "vec4",
500+
"type": "Identifier",
501+
},
502+
"type": "CallExpression",
503+
},
504+
"type": "AssignmentExpression",
505+
},
506+
"type": "ExpressionStatement",
507+
},
508+
]
509+
`)
510+
})
511+
469512
it('parses struct declarations', () => {
470513
expect(parse('struct foo { const bool bar = true; };').body).toStrictEqual<[StructDeclaration]>([
471514
{

tests/precedence.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { ArraySpecifier, Expression, ExpressionStatement, parse } from 'shaderkit'
3+
4+
function format(node: ArraySpecifier | Expression | null, forceParens = false): string {
5+
if (!node) return ''
6+
7+
switch (node.type) {
8+
case 'Identifier':
9+
return node.name
10+
case 'Literal':
11+
return node.value
12+
case 'ArraySpecifier':
13+
return `${node.typeSpecifier.name}${node.dimensions.map((d) => `[${format(d)}]`).join('')}`
14+
case 'ArrayExpression':
15+
return `${format(node.typeSpecifier)}(${node.elements.map((e) => format(e)).join(', ')})`
16+
case 'UnaryExpression':
17+
case 'UpdateExpression': {
18+
const inner = node.prefix
19+
? `${node.operator}${format(node.argument, true)}`
20+
: `${format(node.argument, true)}${node.operator}`
21+
return forceParens ? `(${inner})` : inner
22+
}
23+
case 'BinaryExpression':
24+
case 'LogicalExpression': {
25+
const inner = `${format(node.left, true)} ${node.operator} ${format(node.right, true)}`
26+
return forceParens ? `(${inner})` : inner
27+
}
28+
case 'AssignmentExpression': {
29+
const inner = `${format(node.left, true)} ${node.operator} ${format(node.right, true)}`
30+
return forceParens ? `(${inner})` : inner
31+
}
32+
case 'MemberExpression': {
33+
const obj = format(node.object, true)
34+
const prop = node.computed ? `[${format(node.property)}]` : `.${format(node.property)}`
35+
return `${obj}${prop}`
36+
}
37+
case 'CallExpression': {
38+
const callee = format(node.callee, true)
39+
return `${callee}(${node.arguments.map((a) => format(a)).join(', ')})`
40+
}
41+
case 'ConditionalExpression': {
42+
const inner = `${format(node.test, true)} ? ${format(node.alternate, true)} : ${format(node.consequent, true)}`
43+
return forceParens ? `(${inner})` : inner
44+
}
45+
default:
46+
return node satisfies never
47+
}
48+
}
49+
50+
const ungrouped = /* glsl */ `
51+
arith1 = a + b * c;
52+
arith2 = (a + b) * c;
53+
arith3 = a / b * c;
54+
arith4 = a / (b * c);
55+
arith5 = a - b - c;
56+
arith6 = a - (b - c);
57+
rel1 = a < b || b < c;
58+
rel2 = a < b && b < c;
59+
rel3 = a == b || c != d;
60+
log1 = x || y && z;
61+
log2 = (x || y) && z;
62+
log3 = !(x && y);
63+
tern1 = x ? a + b : c * d;
64+
tern2 = y ? a - b : c / d;
65+
bit1 = ia | ib & ic;
66+
bit2 = ia ^ ib & ic;
67+
bit3 = ia << ib >> ic;
68+
colArith = vec3((arith1 + arith2 + arith3 + arith4 + arith5 + arith6) / 100.0, 0.0, 0.0);
69+
colRel = vec3(float(rel1), float(rel2), float(rel3));
70+
colLog = vec3(float(log1), float(log2), float(log3));
71+
colTern = vec3((tern1 + tern2) / 20.0, 0.5, 0.0);
72+
colBits = vec3(float(bit1 & 1), float(bit2 & 1), float(bit3 & 1));
73+
`.trim()
74+
75+
const grouped = /* glsl */ `
76+
arith1 = (a + (b * c));
77+
arith2 = ((a + b) * c);
78+
arith3 = ((a / b) * c);
79+
arith4 = (a / (b * c));
80+
arith5 = ((a - b) - c);
81+
arith6 = (a - (b - c));
82+
rel1 = ((a < b) || (b < c));
83+
rel2 = ((a < b) && (b < c));
84+
rel3 = ((a == b) || (c != d));
85+
log1 = (x || (y && z));
86+
log2 = ((x || y) && z);
87+
log3 = (!(x && y));
88+
tern1 = (x ? (a + b) : (c * d));
89+
tern2 = (y ? (a - b) : (c / d));
90+
bit1 = (ia | (ib & ic));
91+
bit2 = (ia ^ (ib & ic));
92+
bit3 = ((ia << ib) >> ic);
93+
colArith = vec3((((((arith1 + arith2) + arith3) + arith4) + arith5) + arith6) / 100.0, 0.0, 0.0);
94+
colRel = vec3(float(rel1), float(rel2), float(rel3));
95+
colLog = vec3(float(log1), float(log2), float(log3));
96+
colTern = vec3((tern1 + tern2) / 20.0, 0.5, 0.0);
97+
colBits = vec3(float(bit1 & 1), float(bit2 & 1), float(bit3 & 1));
98+
`.trim()
99+
100+
describe('parser', () => {
101+
it('can handle precedence', () => {
102+
const expressions =
103+
parse(ungrouped)
104+
.body.map((n) => format((n as ExpressionStatement).expression))
105+
.join(';\n') + ';'
106+
expect(expressions).toBe(grouped)
107+
})
108+
})

0 commit comments

Comments
 (0)