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\\"" }' ]));