From a815a8b902beb33544ad72d92f21869444b3057e Mon Sep 17 00:00:00 2001 From: David Majda Date: Sun, 11 May 2014 16:23:04 +0200 Subject: [PATCH] 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. --- lib/compiler/opcodes.js | 4 + lib/compiler/passes/generate-bytecode.js | 52 +++--- lib/compiler/passes/generate-javascript.js | 59 ++++-- .../compiler/passes/generate-bytecode.spec.js | 170 ++++++++---------- 4 files changed, 152 insertions(+), 133 deletions(-) diff --git a/lib/compiler/opcodes.js b/lib/compiler/opcodes.js index aa99a6c..63d6c58 100644 --- a/lib/compiler/opcodes.js +++ b/lib/compiler/opcodes.js @@ -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 diff --git a/lib/compiler/passes/generate-bytecode.js b/lib/compiler/passes/generate-bytecode.js index c23efb6..23c3139 100644 --- a/lib/compiler/passes/generate-bytecode.js +++ b/lib/compiler/passes/generate-bytecode.js @@ -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]) ) ); }, diff --git a/lib/compiler/passes/generate-javascript.js b/lib/compiler/passes/generate-javascript.js index 0250146..342b166 100644 --- a/lib/compiler/passes/generate-javascript.js +++ b/lib/compiler/passes/generate-javascript.js @@ -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++; diff --git a/spec/compiler/passes/generate-bytecode.spec.js b/spec/compiler/passes/generate-bytecode.spec.js index d1523a0..2c9d7d5 100644 --- a/spec/compiler/passes/generate-bytecode.spec.js +++ b/spec/compiler/passes/generate-bytecode.spec.js @@ -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, // - 11, 42, 4, // IF_NOT_ERROR - 14, 3, 2, 2, 18, 3, 19, 4, // * - 11, 26, 5, // IF_NOT_ERROR - 14, 5, 2, 2, 18, 5, 19, 6, // * - 11, 10, 5, // IF_NOT_ERROR + 14, 0, 2, 2, 18, 0, 19, 1, // + 11, 40, 3, // IF_NOT_ERROR + 14, 2, 2, 2, 18, 2, 19, 3, // * + 11, 25, 4, // IF_NOT_ERROR + 14, 4, 2, 2, 18, 4, 19, 5, // * + 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, // - 11, 35, 4, // IF_NOT_ERROR - 14, 3, 2, 2, 18, 3, 19, 4, // * - 11, 19, 5, // IF_NOT_ERROR - 14, 5, 2, 2, 18, 5, 19, 6, // * - 11, 3, 5, // IF_NOT_ERROR + 14, 0, 2, 2, 18, 0, 19, 1, // + 11, 33, 3, // IF_NOT_ERROR + 14, 2, 2, 2, 18, 2, 19, 3, // * + 11, 18, 4, // IF_NOT_ERROR + 14, 4, 2, 2, 18, 4, 19, 5, // * + 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, // + 14, 0, 2, 2, 18, 0, 19, 1, // 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, // + 14, 0, 2, 2, 18, 0, 19, 1, // 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, // - 11, 60, 4, // IF_NOT_ERROR - 14, 3, 2, 2, 18, 3, 19, 4, // * - 11, 44, 5, // IF_NOT_ERROR - 14, 5, 2, 2, 18, 5, 19, 6, // * - 11, 28, 5, // IF_NOT_ERROR + 14, 0, 2, 2, 18, 0, 19, 1, // + 11, 55, 3, // IF_NOT_ERROR + 14, 2, 2, 2, 18, 2, 19, 3, // * + 11, 40, 4, // IF_NOT_ERROR + 14, 4, 2, 2, 18, 4, 19, 5, // * + 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, // - 11, 60, 4, // IF_NOT_ERROR - 14, 3, 2, 2, 18, 3, 19, 4, // * - 11, 44, 5, // IF_NOT_ERROR - 14, 5, 2, 2, 18, 5, 19, 6, // * - 11, 28, 5, // IF_NOT_ERROR + 14, 0, 2, 2, 18, 0, 19, 1, // + 11, 55, 3, // IF_NOT_ERROR + 14, 2, 2, 2, 18, 2, 19, 3, // * + 11, 40, 4, // IF_NOT_ERROR + 14, 4, 2, 2, 18, 4, 19, 5, // * + 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, // - 10, 3, 0, // IF_ERROR + 14, 0, 2, 2, 18, 0, 19, 1, // + 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, // + 29, // PUSH_EMPTY_ARRAY + 14, 0, 2, 2, 18, 0, 19, 1, // 12, 9, // WHILE_NOT_ERROR 6, // * APPEND - 14, 1, 2, 2, 18, 1, 19, 2, // + 14, 0, 2, 2, 18, 0, 19, 1, // 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, // - 11, 12, 4, // IF_NOT_ERROR + 29, // PUSH_EMPTY_ARRAY + 14, 0, 2, 2, 18, 0, 19, 1, // + 11, 12, 3, // IF_NOT_ERROR 12, 9, // * WHILE_NOT_ERROR 6, // * APPEND - 14, 2, 2, 2, 18, 2, 19, 3, // + 14, 0, 2, 2, 18, 0, 19, 1, // 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\\"" }' ]));