58806a3d77
Semantic predicate and action specs which verified label scope didn't exercise labels in subexpressions. This commit adds cases exercising them, including few commented-out cases which reveal #396 (these will be uncommented when the bug gets fixed). Note that added specs exercise all relevant expression types. This is needed because code that makes subexpression labels invisible to the outside is separate for each expression type so one generic test wouldn't generate enough coverage. Part of a fix of #396.
1551 lines
52 KiB
JavaScript
1551 lines
52 KiB
JavaScript
/* global PEG */
|
|
|
|
"use strict";
|
|
|
|
describe("generated parser behavior", function() {
|
|
function varyOptimizationOptions(block) {
|
|
function clone(object) {
|
|
var result = {}, key;
|
|
|
|
for (key in object) {
|
|
if (object.hasOwnProperty(key)) {
|
|
result[key] = object[key];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
var optionsVariants = [
|
|
{ cache: false, optimize: "speed" },
|
|
{ cache: false, optimize: "size" },
|
|
{ cache: true, optimize: "speed" },
|
|
{ cache: true, optimize: "size" }
|
|
],
|
|
i;
|
|
|
|
for (i = 0; i < optionsVariants.length; i++) {
|
|
describe(
|
|
"with options " + jasmine.pp(optionsVariants[i]),
|
|
function() { block(clone(optionsVariants[i])); }
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (arguments.length > 1) {
|
|
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);
|
|
} else {
|
|
return true;
|
|
}
|
|
} catch (e) {
|
|
this.message = function() {
|
|
return "Expected " + jasmine.pp(input) + " "
|
|
+ "with options " + jasmine.pp(options) + " "
|
|
+ "to parse" + (arguments.length > 1 ? " 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,
|
|
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 (details.hasOwnProperty(key)) {
|
|
if (!this.env.equals_(e[key], details[key])) {
|
|
this.message = 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(e[key]) + ".";
|
|
};
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
varyOptimizationOptions(function(options) {
|
|
describe("initializer", function() {
|
|
it("executes the code before parsing starts", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var result = 42; }',
|
|
'start = "a" { return result; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("a", 42);
|
|
});
|
|
|
|
describe("available variables and functions", function() {
|
|
it("|parser| contains the parser object", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var result = parser; }',
|
|
'start = "a" { return result; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("a", parser);
|
|
});
|
|
|
|
it("|options| contains options", 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", function() {
|
|
if (options.cache) {
|
|
it("caches rule match results", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var n = 0; }',
|
|
'start = (a "b") / (a "c") { return n; }',
|
|
'a = "a" { n++; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("ac", 1);
|
|
});
|
|
} else {
|
|
it("doesn't cache rule match results", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var n = 0; }',
|
|
'start = (a "b") / (a "c") { return n; }',
|
|
'a = "a" { n++; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("ac", 2);
|
|
});
|
|
}
|
|
|
|
describe("when the expression matches", function() {
|
|
it("returns its match result", function() {
|
|
var parser = PEG.buildParser('start = "a"');
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
describe("without display name", function() {
|
|
it("reports match failure and doesn't record any expectation", function() {
|
|
var parser = PEG.buildParser('start = "a"');
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
expected: [{ type: "literal", value: "a", description: '"a"' }]
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("with display name", function() {
|
|
it("reports match failure and records an expectation of type \"other\"", function() {
|
|
var parser = PEG.buildParser('start "start" = "a"');
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
expected: [{ type: "other", description: "start" }]
|
|
});
|
|
});
|
|
|
|
it("discards any expectations recorded when matching the expression", function() {
|
|
var parser = PEG.buildParser('start "start" = "a"');
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
expected: [{ type: "other", description: "start" }]
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("literal", function() {
|
|
describe("matching", function() {
|
|
it("matches empty literals", function() {
|
|
var parser = PEG.buildParser('start = ""', options);
|
|
|
|
expect(parser).toParse("");
|
|
});
|
|
|
|
it("matches one-character literals", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toParse("a");
|
|
expect(parser).toFailToParse("b");
|
|
});
|
|
|
|
it("matches multi-character literals", function() {
|
|
var parser = PEG.buildParser('start = "abcd"', options);
|
|
|
|
expect(parser).toParse("abcd");
|
|
expect(parser).toFailToParse("efgh");
|
|
});
|
|
|
|
it("is case sensitive without the \"i\" flag", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toParse("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");
|
|
expect(parser).toParse("A");
|
|
});
|
|
});
|
|
|
|
describe("when it matches", function() {
|
|
it("returns the matched text", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
|
|
it("consumes the matched text", function() {
|
|
var parser = PEG.buildParser('start = "a" .', options);
|
|
|
|
expect(parser).toParse("ab");
|
|
});
|
|
});
|
|
|
|
describe("when it doesn't match", function() {
|
|
it("reports match failure and records an expectation of type \"literal\"", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
expected: [{ type: "literal", value: "a", description: '"a"' }]
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("character class", function() {
|
|
describe("matching", function() {
|
|
it("matches empty classes", function() {
|
|
var parser = PEG.buildParser('start = []', options);
|
|
|
|
expect(parser).toFailToParse("a");
|
|
});
|
|
|
|
it("matches classes with a character list", function() {
|
|
var parser = PEG.buildParser('start = [abc]', options);
|
|
|
|
expect(parser).toParse("a");
|
|
expect(parser).toParse("b");
|
|
expect(parser).toParse("c");
|
|
expect(parser).toFailToParse("d");
|
|
});
|
|
|
|
it("matches classes with a character range", function() {
|
|
var parser = PEG.buildParser('start = [a-c]', options);
|
|
|
|
expect(parser).toParse("a");
|
|
expect(parser).toParse("b");
|
|
expect(parser).toParse("c");
|
|
expect(parser).toFailToParse("d");
|
|
});
|
|
|
|
it("matches inverted classes", function() {
|
|
var parser = PEG.buildParser('start = [^a]', options);
|
|
|
|
expect(parser).toFailToParse("a");
|
|
expect(parser).toParse("b");
|
|
});
|
|
|
|
it("is case sensitive without the \"i\" flag", function() {
|
|
var parser = PEG.buildParser('start = [a]', options);
|
|
|
|
expect(parser).toParse("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");
|
|
expect(parser).toParse("A");
|
|
});
|
|
});
|
|
|
|
describe("when it matches", function() {
|
|
it("returns the matched character", function() {
|
|
var parser = PEG.buildParser('start = [a]', options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
|
|
it("consumes the matched character", function() {
|
|
var parser = PEG.buildParser('start = [a] .', options);
|
|
|
|
expect(parser).toParse("ab");
|
|
});
|
|
});
|
|
|
|
describe("when it doesn't match", function() {
|
|
it("reports match failure and records an expectation of type \"class\"", function() {
|
|
var parser = PEG.buildParser('start = [a]', options);
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
expected: [{ type: "class", value: "[a]", description: "[a]" }]
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("dot", function() {
|
|
describe("matching", function() {
|
|
it("matches any character", function() {
|
|
var parser = PEG.buildParser('start = .', options);
|
|
|
|
expect(parser).toParse("a");
|
|
expect(parser).toParse("b");
|
|
expect(parser).toParse("c");
|
|
});
|
|
});
|
|
|
|
describe("when it matches", function() {
|
|
it("returns the matched character", function() {
|
|
var parser = PEG.buildParser('start = .', options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
|
|
it("consumes the matched character", function() {
|
|
var parser = PEG.buildParser('start = . .', options);
|
|
|
|
expect(parser).toParse("ab");
|
|
});
|
|
});
|
|
|
|
describe("when it doesn't match", function() {
|
|
it("reports match failure and records an expectation of type \"any\"", function() {
|
|
var parser = PEG.buildParser('start = .', options);
|
|
|
|
expect(parser).toFailToParse("", {
|
|
expected: [{ type: "any", description: "any character" }]
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("rule reference", function() {
|
|
describe("when referenced rule's expression matches", function() {
|
|
it("returns its result", function() {
|
|
var parser = PEG.buildParser([
|
|
'start = a',
|
|
'a = "a"'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
});
|
|
|
|
describe("when referenced rule's expression doesn't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser([
|
|
'start = a',
|
|
'a = "a"'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toFailToParse("b");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("positive semantic predicate", function() {
|
|
describe("when the code returns a truthy value", function() {
|
|
it("returns |undefined|", function() {
|
|
var parser = PEG.buildParser('start = &{ return true; }', options);
|
|
|
|
expect(parser).toParse("", undefined);
|
|
});
|
|
});
|
|
|
|
describe("when the code returns a falsey value", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = &{ return false; }', options);
|
|
|
|
expect(parser).toFailToParse("");
|
|
});
|
|
});
|
|
|
|
describe("label variables", function() {
|
|
describe("in containing sequence", function() {
|
|
it("can access variables defined by preceding labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = a:"a" &{ return a === "a"; }',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("a");
|
|
});
|
|
|
|
it("cannot access variable defined by labeled predicate element", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" b:&{ return b === undefined; } "c"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("ac");
|
|
});
|
|
|
|
it("cannot access variables defined by following labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = &{ return a === "a"; } a:"a"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("a");
|
|
});
|
|
|
|
it("cannot access variables defined by subexpressions", function() {
|
|
var testcases = [
|
|
// TODO: Fix #396.
|
|
//
|
|
// {
|
|
// grammar: 'start = (a:"a") &{ return a === "a"; }',
|
|
// input: "a"
|
|
// },
|
|
{
|
|
grammar: 'start = (a:"a")? &{ return a === "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = (a:"a")* &{ return a === "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = (a:"a")+ &{ return a === "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = $(a:"a") &{ return a === "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = &(a:"a") "a" &{ return a === "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = !(a:"a") "b" &{ return a === "a"; }',
|
|
input: "b"
|
|
},
|
|
{
|
|
grammar: 'start = b:(a:"a") &{ return a === "a"; }',
|
|
input: "a"
|
|
},
|
|
// TODO: Fix #396.
|
|
//
|
|
// {
|
|
// grammar: 'start = ("a" b:"b" "c") &{ return b === "b"; }',
|
|
// input: "abc"
|
|
// },
|
|
{
|
|
grammar: 'start = (a:"a" { return a; }) &{ return a === "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = ("a" / b:"b" / "c") &{ return b === "b"; }',
|
|
input: "b"
|
|
}
|
|
],
|
|
parser, i;
|
|
|
|
for (i = 0; i < testcases.length; i++) {
|
|
parser = PEG.buildParser(testcases[i].grammar, options);
|
|
expect(parser).toFailToParse(testcases[i].input);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("in outer sequence", function() {
|
|
it("can access variables defined by preceding labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = a:"a" ("b" &{ return a === "a"; })',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("ab");
|
|
});
|
|
|
|
it("cannot access variable defined by labeled predicate element", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" b:("b" &{ return b === undefined; }) "c"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("abc");
|
|
});
|
|
|
|
it("cannot access variables defined by following labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = ("a" &{ return b === "b"; }) b:"b"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("ab");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("initializer variables & functions", function() {
|
|
it("can access variables defined in the initializer", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var v = 42 }',
|
|
'start = &{ return v === 42; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("");
|
|
});
|
|
|
|
it("can access functions defined in the initializer", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ function f() { return 42; } }',
|
|
'start = &{ return f() === 42; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("");
|
|
});
|
|
});
|
|
|
|
describe("available variables & functions", function() {
|
|
it("|parser| contains the parser object", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var result; }',
|
|
'start = &{ result = parser; return true; } { return result; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("", parser);
|
|
});
|
|
|
|
it("|options| contains options", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var result; }',
|
|
'start = &{ result = options; return true; } { return result; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("", { a: 42 }, { a: 42 });
|
|
});
|
|
|
|
it("|location| returns current location info", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var result; }',
|
|
'start = line (nl+ line)* { return result; }',
|
|
'line = thing (" "+ thing)*',
|
|
'thing = digit / mark',
|
|
'digit = [0-9]',
|
|
'mark = &{ result = location(); return true; } "x"',
|
|
'nl = "\\r"? "\\n"'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", {
|
|
start: { offset: 13, line: 7, column: 5 },
|
|
end: { offset: 13, line: 7, column: 5 }
|
|
});
|
|
|
|
/* Newline representations */
|
|
expect(parser).toParse("1\nx", { // Unix
|
|
start: { offset: 2, line: 2, column: 1 },
|
|
end: { offset: 2, line: 2, column: 1 }
|
|
});
|
|
expect(parser).toParse("1\r\nx", { // Windows
|
|
start: { offset: 3, line: 2, column: 1 },
|
|
end: { offset: 3, line: 2, column: 1 }
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("negative semantic predicate", function() {
|
|
describe("when the code returns a falsey value", function() {
|
|
it("returns |undefined|", function() {
|
|
var parser = PEG.buildParser('start = !{ return false; }', options);
|
|
|
|
expect(parser).toParse("", undefined);
|
|
});
|
|
});
|
|
|
|
describe("when the code returns a truthy value", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = !{ return true; }', options);
|
|
|
|
expect(parser).toFailToParse("");
|
|
});
|
|
});
|
|
|
|
describe("label variables", function() {
|
|
describe("in containing sequence", function() {
|
|
it("can access variables defined by preceding labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = a:"a" !{ return a !== "a"; }',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("a");
|
|
});
|
|
|
|
it("cannot access variable defined by labeled predicate element", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" b:!{ return b !== undefined; } "c"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("ac");
|
|
});
|
|
|
|
it("cannot access variables defined by following labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = !{ return a !== "a"; } a:"a"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("a");
|
|
});
|
|
|
|
it("cannot access variables defined by subexpressions", function() {
|
|
var testcases = [
|
|
// TODO: Fix #396.
|
|
//
|
|
// {
|
|
// grammar: 'start = (a:"a") !{ return a !== "a"; }',
|
|
// input: "a"
|
|
// },
|
|
{
|
|
grammar: 'start = (a:"a")? !{ return a !== "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = (a:"a")* !{ return a !== "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = (a:"a")+ !{ return a !== "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = $(a:"a") !{ return a !== "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = &(a:"a") "a" !{ return a !== "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = !(a:"a") "b" !{ return a !== "a"; }',
|
|
input: "b"
|
|
},
|
|
{
|
|
grammar: 'start = b:(a:"a") !{ return a !== "a"; }',
|
|
input: "a"
|
|
},
|
|
// TODO: Fix #396.
|
|
//
|
|
// {
|
|
// grammar: 'start = ("a" b:"b" "c") !{ return b !== "b"; }',
|
|
// input: "abc"
|
|
// },
|
|
{
|
|
grammar: 'start = (a:"a" { return a; }) !{ return a !== "a"; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = ("a" / b:"b" / "c") !{ return b !== "b"; }',
|
|
input: "b"
|
|
}
|
|
],
|
|
parser, i;
|
|
|
|
for (i = 0; i < testcases.length; i++) {
|
|
parser = PEG.buildParser(testcases[i].grammar, options);
|
|
expect(parser).toFailToParse(testcases[i].input);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("in outer sequence", function() {
|
|
it("can access variables defined by preceding labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = a:"a" ("b" !{ return a !== "a"; })',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("ab");
|
|
});
|
|
|
|
it("cannot access variable defined by labeled predicate element", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" b:("b" !{ return b !== undefined; }) "c"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("abc");
|
|
});
|
|
|
|
it("cannot access variables defined by following labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = ("a" !{ return b !== "b"; }) b:"b"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("ab");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("initializer variables & functions", function() {
|
|
it("can access variables defined in the initializer", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var v = 42 }',
|
|
'start = !{ return v !== 42; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("");
|
|
});
|
|
|
|
it("can access functions defined in the initializer", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ function f() { return 42; } }',
|
|
'start = !{ return f() !== 42; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("");
|
|
});
|
|
});
|
|
|
|
describe("available variables & functions", function() {
|
|
it("|parser| contains the parser object", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var result; }',
|
|
'start = !{ result = parser; return false; } { return result; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("", parser);
|
|
});
|
|
|
|
it("|options| contains options", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var result; }',
|
|
'start = !{ result = options; return false; } { return result; }'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("", { a: 42 }, { a: 42 });
|
|
});
|
|
|
|
it("|location| returns current location info", function() {
|
|
var parser = PEG.buildParser([
|
|
'{ var result; }',
|
|
'start = line (nl+ line)* { return result; }',
|
|
'line = thing (" "+ thing)*',
|
|
'thing = digit / mark',
|
|
'digit = [0-9]',
|
|
'mark = !{ result = location(); return false; } "x"',
|
|
'nl = "\\r"? "\\n"'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", {
|
|
start: { offset: 13, line: 7, column: 5 },
|
|
end: { offset: 13, line: 7, column: 5 }
|
|
});
|
|
|
|
/* Newline representations */
|
|
expect(parser).toParse("1\nx", { // Unix
|
|
start: { offset: 2, line: 2, column: 1 },
|
|
end: { offset: 2, line: 2, column: 1 }
|
|
});
|
|
expect(parser).toParse("1\r\nx", { // Windows
|
|
start: { offset: 3, line: 2, column: 1 },
|
|
end: { offset: 3, line: 2, column: 1 }
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("group", function() {
|
|
describe("when the expression matches", function() {
|
|
it("returns its match result", function() {
|
|
var parser = PEG.buildParser('start = ("a")', options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = ("a")', options);
|
|
|
|
expect(parser).toFailToParse("b");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("optional", function() {
|
|
describe("when the expression matches", function() {
|
|
it("returns its match result", function() {
|
|
var parser = PEG.buildParser('start = "a"?', options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
it("returns |null|", function() {
|
|
var parser = PEG.buildParser('start = "a"?', options);
|
|
|
|
expect(parser).toParse("", null);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("zero or more", function() {
|
|
describe("when the expression matches zero or more times", function() {
|
|
it("returns an array of its match results", 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", function() {
|
|
describe("when the expression matches one or more times", function() {
|
|
it("returns an array of its match results", function() {
|
|
var parser = PEG.buildParser('start = "a"+', options);
|
|
|
|
expect(parser).toParse("a", ["a"]);
|
|
expect(parser).toParse("aaa", ["a", "a", "a"]);
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = "a"+', options);
|
|
|
|
expect(parser).toFailToParse("");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("text", function() {
|
|
describe("when the expression matches", function() {
|
|
it("returns the matched text", function() {
|
|
var parser = PEG.buildParser('start = $("a" "b" "c")', options);
|
|
|
|
expect(parser).toParse("abc", "abc");
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = $("a")', options);
|
|
|
|
expect(parser).toFailToParse("b");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("positive simple predicate", function() {
|
|
describe("when the expression matches", function() {
|
|
it("returns |undefined|", function() {
|
|
var parser = PEG.buildParser('start = &"a" "a"', options);
|
|
|
|
expect(parser).toParse("a", [undefined, "a"]);
|
|
});
|
|
|
|
it("resets parse position", function() {
|
|
var parser = PEG.buildParser('start = &"a" "a"', options);
|
|
|
|
expect(parser).toParse("a");
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = &"a"', options);
|
|
|
|
expect(parser).toFailToParse("b");
|
|
});
|
|
|
|
it("discards any expectations recorded when matching the expression", function() {
|
|
var parser = PEG.buildParser('start = "a" / &"b" / "c"', options);
|
|
|
|
expect(parser).toFailToParse("d", {
|
|
expected: [
|
|
{ type: "literal", value: "a", description: '"a"' },
|
|
{ type: "literal", value: "c", description: '"c"' }
|
|
]
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("negative simple predicate", function() {
|
|
describe("when the expression matches", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = !"a"', options);
|
|
|
|
expect(parser).toFailToParse("a");
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
it("returns |undefined|", function() {
|
|
var parser = PEG.buildParser('start = !"a" "b"', options);
|
|
|
|
expect(parser).toParse("b", [undefined, "b"]);
|
|
});
|
|
|
|
it("resets parse position", function() {
|
|
var parser = PEG.buildParser('start = !"a" "b"', options);
|
|
|
|
expect(parser).toParse("b");
|
|
});
|
|
|
|
it("discards any expectations recorded when matching the expression", function() {
|
|
var parser = PEG.buildParser('start = "a" / !"b" / "c"', options);
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
expected: [
|
|
{ type: "literal", value: "a", description: '"a"' },
|
|
{ type: "literal", value: "c", description: '"c"' }
|
|
]
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("label", function() {
|
|
describe("when the expression matches", function() {
|
|
it("returns its match result", function() {
|
|
var parser = PEG.buildParser('start = a:"a"', options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = a:"a"', options);
|
|
|
|
expect(parser).toFailToParse("b");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("sequence", function() {
|
|
describe("when all expressions match", function() {
|
|
it("returns an array of their match results", function() {
|
|
var parser = PEG.buildParser('start = "a" "b" "c"', options);
|
|
|
|
expect(parser).toParse("abc", ["a", "b", "c"]);
|
|
});
|
|
});
|
|
|
|
describe("when any expression doesn't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = "a" "b" "c"', options);
|
|
|
|
expect(parser).toFailToParse("dbc");
|
|
expect(parser).toFailToParse("adc");
|
|
expect(parser).toFailToParse("abd");
|
|
});
|
|
|
|
it("resets parse position", function() {
|
|
var parser = PEG.buildParser('start = "a" "b" / "a"', options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("action", function() {
|
|
describe("when the expression matches", function() {
|
|
it("returns the value returned by the code", function() {
|
|
var parser = PEG.buildParser('start = "a" { return 42; }', options);
|
|
|
|
expect(parser).toParse("a", 42);
|
|
});
|
|
|
|
describe("label variables", function() {
|
|
describe("in the expression", function() {
|
|
it("can access variable defined by labeled expression", function() {
|
|
var parser = PEG.buildParser('start = a:"a" { return a; }', options);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
|
|
it("can access variables defined by labeled sequence elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = a:"a" b:"b" c:"c" { return [a, b, c]; }',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("abc", ["a", "b", "c"]);
|
|
});
|
|
|
|
it("cannot access variables defined by subexpressions", function() {
|
|
var testcases = [
|
|
// TODO: Fix #396.
|
|
//
|
|
// {
|
|
// grammar: 'start = (a:"a") { return a; }',
|
|
// input: "a"
|
|
// },
|
|
{
|
|
grammar: 'start = (a:"a")? { return a; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = (a:"a")* { return a; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = (a:"a")+ { return a; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = $(a:"a") { return a; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = &(a:"a") "a" { return a; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = !(a:"a") "b" { return a; }',
|
|
input: "b"
|
|
},
|
|
{
|
|
grammar: 'start = b:(a:"a") { return a; }',
|
|
input: "a"
|
|
},
|
|
// TODO: Fix #396.
|
|
//
|
|
// {
|
|
// grammar: 'start = ("a" b:"b" "c") { return b; }',
|
|
// input: "abc"
|
|
// },
|
|
{
|
|
grammar: 'start = (a:"a" { return a; }) { return a; }',
|
|
input: "a"
|
|
},
|
|
{
|
|
grammar: 'start = ("a" / b:"b" / "c") { return b; }',
|
|
input: "b"
|
|
}
|
|
],
|
|
parser, i;
|
|
|
|
for (i = 0; i < testcases.length; i++) {
|
|
parser = PEG.buildParser(testcases[i].grammar, options);
|
|
expect(parser).toFailToParse(testcases[i].input);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("in outer sequence", function() {
|
|
it("can access variables defined by preceding labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = a:"a" ("b" { return a; })',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("ab", ["a", "a"]);
|
|
});
|
|
|
|
it("cannot access variable defined by labeled action element", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" b:("b" { return b; }) c:"c"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("abc");
|
|
});
|
|
|
|
it("cannot access variables defined by following labeled elements", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = ("a" { return b; }) b:"b"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("ab");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("initializer variables & functions", function() {
|
|
it("can access 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 access 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);
|
|
});
|
|
});
|
|
|
|
describe("available variables & functions", function() {
|
|
it("|parser| contains the parser object", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" { return parser; }',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("a", parser);
|
|
});
|
|
|
|
it("|options| contains options", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" { return options; }',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("a", { a: 42 }, { a: 42 });
|
|
});
|
|
|
|
it("|text| returns text matched by the expression", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" { return text(); }',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("a", "a");
|
|
});
|
|
|
|
it("|location| returns location info of the expression", 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 = location(); }',
|
|
'nl = "\\r"? "\\n"'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", {
|
|
start: { offset: 13, line: 7, column: 5 },
|
|
end: { offset: 14, line: 7, column: 6 }
|
|
});
|
|
|
|
/* Newline representations */
|
|
expect(parser).toParse("1\nx", { // Unix
|
|
start: { offset: 2, line: 2, column: 1 },
|
|
end: { offset: 3, line: 2, column: 2 }
|
|
});
|
|
expect(parser).toParse("1\r\nx", { // Windows
|
|
start: { offset: 3, line: 2, column: 1 },
|
|
end: { offset: 4, line: 2, column: 2 }
|
|
});
|
|
});
|
|
|
|
it("|expected| terminates parsing and throws an exception", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" { expected("a"); }',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("a", {
|
|
message: 'Expected a.',
|
|
expected: [{ type: "other", description: "a" }],
|
|
location: {
|
|
start: { offset: 0, line: 1, column: 1 },
|
|
end: { offset: 1, line: 1, column: 2 }
|
|
}
|
|
});
|
|
});
|
|
|
|
it("|error| terminates parsing and throws an exception", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" { error("a"); }',
|
|
options
|
|
);
|
|
|
|
expect(parser).toFailToParse("a", {
|
|
message: "a",
|
|
expected: null,
|
|
location: {
|
|
start: { offset: 0, line: 1, column: 1 },
|
|
end: { offset: 1, line: 1, column: 2 }
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when the expression doesn't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = "a" { return 42; }', options);
|
|
|
|
expect(parser).toFailToParse("b");
|
|
});
|
|
|
|
it("doesn't execute the code", function() {
|
|
var parser = PEG.buildParser(
|
|
'start = "a" { throw "Boom!"; } / "b"',
|
|
options
|
|
);
|
|
|
|
expect(parser).toParse("b");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("choice", function() {
|
|
describe("when any expression matches", function() {
|
|
it("returns its match result", 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");
|
|
});
|
|
});
|
|
|
|
describe("when all expressions don't match", function() {
|
|
it("reports match failure", function() {
|
|
var parser = PEG.buildParser('start = "a" / "b" / "c"', options);
|
|
|
|
expect(parser).toFailToParse("d");
|
|
});
|
|
});
|
|
});
|
|
|
|
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", {
|
|
expected: [{ type: "literal", value: "d", description: '"d"' }]
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("expectations reporting", function() {
|
|
it("reports expectations correctly with no alternative", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toFailToParse("ab", {
|
|
expected: [{ type: "end", description: "end of input" }]
|
|
});
|
|
});
|
|
|
|
it("reports expectations correctly with one alternative", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
expected: [{ type: "literal", value: "a", description: '"a"' }]
|
|
});
|
|
});
|
|
|
|
it("reports expectations correctly with multiple alternatives", function() {
|
|
var parser = PEG.buildParser('start = "a" / "b" / "c"', options);
|
|
|
|
expect(parser).toFailToParse("d", {
|
|
expected: [
|
|
{ type: "literal", value: "a", description: '"a"' },
|
|
{ type: "literal", value: "b", description: '"b"' },
|
|
{ type: "literal", value: "c", description: '"c"' }
|
|
]
|
|
});
|
|
});
|
|
|
|
it("removes duplicates from expectations", function() {
|
|
/*
|
|
* There was a bug in the code that manifested only with three
|
|
* duplicates. This is why the following test uses three choices
|
|
* instead of seemingly sufficient two.
|
|
*
|
|
* See https://github.com/pegjs/pegjs/pull/146.
|
|
*/
|
|
var parser = PEG.buildParser('start = "a" / "a" / "a"', options);
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
expected: [{ type: "literal", value: "a", description: '"a"' }]
|
|
});
|
|
});
|
|
|
|
it("sorts expectations", function() {
|
|
var parser = PEG.buildParser('start = "c" / "b" / "a"', options);
|
|
|
|
expect(parser).toFailToParse("d", {
|
|
expected: [
|
|
{ type: "literal", value: "a", description: '"a"' },
|
|
{ type: "literal", value: "b", description: '"b"' },
|
|
{ type: "literal", value: "c", description: '"c"' }
|
|
]
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("message building", function() {
|
|
it("builds message correctly with no alternative", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toFailToParse("ab", {
|
|
message: 'Expected end of input.'
|
|
});
|
|
});
|
|
|
|
it("builds message correctly with one alternative", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
message: 'Expected "a".'
|
|
});
|
|
});
|
|
|
|
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".'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("position reporting", function() {
|
|
it("reports position correctly with no trailing input", function() {
|
|
var parser = PEG.buildParser('start = "a"', options);
|
|
|
|
expect(parser).toFailToParse("b", {
|
|
location: {
|
|
start: { offset: 0, line: 1, column: 1 },
|
|
end: { 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", {
|
|
location: {
|
|
start: { offset: 1, line: 1, column: 2 },
|
|
end: { 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"'
|
|
].join("\n"), options);
|
|
|
|
expect(parser).toFailToParse("1\n2\n\n3\n\n\n4 5 x", {
|
|
location: {
|
|
start: { offset: 13, line: 7, column: 5 },
|
|
end: { offset: 13, line: 7, column: 5 }
|
|
}
|
|
});
|
|
|
|
/* Newline representations */
|
|
expect(parser).toFailToParse("1\nx", { // Old Mac
|
|
location: {
|
|
start: { offset: 2, line: 2, column: 1 },
|
|
end: { offset: 2, line: 2, column: 1 }
|
|
}
|
|
});
|
|
expect(parser).toFailToParse("1\r\nx", { // Windows
|
|
location: {
|
|
start: { offset: 3, line: 2, column: 1 },
|
|
end: { offset: 3, line: 2, column: 1 }
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
/*
|
|
* 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 = first:Product rest:(("+" / "-") Product)* {',
|
|
' var result = first, i;',
|
|
' for (i = 0; i < rest.length; i++) {',
|
|
' if (rest[i][0] == "+") { result += rest[i][1]; }',
|
|
' if (rest[i][0] == "-") { result -= rest[i][1]; }',
|
|
' }',
|
|
' return result;',
|
|
' }',
|
|
'Product = first:Value rest:(("*" / "/") Value)* {',
|
|
' var result = first, i;',
|
|
' for (i = 0; i < rest.length; i++) {',
|
|
' if (rest[i][0] == "*") { result *= rest[i][1]; }',
|
|
' if (rest[i][0] == "/") { result /= rest[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].join(""); }',
|
|
'B = b:"b" B:B? c:"c" { return [b, B, c].join(""); }'
|
|
].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*)"
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|