Skip to content

Commit ae74d55

Browse files
committed
glayzzle#66 fix precendence on other nodes
1 parent a4673e6 commit ae74d55

File tree

6 files changed

+182
-30
lines changed

6 files changed

+182
-30
lines changed

src/ast.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,21 @@ AST.prototype.prepare = function(kind, parser) {
172172
}
173173
var result = Object.create(node.prototype);
174174
node.apply(result, args);
175+
if (
176+
result.kind === 'bin' &&
177+
result.right &&
178+
typeof result.right.precedence === 'function'
179+
) {
180+
var out = result.right.precedence(result);
181+
if (out) { // shift with precedence
182+
result = out;
183+
}
184+
} else if (result.kind === 'unary') {
185+
var out = result.precedence(result.what);
186+
if (out) { // shift with precedence
187+
result = out;
188+
}
189+
}
175190
return result;
176191
};
177192
};

src/ast/bin.js

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ var binOperatorsPrecedence = [
1313
['or'],
1414
['xor'],
1515
['and'],
16-
// TODO: assignment
17-
// TODO: ternary ? :
16+
// TODO: assignment / not sure that PHP allows this with expressions
17+
['retif'],
1818
['??'],
1919
['||'],
2020
['&&'],
@@ -26,21 +26,13 @@ var binOperatorsPrecedence = [
2626
['<<', '>>'],
2727
['+', '-', '.'],
2828
['*', '/', '%'],
29-
// TODO: unary !
29+
['!'],
3030
['instanceof'],
31-
// TODO: unary ++, --, ~, @, typecasts
31+
// TODO: typecasts
3232
// TODO: [ (array)
3333
// TODO: clone, new
3434
];
3535

36-
// define nodes shifting
37-
var precedence = {};
38-
binOperatorsPrecedence.forEach(function (list, index) {
39-
list.forEach(function (operator) {
40-
precedence[operator] = index + 1;
41-
});
42-
});
43-
4436
/*
4537
x OP1 (y OP2 z)
4638
z OP1 (x OP2 y)
@@ -56,26 +48,28 @@ z OP2 (x OP1 y)
5648
*/
5749
var Bin = Operation.extends(function Bin(type, left, right, location) {
5850
Operation.apply(this, [KIND, location]);
59-
if (right && right.kind === 'bin') {
60-
var lLevel = precedence[type];
61-
var rLevel = precedence[right.type];
62-
if (lLevel && rLevel && rLevel < lLevel) {
63-
// shift precedence
64-
var buffer = right.right;
65-
right.right = right.left;
66-
right.left = left;
67-
left = buffer;
68-
buffer = right.type;
69-
right.type = type;
70-
type = buffer;
71-
buffer = left;
72-
left = right;
73-
right = buffer;
74-
}
75-
}
7651
this.type = type;
7752
this.left = left;
7853
this.right = right;
7954
});
8055

56+
Bin.prototype.precedence = function(node) {
57+
var lLevel = Bin.precedence[node.type];
58+
var rLevel = Bin.precedence[this.type];
59+
if (lLevel && rLevel && rLevel < lLevel) {
60+
// shift precedence
61+
node.right = this.left;
62+
this.left = node;
63+
return this;
64+
}
65+
};
66+
67+
// define nodes shifting
68+
Bin.precedence = {};
69+
binOperatorsPrecedence.forEach(function (list, index) {
70+
list.forEach(function (operator) {
71+
Bin.precedence[operator] = index + 1;
72+
});
73+
});
74+
8175
module.exports = Bin;

src/ast/retif.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
var Statement = require('./statement');
99
var KIND = 'retif';
10+
var Bin = require('./bin');
11+
var PRECEDENCE = Bin.precedence[KIND];
1012

1113
/**
1214
* Defines a short if statement that returns a value
@@ -23,4 +25,21 @@ var RetIf = Statement.extends(function RetIf(test, trueExpr, falseExpr, location
2325
this.falseExpr = falseExpr;
2426
});
2527

28+
/**
29+
* Handles precedence over items
30+
*/
31+
RetIf.prototype.precedence = function(node) {
32+
var what = node.kind === 'bin' ? node.type : node.kind;
33+
var lLevel = Bin.precedence[what];
34+
if (lLevel && PRECEDENCE < lLevel) {
35+
if (node.kind === 'bin') {
36+
node.right = this.test;
37+
this.test = node;
38+
return this;
39+
} else {
40+
throw new Error('@todo ' + node.kind);
41+
}
42+
}
43+
};
44+
2645
module.exports = RetIf;

src/ast/unary.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,16 @@ var Unary = Operation.extends(function Unary(type, what, location) {
2121
this.what = what;
2222
});
2323

24+
Unary.prototype.precedence = function(node) {
25+
if (node.kind === 'bin') {
26+
this.what = node.left;
27+
node.left = this;
28+
return node;
29+
} else if (node.kind === 'retif') {
30+
this.what = node.test;
31+
node.test = this;
32+
return node;
33+
}
34+
};
35+
2436
module.exports = Unary;

test/exprTests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ describe('Test expressions', function() {
8787
'$a > 5 ? true : false;',
8888
'$a ?: false;'
8989
].join('\n'));
90-
console.log(ast);
90+
console.log(ast.children[1]);
91+
ast.children[1].kind.should.be.exactly('retif');
92+
9193
});
9294

9395
it('test silent', function() {

test/precedence.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
var should = require("should");
2+
var parser = require('../src/index');
3+
4+
/**
5+
* Check precedence by using parenthesis on node B
6+
*/
7+
var checkPrecedence = function(a, b) {
8+
if (!a || !b) return false;
9+
if (b.kind === 'parenthesis') {
10+
b = b.inner;
11+
}
12+
for(var k in b) {
13+
if (b.hasOwnProperty(k)) {
14+
if (!a.hasOwnProperty(k)) return false;
15+
if (typeof b[k] === 'object') {
16+
checkPrecedence(a[k], b[k]);
17+
} else {
18+
a[k].should.be.equal(b[k]);
19+
}
20+
}
21+
}
22+
return true;
23+
};
24+
25+
var shouldBeSame = function(a, b) {
26+
var ast = parser.parseEval([
27+
a + ';',
28+
b + ';'
29+
].join('\n'));
30+
checkPrecedence(ast.children[0], ast.children[1]).should.be.true();
31+
};
32+
33+
// START TESTS HERE
34+
describe('Test precedence', function() {
35+
it('test *', function() {
36+
shouldBeSame('5 * 3 - 2', '(5 * 3) - 2');
37+
shouldBeSame('2 - 5 * 3', '2 - (5 * 3)');
38+
});
39+
it('test /', function() {
40+
shouldBeSame('5 / 3 + 2', '(5 / 3) + 2');
41+
shouldBeSame('5 + 3 / 2', '5 + (3 / 2)');
42+
});
43+
it('test %', function() {
44+
shouldBeSame('5 % 3 . 2', '(5 % 3) . 2');
45+
});
46+
it('test instanceof', function() {
47+
shouldBeSame('3 instanceof 2 * 5', '(3 instanceof 2) * 5');
48+
});
49+
it('test <<', function() {
50+
shouldBeSame('1 + 3 << 5', '(1 + 3) << 5');
51+
});
52+
it('test ==', function() {
53+
shouldBeSame('1 + 5 == 3', '(1 + 5 ) == 3');
54+
});
55+
it('test &', function() {
56+
shouldBeSame('1 == 2 & 3', '(1 == 2) & 3');
57+
});
58+
it('test ^', function() {
59+
shouldBeSame('1 & 2 ^ 3', '(1 & 2) ^ 3');
60+
});
61+
it('test |', function() {
62+
shouldBeSame('1 ^ 2 | 3', '(1 ^ 2) | 3;');
63+
});
64+
it('test &&', function() {
65+
var ast = parser.parseEval([
66+
'1 | 2 && 3;',
67+
'(1 | 2) && 3;'
68+
].join('\n'));
69+
checkPrecedence(ast.children[0],ast.children[1]).should.be.true();
70+
});
71+
it('test ||', function() {
72+
var ast = parser.parseEval([
73+
'1 && 2 || 3;',
74+
'(1 && 2) || 3;'
75+
].join('\n'));
76+
checkPrecedence(ast.children[0], ast.children[1]).should.be.true();
77+
});
78+
it('test ??', function() {
79+
var ast = parser.parseEval([
80+
'1 || 2 ?? 3;',
81+
'(1 || 2) ?? 3;'
82+
].join('\n'));
83+
checkPrecedence(ast.children[0], ast.children[1]).should.be.true();
84+
});
85+
it('test ?:', function() {
86+
shouldBeSame('1 ?? 2 ? 3 : 5', '(1 ?? 2) ? 3 : 5');
87+
shouldBeSame('1 and 2 ? 3 : 5', '1 and (2 ? 3 : 5)');
88+
});
89+
it('test =', function() {
90+
shouldBeSame('5 + $a = 1', '5 + ($a = 1)');
91+
shouldBeSame('5 XOR $a += 1', '5 XOR ($a += 1)');
92+
});
93+
it('test OR', function() {
94+
shouldBeSame('5 XOR 4 OR 3', '(5 XOR 4) OR 3');
95+
shouldBeSame('5 OR 4 XOR 3', '5 OR (4 XOR 3)');
96+
});
97+
it('test XOR', function() {
98+
shouldBeSame('5 AND 4 XOR 3', '(5 AND 4) XOR 3');
99+
shouldBeSame('5 XOR 4 AND 3', '5 XOR (4 AND 3)');
100+
});
101+
it('test AND', function() {
102+
shouldBeSame('5 + 4 AND 3', '(5 + 4) AND 3');
103+
shouldBeSame('5 AND 4 + 3', '5 AND (4 + 3)');
104+
});
105+
it('test !', function() {
106+
shouldBeSame('!4 instanceof 3', '(!4) instanceof 3');
107+
shouldBeSame('!4 + 5 instanceof 3', '(!4) + (5 instanceof 3)');
108+
shouldBeSame('6 + !4 + 5', '6 + (!4) + 5');
109+
});
110+
});

0 commit comments

Comments
 (0)