pegjs/spec/unit/compiler/passes/generate-bytecode.spec.js
David Majda 5a4d04fa90 Construct expectations using functions
Until now, expectations were constructed using object literals. This
commit changes the construction to use factory functions.

This change makes generated parsers slightly smaller because property
names don't have to be repeated many times and factory function calls
are more amenable to minifying.

Some numbers based on the aggregate size of parsers generated from
examples/*.pegjs:

  Optimization   Minified?   Size before   Size after   Saving
  ------------------------------------------------------------
  speed          no               719066       716063    0.42%
  speed          yes              188998       180202    4.65%
  size           no               194810       197813    1.52%
  size           yes              108782        99947    8.12%

(Minification was done using "uglify --mangle --compress" with
uglify-js 2.4.24.)
2016-06-18 06:57:09 +02:00

671 lines
23 KiB
JavaScript

/* global peg */
"use strict";
describe("compiler pass |generateBytecode|", function() {
var pass = peg.compiler.passes.generate.generateBytecode;
function bytecodeDetails(bytecode) {
return {
rules: [{ bytecode: bytecode }]
};
}
function constsDetails(consts) { return { consts: consts }; }
describe("for grammar", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST([
'a = "a"',
'b = "b"',
'c = "c"'
].join("\n"), {
rules: [
{ bytecode: [18, 0, 2, 2, 22, 0, 23, 1] },
{ bytecode: [18, 2, 2, 2, 22, 2, 23, 3] },
{ bytecode: [18, 4, 2, 2, 22, 4, 23, 5] }
]
});
});
it("defines correct constants", function() {
expect(pass).toChangeAST([
'a = "a"',
'b = "b"',
'c = "c"'
].join("\n"), constsDetails([
'"a"',
'peg$literalExpectation("a", false)',
'"b"',
'peg$literalExpectation("b", false)',
'"c"',
'peg$literalExpectation("c", false)'
]));
});
});
describe("for rule", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST('start = "a"', bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1 // <expression>
]));
});
});
describe("for named", function() {
var grammar = 'start "start" = "a"';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
28, // SILENT_FAILS_ON
18, 1, 2, 2, 22, 1, 23, 2, // <expression>
29, // SILENT_FAILS_OFF
14, 2, 0, // IF_ERROR
23, 0 // * FAIL
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'peg$otherExpectation("start")',
'"a"',
'peg$literalExpectation("a", false)'
]));
});
});
describe("for choice", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST('start = "a" / "b" / "c"', bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1, // <alternatives[0]>
14, 21, 0, // IF_ERROR
6, // * POP
18, 2, 2, 2, 22, 2, 23, 3, // <alternatives[1]>
14, 9, 0, // IF_ERROR
6, // * POP
18, 4, 2, 2, 22, 4, 23, 5 // <alternatives[2]>
]));
});
});
describe("for action", function() {
describe("without labels", function() {
var grammar = 'start = "a" { code }';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
15, 6, 0, // IF_NOT_ERROR
24, 1, // * LOAD_SAVED_POS
26, 2, 1, 0, // CALL
9 // NIP
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)',
'function() { code }'
]));
});
});
describe("with one label", function() {
var grammar = 'start = a:"a" { code }';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
15, 7, 0, // IF_NOT_ERROR
24, 1, // * LOAD_SAVED_POS
26, 2, 1, 1, 0, // CALL
9 // NIP
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)',
'function(a) { code }'
]));
});
});
describe("with multiple labels", function() {
var grammar = 'start = a:"a" b:"b" c:"c" { code }';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
15, 40, 3, // IF_NOT_ERROR
18, 2, 2, 2, 22, 2, 23, 3, // * <elements[1]>
15, 25, 4, // IF_NOT_ERROR
18, 4, 2, 2, 22, 4, 23, 5, // * <elements[2]>
15, 10, 4, // IF_NOT_ERROR
24, 3, // * LOAD_SAVED_POS
26, 6, 3, 3, 2, 1, 0, // CALL
9, // NIP
8, 3, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
8, 2, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
6, // * POP
7, // POP_CURR_POS
3 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)',
'"b"',
'peg$literalExpectation("b", false)',
'"c"',
'peg$literalExpectation("c", false)',
'function(a, b, c) { code }'
]));
});
});
});
describe("for sequence", function() {
var grammar = 'start = "a" "b" "c"';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
15, 33, 3, // IF_NOT_ERROR
18, 2, 2, 2, 22, 2, 23, 3, // * <elements[1]>
15, 18, 4, // IF_NOT_ERROR
18, 4, 2, 2, 22, 4, 23, 5, // * <elements[2]>
15, 3, 4, // IF_NOT_ERROR
11, 3, // * WRAP
9, // NIP
8, 3, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
8, 2, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
6, // * POP
7, // POP_CURR_POS
3 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)',
'"b"',
'peg$literalExpectation("b", false)',
'"c"',
'peg$literalExpectation("c", false)'
]));
});
});
describe("for labeled", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST('start = a:"a"', bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1 // <expression>
]));
});
});
describe("for text", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST('start = $"a"', bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
15, 2, 1, // IF_NOT_ERROR
6, // * POP
12, // TEXT
9 // * NIP
]));
});
});
describe("for simple_and", function() {
var grammar = 'start = &"a"';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
28, // SILENT_FAILS_ON
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
29, // SILENT_FAILS_OFF
15, 3, 3, // IF_NOT_ERROR
6, // * POP
7, // POP_CURR_POS
1, // PUSH_UNDEFINED
6, // * POP
6, // POP
3 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)'
]));
});
});
describe("for simple_not", function() {
var grammar = 'start = !"a"';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
28, // SILENT_FAILS_ON
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
29, // SILENT_FAILS_OFF
14, 3, 3, // IF_ERROR
6, // * POP
6, // POP
1, // PUSH_UNDEFINED
6, // * POP
7, // POP_CURR_POS
3 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)'
]));
});
});
describe("for optional", function() {
var grammar = 'start = "a"?';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
14, 2, 0, // IF_ERROR
6, // * POP
2 // PUSH_NULL
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)'
]));
});
});
describe("for zero_or_more", function() {
var grammar = 'start = "a"*';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
4, // PUSH_EMPTY_ARRAY
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
16, 9, // WHILE_NOT_ERROR
10, // * APPEND
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
6 // POP
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)'
]));
});
});
describe("for one_or_more", function() {
var grammar = 'start = "a"+';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
4, // PUSH_EMPTY_ARRAY
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
15, 12, 3, // IF_NOT_ERROR
16, 9, // * WHILE_NOT_ERROR
10, // * APPEND
18, 0, 2, 2, 22, 0, 23, 1, // <expression>
6, // POP
6, // * POP
6, // POP
3 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)'
]));
});
});
describe("for group", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST('start = ("a")', bytecodeDetails([
18, 0, 2, 2, 22, 0, 23, 1 // <expression>
]));
});
});
describe("for semantic_and", function() {
describe("without labels", function() {
var grammar = 'start = &{ code }';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
25, // UPDATE_SAVED_POS
26, 0, 0, 0, // CALL
13, 2, 2, // IF
6, // * POP
1, // PUSH_UNDEFINED
6, // * POP
3 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['function() { code }'])
);
});
});
describe("with labels", function() {
var grammar = 'start = a:"a" b:"b" c:"c" &{ code }';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
15, 55, 3, // IF_NOT_ERROR
18, 2, 2, 2, 22, 2, 23, 3, // * <elements[1]>
15, 40, 4, // IF_NOT_ERROR
18, 4, 2, 2, 22, 4, 23, 5, // * <elements[2]>
15, 25, 4, // IF_NOT_ERROR
25, // * UPDATE_SAVED_POS
26, 6, 0, 3, 2, 1, 0, // CALL
13, 2, 2, // IF
6, // * POP
1, // PUSH_UNDEFINED
6, // * POP
3, // PUSH_FAILED
15, 3, 4, // IF_NOT_ERROR
11, 4, // * WRAP
9, // NIP
8, 4, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
8, 3, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
8, 2, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
6, // * POP
7, // POP_CURR_POS
3 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)',
'"b"',
'peg$literalExpectation("b", false)',
'"c"',
'peg$literalExpectation("c", false)',
'function(a, b, c) { code }'
]));
});
});
});
describe("for semantic_not", function() {
describe("without labels", function() {
var grammar = 'start = !{ code }';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
25, // UPDATE_SAVED_POS
26, 0, 0, 0, // CALL
13, 2, 2, // IF
6, // * POP
3, // PUSH_FAILED
6, // * POP
1 // PUSH_UNDEFINED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['function() { code }'])
);
});
});
describe("with labels", function() {
var grammar = 'start = a:"a" b:"b" c:"c" !{ code }';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
5, // PUSH_CURR_POS
18, 0, 2, 2, 22, 0, 23, 1, // <elements[0]>
15, 55, 3, // IF_NOT_ERROR
18, 2, 2, 2, 22, 2, 23, 3, // * <elements[1]>
15, 40, 4, // IF_NOT_ERROR
18, 4, 2, 2, 22, 4, 23, 5, // * <elements[2]>
15, 25, 4, // IF_NOT_ERROR
25, // * UPDATE_SAVED_POS
26, 6, 0, 3, 2, 1, 0, // CALL
13, 2, 2, // IF
6, // * POP
3, // PUSH_FAILED
6, // * POP
1, // PUSH_UNDEFINED
15, 3, 4, // IF_NOT_ERROR
11, 4, // * WRAP
9, // NIP
8, 4, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
8, 3, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
8, 2, // * POP_N
7, // POP_CURR_POS
3, // PUSH_FAILED
6, // * POP
7, // POP_CURR_POS
3 // PUSH_FAILED
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)',
'"b"',
'peg$literalExpectation("b", false)',
'"c"',
'peg$literalExpectation("c", false)',
'function(a, b, c) { code }'
]));
});
});
});
describe("for rule_ref", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST([
'start = other',
'other = "other"'
].join("\n"), {
rules: [
{
bytecode: [27, 1] // RULE
},
{ }
]
});
});
});
describe("for literal", function() {
describe("empty", function() {
var grammar = 'start = ""';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
0, 0 // PUSH
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails(['""']));
});
});
describe("non-empty case-sensitive", function() {
var grammar = 'start = "a"';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
18, 0, 2, 2, // MATCH_STRING
22, 0, // * ACCEPT_STRING
23, 1 // * FAIL
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("a", false)'
]));
});
});
describe("non-empty case-insensitive", function() {
var grammar = 'start = "A"i';
it("generates correct bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
19, 0, 2, 2, // MATCH_STRING_IC
21, 1, // * ACCEPT_N
23, 1 // * FAIL
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(grammar, constsDetails([
'"a"',
'peg$literalExpectation("A", true)'
]));
});
});
});
describe("for class", function() {
it("generates correct bytecode", function() {
expect(pass).toChangeAST('start = [a]', bytecodeDetails([
20, 0, 2, 2, // MATCH_REGEXP
21, 1, // * ACCEPT_N
23, 1 // * FAIL
]));
});
describe("non-empty non-inverted case-sensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST('start = [a]', constsDetails([
'/^[a]/',
'peg$classExpectation(["a"], false, false)'
]));
});
});
describe("non-empty inverted case-sensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST('start = [^a]', constsDetails([
'/^[^a]/',
'peg$classExpectation(["a"], true, false)'
]));
});
});
describe("non-empty non-inverted case-insensitive", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST('start = [a]i', constsDetails([
'/^[a]/i',
'peg$classExpectation(["a"], false, true)'
]));
});
});
describe("non-empty complex", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST('start = [ab-def-hij-l]', constsDetails([
'/^[ab-def-hij-l]/',
'peg$classExpectation(["a", ["b", "d"], "e", ["f", "h"], "i", ["j", "l"]], false, false)'
]));
});
});
describe("empty non-inverted", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST('start = []', constsDetails([
'/^(?!)/',
'peg$classExpectation([], false, false)'
]));
});
});
describe("empty inverted", function() {
it("defines correct constants", function() {
expect(pass).toChangeAST('start = [^]', constsDetails([
'/^[\\S\\s]/',
'peg$classExpectation([], true, false)'
]));
});
});
});
describe("for any", function() {
var grammar = 'start = .';
it("generates bytecode", function() {
expect(pass).toChangeAST(grammar, bytecodeDetails([
17, 2, 2, // MATCH_ANY
21, 1, // * ACCEPT_N
23, 0 // * FAIL
]));
});
it("defines correct constants", function() {
expect(pass).toChangeAST(
grammar,
constsDetails(['peg$anyExpectation()'])
);
});
});
});