From 52d7ec22248e53d11044d1628fda329a7d8402cf Mon Sep 17 00:00:00 2001 From: David Majda Date: Sun, 25 Mar 2012 13:31:28 +0200 Subject: [PATCH] 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. --- src/emitter.js | 195 +++++++++----- src/parser.js | 88 +++---- test/compiler-test.js | 580 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 652 insertions(+), 211 deletions(-) diff --git a/src/emitter.js b/src/emitter.js index 58b47bf..b807178 100644 --- a/src/emitter.js +++ b/src/emitter.js @@ -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,36 +396,38 @@ PEG.compiler.emitter = function(ast, options) { ' return cleanExpected;', ' }', ' ', - ' 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', - ' * there. However IE\'s |split| implementation is so broken that it was', - ' * enough to prevent it.', - ' */', - ' ', - ' var line = 1;', - ' var column = 1;', - ' var seenCR = false;', - ' ', - ' for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) {', - ' var ch = input.charAt(i);', - ' if (ch === "\\n") {', - ' if (!seenCR) { line++; }', - ' column = 1;', - ' seenCR = false;', - ' } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {', - ' line++;', - ' column = 1;', - ' seenCR = true;', - ' } else {', - ' column++;', - ' seenCR = false;', + ' #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', + ' * there. However IE\'s |split| implementation is so broken that it was', + ' * enough to prevent it.', + ' */', + ' ', + ' var line = 1;', + ' var column = 1;', + ' var seenCR = false;', + ' ', + ' for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) {', + ' var ch = input.charAt(i);', + ' if (ch === "\\n") {', + ' if (!seenCR) { line++; }', + ' column = 1;', + ' seenCR = false;', + ' } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {', + ' line++;', + ' column = 1;', + ' seenCR = true;', + ' } else {', + ' column++;', + ' seenCR = false;', + ' }', ' }', + ' ', + ' return { line: line, column: column };', ' }', - ' ', - ' 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;', - ' var errorPosition = computeErrorPosition();', + ' #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); } diff --git a/src/parser.js b/src/parser.js index 41a925d..7cb4fc2 100644 --- a/src/parser.js +++ b/src/parser.js @@ -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) { diff --git a/test/compiler-test.js b/test/compiler-test.js index c9a674a..b6e9dd6 100644 --- a/test/compiler-test.js +++ b/test/compiler-test.js @@ -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*)");