From 89146915ce7e3f593fce96d217c06f287fc15bab Mon Sep 17 00:00:00 2001 From: David Majda Date: Mon, 6 Apr 2015 10:12:31 +0200 Subject: [PATCH] Add location information to AST nodes This will allow to add location information to |GrammarError| exceptions thrown in various passes. --- lib/parser.js | 71 ++++++++++++++++++++++++++++++--------- spec/unit/parser.spec.js | 72 ++++++++++++++++++++++++++++++++++++++++ src/parser.pegjs | 71 ++++++++++++++++++++++++++++++--------- 3 files changed, 184 insertions(+), 30 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 4189d78..290c013 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -35,10 +35,13 @@ module.exports = (function() { return { type: "grammar", initializer: extractOptional(initializer, 0), - rules: extractList(rules, 0) + rules: extractList(rules, 0), + location: location() }; }, - peg$c1 = function(code) { return { type: "initializer", code: code }; }, + peg$c1 = function(code) { + return { type: "initializer", code: code, location: location() }; + }, peg$c2 = "=", peg$c3 = { type: "literal", value: "=", description: "\"=\"" }, peg$c4 = function(name, displayName, expression) { @@ -49,35 +52,59 @@ module.exports = (function() { ? { type: "named", name: displayName[0], - expression: expression + expression: expression, + location: location() } - : expression + : expression, + location: location() }; }, peg$c5 = "/", peg$c6 = { type: "literal", value: "/", description: "\"/\"" }, peg$c7 = function(first, rest) { return rest.length > 0 - ? { type: "choice", alternatives: buildList(first, rest, 3) } + ? { + type: "choice", + alternatives: buildList(first, rest, 3), + location: location() + } : first; }, peg$c8 = function(expression, code) { return code !== null - ? { type: "action", expression: expression, code: code[1] } + ? { + type: "action", + expression: expression, + code: code[1], + location: location() + } : expression; }, peg$c9 = function(first, rest) { return rest.length > 0 - ? { type: "sequence", elements: buildList(first, rest, 1) } + ? { + type: "sequence", + elements: buildList(first, rest, 1), + location: location() + } : first; }, peg$c10 = ":", peg$c11 = { type: "literal", value: ":", description: "\":\"" }, peg$c12 = function(label, expression) { - return { type: "labeled", label: label, expression: expression }; + return { + type: "labeled", + label: label, + expression: expression, + location: location() + }; }, peg$c13 = function(operator, expression) { - return { type: OPS_TO_PREFIXED_TYPES[operator], expression: expression }; + return { + type: OPS_TO_PREFIXED_TYPES[operator], + expression: expression, + location: location() + }; }, peg$c14 = "$", peg$c15 = { type: "literal", value: "$", description: "\"$\"" }, @@ -86,7 +113,11 @@ module.exports = (function() { peg$c18 = "!", peg$c19 = { type: "literal", value: "!", description: "\"!\"" }, peg$c20 = function(expression, operator) { - return { type: OPS_TO_SUFFIXED_TYPES[operator], expression: expression }; + return { + type: OPS_TO_SUFFIXED_TYPES[operator], + expression: expression, + location: location() + }; }, peg$c21 = "?", peg$c22 = { type: "literal", value: "?", description: "\"?\"" }, @@ -100,10 +131,14 @@ module.exports = (function() { peg$c30 = { type: "literal", value: ")", description: "\")\"" }, peg$c31 = function(expression) { return expression; }, peg$c32 = function(name) { - return { type: "rule_ref", name: name }; + return { type: "rule_ref", name: name, location: location() }; }, peg$c33 = function(operator, code) { - return { type: OPS_TO_SEMANTIC_PREDICATE_TYPES[operator], code: code }; + return { + type: OPS_TO_SEMANTIC_PREDICATE_TYPES[operator], + code: code, + location: location() + }; }, peg$c34 = { type: "any", description: "any character" }, peg$c35 = { type: "other", description: "whitespace" }, @@ -155,7 +190,12 @@ module.exports = (function() { peg$c81 = "i", peg$c82 = { type: "literal", value: "i", description: "\"i\"" }, peg$c83 = function(value, ignoreCase) { - return { type: "literal", value: value, ignoreCase: ignoreCase !== null }; + return { + type: "literal", + value: value, + ignoreCase: ignoreCase !== null, + location: location() + }; }, peg$c84 = { type: "other", description: "string" }, peg$c85 = "\"", @@ -177,7 +217,8 @@ module.exports = (function() { parts: filterEmptyStrings(parts), inverted: inverted !== null, ignoreCase: ignoreCase !== null, - rawText: text() + rawText: text(), + location: location() }; }, peg$c99 = "-", @@ -226,7 +267,7 @@ module.exports = (function() { peg$c132 = { type: "class", value: "[0-9a-f]i", description: "[0-9a-f]i" }, peg$c133 = ".", peg$c134 = { type: "literal", value: ".", description: "\".\"" }, - peg$c135 = function() { return { type: "any" }; }, + peg$c135 = function() { return { type: "any", location: location() }; }, peg$c136 = { type: "other", description: "code block" }, peg$c137 = "{", peg$c138 = { type: "literal", value: "{", description: "\"{\"" }, diff --git a/spec/unit/parser.spec.js b/spec/unit/parser.spec.js index 43f1f83..e68189e 100644 --- a/spec/unit/parser.spec.js +++ b/spec/unit/parser.spec.js @@ -98,6 +98,74 @@ describe("PEG.js grammar parser", function() { rules: [ruleA, ruleB] }; + var stripLocation = (function() { + function buildVisitor(functions) { + return function(node) { + return functions[node.type].apply(null, arguments); + }; + } + + function stripLeaf(node) { + delete node.location; + } + + function stripExpression(node) { + delete node.location; + + strip(node.expression); + } + + function stripChildren(property) { + return function(node) { + var i; + + delete node.location; + + for (i = 0; i < node[property].length; i++) { + strip(node[property][i]); + } + }; + } + + var strip = buildVisitor({ + grammar: function(node) { + var i; + + delete node.location; + + if (node.initializer) { + strip(node.initializer); + } + + for (i = 0; i < node.rules.length; i++) { + strip(node.rules[i]); + } + }, + + initializer: stripLeaf, + rule: stripExpression, + named: stripExpression, + choice: stripChildren("alternatives"), + action: stripExpression, + sequence: stripChildren("elements"), + labeled: stripExpression, + text: stripExpression, + simple_and: stripExpression, + simple_not: stripExpression, + optional: stripExpression, + zero_or_more: stripExpression, + one_or_more: stripExpression, + semantic_and: stripLeaf, + semantic_not: stripLeaf, + rule_ref: stripLeaf, + literal: stripLeaf, + "class": stripLeaf, + any: stripLeaf + }); + + return strip; + })(); + beforeEach(function() { this.addMatchers({ toParseAs: function(expected) { @@ -106,6 +174,8 @@ describe("PEG.js grammar parser", function() { try { result = PEG.parser.parse(this.actual); + stripLocation(result); + this.message = function() { return "Expected " + jasmine.pp(this.actual) + " " + (this.isNot ? "not " : "") @@ -132,6 +202,8 @@ describe("PEG.js grammar parser", function() { try { result = PEG.parser.parse(this.actual); + stripLocation(result); + this.message = function() { return "Expected " + jasmine.pp(this.actual) + " to fail to parse" + (details ? " with details " + jasmine.pp(details) : "") + ", " diff --git a/src/parser.pegjs b/src/parser.pegjs index 08f6c4f..1e28815 100644 --- a/src/parser.pegjs +++ b/src/parser.pegjs @@ -79,12 +79,15 @@ Grammar return { type: "grammar", initializer: extractOptional(initializer, 0), - rules: extractList(rules, 0) + rules: extractList(rules, 0), + location: location() }; } Initializer - = code:CodeBlock EOS { return { type: "initializer", code: code }; } + = code:CodeBlock EOS { + return { type: "initializer", code: code, location: location() }; + } Rule = name:IdentifierName __ @@ -99,9 +102,11 @@ Rule ? { type: "named", name: displayName[0], - expression: expression + expression: expression, + location: location() } - : expression + : expression, + location: location() }; } @@ -111,33 +116,55 @@ Expression ChoiceExpression = first:ActionExpression rest:(__ "/" __ ActionExpression)* { return rest.length > 0 - ? { type: "choice", alternatives: buildList(first, rest, 3) } + ? { + type: "choice", + alternatives: buildList(first, rest, 3), + location: location() + } : first; } ActionExpression = expression:SequenceExpression code:(__ CodeBlock)? { return code !== null - ? { type: "action", expression: expression, code: code[1] } + ? { + type: "action", + expression: expression, + code: code[1], + location: location() + } : expression; } SequenceExpression = first:LabeledExpression rest:(__ LabeledExpression)* { return rest.length > 0 - ? { type: "sequence", elements: buildList(first, rest, 1) } + ? { + type: "sequence", + elements: buildList(first, rest, 1), + location: location() + } : first; } LabeledExpression = label:Identifier __ ":" __ expression:PrefixedExpression { - return { type: "labeled", label: label, expression: expression }; + return { + type: "labeled", + label: label, + expression: expression, + location: location() + }; } / PrefixedExpression PrefixedExpression = operator:PrefixedOperator __ expression:SuffixedExpression { - return { type: OPS_TO_PREFIXED_TYPES[operator], expression: expression }; + return { + type: OPS_TO_PREFIXED_TYPES[operator], + expression: expression, + location: location() + }; } / SuffixedExpression @@ -148,7 +175,11 @@ PrefixedOperator SuffixedExpression = expression:PrimaryExpression __ operator:SuffixedOperator { - return { type: OPS_TO_SUFFIXED_TYPES[operator], expression: expression }; + return { + type: OPS_TO_SUFFIXED_TYPES[operator], + expression: expression, + location: location() + }; } / PrimaryExpression @@ -167,12 +198,16 @@ PrimaryExpression RuleReferenceExpression = name:IdentifierName !(__ (StringLiteral __)? "=") { - return { type: "rule_ref", name: name }; + return { type: "rule_ref", name: name, location: location() }; } SemanticPredicateExpression = operator:SemanticPredicateOperator __ code:CodeBlock { - return { type: OPS_TO_SEMANTIC_PREDICATE_TYPES[operator], code: code }; + return { + type: OPS_TO_SEMANTIC_PREDICATE_TYPES[operator], + code: code, + location: location() + }; } SemanticPredicateOperator @@ -306,7 +341,12 @@ BooleanLiteral LiteralMatcher "literal" = value:StringLiteral ignoreCase:"i"? { - return { type: "literal", value: value, ignoreCase: ignoreCase !== null }; + return { + type: "literal", + value: value, + ignoreCase: ignoreCase !== null, + location: location() + }; } StringLiteral "string" @@ -335,7 +375,8 @@ CharacterClassMatcher "character class" parts: filterEmptyStrings(parts), inverted: inverted !== null, ignoreCase: ignoreCase !== null, - rawText: text() + rawText: text(), + location: location() }; } @@ -405,7 +446,7 @@ HexDigit = [0-9a-f]i AnyMatcher - = "." { return { type: "any" }; } + = "." { return { type: "any", location: location() }; } CodeBlock "code block" = "{" code:Code "}" { return code; }