Implement additional PUSH_* bytecode instructions

Implement the following bytecode instructions:

  * PUSH_UNDEFINED
  * PUSH_NULL
  * PUSH_FAILED
  * PUSH_EMPTY_ARRAY

These instructions push simple JavaSccript values to the stack directly,
without going through constants. This makes the bytecode slightly
shorter and the bytecode generator somewhat simpler.

Also note that PUSH_EMPTY_ARRAY allows us to avoid a hack which protects
the [] constant from modification.
redux
David Majda 10 years ago
parent c6f0818d49
commit a815a8b902

@ -3,6 +3,10 @@ var opcodes = {
/* Stack Manipulation */
PUSH: 0, // PUSH c
PUSH_UNDEFINED: 26, // PUSH_UNDEFINED
PUSH_NULL: 27, // PUSH_NULL
PUSH_FAILED: 28, // PUSH_FAILED
PUSH_EMPTY_ARRAY: 29, // PUSH_EMPTY_ARRAY
PUSH_CURR_POS: 1, // PUSH_CURR_POS
POP: 2, // POP
POP_CURR_POS: 3, // POP_CURR_POS

@ -17,6 +17,22 @@ var arrays = require("../../utils/arrays"),
*
* stack.push(consts[c]);
*
* [26] PUSH_UNDEFINED
*
* stack.push(undefined);
*
* [27] PUSH_NULL
*
* stack.push(null);
*
* [28] PUSH_FAILED
*
* stack.push(FAILED);
*
* [29] PUSH_EMPTY_ARRAY
*
* stack.push([]);
*
* [1] PUSH_CURR_POS
*
* stack.push(currPos);
@ -211,9 +227,6 @@ function generateBytecode(ast) {
}
function buildSimplePredicate(expression, negative, context) {
var undefinedIndex = addConst('void 0'),
failedIndex = addConst('peg$FAILED');
return buildSequence(
[op.PUSH_CURR_POS],
[op.SILENT_FAILS_ON],
@ -228,21 +241,19 @@ function generateBytecode(ast) {
buildSequence(
[op.POP],
[negative ? op.POP : op.POP_CURR_POS],
[op.PUSH, undefinedIndex]
[op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
[negative ? op.POP_CURR_POS : op.POP],
[op.PUSH, failedIndex]
[op.PUSH_FAILED]
)
)
);
}
function buildSemanticPredicate(code, negative, context) {
var functionIndex = addFunctionConst(objects.keys(context.env), code),
undefinedIndex = addConst('void 0'),
failedIndex = addConst('peg$FAILED');
var functionIndex = addFunctionConst(objects.keys(context.env), code);
return buildSequence(
[op.REPORT_CURR_POS],
@ -251,11 +262,11 @@ function generateBytecode(ast) {
[op.IF],
buildSequence(
[op.POP],
[op.PUSH, negative ? failedIndex : undefinedIndex]
negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED]
),
buildSequence(
[op.POP],
[op.PUSH, negative ? undefinedIndex : failedIndex]
negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED]
)
)
);
@ -377,7 +388,7 @@ function generateBytecode(ast) {
buildSequence(
processedCount > 1 ? [op.POP_N, processedCount] : [op.POP],
[op.POP_CURR_POS],
[op.PUSH, failedIndex]
[op.PUSH_FAILED]
)
)
);
@ -404,8 +415,6 @@ function generateBytecode(ast) {
}
}
failedIndex = addConst('peg$FAILED');
return buildSequence(
[op.PUSH_CURR_POS],
buildElementsCode(node.elements, {
@ -456,8 +465,6 @@ function generateBytecode(ast) {
},
optional: function(node, context) {
var nullIndex = addConst('null');
return buildSequence(
generate(node.expression, {
sp: context.sp,
@ -466,22 +473,21 @@ function generateBytecode(ast) {
}),
buildCondition(
[op.IF_ERROR],
buildSequence([op.POP], [op.PUSH, nullIndex]),
buildSequence([op.POP], [op.PUSH_NULL]),
[]
)
);
},
zero_or_more: function(node, context) {
var emptyArrayIndex = addConst('[]');
expressionCode = generate(node.expression, {
var expressionCode = generate(node.expression, {
sp: context.sp + 1,
env: { },
action: null
});
return buildSequence(
[op.PUSH, emptyArrayIndex],
[op.PUSH_EMPTY_ARRAY],
expressionCode,
buildAppendLoop(expressionCode),
[op.POP]
@ -489,21 +495,19 @@ function generateBytecode(ast) {
},
one_or_more: function(node, context) {
var emptyArrayIndex = addConst('[]');
failedIndex = addConst('peg$FAILED');
expressionCode = generate(node.expression, {
var expressionCode = generate(node.expression, {
sp: context.sp + 1,
env: { },
action: null
});
return buildSequence(
[op.PUSH, emptyArrayIndex],
[op.PUSH_EMPTY_ARRAY],
expressionCode,
buildCondition(
[op.IF_NOT_ERROR],
buildSequence(buildAppendLoop(expressionCode), [op.POP]),
buildSequence([op.POP], [op.POP], [op.PUSH, failedIndex])
buildSequence([op.POP], [op.POP], [op.PUSH_FAILED])
)
);
},

@ -148,10 +148,6 @@ function generateJavascript(ast, options) {
}
parts.push([
' function protect(object) {',
' return Object.prototype.toString.apply(object) === "[object Array]" ? [] : object;',
' }',
'',
/*
* The point of the outer loop and the |ips| & |ends| stacks is to avoid
* recursive calls for interpreting parts of bytecode. In other words, we
@ -163,14 +159,30 @@ function generateJavascript(ast, options) {
' while (ip < end) {',
' switch (bc[ip]) {',
' case ' + op.PUSH + ':', // PUSH c
/*
* Hack: One of the constants can be an empty array. It needs to be cloned
* because it can be modified later on the stack by |APPEND|.
*/
' stack.push(protect(peg$consts[bc[ip + 1]]));',
' stack.push(peg$consts[bc[ip + 1]]);',
' ip += 2;',
' break;',
'',
' case ' + op.PUSH_UNDEFINED + ':', // PUSH_UNDEFINED
' stack.push(void 0);',
' ip++;',
' break;',
'',
' case ' + op.PUSH_NULL + ':', // PUSH_NULL
' stack.push(null);',
' ip++;',
' break;',
'',
' case ' + op.PUSH_FAILED + ':', // PUSH_FAILED
' stack.push(peg$FAILED);',
' ip++;',
' break;',
'',
' case ' + op.PUSH_EMPTY_ARRAY + ':', // PUSH_EMPTY_ARRAY
' stack.push([]);',
' ip++;',
' break;',
'',
' case ' + op.PUSH_CURR_POS + ':', // PUSH_CURR_POS
' stack.push(peg$currPos);',
' ip++;',
@ -445,14 +457,7 @@ function generateJavascript(ast, options) {
while (ip < end) {
switch (bc[ip]) {
case op.PUSH: // PUSH c
/*
* Hack: One of the constants can be an empty array. It needs to be
* handled specially because it can be modified later on the stack
* by |APPEND|.
*/
parts.push(
stack.push(ast.consts[bc[ip + 1]] === "[]" ? "[]" : c(bc[ip + 1]))
);
parts.push(stack.push(c(bc[ip + 1])));
ip += 2;
break;
@ -461,6 +466,26 @@ function generateJavascript(ast, options) {
ip++;
break;
case op.PUSH_UNDEFINED: // PUSH_UNDEFINED
parts.push(stack.push('void 0'));
ip++;
break;
case op.PUSH_NULL: // PUSH_NULL
parts.push(stack.push('null'));
ip++;
break;
case op.PUSH_FAILED: // PUSH_FAILED
parts.push(stack.push('peg$FAILED'));
ip++;
break;
case op.PUSH_EMPTY_ARRAY: // PUSH_EMPTY_ARRAY
parts.push(stack.push('[]'));
ip++;
break;
case op.POP: // POP
stack.pop();
ip++;

@ -135,30 +135,29 @@ describe("compiler pass |generateBytecode|", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
1, // PUSH_CURR_POS
14, 1, 2, 2, 18, 1, 19, 2, // <elements[0]>
11, 42, 4, // IF_NOT_ERROR
14, 3, 2, 2, 18, 3, 19, 4, // * <elements[1]>
11, 26, 5, // IF_NOT_ERROR
14, 5, 2, 2, 18, 5, 19, 6, // * <elements[2]>
11, 10, 5, // IF_NOT_ERROR
14, 0, 2, 2, 18, 0, 19, 1, // <elements[0]>
11, 40, 3, // IF_NOT_ERROR
14, 2, 2, 2, 18, 2, 19, 3, // * <elements[1]>
11, 25, 4, // IF_NOT_ERROR
14, 4, 2, 2, 18, 4, 19, 5, // * <elements[2]>
11, 10, 4, // IF_NOT_ERROR
20, 3, // * REPORT_SAVED_POS
22, 7, 3, 3, 2, 1, 0, // CALL
22, 6, 3, 3, 2, 1, 0, // CALL
5, // NIP
4, 3, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
4, 2, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
2, // * POP
3, // POP_CURR_POS
0, 0 // PUSH
28 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'peg$FAILED',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
@ -177,29 +176,28 @@ describe("compiler pass |generateBytecode|", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
1, // PUSH_CURR_POS
14, 1, 2, 2, 18, 1, 19, 2, // <elements[0]>
11, 35, 4, // IF_NOT_ERROR
14, 3, 2, 2, 18, 3, 19, 4, // * <elements[1]>
11, 19, 5, // IF_NOT_ERROR
14, 5, 2, 2, 18, 5, 19, 6, // * <elements[2]>
11, 3, 5, // IF_NOT_ERROR
14, 0, 2, 2, 18, 0, 19, 1, // <elements[0]>
11, 33, 3, // IF_NOT_ERROR
14, 2, 2, 2, 18, 2, 19, 3, // * <elements[1]>
11, 18, 4, // IF_NOT_ERROR
14, 4, 2, 2, 18, 4, 19, 5, // * <elements[2]>
11, 3, 4, // IF_NOT_ERROR
7, 3, // * WRAP
5, // NIP
4, 3, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
4, 2, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
2, // * POP
3, // POP_CURR_POS
0, 0 // PUSH
28 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'peg$FAILED',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
@ -237,22 +235,20 @@ describe("compiler pass |generateBytecode|", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
1, // PUSH_CURR_POS
24, // SILENT_FAILS_ON
14, 2, 2, 2, 18, 2, 19, 3, // <expression>
14, 0, 2, 2, 18, 0, 19, 1, // <expression>
25, // SILENT_FAILS_OFF
11, 4, 4, // IF_NOT_ERROR
11, 3, 3, // IF_NOT_ERROR
2, // * POP
3, // POP_CURR_POS
0, 0, // PUSH
26, // PUSH_UNDEFINED
2, // * POP
2, // POP
0, 1 // PUSH
28 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'void 0',
'peg$FAILED',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
@ -266,22 +262,20 @@ describe("compiler pass |generateBytecode|", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
1, // PUSH_CURR_POS
24, // SILENT_FAILS_ON
14, 2, 2, 2, 18, 2, 19, 3, // <expression>
14, 0, 2, 2, 18, 0, 19, 1, // <expression>
25, // SILENT_FAILS_OFF
10, 4, 4, // IF_ERROR
10, 3, 3, // IF_ERROR
2, // * POP
2, // POP
0, 0, // PUSH
26, // PUSH_UNDEFINED
2, // * POP
3, // POP_CURR_POS
0, 1 // PUSH
28 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'void 0',
'peg$FAILED',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
@ -296,18 +290,18 @@ describe("compiler pass |generateBytecode|", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
21, // REPORT_CURR_POS
22, 0, 0, 0, // CALL
9, 3, 3, // IF
9, 2, 2, // IF
2, // * POP
0, 1, // PUSH
26, // PUSH_UNDEFINED
2, // * POP
0, 2 // PUSH
28 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['function() { code }', 'void 0', 'peg$FAILED'])
constsDetails(['function() { code }'])
);
});
});
@ -318,48 +312,46 @@ describe("compiler pass |generateBytecode|", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
1, // PUSH_CURR_POS
14, 1, 2, 2, 18, 1, 19, 2, // <elements[0]>
11, 60, 4, // IF_NOT_ERROR
14, 3, 2, 2, 18, 3, 19, 4, // * <elements[1]>
11, 44, 5, // IF_NOT_ERROR
14, 5, 2, 2, 18, 5, 19, 6, // * <elements[2]>
11, 28, 5, // IF_NOT_ERROR
14, 0, 2, 2, 18, 0, 19, 1, // <elements[0]>
11, 55, 3, // IF_NOT_ERROR
14, 2, 2, 2, 18, 2, 19, 3, // * <elements[1]>
11, 40, 4, // IF_NOT_ERROR
14, 4, 2, 2, 18, 4, 19, 5, // * <elements[2]>
11, 25, 4, // IF_NOT_ERROR
21, // * REPORT_CURR_POS
22, 7, 0, 3, 2, 1, 0, // CALL
9, 3, 3, // IF
22, 6, 0, 3, 2, 1, 0, // CALL
9, 2, 2, // IF
2, // * POP
0, 8, // PUSH
26, // PUSH_UNDEFINED
2, // * POP
0, 0, // PUSH
11, 3, 5, // IF_NOT_ERROR
28, // PUSH_FAILED
11, 3, 4, // IF_NOT_ERROR
7, 4, // * WRAP
5, // NIP
4, 4, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
4, 3, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
4, 2, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
2, // * POP
3, // POP_CURR_POS
0, 0 // PUSH
28 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'peg$FAILED',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
'{ type: "literal", value: "b", description: "\\"b\\"" }',
'"c"',
'{ type: "literal", value: "c", description: "\\"c\\"" }',
'function(a, b, c) { code }',
'void 0'
'function(a, b, c) { code }'
]));
});
});
@ -373,18 +365,18 @@ describe("compiler pass |generateBytecode|", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
21, // REPORT_CURR_POS
22, 0, 0, 0, // CALL
9, 3, 3, // IF
9, 2, 2, // IF
2, // * POP
0, 2, // PUSH
28, // PUSH_FAILED
2, // * POP
0, 1 // PUSH
26 // PUSH_UNDEFINED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['function() { code }', 'void 0', 'peg$FAILED'])
constsDetails(['function() { code }'])
);
});
});
@ -395,48 +387,46 @@ describe("compiler pass |generateBytecode|", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
1, // PUSH_CURR_POS
14, 1, 2, 2, 18, 1, 19, 2, // <elements[0]>
11, 60, 4, // IF_NOT_ERROR
14, 3, 2, 2, 18, 3, 19, 4, // * <elements[1]>
11, 44, 5, // IF_NOT_ERROR
14, 5, 2, 2, 18, 5, 19, 6, // * <elements[2]>
11, 28, 5, // IF_NOT_ERROR
14, 0, 2, 2, 18, 0, 19, 1, // <elements[0]>
11, 55, 3, // IF_NOT_ERROR
14, 2, 2, 2, 18, 2, 19, 3, // * <elements[1]>
11, 40, 4, // IF_NOT_ERROR
14, 4, 2, 2, 18, 4, 19, 5, // * <elements[2]>
11, 25, 4, // IF_NOT_ERROR
21, // * REPORT_CURR_POS
22, 7, 0, 3, 2, 1, 0, // CALL
9, 3, 3, // IF
22, 6, 0, 3, 2, 1, 0, // CALL
9, 2, 2, // IF
2, // * POP
0, 0, // PUSH
28, // PUSH_FAILED
2, // * POP
0, 8, // PUSH
11, 3, 5, // IF_NOT_ERROR
26, // PUSH_UNDEFINED
11, 3, 4, // IF_NOT_ERROR
7, 4, // * WRAP
5, // NIP
4, 4, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
4, 3, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
4, 2, // * POP_N
3, // POP_CURR_POS
0, 0, // PUSH
28, // PUSH_FAILED
2, // * POP
3, // POP_CURR_POS
0, 0 // PUSH
28 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'peg$FAILED',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }',
'"b"',
'{ type: "literal", value: "b", description: "\\"b\\"" }',
'"c"',
'{ type: "literal", value: "c", description: "\\"c\\"" }',
'function(a, b, c) { code }',
'void 0'
'function(a, b, c) { code }'
]));
});
});
@ -447,16 +437,15 @@ describe("compiler pass |generateBytecode|", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
14, 1, 2, 2, 18, 1, 19, 2, // <expression>
10, 3, 0, // IF_ERROR
14, 0, 2, 2, 18, 0, 19, 1, // <expression>
10, 2, 0, // IF_ERROR
2, // * POP
0, 0 // PUSH
27 // PUSH_NULL
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'null',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
@ -468,18 +457,17 @@ describe("compiler pass |generateBytecode|", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
0, 0, // PUSH
14, 1, 2, 2, 18, 1, 19, 2, // <expression>
29, // PUSH_EMPTY_ARRAY
14, 0, 2, 2, 18, 0, 19, 1, // <expression>
12, 9, // WHILE_NOT_ERROR
6, // * APPEND
14, 1, 2, 2, 18, 1, 19, 2, // <expression>
14, 0, 2, 2, 18, 0, 19, 1, // <expression>
2 // POP
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'[]',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));
@ -491,23 +479,21 @@ describe("compiler pass |generateBytecode|", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
0, 0, // PUSH
14, 2, 2, 2, 18, 2, 19, 3, // <expression>
11, 12, 4, // IF_NOT_ERROR
29, // PUSH_EMPTY_ARRAY
14, 0, 2, 2, 18, 0, 19, 1, // <expression>
11, 12, 3, // IF_NOT_ERROR
12, 9, // * WHILE_NOT_ERROR
6, // * APPEND
14, 2, 2, 2, 18, 2, 19, 3, // <expression>
14, 0, 2, 2, 18, 0, 19, 1, // <expression>
2, // POP
2, // * POP
2, // POP
0, 1 // PUSH
28 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'[]',
'peg$FAILED',
'"a"',
'{ type: "literal", value: "a", description: "\\"a\\"" }'
]));

Loading…
Cancel
Save