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

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

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

Loading…
Cancel
Save