Implement |trackLineAndColumn| option for |PEG.buildParser|

This option makes the generated parser track line and column during
parsing. Tracked line and column are made available inside actions and
predicates as |line| and |column| variables.

Note that in actions these variables denote start position of the
action's expression while in predicates they denote the current
position. The slightly different behavior is motivated by expected
usage.
redux
David Majda 13 years ago
parent 9615eb4bb6
commit 52d7ec2224

@ -1,6 +1,7 @@
/* Emits the generated code for the AST. */
PEG.compiler.emitter = function(ast, options) {
options = options || {};
options.trackLineAndColumn = options.trackLineAndColumn || false;
var Codie = (function(undefined) {
function stringEscape(s) {
@ -296,9 +297,9 @@ PEG.compiler.emitter = function(ast, options) {
' startRule = #{string(node.startRule)};',
' }',
' ',
' var pos = 0;',
' #{posInit("pos")};',
' var reportFailures = 0;', // 0 = report, anything > 0 = do not report
' var rightmostFailuresPos = 0;',
' #{posInit("rightmostFailuresPos")};',
' var rightmostFailuresExpected = [];',
' var cache = {};',
' ',
@ -331,13 +332,45 @@ PEG.compiler.emitter = function(ast, options) {
' return \'\\\\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), \'0\', length);',
' }',
' ',
' #if options.trackLineAndColumn',
' function clone(object) {',
' var result = {};',
' for (var key in object) {',
' result[key] = object[key];',
' }',
' return result;',
' }',
' ',
' function advance(pos, n) {',
' var endOffset = pos.offset + n;',
' ',
' for (var offset = pos.offset; offset < endOffset; offset++) {',
' var ch = input.charAt(offset);',
' if (ch === "\\n") {',
' if (!pos.seenCR) { pos.line++; }',
' pos.column = 1;',
' pos.seenCR = false;',
' } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {',
' pos.line++;',
' pos.column = 1;',
' pos.seenCR = true;',
' } else {',
' pos.column++;',
' pos.seenCR = false;',
' }',
' }',
' ',
' pos.offset += n;',
' }',
' ',
' #end',
' function matchFailed(failure) {',
' if (pos < rightmostFailuresPos) {',
' if (#{posOffset("pos")} < #{posOffset("rightmostFailuresPos")}) {',
' return;',
' }',
' ',
' if (pos > rightmostFailuresPos) {',
' rightmostFailuresPos = pos;',
' if (#{posOffset("pos")} > #{posOffset("rightmostFailuresPos")}) {',
' rightmostFailuresPos = #{posClone("pos")};',
' rightmostFailuresExpected = [];',
' }',
' ',
@ -363,6 +396,7 @@ PEG.compiler.emitter = function(ast, options) {
' return cleanExpected;',
' }',
' ',
' #if !options.trackLineAndColumn',
' function computeErrorPosition() {',
' /*',
' * The first idea was to use |String.split| to break the input up to the',
@ -393,6 +427,7 @@ PEG.compiler.emitter = function(ast, options) {
' ',
' return { line: line, column: column };',
' }',
' #end',
' ',
' #if node.initializer',
' #block emit(node.initializer)',
@ -406,28 +441,32 @@ PEG.compiler.emitter = function(ast, options) {
' * 1. The parser successfully parsed the whole input.',
' *',
' * - |result !== null|',
' * - |pos === input.length|',
' * - |#{posOffset("pos")} === input.length|',
' * - |rightmostFailuresExpected| may or may not contain something',
' *',
' * 2. The parser successfully parsed only a part of the input.',
' *',
' * - |result !== null|',
' * - |pos < input.length|',
' * - |#{posOffset("pos")} < input.length|',
' * - |rightmostFailuresExpected| may or may not contain something',
' *',
' * 3. The parser did not successfully parse any part of the input.',
' *',
' * - |result === null|',
' * - |pos === 0|',
' * - |#{posOffset("pos")} === 0|',
' * - |rightmostFailuresExpected| contains at least one failure',
' *',
' * All code following this comment (including called functions) must',
' * handle these states.',
' */',
' if (result === null || pos !== input.length) {',
' var offset = Math.max(pos, rightmostFailuresPos);',
' if (result === null || #{posOffset("pos")} !== input.length) {',
' var offset = Math.max(#{posOffset("pos")}, #{posOffset("rightmostFailuresPos")});',
' var found = offset < input.length ? input.charAt(offset) : null;',
' #if options.trackLineAndColumn',
' var errorPosition = #{posOffset("pos")} > #{posOffset("rightmostFailuresPos")} ? pos : rightmostFailuresPos;',
' #else',
' var errorPosition = computeErrorPosition();',
' #end',
' ',
' throw new this.SyntaxError(',
' cleanupExpected(rightmostFailuresExpected),',
@ -485,10 +524,10 @@ PEG.compiler.emitter = function(ast, options) {
],
rule: [
'function parse_#{node.name}() {',
' var cacheKey = "#{node.name}@" + pos;',
' var cacheKey = "#{node.name}@" + #{posOffset("pos")};',
' var cachedResult = cache[cacheKey];',
' if (cachedResult) {',
' pos = cachedResult.nextPos;',
' pos = #{posClone("cachedResult.nextPos")};',
' return cachedResult.result;',
' }',
' ',
@ -511,7 +550,7 @@ PEG.compiler.emitter = function(ast, options) {
' #end',
' ',
' cache[cacheKey] = {',
' nextPos: pos,',
' nextPos: #{posClone("pos")},',
' result: #{node.resultVar}',
' };',
' return #{node.resultVar};',
@ -527,7 +566,7 @@ PEG.compiler.emitter = function(ast, options) {
'}'
],
sequence: [
'#{node.posVar} = pos;',
'#{posSave(node)};',
'#block code'
],
"sequence.iteration": [
@ -536,26 +575,26 @@ PEG.compiler.emitter = function(ast, options) {
' #block code',
'} else {',
' #{node.resultVar} = null;',
' pos = #{node.posVar};',
' #{posRestore(node)};',
'}'
],
"sequence.inner": [
'#{node.resultVar} = [#{pluck(node.elements, "resultVar").join(", ")}];'
],
simple_and: [
'#{node.posVar} = pos;',
'#{posSave(node)};',
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
'if (#{node.resultVar} !== null) {',
' #{node.resultVar} = "";',
' pos = #{node.posVar};',
' #{posRestore(node)};',
'} else {',
' #{node.resultVar} = null;',
'}'
],
simple_not: [
'#{node.posVar} = pos;',
'#{posSave(node)};',
'reportFailures++;',
'#block emit(node.expression)',
'reportFailures--;',
@ -563,14 +602,14 @@ PEG.compiler.emitter = function(ast, options) {
' #{node.resultVar} = "";',
'} else {',
' #{node.resultVar} = null;',
' pos = #{node.posVar};',
' #{posRestore(node)};',
'}'
],
semantic_and: [
'#{node.resultVar} = (function(#{["offset"].concat(keys(node.params)).join(", ")}) {#{node.code}})(#{["pos"].concat(values(node.params)).join(", ")}) ? "" : null;'
'#{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(values(node.params)).join(", ")}) ? "" : null;'
],
semantic_not: [
'#{node.resultVar} = (function(#{["offset"].concat(keys(node.params)).join(", ")}) {#{node.code}})(#{["pos"].concat(values(node.params)).join(", ")}) ? null : "";'
'#{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(values(node.params)).join(", ")}) ? null : "";'
],
optional: [
'#block emit(node.expression)',
@ -597,13 +636,13 @@ PEG.compiler.emitter = function(ast, options) {
'}'
],
action: [
'#{node.posVar} = pos;',
'#{posSave(node)};',
'#block emit(node.expression)',
'if (#{node.resultVar} !== null) {',
' #{node.resultVar} = (function(#{["offset"].concat(keys(node.params)).join(", ")}) {#{node.code}})(#{[node.posVar].concat(values(node.params)).join(", ")});',
' #{node.resultVar} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [node.posVar + ".offset", node.posVar + ".line", node.posVar + ".column"] : [node.posVar]).concat(values(node.params)).join(", ")});',
'}',
'if (#{node.resultVar} === null) {',
' pos = #{node.posVar};',
' #{posRestore(node)};',
'}'
],
rule_ref: [
@ -615,9 +654,9 @@ PEG.compiler.emitter = function(ast, options) {
'#else',
' #if !node.ignoreCase',
' #if node.value.length === 1',
' if (input.charCodeAt(pos) === #{node.value.charCodeAt(0)}) {',
' if (input.charCodeAt(#{posOffset("pos")}) === #{node.value.charCodeAt(0)}) {',
' #else',
' if (input.substr(pos, #{node.value.length}) === #{string(node.value)}) {',
' if (input.substr(#{posOffset("pos")}, #{node.value.length}) === #{string(node.value)}) {',
' #end',
' #else',
/*
@ -628,14 +667,14 @@ PEG.compiler.emitter = function(ast, options) {
* meaning the result of lowercasing a character can be more
* characters.
*/
' if (input.substr(pos, #{node.value.length}).toLowerCase() === #{string(node.value.toLowerCase())}) {',
' if (input.substr(#{posOffset("pos")}, #{node.value.length}).toLowerCase() === #{string(node.value.toLowerCase())}) {',
' #end',
' #if !node.ignoreCase',
' #{node.resultVar} = #{string(node.value)};',
' #else',
' #{node.resultVar} = input.substr(pos, #{node.value.length});',
' #{node.resultVar} = input.substr(#{posOffset("pos")}, #{node.value.length});',
' #end',
' pos += #{node.value.length};',
' #{posAdvance(node.value.length)};',
' } else {',
' #{node.resultVar} = null;',
' if (reportFailures === 0) {',
@ -645,9 +684,9 @@ PEG.compiler.emitter = function(ast, options) {
'#end'
],
any: [
'if (input.length > pos) {',
' #{node.resultVar} = input.charAt(pos);',
' pos++;',
'if (input.length > #{posOffset("pos")}) {',
' #{node.resultVar} = input.charAt(#{posOffset("pos")});',
' #{posAdvance(1)};',
'} else {',
' #{node.resultVar} = null;',
' if (reportFailures === 0) {',
@ -656,9 +695,9 @@ PEG.compiler.emitter = function(ast, options) {
'}'
],
"class": [
'if (#{regexp}.test(input.charAt(pos))) {',
' #{node.resultVar} = input.charAt(pos);',
' pos++;',
'if (#{regexp}.test(input.charAt(#{posOffset("pos")}))) {',
' #{node.resultVar} = input.charAt(#{posOffset("pos")});',
' #{posAdvance(1)};',
'} else {',
' #{node.resultVar} = null;',
' if (reportFailures === 0) {',
@ -683,6 +722,34 @@ PEG.compiler.emitter = function(ast, options) {
vars.emit = emit;
vars.options = options;
/* Position-handling macros */
if (options.trackLineAndColumn) {
vars.posInit = function(name) {
return "var "
+ name
+ " = "
+ "{ offset: 0, line: 1, column: 1, seenCR: false }";
};
vars.posClone = function(name) { return "clone(" + name + ")"; };
vars.posOffset = function(name) { return name + ".offset"; };
vars.posAdvance = function(n) { return "advance(pos, " + n + ")"; };
} else {
vars.posInit = function(name) { return "var " + name + " = 0"; };
vars.posClone = function(name) { return name; };
vars.posOffset = function(name) { return name; };
vars.posAdvance = function(n) {
return n === 1 ? "pos++" : "pos += " + n;
};
}
vars.posSave = function(node) {
return node.posVar + " = " + vars.posClone("pos");
};
vars.posRestore = function(node) {
return "pos" + " = " + vars.posClone(node.posVar);
};
return templates[name](vars);
}

@ -948,7 +948,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 123) {
result0 = "{";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -971,7 +971,7 @@ PEG.parser = (function(){
if (result1 !== null) {
if (input.charCodeAt(pos) === 125) {
result2 = "}";
pos += 1;
pos++;
} else {
result2 = null;
if (reportFailures === 0) {
@ -1086,7 +1086,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 61) {
result0 = "=";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1134,7 +1134,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 58) {
result0 = ":";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1182,7 +1182,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 59) {
result0 = ";";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1230,7 +1230,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 47) {
result0 = "/";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1278,7 +1278,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 38) {
result0 = "&";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1326,7 +1326,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 33) {
result0 = "!";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1374,7 +1374,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 63) {
result0 = "?";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1422,7 +1422,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 42) {
result0 = "*";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1470,7 +1470,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 43) {
result0 = "+";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1518,7 +1518,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 40) {
result0 = "(";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1566,7 +1566,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 41) {
result0 = ")";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1614,7 +1614,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 46) {
result0 = ".";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1665,7 +1665,7 @@ PEG.parser = (function(){
if (result0 === null) {
if (input.charCodeAt(pos) === 95) {
result0 = "_";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1675,7 +1675,7 @@ PEG.parser = (function(){
if (result0 === null) {
if (input.charCodeAt(pos) === 36) {
result0 = "$";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1692,7 +1692,7 @@ PEG.parser = (function(){
if (result2 === null) {
if (input.charCodeAt(pos) === 95) {
result2 = "_";
pos += 1;
pos++;
} else {
result2 = null;
if (reportFailures === 0) {
@ -1702,7 +1702,7 @@ PEG.parser = (function(){
if (result2 === null) {
if (input.charCodeAt(pos) === 36) {
result2 = "$";
pos += 1;
pos++;
} else {
result2 = null;
if (reportFailures === 0) {
@ -1720,7 +1720,7 @@ PEG.parser = (function(){
if (result2 === null) {
if (input.charCodeAt(pos) === 95) {
result2 = "_";
pos += 1;
pos++;
} else {
result2 = null;
if (reportFailures === 0) {
@ -1730,7 +1730,7 @@ PEG.parser = (function(){
if (result2 === null) {
if (input.charCodeAt(pos) === 36) {
result2 = "$";
pos += 1;
pos++;
} else {
result2 = null;
if (reportFailures === 0) {
@ -1798,7 +1798,7 @@ PEG.parser = (function(){
if (result0 !== null) {
if (input.charCodeAt(pos) === 105) {
result1 = "i";
pos += 1;
pos++;
} else {
result1 = null;
if (reportFailures === 0) {
@ -1909,7 +1909,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 34) {
result0 = "\"";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -1926,7 +1926,7 @@ PEG.parser = (function(){
if (result1 !== null) {
if (input.charCodeAt(pos) === 34) {
result2 = "\"";
pos += 1;
pos++;
} else {
result2 = null;
if (reportFailures === 0) {
@ -2012,7 +2012,7 @@ PEG.parser = (function(){
reportFailures++;
if (input.charCodeAt(pos) === 34) {
result0 = "\"";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2022,7 +2022,7 @@ PEG.parser = (function(){
if (result0 === null) {
if (input.charCodeAt(pos) === 92) {
result0 = "\\";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2089,7 +2089,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 39) {
result0 = "'";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2106,7 +2106,7 @@ PEG.parser = (function(){
if (result1 !== null) {
if (input.charCodeAt(pos) === 39) {
result2 = "'";
pos += 1;
pos++;
} else {
result2 = null;
if (reportFailures === 0) {
@ -2192,7 +2192,7 @@ PEG.parser = (function(){
reportFailures++;
if (input.charCodeAt(pos) === 39) {
result0 = "'";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2202,7 +2202,7 @@ PEG.parser = (function(){
if (result0 === null) {
if (input.charCodeAt(pos) === 92) {
result0 = "\\";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2270,7 +2270,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 91) {
result0 = "[";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2280,7 +2280,7 @@ PEG.parser = (function(){
if (result0 !== null) {
if (input.charCodeAt(pos) === 94) {
result1 = "^";
pos += 1;
pos++;
} else {
result1 = null;
if (reportFailures === 0) {
@ -2304,7 +2304,7 @@ PEG.parser = (function(){
if (result2 !== null) {
if (input.charCodeAt(pos) === 93) {
result3 = "]";
pos += 1;
pos++;
} else {
result3 = null;
if (reportFailures === 0) {
@ -2314,7 +2314,7 @@ PEG.parser = (function(){
if (result3 !== null) {
if (input.charCodeAt(pos) === 105) {
result4 = "i";
pos += 1;
pos++;
} else {
result4 = null;
if (reportFailures === 0) {
@ -2401,7 +2401,7 @@ PEG.parser = (function(){
if (result0 !== null) {
if (input.charCodeAt(pos) === 45) {
result1 = "-";
pos += 1;
pos++;
} else {
result1 = null;
if (reportFailures === 0) {
@ -2534,7 +2534,7 @@ PEG.parser = (function(){
reportFailures++;
if (input.charCodeAt(pos) === 93) {
result0 = "]";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2544,7 +2544,7 @@ PEG.parser = (function(){
if (result0 === null) {
if (input.charCodeAt(pos) === 92) {
result0 = "\\";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2611,7 +2611,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 92) {
result0 = "\\";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -2625,7 +2625,7 @@ PEG.parser = (function(){
if (result1 === null) {
if (input.charCodeAt(pos) === 120) {
result1 = "x";
pos += 1;
pos++;
} else {
result1 = null;
if (reportFailures === 0) {
@ -2635,7 +2635,7 @@ PEG.parser = (function(){
if (result1 === null) {
if (input.charCodeAt(pos) === 117) {
result1 = "u";
pos += 1;
pos++;
} else {
result1 = null;
if (reportFailures === 0) {
@ -2896,7 +2896,7 @@ PEG.parser = (function(){
pos1 = pos;
if (input.charCodeAt(pos) === 92) {
result0 = "\\";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -3377,7 +3377,7 @@ PEG.parser = (function(){
reportFailures++;
if (input.charCodeAt(pos) === 10) {
result0 = "\n";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -3397,7 +3397,7 @@ PEG.parser = (function(){
if (result0 === null) {
if (input.charCodeAt(pos) === 13) {
result0 = "\r";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -3407,7 +3407,7 @@ PEG.parser = (function(){
if (result0 === null) {
if (input.charCodeAt(pos) === 8232) {
result0 = "\u2028";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {
@ -3417,7 +3417,7 @@ PEG.parser = (function(){
if (result0 === null) {
if (input.charCodeAt(pos) === 8233) {
result0 = "\u2029";
pos += 1;
pos++;
} else {
result0 = null;
if (reportFailures === 0) {

@ -2,8 +2,19 @@
module("PEG.compiler");
test("choices", function() {
var parser = PEG.buildParser('start = "a" / "b" / "c"');
function testWithVaryingTrackLineAndColumn(name, callback) {
test(
name + " (with trackLineAndColumn: false) ",
function() { callback({ trackLineAndColumn: false }); }
);
test(
name + " (with trackLineAndColumn: true) ",
function() { callback({ trackLineAndColumn: true }); }
);
}
testWithVaryingTrackLineAndColumn("choices", function(options) {
var parser = PEG.buildParser('start = "a" / "b" / "c"', options);
parses(parser, "a", "a");
parses(parser, "b", "b");
parses(parser, "c", "c");
@ -12,12 +23,12 @@ test("choices", function() {
doesNotParse(parser, "d");
});
test("sequences", function() {
var emptySequenceParser = PEG.buildParser('start = ');
testWithVaryingTrackLineAndColumn("sequences", function(options) {
var emptySequenceParser = PEG.buildParser('start = ', options);
parses(emptySequenceParser, "", []);
doesNotParse(emptySequenceParser, "abc");
var nonEmptySequenceParser = PEG.buildParser('start = "a" "b" "c"');
var nonEmptySequenceParser = PEG.buildParser('start = "a" "b" "c"', options);
parses(nonEmptySequenceParser, "abc", ["a", "b", "c"]);
doesNotParse(nonEmptySequenceParser, "");
doesNotParse(nonEmptySequenceParser, "ab");
@ -28,18 +39,18 @@ test("sequences", function() {
* Test that the parsing position returns after unsuccessful parsing of a
* sequence.
*/
var posTestParser = PEG.buildParser('start = ("a" "b") / "a"');
var posTestParser = PEG.buildParser('start = ("a" "b") / "a"', options);
parses(posTestParser, "a", "a");
});
test("labels", function() {
var parser = PEG.buildParser('start = label:"a"');
testWithVaryingTrackLineAndColumn("labels", function(options) {
var parser = PEG.buildParser('start = label:"a"', options);
parses(parser, "a", "a");
doesNotParse(parser, "b");
});
test("simple and", function() {
var parser = PEG.buildParser('start = "a" &"b" "b"');
testWithVaryingTrackLineAndColumn("simple and", function(options) {
var parser = PEG.buildParser('start = "a" &"b" "b"', options);
parses(parser, "ab", ["a", "", "b"]);
doesNotParse(parser, "ac");
@ -49,8 +60,8 @@ test("simple and", function() {
*/
});
test("simple not", function() {
var parser = PEG.buildParser('start = "a" !"b"');
testWithVaryingTrackLineAndColumn("simple not", function(options) {
var parser = PEG.buildParser('start = "a" !"b"', options);
parses(parser, "a", ["a", ""]);
doesNotParse(parser, "ab");
@ -62,15 +73,24 @@ test("simple not", function() {
parses(posTestParser, "ac", ["a", "", "c"]);
});
test("semantic and", function() {
var acceptingParser = PEG.buildParser('start = "a" &{ return true; } "b"');
test("semantic and (with trackLineAndColumn: false)", function() {
var options = { trackLineAndColumn: false };
var acceptingParser = PEG.buildParser(
'start = "a" &{ return true; } "b"',
options
);
parses(acceptingParser, "ab", ["a", "", "b"]);
var rejectingParser = PEG.buildParser('start = "a" &{ return false; } "b"');
var rejectingParser = PEG.buildParser(
'start = "a" &{ return false; } "b"',
options
);
doesNotParse(rejectingParser, "ab");
var singleElementUnlabeledParser = PEG.buildParser(
'start = "a" &{ return arguments.length === 1 && offset === 1; }'
'start = "a" &{ return arguments.length === 1 && offset === 1; }',
options
);
parses(singleElementUnlabeledParser, "a", ["a", ""]);
@ -80,11 +100,12 @@ test("semantic and", function() {
' && offset === 1',
' && a === "a";',
' }'
].join("\n"));
].join("\n"), options);
parses(singleElementLabeledParser, "a", ["a", ""]);
var multiElementUnlabeledParser = PEG.buildParser(
'start = "a" "b" "c" &{ return arguments.length === 1 && offset === 3; }'
'start = "a" "b" "c" &{ return arguments.length === 1 && offset === 3; }',
options
);
parses(multiElementUnlabeledParser, "abc", ["a", "b", "c", ""]);
@ -95,14 +116,14 @@ test("semantic and", function() {
' && a === "a"',
' && c === "c";',
' }'
].join("\n"));
].join("\n"), options);
parses(multiElementLabeledParser, "abc", ["a", "b", "c", ""]);
var innerElementsUnlabeledParser = PEG.buildParser([
'start = "a"',
' ("b" "c" "d" &{ return arguments.length === 1 && offset === 4; })',
' "e"'
].join("\n"));
].join("\n"), options);
parses(innerElementsUnlabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
var innerElementsLabeledParser = PEG.buildParser([
@ -116,19 +137,138 @@ test("semantic and", function() {
' }',
' )',
' "e"'
].join("\n"));
].join("\n"), options);
parses(innerElementsLabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
});
test("semantic and (with trackLineAndColumn: true)", function() {
var options = { trackLineAndColumn: true };
var acceptingParser = PEG.buildParser(
'start = "a" &{ return true; } "b"',
options
);
parses(acceptingParser, "ab", ["a", "", "b"]);
var rejectingParser = PEG.buildParser(
'start = "a" &{ return false; } "b"',
options
);
doesNotParse(rejectingParser, "ab");
var singleElementUnlabeledParser = PEG.buildParser([
'start = "a" &{',
' return arguments.length === 3',
' && offset === 1',
' && line === 1',
' && column === 2;',
' }'
].join("\n"), options);
parses(singleElementUnlabeledParser, "a", ["a", ""]);
var singleElementLabeledParser = PEG.buildParser([
'start = a:"a" &{',
' return arguments.length === 4',
' && offset === 1',
' && line === 1',
' && column === 2',
' && a === "a";',
' }'
].join("\n"), options);
parses(singleElementLabeledParser, "a", ["a", ""]);
var multiElementUnlabeledParser = PEG.buildParser([
'start = "a" "b" "c" &{',
' return arguments.length === 3',
' && offset === 3',
' && line === 1',
' && column === 4;',
' }'
].join("\n"), options);
parses(multiElementUnlabeledParser, "abc", ["a", "b", "c", ""]);
var multiElementLabeledParser = PEG.buildParser([
'start = a:"a" "b" c:"c" &{',
' return arguments.length === 5',
' && offset === 3',
' && line === 1',
' && column === 4',
' && a === "a"',
' && c === "c";',
' }'
].join("\n"), options);
parses(multiElementLabeledParser, "abc", ["a", "b", "c", ""]);
var innerElementsUnlabeledParser = PEG.buildParser([
'start = "a"',
' (',
' "b" "c" "d" &{',
' return arguments.length === 3',
' && offset === 4',
' && line === 1',
' && column === 5;',
' }',
' )',
' "e"'
].join("\n"), options);
parses(innerElementsUnlabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
var innerElementsLabeledParser = PEG.buildParser([
'start = "a"',
' (',
' b:"b" "c" d:"d" &{',
' return arguments.length === 5',
' && offset === 4',
' && line === 1',
' && column === 5',
' && b === "b"',
' && d === "d";',
' }',
' )',
' "e"'
].join("\n"), options);
parses(innerElementsLabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
var digitsParser = 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);
parses(digitsParser, "1\n2\n\n3\n\n\n4 5 x", [7, 5]);
/* Non-Unix newlines */
parses(digitsParser, "1\rx", [2, 1]); // Old Mac
parses(digitsParser, "1\r\nx", [2, 1]); // Windows
parses(digitsParser, "1\n\rx", [3, 1]); // mismatched
/* Strange newlines */
parses(digitsParser, "1\u2028x", [2, 1]); // line separator
parses(digitsParser, "1\u2029x", [2, 1]); // paragraph separator
});
test("semantic not", function() {
var acceptingParser = PEG.buildParser('start = "a" !{ return false; } "b"');
test("semantic not (with trackLineAndColumn: false)", function() {
var options = { trackLineAndColumn: false };
var acceptingParser = PEG.buildParser(
'start = "a" !{ return false; } "b"',
options
);
parses(acceptingParser, "ab", ["a", "", "b"]);
var rejectingParser = PEG.buildParser('start = "a" !{ return true; } "b"');
var rejectingParser = PEG.buildParser(
'start = "a" !{ return true; } "b"',
options
);
doesNotParse(rejectingParser, "ab");
var singleElementUnlabeledParser = PEG.buildParser(
'start = "a" !{ return arguments.length !== 1 || offset !== 1; }'
'start = "a" !{ return arguments.length !== 1 || offset !== 1; }',
options
);
parses(singleElementUnlabeledParser, "a", ["a", ""]);
@ -138,11 +278,12 @@ test("semantic not", function() {
' || offset !== 1',
' || a !== "a";',
' }'
].join("\n"));
].join("\n"), options);
parses(singleElementLabeledParser, "a", ["a", ""]);
var multiElementUnlabeledParser = PEG.buildParser(
'start = "a" "b" "c" !{ return arguments.length !== 1 || offset !== 3; }'
'start = "a" "b" "c" !{ return arguments.length !== 1 || offset !== 3; }',
options
);
parses(multiElementUnlabeledParser, "abc", ["a", "b", "c", ""]);
@ -153,14 +294,14 @@ test("semantic not", function() {
' || a !== "a"',
' || c !== "c";',
' }'
].join("\n"));
].join("\n"), options);
parses(multiElementLabeledParser, "abc", ["a", "b", "c", ""]);
var innerElementsUnlabeledParser = PEG.buildParser([
'start = "a"',
' ("b" "c" "d" !{ return arguments.length !== 1 || offset !== 4; })',
' "e"'
].join("\n"));
].join("\n"), options);
parses(innerElementsUnlabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
var innerElementsLabeledParser = PEG.buildParser([
@ -174,53 +315,170 @@ test("semantic not", function() {
' }',
' )',
' "e"'
].join("\n"));
].join("\n"), options);
parses(innerElementsLabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
});
test("optional expressions", function() {
var parser = PEG.buildParser('start = "a"?');
test("semantic not (with trackLineAndColumn: true)", function() {
var options = { trackLineAndColumn: true };
var acceptingParser = PEG.buildParser(
'start = "a" !{ return false; } "b"',
options
);
parses(acceptingParser, "ab", ["a", "", "b"]);
var rejectingParser = PEG.buildParser(
'start = "a" !{ return true; } "b"',
options
);
doesNotParse(rejectingParser, "ab");
var singleElementUnlabeledParser = PEG.buildParser([
'start = "a" !{',
' return arguments.length !== 3',
' || offset !== 1',
' || line !== 1',
' || column !== 2;',
' }'
].join("\n"), options);
parses(singleElementUnlabeledParser, "a", ["a", ""]);
var singleElementLabeledParser = PEG.buildParser([
'start = a:"a" !{',
' return arguments.length !== 4',
' || offset !== 1',
' || line !== 1',
' || column !== 2',
' || a !== "a";',
' }'
].join("\n"), options);
parses(singleElementLabeledParser, "a", ["a", ""]);
var multiElementUnlabeledParser = PEG.buildParser([
'start = "a" "b" "c" !{',
' return arguments.length !== 3',
' || offset !== 3',
' || line !== 1',
' || column !== 4;',
' }'
].join("\n"), options);
parses(multiElementUnlabeledParser, "abc", ["a", "b", "c", ""]);
var multiElementLabeledParser = PEG.buildParser([
'start = a:"a" "b" c:"c" !{',
' return arguments.length !== 5',
' || offset !== 3',
' || line !== 1',
' || column !== 4',
' || a !== "a"',
' || c !== "c";',
' }'
].join("\n"), options);
parses(multiElementLabeledParser, "abc", ["a", "b", "c", ""]);
var innerElementsUnlabeledParser = PEG.buildParser([
'start = "a"',
' (',
' "b" "c" "d" !{',
' return arguments.length !== 3',
' || offset !== 4',
' || line !== 1',
' || column !== 5;',
' }',
' )',
' "e"'
].join("\n"), options);
parses(innerElementsUnlabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
var innerElementsLabeledParser = PEG.buildParser([
'start = "a"',
' (',
' b:"b" "c" d:"d" !{',
' return arguments.length !== 5',
' || offset !== 4',
' || line !== 1',
' || column !== 5',
' || b !== "b"',
' || d !== "d";',
' }',
' )',
' "e"'
].join("\n"), options);
parses(innerElementsLabeledParser, "abcde", ["a", ["b", "c", "d", ""], "e"]);
var digitsParser = 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);
parses(digitsParser, "1\n2\n\n3\n\n\n4 5 x", [7, 5]);
/* Non-Unix newlines */
parses(digitsParser, "1\rx", [2, 1]); // Old Mac
parses(digitsParser, "1\r\nx", [2, 1]); // Windows
parses(digitsParser, "1\n\rx", [3, 1]); // mismatched
/* Strange newlines */
parses(digitsParser, "1\u2028x", [2, 1]); // line separator
parses(digitsParser, "1\u2029x", [2, 1]); // paragraph separator
});
testWithVaryingTrackLineAndColumn("optional expressions", function(options) {
var parser = PEG.buildParser('start = "a"?', options);
parses(parser, "", "");
parses(parser, "a", "a");
});
test("zero or more expressions", function() {
var parser = PEG.buildParser('start = "a"*');
testWithVaryingTrackLineAndColumn("zero or more expressions", function(options) {
var parser = PEG.buildParser('start = "a"*', options);
parses(parser, "", []);
parses(parser, "a", ["a"]);
parses(parser, "aaa", ["a", "a", "a"]);
});
test("one or more expressions", function() {
var parser = PEG.buildParser('start = "a"+');
testWithVaryingTrackLineAndColumn("one or more expressions", function(options) {
var parser = PEG.buildParser('start = "a"+', options);
doesNotParse(parser, "");
parses(parser, "a", ["a"]);
parses(parser, "aaa", ["a", "a", "a"]);
});
test("actions", function() {
test("actions (with trackLineAndColumn: false)", function() {
var options = { trackLineAndColumn: false };
var singleElementUnlabeledParser = PEG.buildParser(
'start = "a" { return arguments.length; }'
'start = "a" { return arguments.length; }',
options
);
parses(singleElementUnlabeledParser, "a", 1);
var singleElementLabeledParser = PEG.buildParser(
'start = a:"a" { return [arguments.length, offset, a]; }'
'start = a:"a" { return [arguments.length, offset, a]; }',
options
);
parses(singleElementLabeledParser, "a", [2, 0, "a"]);
var multiElementUnlabeledParser = PEG.buildParser(
'start = "a" "b" "c" { return arguments.length; }'
'start = "a" "b" "c" { return arguments.length; }',
options
);
parses(multiElementUnlabeledParser, "abc", 1);
var multiElementLabeledParser = PEG.buildParser(
'start = a:"a" "b" c:"c" { return [arguments.length, offset, a, c]; }'
'start = a:"a" "b" c:"c" { return [arguments.length, offset, a, c]; }',
options
);
parses(multiElementLabeledParser, "abc", [3, 0, "a", "c"]);
var innerElementsUnlabeledParser = PEG.buildParser(
'start = "a" ("b" "c" "d" { return arguments.length; }) "e"'
'start = "a" ("b" "c" "d" { return arguments.length; }) "e"',
options
);
parses(innerElementsUnlabeledParser, "abcde", ["a", 1, "e"]);
@ -228,77 +486,175 @@ test("actions", function() {
'start = "a"',
' (b:"b" "c" d:"d" { return [arguments.length, offset, b, d]; })',
' "e"'
].join("\n"));
].join("\n"), options);
parses(innerElementsLabeledParser, "abcde", ["a", [3, 1, "b", "d"], "e"]);
/*
* Test that the parsing position returns after successfull parsing of the
* action expression and action returning |null|.
*/
var posTestParser = PEG.buildParser('start = "a" { return null; } / "a"');
var posTestParser = PEG.buildParser(
'start = "a" { return null; } / "a"',
options
);
parses(posTestParser, "a", "a");
/* Test that the action is not called when its expression does not match. */
var notAMatchParser = PEG.buildParser(
'start = "a" { ok(false, "action got called when it should not be"); }'
'start = "a" { ok(false, "action got called when it should not be"); }',
options
);
doesNotParse(notAMatchParser, "b");
});
test("initializer", function() {
test("actions (with trackLineAndColumn: true)", function() {
var options = { trackLineAndColumn: true };
var singleElementUnlabeledParser = PEG.buildParser(
'start = "a" { return arguments.length; }',
options
);
parses(singleElementUnlabeledParser, "a", 3);
var singleElementLabeledParser = PEG.buildParser(
'start = a:"a" { return [arguments.length, offset, line, column, a]; }',
options
);
parses(singleElementLabeledParser, "a", [4, 0, 1, 1, "a"]);
var multiElementUnlabeledParser = PEG.buildParser(
'start = "a" "b" "c" { return arguments.length; }',
options
);
parses(multiElementUnlabeledParser, "abc", 3);
var multiElementLabeledParser = PEG.buildParser([
'start = a:"a" "b" c:"c" {',
' return [arguments.length, offset, line, column, a, c];',
'}'
].join("\n"), options);
parses(multiElementLabeledParser, "abc", [5, 0, 1, 1, "a", "c"]);
var innerElementsUnlabeledParser = PEG.buildParser(
'start = "a" ("b" "c" "d" { return arguments.length; }) "e"',
options
);
parses(innerElementsUnlabeledParser, "abcde", ["a", 3, "e"]);
var innerElementsLabeledParser = PEG.buildParser([
'start = "a"',
' (',
' b:"b" "c" d:"d" {',
' return [arguments.length, offset, line, column, b, d];',
' }',
' )',
' "e"'
].join("\n"), options);
parses(
innerElementsLabeledParser,
"abcde",
["a", [5, 1, 1, 2, "b", "d"], "e"]
);
/*
* Test that the parsing position returns after successfull parsing of the
* action expression and action returning |null|.
*/
var posTestParser = PEG.buildParser(
'start = "a" { return null; } / "a"',
options
);
parses(posTestParser, "a", "a");
/* Test that the action is not called when its expression does not match. */
var notAMatchParser = PEG.buildParser(
'start = "a" { ok(false, "action got called when it should not be"); }',
options
);
doesNotParse(notAMatchParser, "b");
var numbersParser = 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);
parses(numbersParser, "1\n2\n\n3\n\n\n4 5 x", [7, 5]);
/* Non-Unix newlines */
parses(numbersParser, "1\rx", [2, 1]); // Old Mac
parses(numbersParser, "1\r\nx", [2, 1]); // Windows
parses(numbersParser, "1\n\rx", [3, 1]); // mismatched
/* Strange newlines */
parses(numbersParser, "1\u2028x", [2, 1]); // line separator
parses(numbersParser, "1\u2029x", [2, 1]); // paragraph separator
});
testWithVaryingTrackLineAndColumn("initializer", function(options) {
var variableInActionParser = PEG.buildParser(
'{ a = 42; }; start = "a" { return a; }'
'{ a = 42; }; start = "a" { return a; }',
options
);
parses(variableInActionParser, "a", 42);
var functionInActionParser = PEG.buildParser(
'{ function f() { return 42; } }; start = "a" { return f(); }'
'{ function f() { return 42; } }; start = "a" { return f(); }',
options
);
parses(functionInActionParser, "a", 42);
var variableInSemanticAndParser = PEG.buildParser(
'{ a = 42; }; start = "a" &{ return a === 42; }'
'{ a = 42; }; start = "a" &{ return a === 42; }',
options
);
parses(variableInSemanticAndParser, "a", ["a", ""]);
var functionInSemanticAndParser = PEG.buildParser(
'{ function f() { return 42; } }; start = "a" &{ return f() === 42; }'
'{ function f() { return 42; } }; start = "a" &{ return f() === 42; }',
options
);
parses(functionInSemanticAndParser, "a", ["a", ""]);
var variableInSemanticNotParser = PEG.buildParser(
'{ a = 42; }; start = "a" !{ return a !== 42; }'
'{ a = 42; }; start = "a" !{ return a !== 42; }',
options
);
parses(variableInSemanticNotParser, "a", ["a", ""]);
var functionInSemanticNotParser = PEG.buildParser(
'{ function f() { return 42; } }; start = "a" !{ return f() !== 42; }'
'{ function f() { return 42; } }; start = "a" !{ return f() !== 42; }',
options
);
parses(functionInSemanticNotParser, "a", ["a", ""]);
});
test("rule references", function() {
testWithVaryingTrackLineAndColumn("rule references", function(options) {
var parser = PEG.buildParser([
'start = static / dynamic',
'static = "C" / "C++" / "Java" / "C#"',
'dynamic = "Ruby" / "Python" / "JavaScript"'
].join("\n"));
].join("\n"), options);
parses(parser, "Java", "Java");
parses(parser, "Python", "Python");
});
test("literals", function() {
var zeroCharParser = PEG.buildParser('start = ""');
testWithVaryingTrackLineAndColumn("literals", function(options) {
var zeroCharParser = PEG.buildParser('start = ""', options);
parses(zeroCharParser, "", "");
doesNotParse(zeroCharParser, "a");
var oneCharCaseSensitiveParser = PEG.buildParser('start = "a"');
var oneCharCaseSensitiveParser = PEG.buildParser('start = "a"', options);
parses(oneCharCaseSensitiveParser, "a", "a");
doesNotParse(oneCharCaseSensitiveParser, "");
doesNotParse(oneCharCaseSensitiveParser, "A");
doesNotParse(oneCharCaseSensitiveParser, "b");
var multiCharCaseSensitiveParser = PEG.buildParser('start = "abcd"');
var multiCharCaseSensitiveParser = PEG.buildParser('start = "abcd"', options);
parses(multiCharCaseSensitiveParser, "abcd", "abcd");
doesNotParse(multiCharCaseSensitiveParser, "");
doesNotParse(multiCharCaseSensitiveParser, "abc");
@ -306,13 +662,16 @@ test("literals", function() {
doesNotParse(multiCharCaseSensitiveParser, "ABCD");
doesNotParse(multiCharCaseSensitiveParser, "efgh");
var oneCharCaseInsensitiveParser = PEG.buildParser('start = "a"i');
var oneCharCaseInsensitiveParser = PEG.buildParser('start = "a"i', options);
parses(oneCharCaseInsensitiveParser, "a", "a");
parses(oneCharCaseInsensitiveParser, "A", "A");
doesNotParse(oneCharCaseInsensitiveParser, "");
doesNotParse(oneCharCaseInsensitiveParser, "b");
var multiCharCaseInsensitiveParser = PEG.buildParser('start = "abcd"i');
var multiCharCaseInsensitiveParser = PEG.buildParser(
'start = "abcd"i',
options
);
parses(multiCharCaseInsensitiveParser, "abcd", "abcd");
parses(multiCharCaseInsensitiveParser, "ABCD", "ABCD");
doesNotParse(multiCharCaseInsensitiveParser, "");
@ -324,12 +683,12 @@ test("literals", function() {
* Test that the parsing position moves forward after successful parsing of
* a literal.
*/
var posTestParser = PEG.buildParser('start = "a" "b"');
var posTestParser = PEG.buildParser('start = "a" "b"', options);
parses(posTestParser, "ab", ["a", "b"]);
});
test("anys", function() {
var parser = PEG.buildParser('start = .');
testWithVaryingTrackLineAndColumn("anys", function(options) {
var parser = PEG.buildParser('start = .', options);
parses(parser, "a", "a");
doesNotParse(parser, "");
doesNotParse(parser, "ab");
@ -338,22 +697,25 @@ test("anys", function() {
* Test that the parsing position moves forward after successful parsing of
* an any.
*/
var posTestParser = PEG.buildParser('start = . .');
var posTestParser = PEG.buildParser('start = . .', options);
parses(posTestParser, "ab", ["a", "b"]);
});
test("classes", function() {
var emptyClassParser = PEG.buildParser('start = []');
testWithVaryingTrackLineAndColumn("classes", function(options) {
var emptyClassParser = PEG.buildParser('start = []', options);
doesNotParse(emptyClassParser, "");
doesNotParse(emptyClassParser, "a");
doesNotParse(emptyClassParser, "ab");
var invertedEmptyClassParser = PEG.buildParser('start = [^]');
var invertedEmptyClassParser = PEG.buildParser('start = [^]', options);
doesNotParse(invertedEmptyClassParser, "");
parses(invertedEmptyClassParser, "a", "a");
doesNotParse(invertedEmptyClassParser, "ab");
var nonEmptyCaseSensitiveClassParser = PEG.buildParser('start = [ab-d]');
var nonEmptyCaseSensitiveClassParser = PEG.buildParser(
'start = [ab-d]',
options
);
parses(nonEmptyCaseSensitiveClassParser, "a", "a");
parses(nonEmptyCaseSensitiveClassParser, "b", "b");
parses(nonEmptyCaseSensitiveClassParser, "c", "c");
@ -366,7 +728,10 @@ test("classes", function() {
doesNotParse(nonEmptyCaseSensitiveClassParser, "e");
doesNotParse(nonEmptyCaseSensitiveClassParser, "ab");
var invertedNonEmptyCaseSensitiveClassParser = PEG.buildParser('start = [^ab-d]');
var invertedNonEmptyCaseSensitiveClassParser = PEG.buildParser(
'start = [^ab-d]',
options
);
parses(invertedNonEmptyCaseSensitiveClassParser, "A", "A");
parses(invertedNonEmptyCaseSensitiveClassParser, "B", "B");
parses(invertedNonEmptyCaseSensitiveClassParser, "C", "C");
@ -379,7 +744,10 @@ test("classes", function() {
doesNotParse(invertedNonEmptyCaseSensitiveClassParser, "");
doesNotParse(invertedNonEmptyCaseSensitiveClassParser, "ab");
var nonEmptyCaseInsensitiveClassParser = PEG.buildParser('start = [ab-d]i');
var nonEmptyCaseInsensitiveClassParser = PEG.buildParser(
'start = [ab-d]i',
options
);
parses(nonEmptyCaseInsensitiveClassParser, "a", "a");
parses(nonEmptyCaseInsensitiveClassParser, "b", "b");
parses(nonEmptyCaseInsensitiveClassParser, "c", "c");
@ -392,7 +760,10 @@ test("classes", function() {
doesNotParse(nonEmptyCaseInsensitiveClassParser, "e");
doesNotParse(nonEmptyCaseInsensitiveClassParser, "ab");
var invertedNonEmptyCaseInsensitiveClassParser = PEG.buildParser('start = [^ab-d]i');
var invertedNonEmptyCaseInsensitiveClassParser = PEG.buildParser(
'start = [^ab-d]i',
options
);
parses(invertedNonEmptyCaseInsensitiveClassParser, "e", "e");
doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "a", "a");
doesNotParse(invertedNonEmptyCaseInsensitiveClassParser, "b", "b");
@ -409,11 +780,11 @@ test("classes", function() {
* Test that the parsing position moves forward after successful parsing of
* a class.
*/
var posTestParser = PEG.buildParser('start = [ab-d] [ab-d]');
var posTestParser = PEG.buildParser('start = [ab-d] [ab-d]', options);
parses(posTestParser, "ab", ["a", "b"]);
});
test("cache", function() {
testWithVaryingTrackLineAndColumn("cache", function(options) {
/*
* Should trigger a codepath where the cache is used (for the "a" rule).
*/
@ -422,19 +793,19 @@ test("cache", function() {
'a = "a"',
'b = "b"',
'c = "c"'
].join("\n"));
].join("\n"), options);
parses(parser, "ac", ["a", "c"]);
});
test("indempotence", function() {
var parser1 = PEG.buildParser('start = "abcd"');
var parser2 = PEG.buildParser('start = "abcd"');
testWithVaryingTrackLineAndColumn("indempotence", function(options) {
var parser1 = PEG.buildParser('start = "abcd"', options);
var parser2 = PEG.buildParser('start = "abcd"', options);
strictEqual(parser1.toSource(), parser2.toSource());
});
test("error details", function() {
var literalParser = PEG.buildParser('start = "abcd"');
testWithVaryingTrackLineAndColumn("error details", function(options) {
var literalParser = PEG.buildParser('start = "abcd"', options);
doesNotParseWithDetails(
literalParser,
"",
@ -457,7 +828,7 @@ test("error details", function() {
'Expected end of input but "e" found.'
);
var classParser = PEG.buildParser('start = [a-d]');
var classParser = PEG.buildParser('start = [a-d]', options);
doesNotParseWithDetails(
classParser,
"",
@ -465,7 +836,7 @@ test("error details", function() {
null,
'Expected [a-d] but end of input found.'
);
var negativeClassParser = PEG.buildParser('start = [^a-d]');
var negativeClassParser = PEG.buildParser('start = [^a-d]', options);
doesNotParseWithDetails(
negativeClassParser,
"",
@ -474,7 +845,7 @@ test("error details", function() {
'Expected [^a-d] but end of input found.'
);
var anyParser = PEG.buildParser('start = .');
var anyParser = PEG.buildParser('start = .', options);
doesNotParseWithDetails(
anyParser,
"",
@ -483,7 +854,10 @@ test("error details", function() {
'Expected any character but end of input found.'
);
var namedRuleWithLiteralParser = PEG.buildParser('start "digit" = [0-9]');
var namedRuleWithLiteralParser = PEG.buildParser(
'start "digit" = [0-9]',
options
);
doesNotParseWithDetails(
namedRuleWithLiteralParser,
"a",
@ -492,7 +866,7 @@ test("error details", function() {
'Expected digit but "a" found.'
);
var namedRuleWithAnyParser = PEG.buildParser('start "whatever" = .');
var namedRuleWithAnyParser = PEG.buildParser('start "whatever" = .', options);
doesNotParseWithDetails(
namedRuleWithAnyParser,
"",
@ -504,7 +878,7 @@ test("error details", function() {
var namedRuleWithNamedRuleParser = PEG.buildParser([
'start "digits" = digit+',
'digit "digit" = [0-9]'
].join("\n"));
].join("\n"), options);
doesNotParseWithDetails(
namedRuleWithNamedRuleParser,
"",
@ -513,7 +887,7 @@ test("error details", function() {
'Expected digits but end of input found.'
);
var choiceParser1 = PEG.buildParser('start = "a" / "b" / "c"');
var choiceParser1 = PEG.buildParser('start = "a" / "b" / "c"', options);
doesNotParseWithDetails(
choiceParser1,
"def",
@ -522,7 +896,7 @@ test("error details", function() {
'Expected "a", "b" or "c" but "d" found.'
);
var choiceParser2 = PEG.buildParser('start = "a" "b" "c" / "a"');
var choiceParser2 = PEG.buildParser('start = "a" "b" "c" / "a"', options);
doesNotParseWithDetails(
choiceParser2,
"abd",
@ -531,7 +905,7 @@ test("error details", function() {
'Expected "c" but "d" found.'
);
var simpleNotParser = PEG.buildParser('start = !"a" "b"');
var simpleNotParser = PEG.buildParser('start = !"a" "b"', options);
doesNotParseWithDetails(
simpleNotParser,
"c",
@ -540,7 +914,7 @@ test("error details", function() {
'Expected "b" but "c" found.'
);
var simpleAndParser = PEG.buildParser('start = &"a" [a-b]');
var simpleAndParser = PEG.buildParser('start = &"a" [a-b]', options);
doesNotParseWithDetails(
simpleAndParser,
"c",
@ -549,7 +923,7 @@ test("error details", function() {
'Expected end of input but "c" found.'
);
var emptyParser = PEG.buildParser('start = ');
var emptyParser = PEG.buildParser('start = ', options);
doesNotParseWithDetails(
emptyParser,
"something",
@ -558,7 +932,7 @@ test("error details", function() {
'Expected end of input but "s" found.'
);
var duplicateErrorParser = PEG.buildParser('start = "a" / "a"');
var duplicateErrorParser = PEG.buildParser('start = "a" / "a"', options);
doesNotParseWithDetails(
duplicateErrorParser,
"",
@ -567,7 +941,7 @@ test("error details", function() {
'Expected "a" but end of input found.'
);
var unsortedErrorsParser = PEG.buildParser('start = "b" / "a"');
var unsortedErrorsParser = PEG.buildParser('start = "b" / "a"', options);
doesNotParseWithDetails(
unsortedErrorsParser,
"",
@ -577,8 +951,8 @@ test("error details", function() {
);
});
test("error positions", function() {
var simpleParser = PEG.buildParser('start = "a"');
testWithVaryingTrackLineAndColumn("error positions", function(options) {
var simpleParser = PEG.buildParser('start = "a"', options);
/* Regular match failure */
doesNotParseWithPos(simpleParser, "b", 0, 1, 1);
@ -590,7 +964,7 @@ test("error positions", function() {
'start = line (("\\r" / "\\n" / "\\u2028" / "\\u2029")+ line)*',
'line = digits (" "+ digits)*',
'digits = digits:[0-9]+ { return digits.join(""); }'
].join("\n"));
].join("\n"), options);
doesNotParseWithPos(digitsParser, "1\n2\n\n3\n\n\n4 5 x", 13, 7, 5);
@ -604,11 +978,11 @@ test("error positions", function() {
doesNotParseWithPos(digitsParser, "1\u2029x", 2, 2, 1); // paragraph separator
});
test("start rule", function() {
testWithVaryingTrackLineAndColumn("start rule", function(options) {
var parser = PEG.buildParser([
'a = .* { return "alpha"; }',
'b = .* { return "beta"; }'
].join("\n"));
].join("\n"), options);
/* Default start rule = the first one */
parses(parser, "whatever", "alpha");
@ -631,7 +1005,7 @@ test("start rule", function() {
* http://en.wikipedia.org/w/index.php?title=Parsing_expression_grammar&oldid=335106938.
*/
test("arithmetics", function() {
testWithVaryingTrackLineAndColumn("arithmetics", function(options) {
/*
* Value [0-9]+ / '(' Expr ')'
* Product Value (('*' / '/') Value)*
@ -658,7 +1032,7 @@ test("arithmetics", function() {
' }',
'Value = digits:[0-9]+ { return parseInt(digits.join("")); }',
' / "(" expr:Expr ")" { return expr; }'
].join("\n"));
].join("\n"), options);
/* Test "value" rule. */
parses(parser, "0", 0);
@ -686,7 +1060,7 @@ test("arithmetics", function() {
parses(parser, "(1+2)*(3+4)",(1+2)*(3+4));
});
test("non-context-free language", function() {
testWithVaryingTrackLineAndColumn("non-context-free language", function(options) {
/* The following parsing expression grammar describes the classic
* non-context-free language { a^n b^n c^n : n >= 1 }:
*
@ -698,7 +1072,7 @@ test("non-context-free language", function() {
'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"));
].join("\n"), options);
parses(parser, "abc", "abc");
parses(parser, "aaabbbccc", "aaabbbccc");
@ -710,7 +1084,7 @@ test("non-context-free language", function() {
doesNotParse(parser, "aaabbbcccc");
});
test("nested comments", function() {
testWithVaryingTrackLineAndColumn("nested comments", function(options) {
/*
* Begin "(*"
* End "*)"
@ -725,7 +1099,7 @@ test("nested comments", function() {
'Z = .',
'Begin = "(*"',
'End = "*)"'
].join("\n"));
].join("\n"), options);
parses(parser, "(**)", "(**)");
parses(parser, "(*abc*)", "(*abc*)");

Loading…
Cancel
Save