Skip to content

Commit

Permalink
Git repo npmization: Compose PEG.js from Node.js modules
Browse files Browse the repository at this point in the history
PEG.js source code becomes a set of Node.js modules that include each
other as needed. The distribution version is built by bundling these
modules together, wrapping them inside a bit of boilerplate code that
makes |module.exports| and |require| work.

Part of a fix for GH-32.
  • Loading branch information
dmajda committed Nov 10, 2012
1 parent c6cf129 commit 4cda799
Show file tree
Hide file tree
Showing 13 changed files with 347 additions and 315 deletions.
103 changes: 65 additions & 38 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

PEGJS_VERSION = `cat $(VERSION_FILE)`

# ===== Modules =====

# Order matters -- dependencies must be listed before modules dependent on them.
MODULES = utils \
grammar-error \
parser \
compiler/passes/allocate-registers \
compiler/passes/generate-code \
compiler/passes/remove-proxy-rules \
compiler/passes/report-left-recursion \
compiler/passes/report-missing-rules \
compiler/passes \
compiler \
peg

# ===== Directories =====

SRC_DIR = src
Expand Down Expand Up @@ -39,52 +54,64 @@ JASMINE_NODE = jasmine-node
PEGJS = $(BIN_DIR)/pegjs
BENCHMARK_RUN = $(BENCHMARK_DIR)/run

# ===== Preprocessor =====

# A simple preprocessor that recognizes two directives:
#
# @VERSION -- insert PEG.js version
# @include "<file>" -- include <file> here
#
# This could have been implemented many ways. I chose Perl because everyone will
# have it.
PREPROCESS=perl -e ' \
use strict; \
use warnings; \
\
use File::Basename; \
\
sub preprocess { \
my $$file = shift; \
my $$output = ""; \
\
open(my $$f, $$file) or die "Can\x27t open $$file: $$!"; \
while(<$$f>) { \
if (/^\s*\/\/\s*\@include\s*"([^"]*)"\s*$$/) { \
$$output .= preprocess(dirname($$file) . "/" . $$1); \
next; \
} \
\
$$output .= $$_; \
} \
close($$f); \
\
return $$output; \
} \
\
print preprocess($$ARGV[0]); \
'

# ===== Targets =====

# Generate the grammar parser
parser:
$(PEGJS) --export-var PEG.parser $(PARSER_SRC_FILE) $(PARSER_OUT_FILE)
$(PEGJS) $(PARSER_SRC_FILE) $(PARSER_OUT_FILE)

# Build the PEG.js library
build:
mkdir -p $(LIB_DIR)
$(PREPROCESS) $(PEGJS_SRC_FILE) > $(PEGJS_LIB_FILE)
rm -f $(PEGJS_LIB_FILE)

# The following code is inspired by CoffeeScript's Cakefile.

echo '/*' >> $(PEGJS_LIB_FILE)
echo " * PEG.js $(PEGJS_VERSION)" >> $(PEGJS_LIB_FILE)
echo ' *' >> $(PEGJS_LIB_FILE)
echo ' * http://pegjs.majda.cz/' >> $(PEGJS_LIB_FILE)
echo ' *' >> $(PEGJS_LIB_FILE)
echo ' * Copyright (c) 2010-2012 David Majda' >> $(PEGJS_LIB_FILE)
echo ' * Licensed under the MIT license' >> $(PEGJS_LIB_FILE)
echo ' */' >> $(PEGJS_LIB_FILE)
echo 'var PEG = (function(undefined) {' >> $(PEGJS_LIB_FILE)
echo ' var modules = {' >> $(PEGJS_LIB_FILE)
echo ' define: function(name, factory) {' >> $(PEGJS_LIB_FILE)
echo ' var dir = name.replace(/(^|\/)[^/]+$$/, "$$1"),' >> $(PEGJS_LIB_FILE)
echo ' module = { exports: {} };' >> $(PEGJS_LIB_FILE)
echo '' >> $(PEGJS_LIB_FILE)
echo ' function require(path) {' >> $(PEGJS_LIB_FILE)
echo ' var name = dir + path,' >> $(PEGJS_LIB_FILE)
echo ' regexp = /[^\/]+\/\.\.\/|\.\//;' >> $(PEGJS_LIB_FILE)
echo '' >> $(PEGJS_LIB_FILE)
echo " /* Can't use /.../g because we can move backwards in the string. */" >> $(PEGJS_LIB_FILE)
echo ' while (regexp.test(name)) {' >> $(PEGJS_LIB_FILE)
echo ' name = name.replace(regexp, "");' >> $(PEGJS_LIB_FILE)
echo ' }' >> $(PEGJS_LIB_FILE)
echo '' >> $(PEGJS_LIB_FILE)
echo ' return modules[name];' >> $(PEGJS_LIB_FILE)
echo ' }' >> $(PEGJS_LIB_FILE)
echo '' >> $(PEGJS_LIB_FILE)
echo ' factory(module, require);' >> $(PEGJS_LIB_FILE)
echo ' this[name] = module.exports;' >> $(PEGJS_LIB_FILE)
echo ' }' >> $(PEGJS_LIB_FILE)
echo ' };' >> $(PEGJS_LIB_FILE)
echo '' >> $(PEGJS_LIB_FILE)

for module in $(MODULES); do \
echo " modules.define(\"$$module\", function(module, require) {" >> $(PEGJS_LIB_FILE); \
sed -e 's/^/ /' src/$$module.js >> $(PEGJS_LIB_FILE); \
echo ' });' >> $(PEGJS_LIB_FILE); \
echo '' >> $(PEGJS_LIB_FILE); \
done

echo ' return modules["peg"]' >> $(PEGJS_LIB_FILE)
echo '})();' >> $(PEGJS_LIB_FILE)
echo '' >> $(PEGJS_LIB_FILE)
echo 'if (typeof module !== "undefined") {' >> $(PEGJS_LIB_FILE)
echo ' module.exports = PEG;' >> $(PEGJS_LIB_FILE)
echo '}' >> $(PEGJS_LIB_FILE)

# Remove built PEG.js library (created by "build")
clean:
Expand Down
10 changes: 6 additions & 4 deletions src/compiler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
PEG.compiler = {
var utils = require("./utils");

module.exports = {
passes: require("./compiler/passes"),

/*
* Names of passes that will get run during the compilation (in the specified
* order).
Expand All @@ -20,7 +24,7 @@ PEG.compiler = {
compile: function(ast, options) {
var that = this;

each(this.appliedPassNames, function(passName) {
utils.each(this.appliedPassNames, function(passName) {
that.passes[passName](ast, options);
});

Expand All @@ -31,5 +35,3 @@ PEG.compiler = {
return result;
}
};

// @include "compiler/passes.js"
14 changes: 7 additions & 7 deletions src/compiler/passes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* or modify it as needed. If the pass encounters a semantic error, it throws
* |PEG.GrammarError|.
*/
PEG.compiler.passes = {};

// @include "passes/report-missing-rules.js"
// @include "passes/report-left-recursion.js"
// @include "passes/remove-proxy-rules.js"
// @include "passes/allocate-registers.js"
// @include "passes/generate-code.js"
module.exports = {
reportMissingRules: require("./passes/report-missing-rules"),
reportLeftRecursion: require("./passes/report-left-recursion"),
removeProxyRules: require("./passes/remove-proxy-rules"),
allocateRegisters: require("./passes/allocate-registers"),
generateCode: require("./passes/generate-code")
};
14 changes: 8 additions & 6 deletions src/compiler/passes/allocate-registers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var utils = require("../../utils");

/*
* Allocates registers that the generated code for each node will use to store
* match results and parse positions. For "action", "semantic_and" and
Expand All @@ -23,7 +25,7 @@
* point of action/predicate code execution to registers that will contain
* the labeled values.
*/
PEG.compiler.passes.allocateRegisters = function(ast) {
module.exports = function(ast) {
/*
* Register allocator that allocates registers from an unlimited
* integer-indexed pool. It allows allocating and releaseing registers in any
Expand Down Expand Up @@ -151,10 +153,10 @@ PEG.compiler.passes.allocateRegisters = function(ast) {
node.params = vars.buildParams();
}

var compute = buildNodeVisitor({
var compute = utils.buildNodeVisitor({
grammar:
function(node) {
each(node.rules, compute);
utils.each(node.rules, compute);
},

rule:
Expand All @@ -172,7 +174,7 @@ PEG.compiler.passes.allocateRegisters = function(ast) {

choice:
function(node) {
each(node.alternatives, function(alternative) {
utils.each(node.alternatives, function(alternative) {
reuseResult(node, alternative);
scoped(function() {
compute(alternative);
Expand All @@ -194,11 +196,11 @@ PEG.compiler.passes.allocateRegisters = function(ast) {
sequence:
function(node) {
savePos(node, function() {
each(node.elements, function(element) {
utils.each(node.elements, function(element) {
element.resultIndex = registers.alloc();
compute(element);
});
each(node.elements, function(element) {
utils.each(node.elements, function(element) {
registers.release(element.resultIndex);
});
});
Expand Down
30 changes: 16 additions & 14 deletions src/compiler/passes/generate-code.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
var utils = require("../../utils");

/* Generates the parser code. */
PEG.compiler.passes.generateCode = function(ast, options) {
options = clone(options) || {};
defaults(options, {
module.exports = function(ast, options) {
options = utils.clone(options) || {};
utils.defaults(options, {
cache: false,
trackLineAndColumn: false,
allowedStartRules: [ast.startRule]
Expand Down Expand Up @@ -751,12 +753,12 @@ PEG.compiler.passes.generateCode = function(ast, options) {
})();

function fill(name, vars) {
vars.string = quote;
vars.range = range;
vars.map = map;
vars.pluck = pluck;
vars.keys = keys;
vars.values = values;
vars.string = utils.quote;
vars.range = utils.range;
vars.map = utils.map;
vars.pluck = utils.pluck;
vars.keys = utils.keys;
vars.values = utils.values;
vars.emit = emit;
vars.options = options;

Expand Down Expand Up @@ -797,7 +799,7 @@ PEG.compiler.passes.generateCode = function(ast, options) {
return function(node) { return fill(name, { node: node }); };
}

var emit = buildNodeVisitor({
var emit = utils.buildNodeVisitor({
grammar: emitSimple("grammar"),

initializer: function(node) { return node.code; },
Expand Down Expand Up @@ -878,12 +880,12 @@ PEG.compiler.passes.generateCode = function(ast, options) {
if (node.parts.length > 0) {
regexp = '/^['
+ (node.inverted ? '^' : '')
+ map(node.parts, function(part) {
+ utils.map(node.parts, function(part) {
return part instanceof Array
? quoteForRegexpClass(part[0])
? utils.quoteForRegexpClass(part[0])
+ '-'
+ quoteForRegexpClass(part[1])
: quoteForRegexpClass(part);
+ utils.quoteForRegexpClass(part[1])
: utils.quoteForRegexpClass(part);
}).join('')
+ ']/' + (node.ignoreCase ? 'i' : '');
} else {
Expand Down
12 changes: 7 additions & 5 deletions src/compiler/passes/remove-proxy-rules.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
var utils = require("../../utils");

/*
* Removes proxy rules -- that is, rules that only delegate to other rule.
*/
PEG.compiler.passes.removeProxyRules = function(ast) {
module.exports = function(ast) {
function isProxyRule(node) {
return node.type === "rule" && node.expression.type === "rule_ref";
}
Expand All @@ -15,13 +17,13 @@ PEG.compiler.passes.removeProxyRules = function(ast) {

function replaceInSubnodes(propertyName) {
return function(node, from, to) {
each(node[propertyName], function(subnode) {
utils.each(node[propertyName], function(subnode) {
replace(subnode, from, to);
});
};
}

var replace = buildNodeVisitor({
var replace = utils.buildNodeVisitor({
grammar: replaceInSubnodes("rules"),
rule: replaceInExpression,
named: replaceInExpression,
Expand Down Expand Up @@ -54,7 +56,7 @@ PEG.compiler.passes.removeProxyRules = function(ast) {

var indices = [];

each(ast.rules, function(rule, i) {
utils.each(ast.rules, function(rule, i) {
if (isProxyRule(rule)) {
replaceRuleRefs(ast, rule.name, rule.expression.name);
if (rule.name === ast.startRule) {
Expand All @@ -66,7 +68,7 @@ PEG.compiler.passes.removeProxyRules = function(ast) {

indices.reverse();

each(indices, function(index) {
utils.each(indices, function(index) {
ast.rules.splice(index, 1);
});
};
12 changes: 7 additions & 5 deletions src/compiler/passes/report-left-recursion.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var utils = require("../../utils");

/* Checks that no left recursion is present. */
PEG.compiler.passes.reportLeftRecursion = function(ast) {
module.exports = function(ast) {
function nop() {}

function checkExpression(node, appliedRules) {
Expand All @@ -8,13 +10,13 @@ PEG.compiler.passes.reportLeftRecursion = function(ast) {

function checkSubnodes(propertyName) {
return function(node, appliedRules) {
each(node[propertyName], function(subnode) {
utils.each(node[propertyName], function(subnode) {
check(subnode, appliedRules);
});
};
}

var check = buildNodeVisitor({
var check = utils.buildNodeVisitor({
grammar: checkSubnodes("rules"),

rule:
Expand Down Expand Up @@ -44,12 +46,12 @@ PEG.compiler.passes.reportLeftRecursion = function(ast) {

rule_ref:
function(node, appliedRules) {
if (contains(appliedRules, node.name)) {
if (utils.contains(appliedRules, node.name)) {
throw new PEG.GrammarError(
"Left recursion detected for rule \"" + node.name + "\"."
);
}
check(findRuleByName(ast, node.name), appliedRules);
check(utils.findRuleByName(ast, node.name), appliedRules);
},

literal: nop,
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/passes/report-missing-rules.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
var utils = require("../../utils");

/* Checks that all referenced rules exist. */
PEG.compiler.passes.reportMissingRules = function(ast) {
module.exports = function(ast) {
function nop() {}

function checkExpression(node) { check(node.expression); }

function checkSubnodes(propertyName) {
return function(node) { each(node[propertyName], check); };
return function(node) { utils.each(node[propertyName], check); };
}

var check = buildNodeVisitor({
var check = utils.buildNodeVisitor({
grammar: checkSubnodes("rules"),
rule: checkExpression,
named: checkExpression,
Expand All @@ -26,7 +28,7 @@ PEG.compiler.passes.reportMissingRules = function(ast) {

rule_ref:
function(node) {
if (!findRuleByName(ast, node.name)) {
if (!utils.findRuleByName(ast, node.name)) {
throw new PEG.GrammarError(
"Referenced rule \"" + node.name + "\" does not exist."
);
Expand Down
Loading

0 comments on commit 4cda799

Please sign in to comment.