From e4b558832781f7e09895d7a67b2d873ee838ca32 Mon Sep 17 00:00:00 2001 From: David Majda Date: Sun, 13 Jan 2013 11:17:44 +0100 Subject: [PATCH] Plugin API: Split compiler passes into stages The compiler passes are now split into three stages: * check -- passes that check for various error conditions * transform -- passes that transform the AST (e.g. to perform optimizations) * generate -- passes that are related to code generation Splitting the passes into stages is important for plugins. For example, if a plugin wants to add a new optimization pass, it can add it at the end of the "transform" stage without any knowledge about other passes it contains. Similarly, if it wants to generate something else than the default code generator does from the AST, it can just replace all passes in the "generate" stage by its own one(s). More generally, the stages make it possible to write plugins that do not depend on names and actions of specific passes (which I consider internal and subject of change), just on the definition of stages (which I consider a public API with to which semver rules apply). Implements part of GH-106. --- lib/compiler.js | 31 ++++++++++++++----- lib/peg.js | 12 ++++++- .../compiler/passes/generate-bytecode.spec.js | 2 +- .../passes/remove-proxy-rules.spec.js | 2 +- .../passes/report-left-recursion.spec.js | 2 +- .../passes/report-missing-rules.spec.js | 2 +- 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/lib/compiler.js b/lib/compiler.js index febdd81..ab03a37 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -9,11 +9,17 @@ module.exports = { * |PEG.GrammarError|. */ passes: { - reportMissingRules: require("./compiler/passes/report-missing-rules"), - reportLeftRecursion: require("./compiler/passes/report-left-recursion"), - removeProxyRules: require("./compiler/passes/remove-proxy-rules"), - generateBytecode: require("./compiler/passes/generate-bytecode"), - generateJavascript: require("./compiler/passes/generate-javascript") + check: { + reportMissingRules: require("./compiler/passes/report-missing-rules"), + reportLeftRecursion: require("./compiler/passes/report-left-recursion") + }, + transform: { + removeProxyRules: require("./compiler/passes/remove-proxy-rules") + }, + generate: { + generateBytecode: require("./compiler/passes/generate-bytecode"), + generateJavascript: require("./compiler/passes/generate-javascript") + } }, /* @@ -23,7 +29,16 @@ module.exports = { * cause its malfunction. */ compile: function(ast, passes) { - var options = arguments.length > 2 ? utils.clone(arguments[2]) : {}; + var options = arguments.length > 2 ? utils.clone(arguments[2]) : {}, + stage; + + /* + * Extracted into a function just to silence JSHint complaining about + * creating functions in a loop. + */ + function runPass(pass) { + pass(ast, options); + } utils.defaults(options, { allowedStartRules: [ast.rules[0].name], @@ -32,7 +47,9 @@ module.exports = { output: "parser" }); - utils.each(passes, function(p) { p(ast, options); }); + for (stage in passes) { + utils.each(passes[stage], runPass); + } switch (options.output) { case "parser": return eval(ast.code); diff --git a/lib/peg.js b/lib/peg.js index 0410c29..55c6095 100644 --- a/lib/peg.js +++ b/lib/peg.js @@ -20,11 +20,21 @@ module.exports = { * generated parser and cause its malfunction. */ buildParser: function(grammar) { + function convertPasses(passes) { + var converted = {}, stage; + + for (stage in passes) { + converted[stage] = utils.values(passes[stage]); + } + + return converted; + } + var options = arguments.length > 1 ? utils.clone(arguments[1]) : {}, plugins = "plugins" in options ? options.plugins : [], config = { parser: this.parser, - passes: utils.values(this.compiler.passes) + passes: convertPasses(this.compiler.passes) }; utils.each(plugins, function(p) { p.use(config, options); }); diff --git a/spec/compiler/passes/generate-bytecode.spec.js b/spec/compiler/passes/generate-bytecode.spec.js index 8b58dec..1228e33 100644 --- a/spec/compiler/passes/generate-bytecode.spec.js +++ b/spec/compiler/passes/generate-bytecode.spec.js @@ -1,5 +1,5 @@ describe("compiler pass |generateBytecode|", function() { - var pass = PEG.compiler.passes.generateBytecode; + var pass = PEG.compiler.passes.generate.generateBytecode; function bytecodeDetails(bytecode) { return { diff --git a/spec/compiler/passes/remove-proxy-rules.spec.js b/spec/compiler/passes/remove-proxy-rules.spec.js index 319a5e0..fa18369 100644 --- a/spec/compiler/passes/remove-proxy-rules.spec.js +++ b/spec/compiler/passes/remove-proxy-rules.spec.js @@ -1,5 +1,5 @@ describe("compiler pass |removeProxyRules|", function() { - var pass = PEG.compiler.passes.removeProxyRules; + var pass = PEG.compiler.passes.transform.removeProxyRules; function proxyGrammar(rule) { return [rule, 'proxy = proxied', 'proxied = "a"'].join("\n"); diff --git a/spec/compiler/passes/report-left-recursion.spec.js b/spec/compiler/passes/report-left-recursion.spec.js index a18807f..937e4fa 100644 --- a/spec/compiler/passes/report-left-recursion.spec.js +++ b/spec/compiler/passes/report-left-recursion.spec.js @@ -1,5 +1,5 @@ describe("compiler pass |reportLeftRecursion|", function() { - var pass = PEG.compiler.passes.reportLeftRecursion; + var pass = PEG.compiler.passes.check.reportLeftRecursion; beforeEach(function() { this.addMatchers({ diff --git a/spec/compiler/passes/report-missing-rules.spec.js b/spec/compiler/passes/report-missing-rules.spec.js index 0d8f17b..11c94e6 100644 --- a/spec/compiler/passes/report-missing-rules.spec.js +++ b/spec/compiler/passes/report-missing-rules.spec.js @@ -1,5 +1,5 @@ describe("compiler pass |reportMissingRules|", function() { - var pass = PEG.compiler.passes.reportMissingRules; + var pass = PEG.compiler.passes.check.reportMissingRules; beforeEach(function() { this.addMatchers({