diff --git a/README.md b/README.md index 1c19e45..a860d22 100644 --- a/README.md +++ b/README.md @@ -288,11 +288,11 @@ The code inside the predicate can access all variables and functions defined in the initializer at the beginning of the grammar. The code inside the predicate can also access the current parse position using -the `offset` variable. It is a zero-based character index into the input string. -If the `trackLineAndColumn` option was set to `true` when the parser was +the `offset` function. It returns a zero-based character index into the input +string. If the `trackLineAndColumn` option was set to `true` when the parser was generated (or `--track-line-and-column` was used on the command line), the code can also access the current line and column using the `line` and `column` -variables. Both are one-based indexes. +functions. Both return one-based indexes. The code inside the predicate can also access options passed to the parser using the `options` variable. @@ -312,11 +312,11 @@ The code inside the predicate can access all variables and functions defined in the initializer at the beginning of the grammar. The code inside the predicate can also access the current parse position using -the `offset` variable. It is a zero-based character index into the input string. -If the `trackLineAndColumn` option was set to `true` when the parser was +the `offset` function. It returns a zero-based character index into the input +string. If the `trackLineAndColumn` option was set to `true` when the parser was generated (or `--track-line-and-column` was used on the command line), the code can also access the current line and column using the `line` and `column` -variables. Both are one-based indexes. +functions. Both return one-based indexes. The code inside the predicate can also access options passed to the parser using the `options` variable. @@ -351,12 +351,12 @@ initializer at the beginning of the grammar. Curly braces in the action code must be balanced. The code inside the action can also access the parse position at the beginning -of the action's expression using the `offset` variable. It is a zero-based +of the action's expression using the `offset` function. It returns a zero-based character index into the input string. If the `trackLineAndColumn` option was set to `true` when the parser was generated (or `--track-line-and-column` was used on the command line), the code can also access the line and column at the -beginning of the action's expression using the `line` and `column` variables. -Both are one-based indexes. +beginning of the action's expression using the `line` and `column` functions. +Both return one-based indexes. The code inside the action can also access options passed to the parser using the `options` variable. diff --git a/lib/compiler/passes/generate-code.js b/lib/compiler/passes/generate-code.js index f1ed480..8bf93de 100644 --- a/lib/compiler/passes/generate-code.js +++ b/lib/compiler/passes/generate-code.js @@ -334,6 +334,7 @@ module.exports = function(ast, options) { ' }', ' ', ' #{posInit("pos")};', + ' #{posInit("reportedPos")};', ' var reportFailures = 0;', // 0 = report, anything > 0 = do not report ' #{posInit("rightmostFailuresPos")};', ' var rightmostFailuresExpected = [];', @@ -370,7 +371,19 @@ module.exports = function(ast, options) { ' return \'\\\\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), \'0\', length);', ' }', ' ', + ' function offset() {', + ' return #{posOffset("reportedPos")};', + ' }', + ' ', ' #if options.trackLineAndColumn', + ' function line() {', + ' return reportedPos.line;', + ' }', + ' ', + ' function column() {', + ' return reportedPos.column;', + ' }', + ' ', ' function clone(object) {', ' var result = {};', ' for (var key in object) {', @@ -604,7 +617,8 @@ module.exports = function(ast, options) { '#{posSave(node)};', '#block emit(node.expression)', 'if (#{r(node.resultIndex)} !== null) {', - ' #{r(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? [r(node.posIndex) + ".offset", r(node.posIndex) + ".line", r(node.posIndex) + ".column"] : [r(node.posIndex)]).concat(map(values(node.params), r)).join(", ")});', + ' reportedPos = #{posClone(r(node.posIndex))};', + ' #{r(node.resultIndex)} = (function(#{keys(node.params).join(", ")}) {#{node.code}})(#{map(values(node.params), r).join(", ")});', '}', 'if (#{r(node.resultIndex)} === null) {', ' #{posRestore(node)};', @@ -651,10 +665,12 @@ module.exports = function(ast, options) { '}' ], semantic_and: [ - '#{r(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(map(values(node.params), r)).join(", ")}) ? "" : null;' + 'reportedPos = #{posClone("pos")};', + '#{r(node.resultIndex)} = (function(#{keys(node.params).join(", ")}) {#{node.code}})(#{map(values(node.params), r).join(", ")}) ? "" : null;' ], semantic_not: [ - '#{r(node.resultIndex)} = (function(#{(options.trackLineAndColumn ? ["offset", "line", "column"] : ["offset"]).concat(keys(node.params)).join(", ")}) {#{node.code}})(#{(options.trackLineAndColumn ? ["pos.offset", "pos.line", "pos.column"] : ["pos"]).concat(map(values(node.params), r)).join(", ")}) ? null : "";' + 'reportedPos = #{posClone("pos")};', + '#{r(node.resultIndex)} = (function(#{keys(node.params).join(", ")}) {#{node.code}})(#{map(values(node.params), r).join(", ")}) ? null : "";' ], optional: [ '#block emit(node.expression)', diff --git a/lib/parser.js b/lib/parser.js index d03a5bd..5cab64a 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -60,6 +60,7 @@ module.exports = (function(){ } var pos = 0; + var reportedPos = 0; var reportFailures = 0; var rightmostFailuresPos = 0; var rightmostFailuresExpected = []; @@ -91,6 +92,10 @@ module.exports = (function(){ return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); } + function offset() { + return reportedPos; + } + function matchFailed(failure) { if (pos < rightmostFailuresPos) { return; @@ -139,14 +144,15 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, initializer, rules) { + reportedPos = r1; + r0 = (function(initializer, rules) { return { type: "grammar", initializer: initializer !== "" ? initializer : null, rules: rules, startRule: rules[0].name }; - })(r1, r4, r5); + })(r4, r5); } if (r0 === null) { pos = r1; @@ -174,12 +180,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, code) { + reportedPos = r1; + r0 = (function(code) { return { type: "initializer", code: code }; - })(r1, r3); + })(r3); } if (r0 === null) { pos = r1; @@ -226,7 +233,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, name, displayName, expression) { + reportedPos = r1; + r0 = (function(name, displayName, expression) { return { type: "rule", name: name, @@ -238,7 +246,7 @@ module.exports = (function(){ } : expression }; - })(r1, r3, r4, r6); + })(r3, r4, r6); } if (r0 === null) { pos = r1; @@ -296,7 +304,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, head, tail) { + reportedPos = r1; + r0 = (function(head, tail) { if (tail.length > 0) { var alternatives = [head].concat(utils.map( tail, @@ -309,7 +318,7 @@ module.exports = (function(){ } else { return head; } - })(r1, r3, r4); + })(r3, r4); } if (r0 === null) { pos = r1; @@ -341,7 +350,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, elements, code) { + reportedPos = r1; + r0 = (function(elements, code) { var expression = elements.length !== 1 ? { type: "sequence", @@ -353,7 +363,7 @@ module.exports = (function(){ expression: expression, code: code }; - })(r1, r3, r4); + })(r3, r4); } if (r0 === null) { pos = r1; @@ -367,14 +377,15 @@ module.exports = (function(){ r2 = parse_labeled(); } if (r0 !== null) { - r0 = (function(offset, elements) { + reportedPos = r1; + r0 = (function(elements) { return elements.length !== 1 ? { type: "sequence", elements: elements } : elements[0]; - })(r1, r0); + })(r0); } if (r0 === null) { pos = r1; @@ -408,13 +419,14 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, label, expression) { + reportedPos = r1; + r0 = (function(label, expression) { return { type: "labeled", label: label, expression: expression }; - })(r1, r3, r5); + })(r3, r5); } if (r0 === null) { pos = r1; @@ -444,12 +456,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, code) { + reportedPos = r1; + r0 = (function(code) { return { type: "semantic_and", code: code }; - })(r1, r4); + })(r4); } if (r0 === null) { pos = r1; @@ -471,12 +484,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, expression) { + reportedPos = r1; + r0 = (function(expression) { return { type: "simple_and", expression: expression }; - })(r1, r4); + })(r4); } if (r0 === null) { pos = r1; @@ -498,12 +512,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, code) { + reportedPos = r1; + r0 = (function(code) { return { type: "semantic_not", code: code }; - })(r1, r4); + })(r4); } if (r0 === null) { pos = r1; @@ -525,12 +540,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, expression) { + reportedPos = r1; + r0 = (function(expression) { return { type: "simple_not", expression: expression }; - })(r1, r4); + })(r4); } if (r0 === null) { pos = r1; @@ -563,12 +579,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, expression) { + reportedPos = r1; + r0 = (function(expression) { return { type: "optional", expression: expression }; - })(r1, r3); + })(r3); } if (r0 === null) { pos = r1; @@ -590,12 +607,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, expression) { + reportedPos = r1; + r0 = (function(expression) { return { type: "zero_or_more", expression: expression }; - })(r1, r3); + })(r3); } if (r0 === null) { pos = r1; @@ -617,12 +635,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, expression) { + reportedPos = r1; + r0 = (function(expression) { return { type: "one_or_more", expression: expression }; - })(r1, r3); + })(r3); } if (r0 === null) { pos = r1; @@ -677,12 +696,13 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, name) { + reportedPos = r1; + r0 = (function(name) { return { type: "rule_ref", name: name }; - })(r1, r3); + })(r3); } if (r0 === null) { pos = r1; @@ -695,7 +715,8 @@ module.exports = (function(){ r1 = pos; r0 = parse_dot(); if (r0 !== null) { - r0 = (function(offset) { return { type: "any" }; })(r1); + reportedPos = r1; + r0 = (function() { return { type: "any" }; })(); } if (r0 === null) { pos = r1; @@ -723,7 +744,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, expression) { return expression; })(r1, r4); + reportedPos = r1; + r0 = (function(expression) { return expression; })(r4); } if (r0 === null) { pos = r1; @@ -755,7 +777,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, braced) { return braced.substr(1, braced.length - 2); })(r1, r3); + reportedPos = r1; + r0 = (function(braced) { return braced.substr(1, braced.length - 2); })(r3); } if (r0 === null) { pos = r1; @@ -819,9 +842,10 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, parts) { + reportedPos = r1; + r0 = (function(parts) { return "{" + parts.join("") + "}"; - })(r1, r4); + })(r4); } if (r0 === null) { pos = r1; @@ -844,7 +868,8 @@ module.exports = (function(){ r0 = null; } if (r0 !== null) { - r0 = (function(offset, chars) { return chars.join(""); })(r1, r0); + reportedPos = r1; + r0 = (function(chars) { return chars.join(""); })(r0); } if (r0 === null) { pos = r1; @@ -894,7 +919,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "="; })(r1); + reportedPos = r1; + r0 = (function() { return "="; })(); } if (r0 === null) { pos = r1; @@ -929,7 +955,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return ":"; })(r1); + reportedPos = r1; + r0 = (function() { return ":"; })(); } if (r0 === null) { pos = r1; @@ -964,7 +991,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return ";"; })(r1); + reportedPos = r1; + r0 = (function() { return ";"; })(); } if (r0 === null) { pos = r1; @@ -999,7 +1027,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "/"; })(r1); + reportedPos = r1; + r0 = (function() { return "/"; })(); } if (r0 === null) { pos = r1; @@ -1034,7 +1063,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "&"; })(r1); + reportedPos = r1; + r0 = (function() { return "&"; })(); } if (r0 === null) { pos = r1; @@ -1069,7 +1099,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "!"; })(r1); + reportedPos = r1; + r0 = (function() { return "!"; })(); } if (r0 === null) { pos = r1; @@ -1104,7 +1135,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "?"; })(r1); + reportedPos = r1; + r0 = (function() { return "?"; })(); } if (r0 === null) { pos = r1; @@ -1139,7 +1171,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "*"; })(r1); + reportedPos = r1; + r0 = (function() { return "*"; })(); } if (r0 === null) { pos = r1; @@ -1174,7 +1207,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "+"; })(r1); + reportedPos = r1; + r0 = (function() { return "+"; })(); } if (r0 === null) { pos = r1; @@ -1209,7 +1243,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "("; })(r1); + reportedPos = r1; + r0 = (function() { return "("; })(); } if (r0 === null) { pos = r1; @@ -1244,7 +1279,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return ")"; })(r1); + reportedPos = r1; + r0 = (function() { return ")"; })(); } if (r0 === null) { pos = r1; @@ -1279,7 +1315,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "."; })(r1); + reportedPos = r1; + r0 = (function() { return "."; })(); } if (r0 === null) { pos = r1; @@ -1390,9 +1427,10 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, head, tail) { + reportedPos = r1; + r0 = (function(head, tail) { return head + tail.join(""); - })(r1, r3, r4); + })(r3, r4); } if (r0 === null) { pos = r1; @@ -1442,13 +1480,14 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, value, flags) { + reportedPos = r1; + r0 = (function(value, flags) { return { type: "literal", value: value, ignoreCase: flags === "i" }; - })(r1, r3, r4); + })(r3, r4); } if (r0 === null) { pos = r1; @@ -1483,7 +1522,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, string) { return string; })(r1, r3); + reportedPos = r1; + r0 = (function(string) { return string; })(r3); } if (r0 === null) { pos = r1; @@ -1541,7 +1581,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, chars) { return chars.join(""); })(r1, r4); + reportedPos = r1; + r0 = (function(chars) { return chars.join(""); })(r4); } if (r0 === null) { pos = r1; @@ -1629,7 +1670,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, char_) { return char_; })(r1, r4); + reportedPos = r1; + r0 = (function(char_) { return char_; })(r4); } if (r0 === null) { pos = r1; @@ -1683,7 +1725,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, chars) { return chars.join(""); })(r1, r4); + reportedPos = r1; + r0 = (function(chars) { return chars.join(""); })(r4); } if (r0 === null) { pos = r1; @@ -1771,7 +1814,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, char_) { return char_; })(r1, r4); + reportedPos = r1; + r0 = (function(char_) { return char_; })(r4); } if (r0 === null) { pos = r1; @@ -1868,7 +1912,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, inverted, parts, flags) { + reportedPos = r1; + r0 = (function(inverted, parts, flags) { var partsConverted = utils.map(parts, function(part) { return part.data; }); var rawText = "[" + inverted @@ -1884,7 +1929,7 @@ module.exports = (function(){ inverted: inverted === "^", ignoreCase: flags === "i" }; - })(r1, r4, r5, r7); + })(r4, r5, r7); } if (r0 === null) { pos = r1; @@ -1929,7 +1974,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, begin, end) { + reportedPos = r1; + r0 = (function(begin, end) { if (begin.data.charCodeAt(0) > end.data.charCodeAt(0)) { throw new this.SyntaxError( "Invalid character range: " + begin.rawText + "-" + end.rawText + "." @@ -1941,7 +1987,7 @@ module.exports = (function(){ // FIXME: Get the raw text from the input directly. rawText: begin.rawText + "-" + end.rawText }; - })(r1, r3, r5); + })(r3, r5); } if (r0 === null) { pos = r1; @@ -1955,13 +2001,14 @@ module.exports = (function(){ r1 = pos; r0 = parse_bracketDelimitedCharacter(); if (r0 !== null) { - r0 = (function(offset, char_) { + reportedPos = r1; + r0 = (function(char_) { return { data: char_, // FIXME: Get the raw text from the input directly. rawText: utils.quoteForRegexpClass(char_) }; - })(r1, r0); + })(r0); } if (r0 === null) { pos = r1; @@ -2049,7 +2096,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, char_) { return char_; })(r1, r4); + reportedPos = r1; + r0 = (function(char_) { return char_; })(r4); } if (r0 === null) { pos = r1; @@ -2132,7 +2180,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, char_) { + reportedPos = r1; + r0 = (function(char_) { return char_ .replace("b", "\b") .replace("f", "\f") @@ -2140,7 +2189,7 @@ module.exports = (function(){ .replace("r", "\r") .replace("t", "\t") .replace("v", "\x0B"); // IE does not recognize "\v". - })(r1, r5); + })(r5); } if (r0 === null) { pos = r1; @@ -2184,7 +2233,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset) { return "\x00"; })(r1); + reportedPos = r1; + r0 = (function() { return "\x00"; })(); } if (r0 === null) { pos = r1; @@ -2225,9 +2275,10 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, h1, h2) { + reportedPos = r1; + r0 = (function(h1, h2) { return String.fromCharCode(parseInt(h1 + h2, 16)); - })(r1, r4, r5); + })(r4, r5); } if (r0 === null) { pos = r1; @@ -2280,9 +2331,10 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, h1, h2, h3, h4) { + reportedPos = r1; + r0 = (function(h1, h2, h3, h4) { return String.fromCharCode(parseInt(h1 + h2 + h3 + h4, 16)); - })(r1, r4, r5, r6, r7); + })(r4, r5, r6, r7); } if (r0 === null) { pos = r1; @@ -2317,7 +2369,8 @@ module.exports = (function(){ pos = r2; } if (r0 !== null) { - r0 = (function(offset, eol) { return eol; })(r1, r4); + reportedPos = r1; + r0 = (function(eol) { return eol; })(r4); } if (r0 === null) { pos = r1; diff --git a/spec/generated-parser.spec.js b/spec/generated-parser.spec.js index cd2e641..1c8092c 100644 --- a/spec/generated-parser.spec.js +++ b/spec/generated-parser.spec.js @@ -258,9 +258,9 @@ describe("generated parser", function() { expect(parser).toParse("a", "a"); }); - it("can use the |offset| variable to get the current parse position", function() { + it("can use the |offset| function to get the current parse position", function() { var parser = PEG.buildParser( - 'start = "a" ("b" { return offset; })', + 'start = "a" ("b" { return offset(); })', options ); @@ -268,14 +268,14 @@ describe("generated parser", function() { }); if (options.trackLineAndColumn) { - it("can use the |line| and |column| variables to get the current line and column", function() { + it("can use the |line| and |column| functions to get the current line and column", function() { var parser = PEG.buildParser([ '{ var result; }', 'start = line (nl+ line)* { return result; }', 'line = thing (" "+ thing)*', 'thing = digit / mark', 'digit = [0-9]', - 'mark = "x" { result = [line, column]; }', + 'mark = "x" { result = [line(), column()]; }', 'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")' ].join("\n"), options); @@ -422,9 +422,9 @@ describe("generated parser", function() { expect(parser).toParse("a", ["a", ""]); }); - it("can use the |offset| variable to get the current parse position", function() { + it("can use the |offset| function to get the current parse position", function() { var parser = PEG.buildParser( - 'start = "a" &{ return offset === 1; }', + 'start = "a" &{ return offset() === 1; }', options ); @@ -432,14 +432,14 @@ describe("generated parser", function() { }); if (options.trackLineAndColumn) { - it("can use the |line| and |column| variables to get the current line and column", function() { + it("can use the |line| and |column| functions to get the current line and column", function() { var parser = PEG.buildParser([ '{ var result; }', 'start = line (nl+ line)* { return result; }', 'line = thing (" "+ thing)*', 'thing = digit / mark', 'digit = [0-9]', - 'mark = &{ result = [line, column]; return true; } "x"', + 'mark = &{ result = [line(), column()]; return true; } "x"', 'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")' ].join("\n"), options); @@ -506,9 +506,9 @@ describe("generated parser", function() { expect(parser).toParse("a", ["a", ""]); }); - it("can use the |offset| variable to get the current parse position", function() { + it("can use the |offset| function to get the current parse position", function() { var parser = PEG.buildParser( - 'start = "a" !{ return offset !== 1; }', + 'start = "a" !{ return offset() !== 1; }', options ); @@ -516,14 +516,14 @@ describe("generated parser", function() { }); if (options.trackLineAndColumn) { - it("can use the |line| and |column| variables to get the current line and column", function() { + it("can use the |line| and |column| functions to get the current line and column", function() { var parser = PEG.buildParser([ '{ var result; }', 'start = line (nl+ line)* { return result; }', 'line = thing (" "+ thing)*', 'thing = digit / mark', 'digit = [0-9]', - 'mark = !{ result = [line, column]; return false; } "x"', + 'mark = !{ result = [line(), column()]; return false; } "x"', 'nl = ("\\r" / "\\n" / "\\u2028" / "\\u2029")' ].join("\n"), options);