Skip to content

Commit

Permalink
Issue pegjs#38 (import): Add pass for include (at AST level) one gram…
Browse files Browse the repository at this point in the history
…mar to another.
  • Loading branch information
Mingun committed Jan 4, 2016
1 parent 6833a73 commit e5e8f08
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 0 deletions.
4 changes: 4 additions & 0 deletions lib/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ var compiler = {
* |PEG.GrammarError|.
*/
passes: {
precheck: {
includeGrammars: require("./compiler/passes/include-grammars")
},
check: {
reportMissingRules: require("./compiler/passes/report-missing-rules"),
reportLeftRecursion: require("./compiler/passes/report-left-recursion"),
Expand Down Expand Up @@ -59,6 +62,7 @@ var compiler = {
switch (options.output) {
case "parser": return eval(ast.code);
case "source": return ast.code;
case "ast" : return ast;
}
}
};
Expand Down
103 changes: 103 additions & 0 deletions lib/compiler/passes/include-grammars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
var GrammarError = require("../../grammar-error"),
objects = require("../../utils/objects"),
arrays = require("../../utils/arrays"),
visitor = require("../visitor");
Resolver = require("../resolver");

/* Include one grammar to another recursively. Cyclic includes not supported. */
function includeGrammars(ast, options) {
options = objects.clone(options);
options.output = "ast";

var resolver = options.resolver || new Resolver();

function doInclude(ast, aliases) {
// Mapping from alias to default rule of imported grammar
var defaultRules = {};
// Arrays with initiaizers and rules from all imported grammars in order of import
var initializers = [];
var rules = [];

function appendFrom(ast) {
if (ast.initializers) {
Array.prototype.push.apply(initializers, ast.initializers);
}
Array.prototype.push.apply(rules, ast.rules);
}

function fixCode(node, prefix) {
if (node.namespace) {
prefix = prefix.concat(node.namespace);
}
node.namespace = prefix.join('.');
if (node.expression) {
addPrefix(node.expression, prefix);
}
}
var addPrefix = visitor.build({
initializer: fixCode,
action: fixCode,
semantic_and: fixCode,
semantic_not: fixCode,

rule: function(node, prefix) {
node.name = prefix.concat(node.name).join('$');
addPrefix(node.expression, prefix);
},
rule_ref: function(node, prefix) {
var name = node.name;
// If referred rule not from this grammar.
if (node.namespace) {
// If |name| not set, use default rule of grammar
name = name || defaultRules[node.namespace];
prefix = prefix.concat(node.namespace);

// Make rule be part of this grammar -- for preventing of repeated processing.
node.namespace = null;
}
if (!name) {
throw new GrammarError(node);
}
node.name = prefix.concat(name).join('$');
}
});
var include = visitor.build({
grammar: function(node) {
if (node.imports) {
// Fill |rules| and |initializers| arrays
arrays.each(node.imports, include);
}
// Mark that all imports resolved.
delete node.imports;
addPrefix(node, aliases);

// Append included rules and initializers first -- this need for generating action functions
// and initializers call in correct order.
appendFrom(node);


// Update AST
node.initializers = initializers;
node.rules = rules;
},
"import": function(node) {
var importedAst = resolver.resolve(node.path, node.alias, options);

// Resolve default names before they changed in |doInclude|
defaultRules[node.alias] = importedAst.rules[0].name;

doInclude(importedAst, aliases.concat(node.alias));

appendFrom(importedAst);
if (resolver.done) {
resolver.done(node.path, node.alias, options);
}
}
});

include(ast);
}
doInclude(ast, []);
}

module.exports = includeGrammars;
40 changes: 40 additions & 0 deletions lib/compiler/resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
var GrammarError = require("../grammar-error"),
parser = require("../parser"),
arrays = require("../utils/arrays");

var imported = [];

function Resolver() {
if (arguments.length > 0) {
var read = arguments[0];
if (typeof read !== 'function') {
throw new GrammarError('Expected function for Resolver argument, but got '+(typeof read));
}
this.read = read;
}
}

Resolver.prototype = {
read: function(path) {
var fullPath = require("path").resolve(path);
return {
path: fullPath,
data: require("fs").readFileSync(fullPath, 'utf-8')
};
},
resolve: function(path, alias, options) {
var data = this.read(path, alias, options);
var has = arrays.contains(imported, function(e) { return e.path === data.path; });
imported.push({ path: data.path, alias: alias });
if (has) {
var message = arrays.map(imported, function(e) { return e.path + ' as "' + e.alias + '"'; });
throw new GrammarError("Cyclic include dependence for included grammars!:\n" + message.join('\n'));
}
return parser.parse(data.data, options);
},
done: function() {
imported.pop();
}
};

module.exports = Resolver;
1 change: 1 addition & 0 deletions lib/peg.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var PEG = {
GrammarError: require("./grammar-error"),
parser: require("./parser"),
compiler: require("./compiler"),
Resolver: require("./compiler/resolver"),

/*
* Generates a parser from a specified grammar and returns it.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
"lib/compiler/js.js",
"lib/compiler/opcodes.js",
"lib/compiler/visitor.js",
"lib/compiler/resolver.js",
"lib/compiler/passes/generate-bytecode.js",
"lib/compiler/passes/generate-js.js",
"lib/compiler/passes/remove-proxy-rules.js",
"lib/compiler/passes/report-left-recursion.js",
"lib/compiler/passes/report-infinite-loops.js",
"lib/compiler/passes/report-missing-rules.js",
"lib/compiler/passes/include-grammars.js",
"lib/grammar-error.js",
"lib/parser.js",
"lib/peg.js",
Expand Down

0 comments on commit e5e8f08

Please sign in to comment.