From 5adad3ae12f4e6ddeeb5a1a70dadf925c95e4dd8 Mon Sep 17 00:00:00 2001 From: David Majda Date: Thu, 8 May 2014 11:48:56 +0200 Subject: [PATCH] Utility functions cleanup: Split lib/utils.js Split lib/utils.js into multiple files. Some of the functions were generic, these were moved into files in lib/utils. Other funtions were specific for the compiler, these were moved to files in lib/compiler. This commit only moves functions around -- there is no renaming and cleanup performed. Both will come later. --- Makefile | 7 +- lib/compiler.js | 9 +- lib/compiler/asts.js | 14 ++ lib/compiler/javascript.js | 99 ++++++++ lib/compiler/passes/generate-bytecode.js | 46 ++-- lib/compiler/passes/generate-javascript.js | 30 +-- lib/compiler/passes/remove-proxy-rules.js | 13 +- lib/compiler/passes/report-left-recursion.js | 14 +- lib/compiler/passes/report-missing-rules.js | 12 +- lib/compiler/visitor.js | 17 ++ lib/grammar-error.js | 4 +- lib/peg.js | 9 +- lib/utils.js | 236 ------------------- lib/utils/arrays.js | 71 ++++++ lib/utils/classes.js | 14 ++ lib/utils/objects.js | 44 ++++ package.json | 7 +- 17 files changed, 346 insertions(+), 300 deletions(-) create mode 100644 lib/compiler/asts.js create mode 100644 lib/compiler/javascript.js create mode 100644 lib/compiler/visitor.js delete mode 100644 lib/utils.js create mode 100644 lib/utils/arrays.js create mode 100644 lib/utils/classes.js create mode 100644 lib/utils/objects.js diff --git a/Makefile b/Makefile index a9d96fb..c3a78b9 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,15 @@ PEGJS_VERSION = `cat $(VERSION_FILE)` # ===== Modules ===== # Order matters -- dependencies must be listed before modules dependent on them. -MODULES = utils \ +MODULES = utils/arrays \ + utils/objects \ + utils/classes \ grammar-error \ parser \ + compiler/asts \ + compiler/visitor \ compiler/opcodes \ + compiler/javascript \ compiler/passes/generate-bytecode \ compiler/passes/generate-javascript \ compiler/passes/remove-proxy-rules \ diff --git a/lib/compiler.js b/lib/compiler.js index 95e682b..fbae304 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -1,4 +1,5 @@ -var utils = require("./utils"); +var arrays = require("./utils/arrays"), + objects = require("./utils/objects"); var compiler = { /* @@ -29,10 +30,10 @@ var compiler = { * cause its malfunction. */ compile: function(ast, passes) { - var options = arguments.length > 2 ? utils.clone(arguments[2]) : {}, + var options = arguments.length > 2 ? objects.clone(arguments[2]) : {}, stage; - utils.defaults(options, { + objects.defaults(options, { allowedStartRules: [ast.rules[0].name], cache: false, optimize: "speed", @@ -41,7 +42,7 @@ var compiler = { for (stage in passes) { if (passes.hasOwnProperty(stage)) { - utils.each(passes[stage], function(p) { p(ast, options); }); + arrays.each(passes[stage], function(p) { p(ast, options); }); } } diff --git a/lib/compiler/asts.js b/lib/compiler/asts.js new file mode 100644 index 0000000..d7d59f6 --- /dev/null +++ b/lib/compiler/asts.js @@ -0,0 +1,14 @@ +var arrays = require("../utils/arrays"); + +/* AST utilities. */ +var asts = { + findRuleByName: function(ast, name) { + return arrays.find(ast.rules, function(r) { return r.name === name; }); + }, + + indexOfRuleByName: function(ast, name) { + return arrays.indexOf(ast.rules, function(r) { return r.name === name; }); + } +}; + +module.exports = asts; diff --git a/lib/compiler/javascript.js b/lib/compiler/javascript.js new file mode 100644 index 0000000..e573358 --- /dev/null +++ b/lib/compiler/javascript.js @@ -0,0 +1,99 @@ +/* JavaScript code generation helpers. */ +var javascript = { + /* + * Returns a string padded on the left to a desired length with a character. + * + * The code needs to be in sync with the code template in the compilation + * function for "action" nodes. + */ + padLeft: function(input, padding, length) { + var result = input; + + var padLength = length - input.length; + for (var i = 0; i < padLength; i++) { + result = padding + result; + } + + return result; + }, + + /* + * Returns an escape sequence for given character. Uses \x for characters <= + * 0xFF to save space, \u for the rest. + * + * The code needs to be in sync with the code template in the compilation + * function for "action" nodes. + */ + escape: function(ch) { + var charCode = ch.charCodeAt(0); + var escapeChar; + var length; + + if (charCode <= 0xFF) { + escapeChar = 'x'; + length = 2; + } else { + escapeChar = 'u'; + length = 4; + } + + return '\\' + escapeChar + javascript.padLeft(charCode.toString(16).toUpperCase(), '0', length); + }, + + /* + * Surrounds the string with quotes and escapes characters inside so that the + * result is a valid JavaScript string. + * + * The code needs to be in sync with the code template in the compilation + * function for "action" nodes. + */ + quote: function(s) { + /* + * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string + * literal except for the closing quote character, backslash, carriage + * return, line separator, paragraph separator, and line feed. Any character + * may appear in the form of an escape sequence. + * + * For portability, we also escape all control and non-ASCII characters. + * Note that "\0" and "\v" escape sequences are not used because JSHint does + * not like the first and IE the second. + */ + return '"' + s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character + .replace(/\x08/g, '\\b') // backspace + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, javascript.escape) + + '"'; + }, + + /* + * Escapes characters inside the string so that it can be used as a list of + * characters in a character class of a regular expression. + */ + quoteForRegexpClass: function(s) { + /* + * Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1. + * + * For portability, we also escape all control and non-ASCII characters. + */ + return s + .replace(/\\/g, '\\\\') // backslash + .replace(/\//g, '\\/') // closing slash + .replace(/\]/g, '\\]') // closing bracket + .replace(/\^/g, '\\^') // caret + .replace(/-/g, '\\-') // dash + .replace(/\0/g, '\\0') // null + .replace(/\t/g, '\\t') // horizontal tab + .replace(/\n/g, '\\n') // line feed + .replace(/\v/g, '\\x0B') // vertical tab + .replace(/\f/g, '\\f') // form feed + .replace(/\r/g, '\\r') // carriage return + .replace(/[\x01-\x08\x0E-\x1F\x80-\uFFFF]/g, javascript.escape); + } +}; + +module.exports = javascript; diff --git a/lib/compiler/passes/generate-bytecode.js b/lib/compiler/passes/generate-bytecode.js index 97605ff..e848179 100644 --- a/lib/compiler/passes/generate-bytecode.js +++ b/lib/compiler/passes/generate-bytecode.js @@ -1,5 +1,9 @@ -var utils = require("../../utils"), - op = require("../opcodes"); +var arrays = require("../../utils/arrays"), + objects = require("../../utils/objects"), + asts = require("../asts"), + visitor = require("../visitor"), + op = require("../opcodes"), + js = require("../javascript"); /* Generates bytecode. * @@ -173,7 +177,7 @@ function generateBytecode(ast) { var consts = []; function addConst(value) { - var index = utils.indexOf(consts, function(c) { return c === value; }); + var index = arrays.indexOf(consts, function(c) { return c === value; }); return index === -1 ? consts.push(value) - 1 : index; } @@ -201,7 +205,7 @@ function generateBytecode(ast) { } function buildCall(functionIndex, delta, env, sp) { - var params = utils.map( utils.values(env), function(p) { return sp - p; }); + var params = arrays.map( objects.values(env), function(p) { return sp - p; }); return [op.CALL, functionIndex, delta, params.length].concat(params); } @@ -236,7 +240,7 @@ function generateBytecode(ast) { } function buildSemanticPredicate(code, negative, context) { - var functionIndex = addFunctionConst(utils.keys(context.env), code), + var functionIndex = addFunctionConst(objects.keys(context.env), code), undefinedIndex = addConst('void 0'), failedIndex = addConst('peg$FAILED'); @@ -264,9 +268,9 @@ function generateBytecode(ast) { ); } - var generate = utils.buildNodeVisitor({ + var generate = visitor.buildNodeVisitor({ grammar: function(node) { - utils.each(node.rules, generate); + arrays.each(node.rules, generate); node.consts = consts; }, @@ -281,7 +285,7 @@ function generateBytecode(ast) { named: function(node, context) { var nameIndex = addConst( - '{ type: "other", description: ' + utils.quote(node.name) + ' }' + '{ type: "other", description: ' + js.quote(node.name) + ' }' ); /* @@ -331,7 +335,7 @@ function generateBytecode(ast) { env: env, action: node }), - functionIndex = addFunctionConst(utils.keys(env), node.code); + functionIndex = addFunctionConst(objects.keys(env), node.code); return emitCall ? buildSequence( @@ -380,7 +384,7 @@ function generateBytecode(ast) { } else { if (context.action) { functionIndex = addFunctionConst( - utils.keys(context.env), + objects.keys(context.env), context.action.code ); @@ -505,7 +509,7 @@ function generateBytecode(ast) { }, rule_ref: function(node) { - return [op.RULE, utils.indexOfRuleByName(ast, node.name)]; + return [op.RULE, asts.indexOfRuleByName(ast, node.name)]; }, literal: function(node) { @@ -513,14 +517,14 @@ function generateBytecode(ast) { if (node.value.length > 0) { stringIndex = addConst(node.ignoreCase - ? utils.quote(node.value.toLowerCase()) - : utils.quote(node.value) + ? js.quote(node.value.toLowerCase()) + : js.quote(node.value) ); expectedIndex = addConst([ '{', 'type: "literal",', - 'value: ' + utils.quote(node.value) + ',', - 'description: ' + utils.quote(utils.quote(node.value)), + 'value: ' + js.quote(node.value) + ',', + 'description: ' + js.quote(js.quote(node.value)), '}' ].join(' ')); @@ -551,12 +555,12 @@ function generateBytecode(ast) { if (node.parts.length > 0) { regexp = '/^[' + (node.inverted ? '^' : '') - + utils.map(node.parts, function(part) { + + arrays.map(node.parts, function(part) { return part instanceof Array - ? utils.quoteForRegexpClass(part[0]) + ? js.quoteForRegexpClass(part[0]) + '-' - + utils.quoteForRegexpClass(part[1]) - : utils.quoteForRegexpClass(part); + + js.quoteForRegexpClass(part[1]) + : js.quoteForRegexpClass(part); }).join('') + ']/' + (node.ignoreCase ? 'i' : ''); } else { @@ -571,8 +575,8 @@ function generateBytecode(ast) { expectedIndex = addConst([ '{', 'type: "class",', - 'value: ' + utils.quote(node.rawText) + ',', - 'description: ' + utils.quote(node.rawText), + 'value: ' + js.quote(node.rawText) + ',', + 'description: ' + js.quote(node.rawText), '}' ].join(' ')); diff --git a/lib/compiler/passes/generate-javascript.js b/lib/compiler/passes/generate-javascript.js index 85b7a98..7b9c60b 100644 --- a/lib/compiler/passes/generate-javascript.js +++ b/lib/compiler/passes/generate-javascript.js @@ -1,5 +1,7 @@ -var utils = require("../../utils"), - op = require("../opcodes"); +var arrays = require("../../utils/arrays"), + asts = require("../asts"), + op = require("../opcodes"), + js = require("../javascript"); /* Generates parser JavaScript code. */ function generateJavaScript(ast, options) { @@ -17,11 +19,11 @@ function generateJavaScript(ast, options) { '],', '', 'peg$bytecode = [', - indent2(utils.map( + indent2(arrays.map( ast.rules, function(rule) { return 'peg$decode(' - + utils.quote(utils.map( + + js.quote(arrays.map( rule.bytecode, function(b) { return String.fromCharCode(b + 32); } ).join('')) @@ -31,7 +33,7 @@ function generateJavaScript(ast, options) { '],' ].join('\n'); } else { - return utils.map( + return arrays.map( ast.consts, function(c, i) { return 'peg$c' + i + ' = ' + c + ','; } ).join('\n'); @@ -352,7 +354,7 @@ function generateJavaScript(ast, options) { return s(this.sp--); } else { n = arguments[0]; - values = utils.map(utils.range(this.sp - n + 1, this.sp + 1), s); + values = arrays.map(arrays.range(this.sp - n + 1, this.sp + 1), s); this.sp -= n; return values; @@ -433,7 +435,7 @@ function generateJavaScript(ast, options) { paramsLength = bc[ip + baseLength - 1]; var value = c(bc[ip + 1]) + '(' - + utils.map( + + arrays.map( bc.slice(ip + baseLength, ip + baseLength + paramsLength), function(p) { return stack.index(p); } ).join(', ') @@ -626,13 +628,13 @@ function generateJavaScript(ast, options) { parts.push([ 'function peg$parse' + rule.name + '() {', - ' var ' + utils.map(utils.range(0, stack.maxSp + 1), s).join(', ') + ';', + ' var ' + arrays.map(arrays.range(0, stack.maxSp + 1), s).join(', ') + ';', '' ].join('\n')); if (options.cache) { parts.push(indent2( - generateCacheHeader(utils.indexOfRuleByName(ast, rule.name)) + generateCacheHeader(asts.indexOfRuleByName(ast, rule.name)) )); } @@ -692,12 +694,12 @@ function generateJavaScript(ast, options) { if (options.optimize === "size") { startRuleIndices = '{ ' - + utils.map( + + arrays.map( options.allowedStartRules, - function(r) { return r + ': ' + utils.indexOfRuleByName(ast, r); } + function(r) { return r + ': ' + asts.indexOfRuleByName(ast, r); } ).join(', ') + ' }'; - startRuleIndex = utils.indexOfRuleByName(ast, options.allowedStartRules[0]); + startRuleIndex = asts.indexOfRuleByName(ast, options.allowedStartRules[0]); parts.push([ ' peg$startRuleIndices = ' + startRuleIndices + ',', @@ -705,7 +707,7 @@ function generateJavaScript(ast, options) { ].join('\n')); } else { startRuleFunctions = '{ ' - + utils.map( + + arrays.map( options.allowedStartRules, function(r) { return r + ': peg$parse' + r; } ).join(', ') @@ -936,7 +938,7 @@ function generateJavaScript(ast, options) { parts.push(indent4(generateInterpreter())); parts.push(''); } else { - utils.each(ast.rules, function(rule) { + arrays.each(ast.rules, function(rule) { parts.push(indent4(generateRuleFunction(rule))); parts.push(''); }); diff --git a/lib/compiler/passes/remove-proxy-rules.js b/lib/compiler/passes/remove-proxy-rules.js index 406d257..35b55f5 100644 --- a/lib/compiler/passes/remove-proxy-rules.js +++ b/lib/compiler/passes/remove-proxy-rules.js @@ -1,4 +1,5 @@ -var utils = require("../../utils"); +var arrays = require("../../utils/arrays"), + visitor = require("../visitor"); /* * Removes proxy rules -- that is, rules that only delegate to other rule. @@ -17,13 +18,13 @@ function removeProxyRules(ast, options) { function replaceInSubnodes(propertyName) { return function(node, from, to) { - utils.each(node[propertyName], function(subnode) { + arrays.each(node[propertyName], function(subnode) { replace(subnode, from, to); }); }; } - var replace = utils.buildNodeVisitor({ + var replace = visitor.buildNodeVisitor({ grammar: replaceInSubnodes("rules"), rule: replaceInExpression, named: replaceInExpression, @@ -57,10 +58,10 @@ function removeProxyRules(ast, options) { var indices = []; - utils.each(ast.rules, function(rule, i) { + arrays.each(ast.rules, function(rule, i) { if (isProxyRule(rule)) { replaceRuleRefs(ast, rule.name, rule.expression.name); - if (!utils.contains(options.allowedStartRules, rule.name)) { + if (!arrays.contains(options.allowedStartRules, rule.name)) { indices.push(i); } } @@ -68,7 +69,7 @@ function removeProxyRules(ast, options) { indices.reverse(); - utils.each(indices, function(index) { + arrays.each(indices, function(index) { ast.rules.splice(index, 1); }); } diff --git a/lib/compiler/passes/report-left-recursion.js b/lib/compiler/passes/report-left-recursion.js index 0387971..480de26 100644 --- a/lib/compiler/passes/report-left-recursion.js +++ b/lib/compiler/passes/report-left-recursion.js @@ -1,5 +1,7 @@ -var utils = require("../../utils"), - GrammarError = require("../../grammar-error"); +var arrays = require("../../utils/arrays"), + GrammarError = require("../../grammar-error"), + asts = require("../asts"), + visitor = require("../visitor"); /* Checks that no left recursion is present. */ function reportLeftRecursion(ast) { @@ -11,13 +13,13 @@ function reportLeftRecursion(ast) { function checkSubnodes(propertyName) { return function(node, appliedRules) { - utils.each(node[propertyName], function(subnode) { + arrays.each(node[propertyName], function(subnode) { check(subnode, appliedRules); }); }; } - var check = utils.buildNodeVisitor({ + var check = visitor.buildNodeVisitor({ grammar: checkSubnodes("rules"), rule: @@ -48,12 +50,12 @@ function reportLeftRecursion(ast) { rule_ref: function(node, appliedRules) { - if (utils.contains(appliedRules, node.name)) { + if (arrays.contains(appliedRules, node.name)) { throw new GrammarError( "Left recursion detected for rule \"" + node.name + "\"." ); } - check(utils.findRuleByName(ast, node.name), appliedRules); + check(asts.findRuleByName(ast, node.name), appliedRules); }, literal: nop, diff --git a/lib/compiler/passes/report-missing-rules.js b/lib/compiler/passes/report-missing-rules.js index 22d1104..b02ed14 100644 --- a/lib/compiler/passes/report-missing-rules.js +++ b/lib/compiler/passes/report-missing-rules.js @@ -1,5 +1,7 @@ -var utils = require("../../utils"), - GrammarError = require("../../grammar-error"); +var arrays = require("../../utils/arrays"), + GrammarError = require("../../grammar-error"), + asts = require("../asts"), + visitor = require("../visitor"); /* Checks that all referenced rules exist. */ function reportMissingRules(ast) { @@ -8,10 +10,10 @@ function reportMissingRules(ast) { function checkExpression(node) { check(node.expression); } function checkSubnodes(propertyName) { - return function(node) { utils.each(node[propertyName], check); }; + return function(node) { arrays.each(node[propertyName], check); }; } - var check = utils.buildNodeVisitor({ + var check = visitor.buildNodeVisitor({ grammar: checkSubnodes("rules"), rule: checkExpression, named: checkExpression, @@ -30,7 +32,7 @@ function reportMissingRules(ast) { rule_ref: function(node) { - if (!utils.findRuleByName(ast, node.name)) { + if (!asts.findRuleByName(ast, node.name)) { throw new GrammarError( "Referenced rule \"" + node.name + "\" does not exist." ); diff --git a/lib/compiler/visitor.js b/lib/compiler/visitor.js new file mode 100644 index 0000000..bd013a9 --- /dev/null +++ b/lib/compiler/visitor.js @@ -0,0 +1,17 @@ +/* Simple AST node visitor builder. */ +var visitor = { + /* + * Builds a node visitor -- a function which takes a node and any number of + * other parameters, calls an appropriate function according to the node type, + * passes it all its parameters and returns its value. The functions for + * various node types are passed in a parameter to |buildNodeVisitor| as a + * hash. + */ + buildNodeVisitor: function(functions) { + return function(node) { + return functions[node.type].apply(null, arguments); + }; + } +}; + +module.exports = visitor; diff --git a/lib/grammar-error.js b/lib/grammar-error.js index 7f8d5b4..7497490 100644 --- a/lib/grammar-error.js +++ b/lib/grammar-error.js @@ -1,4 +1,4 @@ -var utils = require("./utils"); +var classes = require("./utils/classes"); /* Thrown when the grammar contains an error. */ function GrammarError(message) { @@ -6,6 +6,6 @@ function GrammarError(message) { this.message = message; } -utils.subclass(GrammarError, Error); +classes.subclass(GrammarError, Error); module.exports = GrammarError; diff --git a/lib/peg.js b/lib/peg.js index 2005219..6e7c509 100644 --- a/lib/peg.js +++ b/lib/peg.js @@ -1,4 +1,5 @@ -var utils = require("./utils"); +var arrays = require("./utils/arrays"), + objects = require("./utils/objects"); var PEG = { /* PEG.js version (uses semantic versioning). */ @@ -25,21 +26,21 @@ var PEG = { for (stage in passes) { if (passes.hasOwnProperty(stage)) { - converted[stage] = utils.values(passes[stage]); + converted[stage] = objects.values(passes[stage]); } } return converted; } - var options = arguments.length > 1 ? utils.clone(arguments[1]) : {}, + var options = arguments.length > 1 ? objects.clone(arguments[1]) : {}, plugins = "plugins" in options ? options.plugins : [], config = { parser: this.parser, passes: convertPasses(this.compiler.passes) }; - utils.each(plugins, function(p) { p.use(config, options); }); + arrays.each(plugins, function(p) { p.use(config, options); }); return this.compiler.compile( config.parser.parse(grammar), diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index d491099..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,236 +0,0 @@ -var utils = { - /* Like Python's |range|, but without |step|. */ - range: function(start, stop) { - if (stop === undefined) { - stop = start; - start = 0; - } - - var result = new Array(Math.max(0, stop - start)); - for (var i = 0, j = start; j < stop; i++, j++) { - result[i] = j; - } - return result; - }, - - find: function(array, callback) { - var length = array.length; - for (var i = 0; i < length; i++) { - if (callback(array[i])) { - return array[i]; - } - } - }, - - indexOf: function(array, callback) { - var length = array.length; - for (var i = 0; i < length; i++) { - if (callback(array[i])) { - return i; - } - } - return -1; - }, - - contains: function(array, value) { - /* - * Stupid IE does not have Array.prototype.indexOf, otherwise this function - * would be a one-liner. - */ - var length = array.length; - for (var i = 0; i < length; i++) { - if (array[i] === value) { - return true; - } - } - return false; - }, - - each: function(array, callback) { - var length = array.length; - for (var i = 0; i < length; i++) { - callback(array[i], i); - } - }, - - map: function(array, callback) { - var result = []; - var length = array.length; - for (var i = 0; i < length; i++) { - result[i] = callback(array[i], i); - } - return result; - }, - - pluck: function(array, key) { - return utils.map(array, function (e) { return e[key]; }); - }, - - keys: function(object) { - var result = []; - for (var key in object) { - if (object.hasOwnProperty(key)) { - result.push(key); - } - } - return result; - }, - - values: function(object) { - var result = []; - for (var key in object) { - if (object.hasOwnProperty(key)) { - result.push(object[key]); - } - } - return result; - }, - - clone: function(object) { - var result = {}; - for (var key in object) { - if (object.hasOwnProperty(key)) { - result[key] = object[key]; - } - } - return result; - }, - - defaults: function(object, defaults) { - for (var key in defaults) { - if (defaults.hasOwnProperty(key)) { - if (!(key in object)) { - object[key] = defaults[key]; - } - } - } - }, - - /* - * The code needs to be in sync with the code template in the compilation - * function for "action" nodes. - */ - subclass: function(child, parent) { - function ctor() { this.constructor = child; } - ctor.prototype = parent.prototype; - child.prototype = new ctor(); - }, - - /* - * Returns a string padded on the left to a desired length with a character. - * - * The code needs to be in sync with the code template in the compilation - * function for "action" nodes. - */ - padLeft: function(input, padding, length) { - var result = input; - - var padLength = length - input.length; - for (var i = 0; i < padLength; i++) { - result = padding + result; - } - - return result; - }, - - /* - * Returns an escape sequence for given character. Uses \x for characters <= - * 0xFF to save space, \u for the rest. - * - * The code needs to be in sync with the code template in the compilation - * function for "action" nodes. - */ - escape: function(ch) { - var charCode = ch.charCodeAt(0); - var escapeChar; - var length; - - if (charCode <= 0xFF) { - escapeChar = 'x'; - length = 2; - } else { - escapeChar = 'u'; - length = 4; - } - - return '\\' + escapeChar + utils.padLeft(charCode.toString(16).toUpperCase(), '0', length); - }, - - /* - * Surrounds the string with quotes and escapes characters inside so that the - * result is a valid JavaScript string. - * - * The code needs to be in sync with the code template in the compilation - * function for "action" nodes. - */ - quote: function(s) { - /* - * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string - * literal except for the closing quote character, backslash, carriage - * return, line separator, paragraph separator, and line feed. Any character - * may appear in the form of an escape sequence. - * - * For portability, we also escape all control and non-ASCII characters. - * Note that "\0" and "\v" escape sequences are not used because JSHint does - * not like the first and IE the second. - */ - return '"' + s - .replace(/\\/g, '\\\\') // backslash - .replace(/"/g, '\\"') // closing quote character - .replace(/\x08/g, '\\b') // backspace - .replace(/\t/g, '\\t') // horizontal tab - .replace(/\n/g, '\\n') // line feed - .replace(/\f/g, '\\f') // form feed - .replace(/\r/g, '\\r') // carriage return - .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, utils.escape) - + '"'; - }, - - /* - * Escapes characters inside the string so that it can be used as a list of - * characters in a character class of a regular expression. - */ - quoteForRegexpClass: function(s) { - /* - * Based on ECMA-262, 5th ed., 7.8.5 & 15.10.1. - * - * For portability, we also escape all control and non-ASCII characters. - */ - return s - .replace(/\\/g, '\\\\') // backslash - .replace(/\//g, '\\/') // closing slash - .replace(/\]/g, '\\]') // closing bracket - .replace(/\^/g, '\\^') // caret - .replace(/-/g, '\\-') // dash - .replace(/\0/g, '\\0') // null - .replace(/\t/g, '\\t') // horizontal tab - .replace(/\n/g, '\\n') // line feed - .replace(/\v/g, '\\x0B') // vertical tab - .replace(/\f/g, '\\f') // form feed - .replace(/\r/g, '\\r') // carriage return - .replace(/[\x01-\x08\x0E-\x1F\x80-\uFFFF]/g, utils.escape); - }, - - /* - * Builds a node visitor -- a function which takes a node and any number of - * other parameters, calls an appropriate function according to the node type, - * passes it all its parameters and returns its value. The functions for - * various node types are passed in a parameter to |buildNodeVisitor| as a - * hash. - */ - buildNodeVisitor: function(functions) { - return function(node) { - return functions[node.type].apply(null, arguments); - }; - }, - - findRuleByName: function(ast, name) { - return utils.find(ast.rules, function(r) { return r.name === name; }); - }, - - indexOfRuleByName: function(ast, name) { - return utils.indexOf(ast.rules, function(r) { return r.name === name; }); - } -}; - -module.exports = utils; diff --git a/lib/utils/arrays.js b/lib/utils/arrays.js new file mode 100644 index 0000000..7d1337d --- /dev/null +++ b/lib/utils/arrays.js @@ -0,0 +1,71 @@ +/* Array utilities. */ +var arrays = { + /* Like Python's |range|, but without |step|. */ + range: function(start, stop) { + if (stop === undefined) { + stop = start; + start = 0; + } + + var result = new Array(Math.max(0, stop - start)); + for (var i = 0, j = start; j < stop; i++, j++) { + result[i] = j; + } + return result; + }, + + find: function(array, callback) { + var length = array.length; + for (var i = 0; i < length; i++) { + if (callback(array[i])) { + return array[i]; + } + } + }, + + indexOf: function(array, callback) { + var length = array.length; + for (var i = 0; i < length; i++) { + if (callback(array[i])) { + return i; + } + } + return -1; + }, + + contains: function(array, value) { + /* + * Stupid IE does not have Array.prototype.indexOf, otherwise this function + * would be a one-liner. + */ + var length = array.length; + for (var i = 0; i < length; i++) { + if (array[i] === value) { + return true; + } + } + return false; + }, + + each: function(array, callback) { + var length = array.length; + for (var i = 0; i < length; i++) { + callback(array[i], i); + } + }, + + map: function(array, callback) { + var result = []; + var length = array.length; + for (var i = 0; i < length; i++) { + result[i] = callback(array[i], i); + } + return result; + }, + + pluck: function(array, key) { + return arrays.map(array, function (e) { return e[key]; }); + } +}; + +module.exports = arrays; diff --git a/lib/utils/classes.js b/lib/utils/classes.js new file mode 100644 index 0000000..7fdad13 --- /dev/null +++ b/lib/utils/classes.js @@ -0,0 +1,14 @@ +/* Class utilities */ +var classes = { + /* + * The code needs to be in sync with the code template in the compilation + * function for "action" nodes. + */ + subclass: function(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + }, +}; + +module.exports = classes; diff --git a/lib/utils/objects.js b/lib/utils/objects.js new file mode 100644 index 0000000..6c99b14 --- /dev/null +++ b/lib/utils/objects.js @@ -0,0 +1,44 @@ +/* Object utilities. */ +var objects = { + keys: function(object) { + var result = []; + for (var key in object) { + if (object.hasOwnProperty(key)) { + result.push(key); + } + } + return result; + }, + + values: function(object) { + var result = []; + for (var key in object) { + if (object.hasOwnProperty(key)) { + result.push(object[key]); + } + } + return result; + }, + + clone: function(object) { + var result = {}; + for (var key in object) { + if (object.hasOwnProperty(key)) { + result[key] = object[key]; + } + } + return result; + }, + + defaults: function(object, defaults) { + for (var key in defaults) { + if (defaults.hasOwnProperty(key)) { + if (!(key in object)) { + object[key] = defaults[key]; + } + } + } + } +}; + +module.exports = objects; diff --git a/package.json b/package.json index a12a8b3..cd728f7 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,10 @@ "examples/javascript.pegjs", "examples/json.pegjs", "lib/compiler.js", + "lib/compiler/asts.js", + "lib/compiler/javascript.js", "lib/compiler/opcodes.js", + "lib/compiler/visitor.js", "lib/compiler/passes/generate-bytecode.js", "lib/compiler/passes/generate-javascript.js", "lib/compiler/passes/remove-proxy-rules.js", @@ -28,7 +31,9 @@ "lib/grammar-error.js", "lib/parser.js", "lib/peg.js", - "lib/utils.js", + "lib/utils/arrays.js", + "lib/utils/classes.js", + "lib/utils/objects.js", "package.json" ], "main": "lib/peg",