You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
864 lines
32 KiB
JavaScript
864 lines
32 KiB
JavaScript
var utils = require("../../utils");
|
|
|
|
/* Generates the parser code. */
|
|
module.exports = function(ast, options) {
|
|
options = utils.clone(options);
|
|
utils.defaults(options, {
|
|
cache: false,
|
|
allowedStartRules: [ast.startRule]
|
|
});
|
|
|
|
/*
|
|
* Codie 1.1.0
|
|
*
|
|
* https://github.com/dmajda/codie
|
|
*
|
|
* Copyright (c) 2011-2012 David Majda
|
|
* Licensend under the MIT license.
|
|
*/
|
|
var Codie = (function(undefined) {
|
|
|
|
function stringEscape(s) {
|
|
function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
|
|
|
|
/*
|
|
* 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 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 double quote
|
|
.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\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
|
|
.replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); })
|
|
.replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); })
|
|
.replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); });
|
|
}
|
|
|
|
function push(s) { return '__p.push(' + s + ');'; }
|
|
|
|
function pushRaw(template, length, state) {
|
|
function unindent(code, level, unindentFirst) {
|
|
return code.replace(
|
|
new RegExp('^.{' + level +'}', "gm"),
|
|
function(str, offset) {
|
|
if (offset === 0) {
|
|
return unindentFirst ? '' : str;
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
var escaped = stringEscape(unindent(
|
|
template.substring(0, length),
|
|
state.indentLevel(),
|
|
state.atBOL
|
|
));
|
|
|
|
return escaped.length > 0 ? push('"' + escaped + '"') : '';
|
|
}
|
|
|
|
|
|
var Codie = {
|
|
/* Codie version (uses semantic versioning). */
|
|
VERSION: "1.1.0",
|
|
|
|
/*
|
|
* Specifies by how many characters do #if/#else and #for unindent their
|
|
* content in the generated code.
|
|
*/
|
|
indentStep: 2,
|
|
|
|
/* Description of #-commands. Extend to define your own commands. */
|
|
commands: {
|
|
"if": {
|
|
params: /^(.*)$/,
|
|
compile: function(state, prefix, params) {
|
|
return ['if(' + params[0] + '){', []];
|
|
},
|
|
stackOp: "push"
|
|
},
|
|
"else": {
|
|
params: /^$/,
|
|
compile: function(state) {
|
|
var stack = state.commandStack,
|
|
insideElse = stack[stack.length - 1] === "else",
|
|
insideIf = stack[stack.length - 1] === "if";
|
|
|
|
if (insideElse) { throw new Error("Multiple #elses."); }
|
|
if (!insideIf) { throw new Error("Using #else outside of #if."); }
|
|
|
|
return ['}else{', []];
|
|
},
|
|
stackOp: "replace"
|
|
},
|
|
"for": {
|
|
params: /^([a-zA-Z_][a-zA-Z0-9_]*)[ \t]+in[ \t]+(.*)$/,
|
|
init: function(state) {
|
|
state.forCurrLevel = 0; // current level of #for loop nesting
|
|
state.forMaxLevel = 0; // maximum level of #for loop nesting
|
|
},
|
|
compile: function(state, prefix, params) {
|
|
var c = '__c' + state.forCurrLevel, // __c for "collection"
|
|
l = '__l' + state.forCurrLevel, // __l for "length"
|
|
i = '__i' + state.forCurrLevel; // __i for "index"
|
|
|
|
state.forCurrLevel++;
|
|
if (state.forMaxLevel < state.forCurrLevel) {
|
|
state.forMaxLevel = state.forCurrLevel;
|
|
}
|
|
|
|
return [
|
|
c + '=' + params[1] + ';'
|
|
+ l + '=' + c + '.length;'
|
|
+ 'for(' + i + '=0;' + i + '<' + l + ';' + i + '++){'
|
|
+ params[0] + '=' + c + '[' + i + '];',
|
|
[params[0], c, l, i]
|
|
];
|
|
},
|
|
exit: function(state) { state.forCurrLevel--; },
|
|
stackOp: "push"
|
|
},
|
|
"end": {
|
|
params: /^$/,
|
|
compile: function(state) {
|
|
var stack = state.commandStack, exit;
|
|
|
|
if (stack.length === 0) { throw new Error("Too many #ends."); }
|
|
|
|
exit = Codie.commands[stack[stack.length - 1]].exit;
|
|
if (exit) { exit(state); }
|
|
|
|
return ['}', []];
|
|
},
|
|
stackOp: "pop"
|
|
},
|
|
"block": {
|
|
params: /^(.*)$/,
|
|
compile: function(state, prefix, params) {
|
|
var x = '__x', // __x for "prefix",
|
|
n = '__n', // __n for "lines"
|
|
l = '__l', // __l for "length"
|
|
i = '__i'; // __i for "index"
|
|
|
|
/*
|
|
* Originally, the generated code used |String.prototype.replace|, but
|
|
* it is buggy in certain versions of V8 so it was rewritten. See the
|
|
* tests for details.
|
|
*/
|
|
return [
|
|
x + '="' + stringEscape(prefix.substring(state.indentLevel())) + '";'
|
|
+ n + '=(' + params[0] + ').toString().split("\\n");'
|
|
+ l + '=' + n + '.length;'
|
|
+ 'for(' + i + '=0;' + i + '<' + l + ';' + i + '++){'
|
|
+ n + '[' + i +']=' + x + '+' + n + '[' + i + ']+"\\n";'
|
|
+ '}'
|
|
+ push(n + '.join("")'),
|
|
[x, n, l, i]
|
|
];
|
|
},
|
|
stackOp: "nop"
|
|
}
|
|
},
|
|
|
|
/*
|
|
* Compiles a template into a function. When called, this function will
|
|
* execute the template in the context of an object passed in a parameter and
|
|
* return the result.
|
|
*/
|
|
template: function(template) {
|
|
var stackOps = {
|
|
push: function(stack, name) { stack.push(name); },
|
|
replace: function(stack, name) { stack[stack.length - 1] = name; },
|
|
pop: function(stack) { stack.pop(); },
|
|
nop: function() { }
|
|
};
|
|
|
|
function compileExpr(state, expr) {
|
|
state.atBOL = false;
|
|
return [push(expr), []];
|
|
}
|
|
|
|
function compileCommand(state, prefix, name, params) {
|
|
var command, match, result;
|
|
|
|
command = Codie.commands[name];
|
|
if (!command) { throw new Error("Unknown command: #" + name + "."); }
|
|
|
|
match = command.params.exec(params);
|
|
if (match === null) {
|
|
throw new Error(
|
|
"Invalid params for command #" + name + ": " + params + "."
|
|
);
|
|
}
|
|
|
|
result = command.compile(state, prefix, match.slice(1));
|
|
stackOps[command.stackOp](state.commandStack, name);
|
|
state.atBOL = true;
|
|
return result;
|
|
}
|
|
|
|
var state = { // compilation state
|
|
commandStack: [], // stack of commands as they were nested
|
|
atBOL: true, // is the next character to process at BOL?
|
|
indentLevel: function() {
|
|
return Codie.indentStep * this.commandStack.length;
|
|
}
|
|
},
|
|
code = '', // generated template function code
|
|
vars = ['__p=[]'], // variables used by generated code
|
|
name, match, result, i;
|
|
|
|
/* Initialize state. */
|
|
for (name in Codie.commands) {
|
|
if (Codie.commands[name].init) { Codie.commands[name].init(state); }
|
|
}
|
|
|
|
/* Compile the template. */
|
|
while ((match = /^([ \t]*)#([a-zA-Z_][a-zA-Z0-9_]*)(?:[ \t]+([^ \t\n][^\n]*))?[ \t]*(?:\n|$)|#\{([^}]*)\}/m.exec(template)) !== null) {
|
|
code += pushRaw(template, match.index, state);
|
|
result = match[2] !== undefined && match[2] !== ""
|
|
? compileCommand(state, match[1], match[2], match[3] || "") // #-command
|
|
: compileExpr(state, match[4]); // #{...}
|
|
code += result[0];
|
|
vars = vars.concat(result[1]);
|
|
template = template.substring(match.index + match[0].length);
|
|
}
|
|
code += pushRaw(template, template.length, state);
|
|
|
|
/* Check the final state. */
|
|
if (state.commandStack.length > 0) { throw new Error("Missing #end."); }
|
|
|
|
/* Sanitize the list of variables used by commands. */
|
|
vars.sort();
|
|
for (i = 0; i < vars.length; i++) {
|
|
if (vars[i] === vars[i - 1]) { vars.splice(i--, 1); }
|
|
}
|
|
|
|
/* Create the resulting function. */
|
|
return new Function("__v", [
|
|
'__v=__v||{};',
|
|
'var ' + vars.join(',') + ';',
|
|
'with(__v){',
|
|
code,
|
|
'return __p.join("").replace(/^\\n+|\\n+$/g,"");};'
|
|
].join(''));
|
|
}
|
|
};
|
|
|
|
return Codie;
|
|
|
|
})();
|
|
|
|
var templates = (function() {
|
|
var name,
|
|
templates = {},
|
|
sources = {
|
|
grammar: [
|
|
'(function(){',
|
|
' /*',
|
|
' * Generated by PEG.js 0.7.0.',
|
|
' *',
|
|
' * http://pegjs.majda.cz/',
|
|
' */',
|
|
' ',
|
|
/* This needs to be in sync with |subclass| in utils.js. */
|
|
' function subclass(child, parent) {',
|
|
' function ctor() { this.constructor = child; }',
|
|
' ctor.prototype = parent.prototype;',
|
|
' child.prototype = new ctor();',
|
|
' }',
|
|
' ',
|
|
/* This needs to be in sync with |quote| in utils.js. */
|
|
' function quote(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 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, escape)',
|
|
' + \'"\';',
|
|
' }',
|
|
' ',
|
|
' var result = {',
|
|
' /*',
|
|
' * Parses the input with a generated parser. If the parsing is successful,',
|
|
' * returns a value explicitly or implicitly specified by the grammar from',
|
|
' * which the parser was generated (see |PEG.buildParser|). If the parsing is',
|
|
' * unsuccessful, throws |PEG.parser.SyntaxError| describing the error.',
|
|
' */',
|
|
' parse: function(input) {',
|
|
' var parseFunctions = {',
|
|
' #for rule in options.allowedStartRules',
|
|
' #{string(rule) + ": parse_" + rule + (rule !== options.allowedStartRules[options.allowedStartRules.length - 1] ? "," : "")}',
|
|
' #end',
|
|
' };',
|
|
' ',
|
|
' var options = arguments.length > 1 ? arguments[1] : {},',
|
|
' startRule;',
|
|
' ',
|
|
' if (options.startRule !== undefined) {',
|
|
' startRule = options.startRule;',
|
|
' ',
|
|
' if (parseFunctions[startRule] === undefined) {',
|
|
' throw new Error("Can\'t start parsing from rule " + quote(startRule) + ".");',
|
|
' }',
|
|
' } else {',
|
|
' startRule = #{string(options.allowedStartRules[0])};',
|
|
' }',
|
|
' ',
|
|
' var pos = 0;',
|
|
' var reportedPos = 0;',
|
|
' var cachedReportedPos = 0;',
|
|
' var cachedReportedPosDetails = { line: 1, column: 1, seenCR: false };',
|
|
' var reportFailures = 0;', // 0 = report, anything > 0 = do not report
|
|
' var rightmostFailuresPos = 0;',
|
|
' var rightmostFailuresExpected = [];',
|
|
' #if options.cache',
|
|
' var cache = {};',
|
|
' #end',
|
|
' ',
|
|
/* This needs to be in sync with |padLeft| in utils.js. */
|
|
' function padLeft(input, padding, length) {',
|
|
' var result = input;',
|
|
' ',
|
|
' var padLength = length - input.length;',
|
|
' for (var i = 0; i < padLength; i++) {',
|
|
' result = padding + result;',
|
|
' }',
|
|
' ',
|
|
' return result;',
|
|
' }',
|
|
' ',
|
|
/* This needs to be in sync with |escape| in utils.js. */
|
|
' function escape(ch) {',
|
|
' var charCode = ch.charCodeAt(0);',
|
|
' var escapeChar;',
|
|
' var length;',
|
|
' ',
|
|
' if (charCode <= 0xFF) {',
|
|
' escapeChar = \'x\';',
|
|
' length = 2;',
|
|
' } else {',
|
|
' escapeChar = \'u\';',
|
|
' length = 4;',
|
|
' }',
|
|
' ',
|
|
' return \'\\\\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), \'0\', length);',
|
|
' }',
|
|
' ',
|
|
' function computeReportedPosDetails() {',
|
|
' function advanceCachedReportedPos() {',
|
|
' var ch;',
|
|
' ',
|
|
' for (; cachedReportedPos < reportedPos; cachedReportedPos++) {',
|
|
' ch = input.charAt(cachedReportedPos);',
|
|
' if (ch === "\\n") {',
|
|
' if (!cachedReportedPosDetails.seenCR) { cachedReportedPosDetails.line++; }',
|
|
' cachedReportedPosDetails.column = 1;',
|
|
' cachedReportedPosDetails.seenCR = false;',
|
|
' } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {',
|
|
' cachedReportedPosDetails.line++;',
|
|
' cachedReportedPosDetails.column = 1;',
|
|
' cachedReportedPosDetails.seenCR = true;',
|
|
' } else {',
|
|
' cachedReportedPosDetails.column++;',
|
|
' cachedReportedPosDetails.seenCR = false;',
|
|
' }',
|
|
' }',
|
|
' }',
|
|
' ',
|
|
' if (cachedReportedPos !== reportedPos) {',
|
|
' if (cachedReportedPos > reportedPos) {',
|
|
' cachedReportedPos = 0;',
|
|
' cachedReportedPosDetails = { line: 1, column: 1, seenCR: false };',
|
|
' }',
|
|
' advanceCachedReportedPos();',
|
|
' }',
|
|
' ',
|
|
' return cachedReportedPosDetails;',
|
|
' }',
|
|
' ',
|
|
' function offset() {',
|
|
' return reportedPos;',
|
|
' }',
|
|
' ',
|
|
' function line() {',
|
|
' return computeReportedPosDetails().line;',
|
|
' }',
|
|
' ',
|
|
' function column() {',
|
|
' return computeReportedPosDetails().column;',
|
|
' }',
|
|
' ',
|
|
' function matchFailed(failure) {',
|
|
' if (pos < rightmostFailuresPos) {',
|
|
' return;',
|
|
' }',
|
|
' ',
|
|
' if (pos > rightmostFailuresPos) {',
|
|
' rightmostFailuresPos = pos;',
|
|
' rightmostFailuresExpected = [];',
|
|
' }',
|
|
' ',
|
|
' rightmostFailuresExpected.push(failure);',
|
|
' }',
|
|
' ',
|
|
' #for rule in node.rules',
|
|
' #block emit(rule)',
|
|
' ',
|
|
' #end',
|
|
' ',
|
|
' function cleanupExpected(expected) {',
|
|
' expected.sort();',
|
|
' ',
|
|
' var lastExpected = null;',
|
|
' var cleanExpected = [];',
|
|
' for (var i = 0; i < expected.length; i++) {',
|
|
' if (expected[i] !== lastExpected) {',
|
|
' cleanExpected.push(expected[i]);',
|
|
' lastExpected = expected[i];',
|
|
' }',
|
|
' }',
|
|
' return cleanExpected;',
|
|
' }',
|
|
' ',
|
|
' #if node.initializer',
|
|
' #block emit(node.initializer)',
|
|
' #end',
|
|
' ',
|
|
' var result = parseFunctions[startRule]();',
|
|
' ',
|
|
' /*',
|
|
' * The parser is now in one of the following three states:',
|
|
' *',
|
|
' * 1. The parser successfully parsed the whole input.',
|
|
' *',
|
|
' * - |result !== null|',
|
|
' * - |pos === input.length|',
|
|
' * - |rightmostFailuresExpected| may or may not contain something',
|
|
' *',
|
|
' * 2. The parser successfully parsed only a part of the input.',
|
|
' *',
|
|
' * - |result !== null|',
|
|
' * - |pos < input.length|',
|
|
' * - |rightmostFailuresExpected| may or may not contain something',
|
|
' *',
|
|
' * 3. The parser did not successfully parse any part of the input.',
|
|
' *',
|
|
' * - |result === null|',
|
|
' * - |pos === 0|',
|
|
' * - |rightmostFailuresExpected| contains at least one failure',
|
|
' *',
|
|
' * All code following this comment (including called functions) must',
|
|
' * handle these states.',
|
|
' */',
|
|
' if (result === null || pos !== input.length) {',
|
|
' reportedPos = Math.max(pos, rightmostFailuresPos);',
|
|
' var found = reportedPos < input.length ? input.charAt(reportedPos) : null;',
|
|
' var reportedPosDetails = computeReportedPosDetails();',
|
|
' ',
|
|
' throw new this.SyntaxError(',
|
|
' cleanupExpected(rightmostFailuresExpected),',
|
|
' found,',
|
|
' reportedPos,',
|
|
' reportedPosDetails.line,',
|
|
' reportedPosDetails.column',
|
|
' );',
|
|
' }',
|
|
' ',
|
|
' return result;',
|
|
' }',
|
|
' };',
|
|
' ',
|
|
' /* Thrown when a parser encounters a syntax error. */',
|
|
' ',
|
|
' result.SyntaxError = function(expected, found, offset, line, column) {',
|
|
' function buildMessage(expected, found) {',
|
|
' var expectedHumanized, foundHumanized;',
|
|
' ',
|
|
' switch (expected.length) {',
|
|
' case 0:',
|
|
' expectedHumanized = "end of input";',
|
|
' break;',
|
|
' case 1:',
|
|
' expectedHumanized = expected[0];',
|
|
' break;',
|
|
' default:',
|
|
' expectedHumanized = expected.slice(0, expected.length - 1).join(", ")',
|
|
' + " or "',
|
|
' + expected[expected.length - 1];',
|
|
' }',
|
|
' ',
|
|
' foundHumanized = found ? quote(found) : "end of input";',
|
|
' ',
|
|
' return "Expected " + expectedHumanized + " but " + foundHumanized + " found.";',
|
|
' }',
|
|
' ',
|
|
' this.name = "SyntaxError";',
|
|
' this.expected = expected;',
|
|
' this.found = found;',
|
|
' this.message = buildMessage(expected, found);',
|
|
' this.offset = offset;',
|
|
' this.line = line;',
|
|
' this.column = column;',
|
|
' };',
|
|
' ',
|
|
' subclass(result.SyntaxError, Error);',
|
|
' ',
|
|
' return result;',
|
|
'})()'
|
|
],
|
|
rule: [
|
|
'function parse_#{node.name}() {',
|
|
' #if options.cache',
|
|
' var cacheKey = "#{node.name}@" + pos;',
|
|
' var cachedResult = cache[cacheKey];',
|
|
' if (cachedResult) {',
|
|
' pos = cachedResult.nextPos;',
|
|
' return cachedResult.result;',
|
|
' }',
|
|
' ',
|
|
' #end',
|
|
' #if node.registerCount > 0',
|
|
' var #{map(range(node.registerCount), r).join(", ")};',
|
|
' #end',
|
|
' ',
|
|
' #block emit(node.expression)',
|
|
' #if options.cache',
|
|
' ',
|
|
' cache[cacheKey] = {',
|
|
' nextPos: pos,',
|
|
' result: #{r(node.expression.resultIndex)}',
|
|
' };',
|
|
' #end',
|
|
' return #{r(node.expression.resultIndex)};',
|
|
'}'
|
|
],
|
|
named: [
|
|
'reportFailures++;',
|
|
'#block emit(node.expression)',
|
|
'reportFailures--;',
|
|
'if (reportFailures === 0 && #{r(node.resultIndex)} === null) {',
|
|
' matchFailed(#{string(node.name)});',
|
|
'}'
|
|
],
|
|
choice: [
|
|
'#block emit(alternative)',
|
|
'#block nextAlternativesCode'
|
|
],
|
|
"choice.next": [
|
|
'if (#{r(node.resultIndex)} === null) {',
|
|
' #block code',
|
|
'}'
|
|
],
|
|
action: [
|
|
'#{r(node.posIndex)} = pos;',
|
|
'#block emit(node.expression)',
|
|
'if (#{r(node.resultIndex)} !== null) {',
|
|
' reportedPos = #{r(node.posIndex)};',
|
|
' #{r(node.resultIndex)} = (function(#{keys(node.params).join(", ")}) {#{node.code}})(#{map(values(node.params), r).join(", ")});',
|
|
'}',
|
|
'if (#{r(node.resultIndex)} === null) {',
|
|
' pos = #{r(node.posIndex)};',
|
|
'}'
|
|
],
|
|
sequence: [
|
|
'#{r(node.posIndex)} = pos;',
|
|
'#block code'
|
|
],
|
|
"sequence.iteration": [
|
|
'#block emit(element)',
|
|
'if (#{r(element.resultIndex)} !== null) {',
|
|
' #block code',
|
|
'} else {',
|
|
' #{r(node.resultIndex)} = null;',
|
|
' pos = #{r(node.posIndex)};',
|
|
'}'
|
|
],
|
|
"sequence.inner": [
|
|
'#{r(node.resultIndex)} = [#{map(pluck(node.elements, "resultIndex"), r).join(", ")}];'
|
|
],
|
|
text: [
|
|
'#{r(node.posIndex)} = pos;',
|
|
'#block emit(node.expression)',
|
|
'if (#{r(node.resultIndex)} !== null) {',
|
|
' #{r(node.resultIndex)} = input.substring(pos, #{r(node.posIndex)});',
|
|
'}'
|
|
],
|
|
simple_and: [
|
|
'#{r(node.posIndex)} = pos;',
|
|
'reportFailures++;',
|
|
'#block emit(node.expression)',
|
|
'reportFailures--;',
|
|
'if (#{r(node.resultIndex)} !== null) {',
|
|
' #{r(node.resultIndex)} = "";',
|
|
' pos = #{r(node.posIndex)};',
|
|
'} else {',
|
|
' #{r(node.resultIndex)} = null;',
|
|
'}'
|
|
],
|
|
simple_not: [
|
|
'#{r(node.posIndex)} = pos;',
|
|
'reportFailures++;',
|
|
'#block emit(node.expression)',
|
|
'reportFailures--;',
|
|
'if (#{r(node.resultIndex)} === null) {',
|
|
' #{r(node.resultIndex)} = "";',
|
|
'} else {',
|
|
' #{r(node.resultIndex)} = null;',
|
|
' pos = #{r(node.posIndex)};',
|
|
'}'
|
|
],
|
|
semantic_and: [
|
|
'reportedPos = pos;',
|
|
'#{r(node.resultIndex)} = (function(#{keys(node.params).join(", ")}) {#{node.code}})(#{map(values(node.params), r).join(", ")}) ? "" : null;'
|
|
],
|
|
semantic_not: [
|
|
'reportedPos = pos;',
|
|
'#{r(node.resultIndex)} = (function(#{keys(node.params).join(", ")}) {#{node.code}})(#{map(values(node.params), r).join(", ")}) ? null : "";'
|
|
],
|
|
optional: [
|
|
'#block emit(node.expression)',
|
|
'#{r(node.resultIndex)} = #{r(node.resultIndex)} !== null ? #{r(node.resultIndex)} : "";'
|
|
],
|
|
zero_or_more: [
|
|
'#{r(node.resultIndex)} = [];',
|
|
'#block emit(node.expression)',
|
|
'while (#{r(node.expression.resultIndex)} !== null) {',
|
|
' #{r(node.resultIndex)}.push(#{r(node.expression.resultIndex)});',
|
|
' #block emit(node.expression)',
|
|
'}'
|
|
],
|
|
one_or_more: [
|
|
'#block emit(node.expression)',
|
|
'if (#{r(node.expression.resultIndex)} !== null) {',
|
|
' #{r(node.resultIndex)} = [];',
|
|
' while (#{r(node.expression.resultIndex)} !== null) {',
|
|
' #{r(node.resultIndex)}.push(#{r(node.expression.resultIndex)});',
|
|
' #block emit(node.expression)',
|
|
' }',
|
|
'} else {',
|
|
' #{r(node.resultIndex)} = null;',
|
|
'}'
|
|
],
|
|
rule_ref: [
|
|
'#{r(node.resultIndex)} = parse_#{node.name}();'
|
|
],
|
|
literal: [
|
|
'#if node.value.length === 0',
|
|
' #{r(node.resultIndex)} = "";',
|
|
'#else',
|
|
' #if !node.ignoreCase',
|
|
' #if node.value.length === 1',
|
|
' if (input.charCodeAt(pos) === #{node.value.charCodeAt(0)}) {',
|
|
' #else',
|
|
' if (input.substr(pos, #{node.value.length}) === #{string(node.value)}) {',
|
|
' #end',
|
|
' #else',
|
|
/*
|
|
* One-char literals are not optimized when case-insensitive
|
|
* matching is enabled. This is because there is no simple way to
|
|
* lowercase a character code that works for character outside ASCII
|
|
* letters. Moreover, |toLowerCase| can change string length,
|
|
* meaning the result of lowercasing a character can be more
|
|
* characters.
|
|
*/
|
|
' if (input.substr(pos, #{node.value.length}).toLowerCase() === #{string(node.value.toLowerCase())}) {',
|
|
' #end',
|
|
' #if !node.ignoreCase',
|
|
' #{r(node.resultIndex)} = #{string(node.value)};',
|
|
' #else',
|
|
' #{r(node.resultIndex)} = input.substr(pos, #{node.value.length});',
|
|
' #end',
|
|
' #{node.value.length > 1 ? "pos += " + node.value.length : "pos++"};',
|
|
' } else {',
|
|
' #{r(node.resultIndex)} = null;',
|
|
' if (reportFailures === 0) {',
|
|
' matchFailed(#{string(string(node.value))});',
|
|
' }',
|
|
' }',
|
|
'#end'
|
|
],
|
|
"class": [
|
|
'if (#{regexp}.test(input.charAt(pos))) {',
|
|
' #{r(node.resultIndex)} = input.charAt(pos);',
|
|
' pos++;',
|
|
'} else {',
|
|
' #{r(node.resultIndex)} = null;',
|
|
' if (reportFailures === 0) {',
|
|
' matchFailed(#{string(node.rawText)});',
|
|
' }',
|
|
'}'
|
|
],
|
|
any: [
|
|
'if (input.length > pos) {',
|
|
' #{r(node.resultIndex)} = input.charAt(pos);',
|
|
' pos++;',
|
|
'} else {',
|
|
' #{r(node.resultIndex)} = null;',
|
|
' if (reportFailures === 0) {',
|
|
' matchFailed("any character");',
|
|
' }',
|
|
'}'
|
|
]
|
|
};
|
|
|
|
for (name in sources) {
|
|
templates[name] = Codie.template(sources[name].join('\n'));
|
|
}
|
|
|
|
return templates;
|
|
})();
|
|
|
|
function fill(name, vars) {
|
|
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;
|
|
|
|
vars.r = function(index) { return "r" + index; };
|
|
|
|
return templates[name](vars);
|
|
}
|
|
|
|
function emitSimple(name) {
|
|
return function(node) { return fill(name, { node: node }); };
|
|
}
|
|
|
|
var emit = utils.buildNodeVisitor({
|
|
grammar: emitSimple("grammar"),
|
|
|
|
initializer: function(node) { return node.code; },
|
|
|
|
rule: emitSimple("rule"),
|
|
|
|
/*
|
|
* The contract for all code fragments generated by the following functions
|
|
* is as follows.
|
|
*
|
|
* The code fragment tries to match a part of the input starting with the
|
|
* position indicated in |pos|. That position may point past the end of the
|
|
* input.
|
|
*
|
|
* * If the code fragment matches the input, it advances |pos| to point to
|
|
* the first chracter following the matched part of the input and sets a
|
|
* register with index specified by |node.resultIndex| to an appropriate
|
|
* value. This value is always non-|null|.
|
|
*
|
|
* * If the code fragment does not match the input, it returns with |pos|
|
|
* set to the original value and it sets a register with index specified
|
|
* by |node.posIndex| to |null|.
|
|
*
|
|
* The code uses only registers with indices specified by |node.resultIndex|
|
|
* and |node.posIndex| where |node| is the processed node or some of its
|
|
* subnodes. It does not use any other registers.
|
|
*/
|
|
|
|
named: emitSimple("named"),
|
|
|
|
choice: function(node) {
|
|
var code, nextAlternativesCode;
|
|
|
|
for (var i = node.alternatives.length - 1; i >= 0; i--) {
|
|
nextAlternativesCode = i !== node.alternatives.length - 1
|
|
? fill("choice.next", { node: node, code: code })
|
|
: '';
|
|
code = fill("choice", {
|
|
alternative: node.alternatives[i],
|
|
nextAlternativesCode: nextAlternativesCode
|
|
});
|
|
}
|
|
|
|
return code;
|
|
},
|
|
|
|
action: emitSimple("action"),
|
|
|
|
sequence: function(node) {
|
|
var code = fill("sequence.inner", { node: node });
|
|
|
|
for (var i = node.elements.length - 1; i >= 0; i--) {
|
|
code = fill("sequence.iteration", {
|
|
node: node,
|
|
element: node.elements[i],
|
|
code: code
|
|
});
|
|
}
|
|
|
|
return fill("sequence", { node: node, code: code });
|
|
},
|
|
|
|
labeled: function(node) { return emit(node.expression); },
|
|
|
|
text: emitSimple("text"),
|
|
simple_and: emitSimple("simple_and"),
|
|
simple_not: emitSimple("simple_not"),
|
|
semantic_and: emitSimple("semantic_and"),
|
|
semantic_not: emitSimple("semantic_not"),
|
|
optional: emitSimple("optional"),
|
|
zero_or_more: emitSimple("zero_or_more"),
|
|
one_or_more: emitSimple("one_or_more"),
|
|
rule_ref: emitSimple("rule_ref"),
|
|
literal: emitSimple("literal"),
|
|
|
|
"class": function(node) {
|
|
var regexp;
|
|
|
|
if (node.parts.length > 0) {
|
|
regexp = '/^['
|
|
+ (node.inverted ? '^' : '')
|
|
+ utils.map(node.parts, function(part) {
|
|
return part instanceof Array
|
|
? utils.quoteForRegexpClass(part[0])
|
|
+ '-'
|
|
+ utils.quoteForRegexpClass(part[1])
|
|
: utils.quoteForRegexpClass(part);
|
|
}).join('')
|
|
+ ']/' + (node.ignoreCase ? 'i' : '');
|
|
} else {
|
|
/*
|
|
* Stupid IE considers regexps /[]/ and /[^]/ syntactically invalid, so
|
|
* we translate them into euqivalents it can handle.
|
|
*/
|
|
regexp = node.inverted ? '/^[\\S\\s]/' : '/^(?!)/';
|
|
}
|
|
|
|
return fill("class", { node: node, regexp: regexp });
|
|
},
|
|
|
|
any: emitSimple("any")
|
|
});
|
|
|
|
ast.code = emit(ast);
|
|
};
|