|
|
|
describe("generated parser", function() {
|
|
|
|
function vary(names, block) {
|
Code generator rewrite
This is a complete rewrite of the PEG.js code generator. Its goals are:
1. Allow optimizing the generated parser code for code size as well as
for parsing speed.
2. Prepare ground for future optimizations and big features (like
incremental parsing).
2. Replace the old template-based code-generation system with
something more lightweight and flexible.
4. General code cleanup (structure, style, variable names, ...).
New Architecture
----------------
The new code generator consists of two steps:
* Bytecode generator -- produces bytecode for an abstract virtual
machine
* JavaScript generator -- produces JavaScript code based on the
bytecode
The abstract virtual machine is stack-based. Originally I wanted to make
it register-based, but it turned out that all the code related to it
would be more complex and the bytecode itself would be longer (because
of explicit register specifications in instructions). The only downsides
of the stack-based approach seem to be few small inefficiencies (see
e.g. the |NIP| instruction), which seem to be insignificant.
The new generator allows optimizing for parsing speed or code size (you
can choose using the |optimize| option of the |PEG.buildParser| method
or the --optimize/-o option on the command-line).
When optimizing for size, the JavaScript generator emits the bytecode
together with its constant table and a generic bytecode interpreter.
Because the interpreter is small and the bytecode and constant table
grow only slowly with size of the grammar, the resulting parser is also
small.
When optimizing for speed, the JavaScript generator just compiles the
bytecode into JavaScript. The generated code is relatively efficient, so
the resulting parser is fast.
Internal Identifiers
--------------------
As a small bonus, all internal identifiers visible to user code in the
initializer, actions and predicates are prefixed by |peg$|. This lowers
the chance that identifiers in user code will conflict with the ones
from PEG.js. It also makes using any internals in user code ugly, which
is a good thing. This solves GH-92.
Performance
-----------
The new code generator improved parsing speed and parser code size
significantly. The generated parsers are now:
* 39% faster when optimizing for speed
* 69% smaller when optimizing for size (without minification)
* 31% smaller when optimizing for size (with minification)
(Parsing speed was measured using the |benchmark/run| script. Code size
was measured by generating parsers for examples in the |examples|
directory and adding up the file sizes. Minification was done by |uglify
--ascii| in version 1.3.4.)
Final Note
----------
This is just a beginning! The new code generator lays a foundation upon
which many optimizations and improvements can (and will) be made.
Stay tuned :-)
12 years ago
|
|
|
var values = {
|
|
|
|
cache: [false, true],
|
|
|
|
optimize: ["speed", "size"]
|
|
|
|
};
|
|
|
|
|
|
|
|
function varyStep(names, options) {
|
|
|
|
var clonedOptions = {}, key, name, i;
|
|
|
|
|
|
|
|
if (names.length === 0) {
|
|
|
|
/*
|
|
|
|
* We have to clone the options so that the block can save them safely
|
|
|
|
* (e.g. by capturing in a closure) without the risk that they will be
|
|
|
|
* changed later.
|
|
|
|
*/
|
|
|
|
for (key in options) {
|
|
|
|
clonedOptions[key] = options[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
describe(
|
|
|
|
"with options " + jasmine.pp(clonedOptions),
|
|
|
|
function() { block(clonedOptions); }
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
name = names[0];
|
|
|
|
for (i = 0; i < values[name].length; i++) {
|
|
|
|
options[name] = values[name][i];
|
|
|
|
varyStep(names.slice(1), options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
varyStep(names, {});
|
|
|
|
}
|
|
|
|
|
|
|
|
function varyAll(block) {
|
Code generator rewrite
This is a complete rewrite of the PEG.js code generator. Its goals are:
1. Allow optimizing the generated parser code for code size as well as
for parsing speed.
2. Prepare ground for future optimizations and big features (like
incremental parsing).
2. Replace the old template-based code-generation system with
something more lightweight and flexible.
4. General code cleanup (structure, style, variable names, ...).
New Architecture
----------------
The new code generator consists of two steps:
* Bytecode generator -- produces bytecode for an abstract virtual
machine
* JavaScript generator -- produces JavaScript code based on the
bytecode
The abstract virtual machine is stack-based. Originally I wanted to make
it register-based, but it turned out that all the code related to it
would be more complex and the bytecode itself would be longer (because
of explicit register specifications in instructions). The only downsides
of the stack-based approach seem to be few small inefficiencies (see
e.g. the |NIP| instruction), which seem to be insignificant.
The new generator allows optimizing for parsing speed or code size (you
can choose using the |optimize| option of the |PEG.buildParser| method
or the --optimize/-o option on the command-line).
When optimizing for size, the JavaScript generator emits the bytecode
together with its constant table and a generic bytecode interpreter.
Because the interpreter is small and the bytecode and constant table
grow only slowly with size of the grammar, the resulting parser is also
small.
When optimizing for speed, the JavaScript generator just compiles the
bytecode into JavaScript. The generated code is relatively efficient, so
the resulting parser is fast.
Internal Identifiers
--------------------
As a small bonus, all internal identifiers visible to user code in the
initializer, actions and predicates are prefixed by |peg$|. This lowers
the chance that identifiers in user code will conflict with the ones
from PEG.js. It also makes using any internals in user code ugly, which
is a good thing. This solves GH-92.
Performance
-----------
The new code generator improved parsing speed and parser code size
significantly. The generated parsers are now:
* 39% faster when optimizing for speed
* 69% smaller when optimizing for size (without minification)
* 31% smaller when optimizing for size (with minification)
(Parsing speed was measured using the |benchmark/run| script. Code size
was measured by generating parsers for examples in the |examples|
directory and adding up the file sizes. Minification was done by |uglify
--ascii| in version 1.3.4.)
Final Note
----------
This is just a beginning! The new code generator lays a foundation upon
which many optimizations and improvements can (and will) be made.
Stay tuned :-)
12 years ago
|
|
|
vary(["cache", "optimize"], block);
|
|
|
|
}
|
|
|
|
|
|
|
|
beforeEach(function() {
|
|
|
|
this.addMatchers({
|
|
|
|
toParse: function(input) {
|
|
|
|
var options = arguments.length > 2 ? arguments[1] : {},
|
|
|
|
expected = arguments[arguments.length - 1],
|
|
|
|
result;
|
|
|
|
|
|
|
|
try {
|
|
|
|
result = this.actual.parse(input, options);
|
|
|
|
|
|
|
|
this.message = function() {
|
|
|
|
return "Expected " + jasmine.pp(input) + " "
|
|
|
|
+ "with options " + jasmine.pp(options) + " "
|
|
|
|
+ (this.isNot ? "not " : "")
|
|
|
|
+ "to parse as " + jasmine.pp(expected) + ", "
|
|
|
|
+ "but it parsed as " + jasmine.pp(result) + ".";
|
|
|
|
};
|
|
|
|
|
|
|
|
return this.env.equals_(result, expected);
|
|
|
|
} catch (e) {
|
|
|
|
this.message = function() {
|
|
|
|
return "Expected " + jasmine.pp(input) + " "
|
|
|
|
+ "with options " + jasmine.pp(options) + " "
|
|
|
|
+ "to parse as " + jasmine.pp(expected) + ", "
|
|
|
|
+ "but it failed to parse with message "
|
|
|
|
+ jasmine.pp(e.message) + ".";
|
|
|
|
};
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toFailToParse: function(input) {
|
|
|
|
var options = arguments.length > 2 ? arguments[1] : {},
|
|
|
|
details = arguments.length > 1
|
|
|
|
? arguments[arguments.length - 1]
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extracted into a function just to silence JSHint complaining about
|
|
|
|
* creating functions in a loop.
|
|
|
|
*/
|
|
|
|
function buildKeyMessage(key, value) {
|
|
|
|
return function() {
|
|
|
|
return "Expected " + jasmine.pp(input) + " "
|
|
|
|
+ "with options " + jasmine.pp(options) + " "
|
|
|
|
+ "to fail to parse"
|
|
|
|
+ (details ? " with details " + jasmine.pp(details) : "") + ", "
|
|
|
|
+ "but " + jasmine.pp(key) + " "
|
|
|
|
+ "is " + jasmine.pp(value) + ".";
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var result;
|
|
|
|
|
|
|
|
try {
|
|
|
|
result = this.actual.parse(input, options);
|
|
|
|
|
|
|
|
this.message = function() {
|
|
|
|
return "Expected " + jasmine.pp(input) + " "
|
|
|
|
+ "with options " + jasmine.pp(options) + " "
|
|
|
|
+ "to fail to parse"
|
|
|
|
+ (details ? " with details " + jasmine.pp(details) : "") + ", "
|
|
|
|
+ "but it parsed as " + jasmine.pp(result) + ".";
|
|
|
|
};
|
|
|
|
|
|
|
|
return false;
|
|
|
|
} catch (e) {
|
|
|
|
/*
|
|
|
|
* Should be at the top level but then JSHint complains about bad for
|
|
|
|
* in variable.
|
|
|
|
*/
|
|
|
|
var key;
|
|
|
|
|
|
|
|
if (this.isNot) {
|
|
|
|
this.message = function() {
|
|
|
|
return "Expected " + jasmine.pp(input)
|
|
|
|
+ "with options " + jasmine.pp(options) + " "
|
|
|
|
+ "to parse, "
|
|
|
|
+ "but it failed with message "
|
|
|
|
+ jasmine.pp(e.message) + ".";
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
if (details) {
|
|
|
|
for (key in details) {
|
|
|
|
if (!this.env.equals_(e[key], details[key])) {
|
|
|
|
this.message = buildKeyMessage(key, e[key]);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("parse", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'a = "x" { return "a"; }',
|
|
|
|
'b = "x" { return "b"; }',
|
|
|
|
'c = "x" { return "c"; }'
|
|
|
|
].join("\n"), { allowedStartRules: ["b", "c"] });
|
|
|
|
|
|
|
|
describe("start rule", function() {
|
|
|
|
describe("without the |startRule| option", function() {
|
|
|
|
it("uses the first allowed rule", function() {
|
|
|
|
expect(parser).toParse("x", "b");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("when the |startRule| option specifies allowed rule", function() {
|
|
|
|
it("uses the specified rule", function() {
|
|
|
|
expect(parser).toParse("x", { startRule: "b" }, "b");
|
|
|
|
expect(parser).toParse("x", { startRule: "c" }, "c");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("when the |startRule| option specifies disallowed rule", function() {
|
|
|
|
it("throws exception", function() {
|
|
|
|
expect(parser).toFailToParse("x", { startRule: "a" }, {
|
|
|
|
message: "Can't start parsing from rule \"a\"."
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
varyAll(function(options) {
|
|
|
|
describe("initializer code", function() {
|
|
|
|
it("runs before the parsing begins", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result = 42; }',
|
|
|
|
'start = "a" { return result; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", 42);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |text| function", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result = text(); }',
|
|
|
|
'start = "a" { return result; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |offset| function to get the current parse position", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result = offset(); }',
|
|
|
|
'start = "a" { return result; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |line| and |column| functions to get the current line and column", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result = [line(), column()]; }',
|
|
|
|
'start = "a" { return result; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", [1, 1]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use options passed to the parser", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result = options; }',
|
|
|
|
'start = "a" { return result; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", { a: 42 }, { a: 42 });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("rule matching", function() {
|
|
|
|
var grammar = [
|
|
|
|
'{ var n = 0; }',
|
|
|
|
'start = (a "b") / (a "c") { return n; }',
|
|
|
|
'a = "a" { n++; }'
|
|
|
|
].join("\n");
|
|
|
|
|
|
|
|
if (options.cache) {
|
|
|
|
it("caches rule match results", function() {
|
|
|
|
var parser = PEG.buildParser(grammar, options);
|
|
|
|
|
|
|
|
expect(parser).toParse("ac", 1);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
it("does not cache rule match results", function() {
|
|
|
|
var parser = PEG.buildParser(grammar, options);
|
|
|
|
|
|
|
|
expect(parser).toParse("ac", 2);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("named matching", function() {
|
|
|
|
var parser = PEG.buildParser('start "start" = "a"');
|
|
|
|
|
|
|
|
it("delegates to the expression", function() {
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toFailToParse("b");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("overwrites expected string on failure", function() {
|
|
|
|
expect(parser).toFailToParse("b", { expected: ["start"] });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("choice matching", function() {
|
|
|
|
it("matches correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" / "b" / "c"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toParse("b", "b");
|
|
|
|
expect(parser).toParse("c", "c");
|
|
|
|
expect(parser).toFailToParse("d");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("action code", function() {
|
|
|
|
it("tranforms the expression result by returnung a non-|null| value", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" { return 42; }', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", 42);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("causes match failure by returning |null|", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" { return null; }', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("a");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("is not called when the expression does not match", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" { throw "Boom!"; } / "b"',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("b", "b");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use label variables", function() {
|
|
|
|
var parser = PEG.buildParser('start = a:"a" { return a; }', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |text| function to get the text matched by the expression", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" "b" "c" { return text(); }',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("abc", "abc");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |offset| function to get the current parse position", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" ("b" { return offset(); })',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("ab", ["a", 1]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |line| and |column| functions to get the current line and column", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result; }',
|
|
|
|
'start = line (nl+ line)* { return result; }',
|
|
|
|
'line = thing (" "+ thing)*',
|
|
|
|
'thing = digit / mark',
|
|
|
|
'digit = [0-9]',
|
|
|
|
'mark = "x" { result = [line(), column()]; }',
|
|
|
|
'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", [7, 5]);
|
|
|
|
|
|
|
|
/* Non-Unix newlines */
|
|
|
|
expect(parser).toParse("1\rx", [2, 1]); // Old Mac
|
|
|
|
expect(parser).toParse("1\r\nx", [2, 1]); // Windows
|
|
|
|
expect(parser).toParse("1\n\rx", [3, 1]); // mismatched
|
|
|
|
|
|
|
|
/* Strange newlines */
|
|
|
|
expect(parser).toParse("1\u2028x", [2, 1]); // line separator
|
|
|
|
expect(parser).toParse("1\u2029x", [2, 1]); // paragraph separator
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use variables defined in the initializer", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var v = 42 }',
|
|
|
|
'start = "a" { return v; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", 42);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use functions defined in the initializer", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ function f() { return 42; } }',
|
|
|
|
'start = "a" { return f(); }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", 42);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use options passed to the parser", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" { return options; }',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", { a: 42 }, { a: 42 });
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not advance position when the expression matches but the action returns |null|", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" { return null; } / "a"',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("sequence matching", function() {
|
|
|
|
it("matches empty sequence correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = ', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("", []);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("matches non-empty sequence correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" "b" "c"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("abc", ["a", "b", "c"]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not advance position on failure", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" "b" / "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("labeled matching", function() {
|
|
|
|
it("delegates to the expression", function() {
|
|
|
|
var parser = PEG.buildParser('start = a:"a"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toFailToParse("b");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("text matching", function() {
|
|
|
|
it("matches correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = $("a" "b" "c")', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("abc", "abc");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("simple and matching", function() {
|
|
|
|
it("matches correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = &"a" "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["", "a"]);
|
|
|
|
expect(parser).toFailToParse("b");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not advance position on success", function() {
|
|
|
|
var parser = PEG.buildParser('start = &"a" "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["", "a"]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not influence expected strings on failure", function() {
|
|
|
|
var parser = PEG.buildParser('start = &"a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", { expected: [] });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("simple not matching", function() {
|
|
|
|
it("matches correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = !"a" "b"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("b", ["", "b"]);
|
|
|
|
expect(parser).toFailToParse("a");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not advance position on failure", function() {
|
|
|
|
var parser = PEG.buildParser('start = !"a" / "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not influence expected strings on failure", function() {
|
|
|
|
var parser = PEG.buildParser('start = !"a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("a", { expected: [] });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("semantic and code", function() {
|
|
|
|
it("causes successful match by returning |true|", function() {
|
|
|
|
var parser = PEG.buildParser('start = &{ return true; }', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("", "");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("causes match failure by returning |false|", function() {
|
|
|
|
var parser = PEG.buildParser('start = &{ return false; }', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use label variables", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = a:"a" &{ return a === "a"; }',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |text| function", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" &{ return text() === ""; }',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |offset| function to get the current parse position", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" &{ return offset() === 1; }',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |line| and |column| functions to get the current line and column", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result; }',
|
|
|
|
'start = line (nl+ line)* { return result; }',
|
|
|
|
'line = thing (" "+ thing)*',
|
|
|
|
'thing = digit / mark',
|
|
|
|
'digit = [0-9]',
|
|
|
|
'mark = &{ result = [line(), column()]; return true; } "x"',
|
|
|
|
'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", [7, 5]);
|
|
|
|
|
|
|
|
/* Non-Unix newlines */
|
|
|
|
expect(parser).toParse("1\rx", [2, 1]); // Old Mac
|
|
|
|
expect(parser).toParse("1\r\nx", [2, 1]); // Windows
|
|
|
|
expect(parser).toParse("1\n\rx", [3, 1]); // mismatched
|
|
|
|
|
|
|
|
/* Strange newlines */
|
|
|
|
expect(parser).toParse("1\u2028x", [2, 1]); // line separator
|
|
|
|
expect(parser).toParse("1\u2029x", [2, 1]); // paragraph separator
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use variables defined in the initializer", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var v = 42 }',
|
|
|
|
'start = "a" &{ return v === 42; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use functions defined in the initializer", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ function f() { return 42; } }',
|
|
|
|
'start = "a" &{ return f() === 42; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use options passed to the parser", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result; }',
|
|
|
|
'start = "a" &{ result = options; return true; } { return result; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", { a: 42 }, { a: 42 });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("semantic not code", function() {
|
|
|
|
it("causes successful match by returning |false|", function() {
|
|
|
|
var parser = PEG.buildParser('start = !{ return false; }', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("", "");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("causes match failure by returning |true|", function() {
|
|
|
|
var parser = PEG.buildParser('start = !{ return true; }', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use label variables", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = a:"a" !{ return a !== "a"; }',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |text| function", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" !{ return text() !== ""; }',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |offset| function to get the current parse position", function() {
|
|
|
|
var parser = PEG.buildParser(
|
|
|
|
'start = "a" !{ return offset() !== 1; }',
|
|
|
|
options
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use the |line| and |column| functions to get the current line and column", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result; }',
|
|
|
|
'start = line (nl+ line)* { return result; }',
|
|
|
|
'line = thing (" "+ thing)*',
|
|
|
|
'thing = digit / mark',
|
|
|
|
'digit = [0-9]',
|
|
|
|
'mark = !{ result = [line(), column()]; return false; } "x"',
|
|
|
|
'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", [7, 5]);
|
|
|
|
|
|
|
|
/* Non-Unix newlines */
|
|
|
|
expect(parser).toParse("1\rx", [2, 1]); // Old Mac
|
|
|
|
expect(parser).toParse("1\r\nx", [2, 1]); // Windows
|
|
|
|
expect(parser).toParse("1\n\rx", [3, 1]); // mismatched
|
|
|
|
|
|
|
|
/* Strange newlines */
|
|
|
|
expect(parser).toParse("1\u2028x", [2, 1]); // line separator
|
|
|
|
expect(parser).toParse("1\u2029x", [2, 1]); // paragraph separator
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use variables defined in the initializer", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var v = 42 }',
|
|
|
|
'start = "a" !{ return v !== 42; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use functions defined in the initializer", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ function f() { return 42; } }',
|
|
|
|
'start = "a" !{ return f() !== 42; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", ["a", ""]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can use options passed to the parser", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'{ var result; }',
|
|
|
|
'start = "a" !{ result = options; return false; } { return result; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", { a: 42 }, { a: 42 });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("optional matching", function() {
|
|
|
|
it("matches correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"?', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("", "");
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("zero or more matching", function() {
|
|
|
|
it("matches correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"*', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("", []);
|
|
|
|
expect(parser).toParse("a", ["a"]);
|
|
|
|
expect(parser).toParse("aaa", ["a", "a", "a"]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("one or more matching", function() {
|
|
|
|
it("matches correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"+', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("");
|
|
|
|
expect(parser).toParse("a", ["a"]);
|
|
|
|
expect(parser).toParse("aaa", ["a", "a", "a"]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("rule reference matching", function() {
|
|
|
|
it("follows rule references", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'start = static / dynamic',
|
|
|
|
'static = "C" / "C++" / "Java" / "C#"',
|
|
|
|
'dynamic = "Ruby" / "Python" / "JavaScript"'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("Java", "Java");
|
|
|
|
expect(parser).toParse("Python", "Python");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("literal matching", function() {
|
|
|
|
it("matches empty literal correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = ""', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("", "");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("matches one-character literal correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toFailToParse("b");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("matches multiple-character literal correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = "abcd"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("abcd", "abcd");
|
|
|
|
expect(parser).toFailToParse("ebcd");
|
|
|
|
expect(parser).toFailToParse("afcd");
|
|
|
|
expect(parser).toFailToParse("abgd");
|
|
|
|
expect(parser).toFailToParse("abch");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("is case sensitive without the \"i\" flag", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toFailToParse("A");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("is case insensitive with the \"i\" flag", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"i', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toParse("A", "A");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("advances position on success", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" .', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("ab", ["a", "b"]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("sets expected string correctly on failure", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", { expected: ['"a"'] });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("class matching", function() {
|
|
|
|
it("matches empty class correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = []', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("a");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("matches class with a character list correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = [abc]', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toParse("b", "b");
|
|
|
|
expect(parser).toParse("c", "c");
|
|
|
|
expect(parser).toFailToParse("d");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("matches class with a range correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = [a-c]', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toParse("b", "b");
|
|
|
|
expect(parser).toParse("c", "c");
|
|
|
|
expect(parser).toFailToParse("d");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("matches inverted class correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = [^a]', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("a");
|
|
|
|
expect(parser).toParse("b", "b");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("is case sensitive without the \"i\" flag", function() {
|
|
|
|
var parser = PEG.buildParser('start = [a]', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toFailToParse("A");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("is case insensitive with the \"i\" flag", function() {
|
|
|
|
var parser = PEG.buildParser('start = [a]i', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
expect(parser).toParse("A", "A");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("advances position on success", function() {
|
|
|
|
var parser = PEG.buildParser('start = [a] .', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("ab", ["a", "b"]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("sets expected string correctly on failure", function() {
|
|
|
|
var parser = PEG.buildParser('start = [a]', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", { expected: ["[a]"] });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("any matching", function() {
|
|
|
|
it("matches correctly", function() {
|
|
|
|
var parser = PEG.buildParser('start = .', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("advances position on success", function() {
|
|
|
|
var parser = PEG.buildParser('start = . .', options);
|
|
|
|
|
|
|
|
expect(parser).toParse("ab", ["a", "b"]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("sets expected string correctly on failure", function() {
|
|
|
|
var parser = PEG.buildParser('start = .', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("", { expected: ['any character'] });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("error reporting", function() {
|
|
|
|
describe("behavior", function() {
|
|
|
|
it("reports only the rightmost error", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" "b" / "a" "c" "d"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("ace", { offset: 2, expected: ['"d"'] });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("expected strings reporting", function() {
|
|
|
|
it("reports expected strings correctly with no alternative", function() {
|
|
|
|
var parser = PEG.buildParser('start = ', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("a", { expected: [] });
|
|
|
|
});
|
|
|
|
|
|
|
|
it("reports expected strings correctly with one alternative", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", { expected: ['"a"'] });
|
|
|
|
});
|
|
|
|
|
|
|
|
it("reports expected strings correctly with multiple alternatives", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" / "b" / "c"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("d", {
|
|
|
|
expected: ['"a"', '"b"', '"c"']
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("removes duplicates from expected strings", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" / "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", { expected: ['"a"'] });
|
|
|
|
});
|
|
|
|
|
|
|
|
it("sorts expected strings", function() {
|
|
|
|
var parser = PEG.buildParser('start = "c" / "b" / "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("d", {
|
|
|
|
expected: ['"a"', '"b"', '"c"']
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("found string reporting", function() {
|
|
|
|
it("reports found string correctly at the end of input", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("", { found: null });
|
|
|
|
});
|
|
|
|
|
|
|
|
it("reports found string correctly in the middle of input", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", { found: "b" });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("message building", function() {
|
|
|
|
it("builds message correctly with no alternative", function() {
|
|
|
|
var parser = PEG.buildParser('start = ', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("a", {
|
|
|
|
message: 'Expected end of input but "a" found.'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("builds message correctly with one alternative", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
|
|
message: 'Expected "a" but "b" found.'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("builds message correctly with multiple alternatives", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a" / "b" / "c"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("d", {
|
|
|
|
message: 'Expected "a", "b" or "c" but "d" found.'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("builds message correctly at the end of input", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("", {
|
|
|
|
message: 'Expected "a" but end of input found.'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("builds message correctly in the middle of input", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
|
|
message: 'Expected "a" but "b" found.'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("position reporting", function() {
|
|
|
|
it("reports position correctly with invalid input", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("b", { offset: 0, line: 1, column: 1 });
|
|
|
|
});
|
|
|
|
|
|
|
|
it("reports position correctly with trailing input", function() {
|
|
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("aa", { offset: 1, line: 1, column: 2});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("reports position correctly in complex cases", function() {
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'start = line (nl+ line)*',
|
|
|
|
'line = digit (" "+ digit)*',
|
|
|
|
'digit = [0-9]',
|
|
|
|
'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toFailToParse("1\n2\n\n3\n\n\n4 5 x", {
|
|
|
|
offset: 13,
|
|
|
|
line: 7,
|
|
|
|
column: 5
|
|
|
|
});
|
|
|
|
|
|
|
|
/* Non-Unix newlines */
|
|
|
|
expect(parser).toFailToParse("1\rx", { // Old Mac
|
|
|
|
offset: 2,
|
|
|
|
line: 2,
|
|
|
|
column: 1
|
|
|
|
});
|
|
|
|
expect(parser).toFailToParse("1\r\nx", { // Windows
|
|
|
|
offset: 3,
|
|
|
|
line: 2,
|
|
|
|
column: 1
|
|
|
|
});
|
|
|
|
expect(parser).toFailToParse("1\n\rx", { // mismatched
|
|
|
|
offset: 3,
|
|
|
|
line: 3,
|
|
|
|
column: 1
|
|
|
|
});
|
|
|
|
|
|
|
|
/* Strange newlines */
|
|
|
|
expect(parser).toFailToParse("1\u2028x", { // line separator
|
|
|
|
offset: 2,
|
|
|
|
line: 2,
|
|
|
|
column: 1
|
|
|
|
});
|
|
|
|
expect(parser).toFailToParse("1\u2029x", { // paragraph separator
|
|
|
|
offset: 2,
|
|
|
|
line: 2,
|
|
|
|
column: 1
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("allowed start rules", function() {
|
|
|
|
var grammar = [
|
|
|
|
'a = "x"',
|
|
|
|
'b = "x"',
|
|
|
|
'c = "x"'
|
|
|
|
].join("\n");
|
|
|
|
|
|
|
|
describe("without the |allowedStartRules| option", function() {
|
|
|
|
var parser = PEG.buildParser(grammar);
|
|
|
|
|
|
|
|
it("allows the first rule", function() {
|
|
|
|
expect(parser).toParse("x", { startRule: "a" }, "x");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not allow any other rules", function() {
|
|
|
|
expect(parser).toFailToParse("x", { startRule: "b" }, { });
|
|
|
|
expect(parser).toFailToParse("x", { startRule: "c" }, { });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("with the |allowedStartRules| option", function() {
|
|
|
|
var parser = PEG.buildParser(grammar, { allowedStartRules: ["b", "c"] });
|
|
|
|
|
|
|
|
it("allows the specified rules", function() {
|
|
|
|
expect(parser).toParse("x", { startRule: "b" }, "x");
|
|
|
|
expect(parser).toParse("x", { startRule: "c" }, "x");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not allow any other rules", function() {
|
|
|
|
expect(parser).toFailToParse("x", { startRule: "a" }, { });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("output", function() {
|
|
|
|
var grammar = 'start = "a"';
|
|
|
|
|
|
|
|
describe("without the |output| option", function() {
|
|
|
|
it("returns a parser object", function() {
|
|
|
|
var parser = PEG.buildParser(grammar);
|
|
|
|
|
|
|
|
expect(typeof parser).toBe("object");
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("when the |output| option is set to \"parser\"", function() {
|
|
|
|
it("returns a parser object", function() {
|
|
|
|
var parser = PEG.buildParser(grammar, { output: "parser" });
|
|
|
|
|
|
|
|
expect(typeof parser).toBe("object");
|
|
|
|
expect(parser).toParse("a", "a");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("when the |output| option is set to \"source\"", function() {
|
|
|
|
it("returns a parser source code", function() {
|
|
|
|
var source = PEG.buildParser(grammar, { output: "source" });
|
|
|
|
|
|
|
|
expect(typeof source).toBe("string");
|
|
|
|
expect(eval(source)).toParse("a", "a");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Following examples are from Wikipedia, see
|
|
|
|
* http://en.wikipedia.org/w/index.php?title=Parsing_expression_grammar&oldid=335106938.
|
|
|
|
*/
|
|
|
|
describe("complex examples", function() {
|
|
|
|
it("handles arithmetics example correctly", function() {
|
|
|
|
/*
|
|
|
|
* Value ← [0-9]+ / '(' Expr ')'
|
|
|
|
* Product ← Value (('*' / '/') Value)*
|
|
|
|
* Sum ← Product (('+' / '-') Product)*
|
|
|
|
* Expr ← Sum
|
|
|
|
*/
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'Expr = Sum',
|
|
|
|
'Sum = head:Product tail:(("+" / "-") Product)* {',
|
|
|
|
' var result = head, i;',
|
|
|
|
' for (i = 0; i < tail.length; i++) {',
|
|
|
|
' if (tail[i][0] == "+") { result += tail[i][1]; }',
|
|
|
|
' if (tail[i][0] == "-") { result -= tail[i][1]; }',
|
|
|
|
' }',
|
|
|
|
' return result;',
|
|
|
|
' }',
|
|
|
|
'Product = head:Value tail:(("*" / "/") Value)* {',
|
|
|
|
' var result = head, i;',
|
|
|
|
' for (i = 0; i < tail.length; i++) {',
|
|
|
|
' if (tail[i][0] == "*") { result *= tail[i][1]; }',
|
|
|
|
' if (tail[i][0] == "/") { result /= tail[i][1]; }',
|
|
|
|
' }',
|
|
|
|
' return result;',
|
|
|
|
' }',
|
|
|
|
'Value = digits:[0-9]+ { return parseInt(digits.join(""), 10); }',
|
|
|
|
' / "(" expr:Expr ")" { return expr; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
/* The "value" rule */
|
|
|
|
expect(parser).toParse("0", 0);
|
|
|
|
expect(parser).toParse("123", 123);
|
|
|
|
expect(parser).toParse("(42+43)", 42+43);
|
|
|
|
|
|
|
|
/* The "product" rule */
|
|
|
|
expect(parser).toParse("42", 42);
|
|
|
|
expect(parser).toParse("42*43", 42*43);
|
|
|
|
expect(parser).toParse("42*43*44*45", 42*43*44*45);
|
|
|
|
expect(parser).toParse("42/43", 42/43);
|
|
|
|
expect(parser).toParse("42/43/44/45", 42/43/44/45);
|
|
|
|
|
|
|
|
/* The "sum" rule */
|
|
|
|
expect(parser).toParse("42*43", 42*43);
|
|
|
|
expect(parser).toParse("42*43+44*45", 42*43+44*45);
|
|
|
|
expect(parser).toParse("42*43+44*45+46*47+48*49", 42*43+44*45+46*47+48*49);
|
|
|
|
expect(parser).toParse("42*43-44*45", 42*43-44*45);
|
|
|
|
expect(parser).toParse("42*43-44*45-46*47-48*49", 42*43-44*45-46*47-48*49);
|
|
|
|
|
|
|
|
/* The "expr" rule */
|
|
|
|
expect(parser).toParse("42+43", 42+43);
|
|
|
|
|
|
|
|
/* Complex test */
|
|
|
|
expect(parser).toParse("(1+2)*(3+4)", (1+2)*(3+4));
|
|
|
|
});
|
|
|
|
|
|
|
|
it("handles non-context-free language correctly", function() {
|
|
|
|
/* The following parsing expression grammar describes the classic
|
|
|
|
* non-context-free language { a^n b^n c^n : n >= 1 }:
|
|
|
|
*
|
|
|
|
* S ← &(A c) a+ B !(a/b/c)
|
|
|
|
* A ← a A? b
|
|
|
|
* B ← b B? c
|
|
|
|
*/
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'S = &(A "c") a:"a"+ B:B !("a" / "b" / "c") { return a.join("") + B; }',
|
|
|
|
'A = a:"a" A:A? b:"b" { return a + A + b; }',
|
|
|
|
'B = b:"b" B:B? c:"c" { return b + B + c; }'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("abc", "abc");
|
|
|
|
expect(parser).toParse("aaabbbccc", "aaabbbccc");
|
|
|
|
expect(parser).toFailToParse("aabbbccc");
|
|
|
|
expect(parser).toFailToParse("aaaabbbccc");
|
|
|
|
expect(parser).toFailToParse("aaabbccc");
|
|
|
|
expect(parser).toFailToParse("aaabbbbccc");
|
|
|
|
expect(parser).toFailToParse("aaabbbcc");
|
|
|
|
expect(parser).toFailToParse("aaabbbcccc");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("handles nested comments example correctly", function() {
|
|
|
|
/*
|
|
|
|
* Begin ← "(*"
|
|
|
|
* End ← "*)"
|
|
|
|
* C ← Begin N* End
|
|
|
|
* N ← C / (!Begin !End Z)
|
|
|
|
* Z ← any single character
|
|
|
|
*/
|
|
|
|
var parser = PEG.buildParser([
|
|
|
|
'C = begin:Begin ns:N* end:End { return begin + ns.join("") + end; }',
|
|
|
|
'N = C',
|
|
|
|
' / !Begin !End z:Z { return z; }',
|
|
|
|
'Z = .',
|
|
|
|
'Begin = "(*"',
|
|
|
|
'End = "*)"'
|
|
|
|
].join("\n"), options);
|
|
|
|
|
|
|
|
expect(parser).toParse("(**)", "(**)");
|
|
|
|
expect(parser).toParse("(*abc*)", "(*abc*)");
|
|
|
|
expect(parser).toParse("(*(**)*)", "(*(**)*)");
|
|
|
|
expect(parser).toParse(
|
|
|
|
"(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)",
|
|
|
|
"(*abc(*def*)ghi(*(*(*jkl*)*)*)mno*)"
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|