diff --git a/README.md b/README.md index 1f8fc55..7243390 100644 --- a/README.md +++ b/README.md @@ -321,10 +321,17 @@ otherwise consider the match failed. 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` function. It returns a zero-based character index into the input -string. The code can also access the current line and column using the `line` -and `column` functions. Both return one-based indexes. +The code inside the predicate can also access location information using the +`location` function. It returns an object like this: + + { + start: { offset: 23, line: 5, column: 6 }, + end: { offset: 23, line: 5, column: 6 } + } + +The `start` and `end` properties both refer to the current parse position. The +`offset` property contains an offset as a zero-based index and `line` and +`column` properties contain a line and a column as one-based indices. The code inside the predicate can also access the parser object using the `parser` variable and options passed to the parser using the `options` variable. @@ -343,10 +350,17 @@ otherwise consider the match failed. 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` function. It returns a zero-based character index into the input -string. The code can also access the current line and column using the `line` -and `column` functions. Both return one-based indexes. +The code inside the predicate can also access location information using the +`location` function. It returns an object like this: + + { + start: { offset: 23, line: 5, column: 6 }, + end: { offset: 23, line: 5, column: 6 } + } + +The `start` and `end` properties both refer to the current parse position. The +`offset` property contains an offset as a zero-based index and `line` and +`column` properties contain a line and a column as one-based indices. The code inside the predicate can also access the parser object using the `parser` variable and options passed to the parser using the `options` variable. @@ -397,11 +411,19 @@ must be balanced. The code inside the action can also access the string matched by the expression using the `text` function. -The code inside the action can also access the parse position at the beginning -of the action's expression using the `offset` function. It returns a zero-based -character index into the input string. The code can also access the line and -column at the 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 location information using the +`location` function. It returns an object like this: + + { + start: { offset: 23, line: 5, column: 6 }, + end: { offset: 25, line: 5, column: 8 } + } + +The `start` property refers to the position at the beginning of the expression, +the `end` property refers to position after the end of the expression. The +`offset` property contains an offset as a zero-based index and `line` and +`column` properties contain a line and a column as one-based indices. The code inside the action can also access the parser object using the `parser` variable and options passed to the parser using the `options` variable. diff --git a/lib/compiler/passes/generate-javascript.js b/lib/compiler/passes/generate-javascript.js index 92541e6..9135abc 100644 --- a/lib/compiler/passes/generate-javascript.js +++ b/lib/compiler/passes/generate-javascript.js @@ -928,16 +928,22 @@ function generateJavascript(ast, options) { ' return input.substring(peg$reportedPos, peg$currPos);', ' }', '', - ' function offset() {', - ' return peg$reportedPos;', - ' }', - '', - ' function line() {', - ' return peg$computePosDetails(peg$reportedPos).line;', - ' }', + ' function location() {', + ' var reportedPosDetails = peg$computePosDetails(peg$reportedPos),', + ' currPosDetails = peg$computePosDetails(peg$currPos);', '', - ' function column() {', - ' return peg$computePosDetails(peg$reportedPos).column;', + ' return {', + ' start: {', + ' offset: peg$reportedPos,', + ' line: reportedPosDetails.line,', + ' column: reportedPosDetails.column', + ' },', + ' end: {', + ' offset: peg$currPos,', + ' line: currPosDetails.line,', + ' column: currPosDetails.column', + ' }', + ' };', ' }', '', ' function expected(description) {', @@ -982,7 +988,11 @@ function generateJavascript(ast, options) { ' peg$cachedPos = pos;', ' }', '', - ' return peg$cachedPosDetails;', + ' return {', + ' line: peg$cachedPosDetails.line,', + ' column: peg$cachedPosDetails.column,', + ' seenCR: peg$cachedPosDetails.seenCR', + ' };', ' }', '', ' function peg$fail(expected) {', diff --git a/lib/parser.js b/lib/parser.js index b490578..46d2fe8 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -356,16 +356,22 @@ module.exports = (function() { return input.substring(peg$reportedPos, peg$currPos); } - function offset() { - return peg$reportedPos; - } - - function line() { - return peg$computePosDetails(peg$reportedPos).line; - } - - function column() { - return peg$computePosDetails(peg$reportedPos).column; + function location() { + var reportedPosDetails = peg$computePosDetails(peg$reportedPos), + currPosDetails = peg$computePosDetails(peg$currPos); + + return { + start: { + offset: peg$reportedPos, + line: reportedPosDetails.line, + column: reportedPosDetails.column + }, + end: { + offset: peg$currPos, + line: currPosDetails.line, + column: currPosDetails.column + } + }; } function expected(description) { @@ -410,7 +416,11 @@ module.exports = (function() { peg$cachedPos = pos; } - return peg$cachedPosDetails; + return { + line: peg$cachedPosDetails.line, + column: peg$cachedPosDetails.column, + seenCR: peg$cachedPosDetails.seenCR + }; } function peg$fail(expected) { diff --git a/spec/behavior/generated-parser-behavior.spec.js b/spec/behavior/generated-parser-behavior.spec.js index 651cf31..b362b31 100644 --- a/spec/behavior/generated-parser-behavior.spec.js +++ b/spec/behavior/generated-parser-behavior.spec.js @@ -580,27 +580,45 @@ describe("generated parser behavior", function() { expect(parser).toParse("", { a: 42 }, { a: 42 }); }); - it("|offset|, |line|, and |column| return current parse position, line, and column", function() { + it("|location| returns current location info", function() { var parser = PEG.buildParser([ '{ var result; }', 'start = line (nl+ line)* { return result; }', 'line = thing (" "+ thing)*', 'thing = digit / mark', 'digit = [0-9]', - 'mark = &{ result = [offset(), line(), column()]; return true; } "x"', + 'mark = &{ result = location(); return true; } "x"', 'nl = [\\r"\\n\\u2028\\u2029]' ].join("\n"), options); - expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", [13, 7, 5]); + expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", { + start: { offset: 13, line: 7, column: 5 }, + end: { offset: 13, line: 7, column: 5 }, + }); /* Non-Unix newlines */ - expect(parser).toParse("1\rx", [2, 2, 1]); // Old Mac - expect(parser).toParse("1\r\nx", [3, 2, 1]); // Windows - expect(parser).toParse("1\n\rx", [3, 3, 1]); // mismatched + expect(parser).toParse("1\rx", { // Old Mac + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 2, line: 2, column: 1 }, + }); + expect(parser).toParse("1\r\nx", { // Windows + start: { offset: 3, line: 2, column: 1 }, + end: { offset: 3, line: 2, column: 1 }, + }); + expect(parser).toParse("1\n\rx", { // mismatched + start: { offset: 3, line: 3, column: 1 }, + end: { offset: 3, line: 3, column: 1 }, + }); /* Strange newlines */ - expect(parser).toParse("1\u2028x", [2, 2, 1]); // line separator - expect(parser).toParse("1\u2029x", [2, 2, 1]); // paragraph separator + expect(parser).toParse("1\u2028x", { // line separator + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 2, line: 2, column: 1 }, + }); + expect(parser).toParse("1\u2029x", { // paragraph separator + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 2, line: 2, column: 1 }, + }); }); }); }); @@ -762,27 +780,45 @@ describe("generated parser behavior", function() { expect(parser).toParse("", { a: 42 }, { a: 42 }); }); - it("|offset|, |line|, and |column| return current parse position, line, and column", function() { + it("|location| returns current location info", function() { var parser = PEG.buildParser([ '{ var result; }', 'start = line (nl+ line)* { return result; }', 'line = thing (" "+ thing)*', 'thing = digit / mark', 'digit = [0-9]', - 'mark = !{ result = [offset(), line(), column()]; return false; } "x"', + 'mark = !{ result = location(); return false; } "x"', 'nl = [\\r"\\n\\u2028\\u2029]' ].join("\n"), options); - expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", [13, 7, 5]); + expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", { + start: { offset: 13, line: 7, column: 5 }, + end: { offset: 13, line: 7, column: 5 }, + }); /* Non-Unix newlines */ - expect(parser).toParse("1\rx", [2, 2, 1]); // Old Mac - expect(parser).toParse("1\r\nx", [3, 2, 1]); // Windows - expect(parser).toParse("1\n\rx", [3, 3, 1]); // mismatched + expect(parser).toParse("1\rx", { // Old Mac + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 2, line: 2, column: 1 }, + }); + expect(parser).toParse("1\r\nx", { // Windows + start: { offset: 3, line: 2, column: 1 }, + end: { offset: 3, line: 2, column: 1 }, + }); + expect(parser).toParse("1\n\rx", { // mismatched + start: { offset: 3, line: 3, column: 1 }, + end: { offset: 3, line: 3, column: 1 }, + }); /* Strange newlines */ - expect(parser).toParse("1\u2028x", [2, 2, 1]); // line separator - expect(parser).toParse("1\u2029x", [2, 2, 1]); // paragraph separator + expect(parser).toParse("1\u2028x", { // line separator + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 2, line: 2, column: 1 }, + }); + expect(parser).toParse("1\u2029x", { // paragraph separator + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 2, line: 2, column: 1 }, + }); }); }); }); @@ -1158,27 +1194,45 @@ describe("generated parser behavior", function() { expect(parser).toParse("a", "a"); }); - it("|offset|, |line|, and |column| return parse position, line, and column at the beginning of the expression", function() { + it("|location| returns location info of the expression", function() { var parser = PEG.buildParser([ '{ var result; }', 'start = line (nl+ line)* { return result; }', 'line = thing (" "+ thing)*', 'thing = digit / mark', 'digit = [0-9]', - 'mark = "x" { result = [offset(), line(), column()]; }', + 'mark = "x" { result = location(); }', 'nl = [\\r\\n\\u2028\\u2029]' ].join("\n"), options); - expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", [13, 7, 5]); + expect(parser).toParse("1\n2\n\n3\n\n\n4 5 x", { + start: { offset: 13, line: 7, column: 5 }, + end: { offset: 14, line: 7, column: 6 }, + }); /* Non-Unix newlines */ - expect(parser).toParse("1\rx", [2, 2, 1]); // Old Mac - expect(parser).toParse("1\r\nx", [3, 2, 1]); // Windows - expect(parser).toParse("1\n\rx", [3, 3, 1]); // mismatched + expect(parser).toParse("1\rx", { // Old Mac + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 3, line: 2, column: 2 }, + }); + expect(parser).toParse("1\r\nx", { // Windows + start: { offset: 3, line: 2, column: 1 }, + end: { offset: 4, line: 2, column: 2 }, + }); + expect(parser).toParse("1\n\rx", { // mismatched + start: { offset: 3, line: 3, column: 1 }, + end: { offset: 4, line: 3, column: 2 }, + }); /* Strange newlines */ - expect(parser).toParse("1\u2028x", [2, 2, 1]); // line separator - expect(parser).toParse("1\u2029x", [2, 2, 1]); // paragraph separator + expect(parser).toParse("1\u2028x", { // line separator + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 3, line: 2, column: 2 }, + }); + expect(parser).toParse("1\u2029x", { // paragraph separator + start: { offset: 2, line: 2, column: 1 }, + end: { offset: 3, line: 2, column: 2 }, + }); }); it("|expected| terminates parsing and throws an exception", function() {