UNPKG

pgn-parser

Version:
1,672 lines (1,465 loc) 265 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define("pgnParser", [], factory); else if(typeof exports === 'object') exports["pgnParser"] = factory(); else root["pgnParser"] = factory(); })(this, () => { return /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./node_modules/pegjs/lib/compiler/asts.js": /*!*************************************************!*\ !*** ./node_modules/pegjs/lib/compiler/asts.js ***! \*************************************************/ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; var arrays = __webpack_require__(/*! ../utils/arrays */ "./node_modules/pegjs/lib/utils/arrays.js"), visitor = __webpack_require__(/*! ./visitor */ "./node_modules/pegjs/lib/compiler/visitor.js"); /* AST utilities. */ var asts = { findRule: function(ast, name) { return arrays.find(ast.rules, function(r) { return r.name === name; }); }, indexOfRule: function(ast, name) { return arrays.indexOf(ast.rules, function(r) { return r.name === name; }); }, alwaysConsumesOnSuccess: function(ast, node) { function consumesTrue() { return true; } function consumesFalse() { return false; } function consumesExpression(node) { return consumes(node.expression); } var consumes = visitor.build({ rule: consumesExpression, named: consumesExpression, choice: function(node) { return arrays.every(node.alternatives, consumes); }, action: consumesExpression, sequence: function(node) { return arrays.some(node.elements, consumes); }, labeled: consumesExpression, text: consumesExpression, simple_and: consumesFalse, simple_not: consumesFalse, optional: consumesFalse, zero_or_more: consumesFalse, one_or_more: consumesExpression, group: consumesExpression, semantic_and: consumesFalse, semantic_not: consumesFalse, rule_ref: function(node) { return consumes(asts.findRule(ast, node.name)); }, literal: function(node) { return node.value !== ""; }, "class": consumesTrue, any: consumesTrue }); return consumes(node); } }; module.exports = asts; /***/ }), /***/ "./node_modules/pegjs/lib/compiler/index.js": /*!**************************************************!*\ !*** ./node_modules/pegjs/lib/compiler/index.js ***! \**************************************************/ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; var arrays = __webpack_require__(/*! ../utils/arrays */ "./node_modules/pegjs/lib/utils/arrays.js"), objects = __webpack_require__(/*! ../utils/objects */ "./node_modules/pegjs/lib/utils/objects.js"); var compiler = { /* * AST node visitor builder. Useful mainly for plugins which manipulate the * AST. */ visitor: __webpack_require__(/*! ./visitor */ "./node_modules/pegjs/lib/compiler/visitor.js"), /* * Compiler passes. * * Each pass is a function that is passed the AST. It can perform checks on it * or modify it as needed. If the pass encounters a semantic error, it throws * |peg.GrammarError|. */ passes: { check: { reportUndefinedRules: __webpack_require__(/*! ./passes/report-undefined-rules */ "./node_modules/pegjs/lib/compiler/passes/report-undefined-rules.js"), reportDuplicateRules: __webpack_require__(/*! ./passes/report-duplicate-rules */ "./node_modules/pegjs/lib/compiler/passes/report-duplicate-rules.js"), reportDuplicateLabels: __webpack_require__(/*! ./passes/report-duplicate-labels */ "./node_modules/pegjs/lib/compiler/passes/report-duplicate-labels.js"), reportInfiniteRecursion: __webpack_require__(/*! ./passes/report-infinite-recursion */ "./node_modules/pegjs/lib/compiler/passes/report-infinite-recursion.js"), reportInfiniteRepetition: __webpack_require__(/*! ./passes/report-infinite-repetition */ "./node_modules/pegjs/lib/compiler/passes/report-infinite-repetition.js") }, transform: { removeProxyRules: __webpack_require__(/*! ./passes/remove-proxy-rules */ "./node_modules/pegjs/lib/compiler/passes/remove-proxy-rules.js") }, generate: { generateBytecode: __webpack_require__(/*! ./passes/generate-bytecode */ "./node_modules/pegjs/lib/compiler/passes/generate-bytecode.js"), generateJS: __webpack_require__(/*! ./passes/generate-js */ "./node_modules/pegjs/lib/compiler/passes/generate-js.js") } }, /* * Generates a parser from a specified grammar AST. Throws |peg.GrammarError| * if the AST contains a semantic error. Note that not all errors are detected * during the generation and some may protrude to the generated parser and * cause its malfunction. */ compile: function(ast, passes, options) { options = options !== void 0 ? options : {}; var stage; options = objects.clone(options); objects.defaults(options, { allowedStartRules: [ast.rules[0].name], cache: false, dependencies: {}, exportVar: null, format: "bare", optimize: "speed", output: "parser", trace: false }); for (stage in passes) { if (passes.hasOwnProperty(stage)) { arrays.each(passes[stage], function(p) { p(ast, options); }); } } switch (options.output) { case "parser": return eval(ast.code); case "source": return ast.code; } } }; module.exports = compiler; /***/ }), /***/ "./node_modules/pegjs/lib/compiler/js.js": /*!***********************************************!*\ !*** ./node_modules/pegjs/lib/compiler/js.js ***! \***********************************************/ /***/ ((module) => { "use strict"; function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } /* JavaScript code generation helpers. */ var js = { stringEscape: 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 the "\v" escape sequence is not used because IE does not like * it. */ return s .replace(/\\/g, '\\\\') // backslash .replace(/"/g, '\\"') // closing double quote .replace(/\0/g, '\\0') // null .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-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) .replace(/[\x10-\x1F\x7F-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) .replace(/[\u0100-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) .replace(/[\u1000-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); }, regexpClassEscape: 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(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) .replace(/[\x10-\x1F\x7F-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) .replace(/[\u0100-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) .replace(/[\u1000-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); } }; module.exports = js; /***/ }), /***/ "./node_modules/pegjs/lib/compiler/opcodes.js": /*!****************************************************!*\ !*** ./node_modules/pegjs/lib/compiler/opcodes.js ***! \****************************************************/ /***/ ((module) => { "use strict"; /* Bytecode instruction opcodes. */ var opcodes = { /* Stack Manipulation */ PUSH: 0, // PUSH c PUSH_UNDEFINED: 1, // PUSH_UNDEFINED PUSH_NULL: 2, // PUSH_NULL PUSH_FAILED: 3, // PUSH_FAILED PUSH_EMPTY_ARRAY: 4, // PUSH_EMPTY_ARRAY PUSH_CURR_POS: 5, // PUSH_CURR_POS POP: 6, // POP POP_CURR_POS: 7, // POP_CURR_POS POP_N: 8, // POP_N n NIP: 9, // NIP APPEND: 10, // APPEND WRAP: 11, // WRAP n TEXT: 12, // TEXT /* Conditions and Loops */ IF: 13, // IF t, f IF_ERROR: 14, // IF_ERROR t, f IF_NOT_ERROR: 15, // IF_NOT_ERROR t, f WHILE_NOT_ERROR: 16, // WHILE_NOT_ERROR b /* Matching */ MATCH_ANY: 17, // MATCH_ANY a, f, ... MATCH_STRING: 18, // MATCH_STRING s, a, f, ... MATCH_STRING_IC: 19, // MATCH_STRING_IC s, a, f, ... MATCH_REGEXP: 20, // MATCH_REGEXP r, a, f, ... ACCEPT_N: 21, // ACCEPT_N n ACCEPT_STRING: 22, // ACCEPT_STRING s FAIL: 23, // FAIL e /* Calls */ LOAD_SAVED_POS: 24, // LOAD_SAVED_POS p UPDATE_SAVED_POS: 25, // UPDATE_SAVED_POS CALL: 26, // CALL f, n, pc, p1, p2, ..., pN /* Rules */ RULE: 27, // RULE r /* Failure Reporting */ SILENT_FAILS_ON: 28, // SILENT_FAILS_ON SILENT_FAILS_OFF: 29 // SILENT_FAILS_OFF }; module.exports = opcodes; /***/ }), /***/ "./node_modules/pegjs/lib/compiler/passes/generate-bytecode.js": /*!*********************************************************************!*\ !*** ./node_modules/pegjs/lib/compiler/passes/generate-bytecode.js ***! \*********************************************************************/ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; var arrays = __webpack_require__(/*! ../../utils/arrays */ "./node_modules/pegjs/lib/utils/arrays.js"), objects = __webpack_require__(/*! ../../utils/objects */ "./node_modules/pegjs/lib/utils/objects.js"), asts = __webpack_require__(/*! ../asts */ "./node_modules/pegjs/lib/compiler/asts.js"), visitor = __webpack_require__(/*! ../visitor */ "./node_modules/pegjs/lib/compiler/visitor.js"), op = __webpack_require__(/*! ../opcodes */ "./node_modules/pegjs/lib/compiler/opcodes.js"), js = __webpack_require__(/*! ../js */ "./node_modules/pegjs/lib/compiler/js.js"); /* Generates bytecode. * * Instructions * ============ * * Stack Manipulation * ------------------ * * [0] PUSH c * * stack.push(consts[c]); * * [1] PUSH_UNDEFINED * * stack.push(undefined); * * [2] PUSH_NULL * * stack.push(null); * * [3] PUSH_FAILED * * stack.push(FAILED); * * [4] PUSH_EMPTY_ARRAY * * stack.push([]); * * [5] PUSH_CURR_POS * * stack.push(currPos); * * [6] POP * * stack.pop(); * * [7] POP_CURR_POS * * currPos = stack.pop(); * * [8] POP_N n * * stack.pop(n); * * [9] NIP * * value = stack.pop(); * stack.pop(); * stack.push(value); * * [10] APPEND * * value = stack.pop(); * array = stack.pop(); * array.push(value); * stack.push(array); * * [11] WRAP n * * stack.push(stack.pop(n)); * * [12] TEXT * * stack.push(input.substring(stack.pop(), currPos)); * * Conditions and Loops * -------------------- * * [13] IF t, f * * if (stack.top()) { * interpret(ip + 3, ip + 3 + t); * } else { * interpret(ip + 3 + t, ip + 3 + t + f); * } * * [14] IF_ERROR t, f * * if (stack.top() === FAILED) { * interpret(ip + 3, ip + 3 + t); * } else { * interpret(ip + 3 + t, ip + 3 + t + f); * } * * [15] IF_NOT_ERROR t, f * * if (stack.top() !== FAILED) { * interpret(ip + 3, ip + 3 + t); * } else { * interpret(ip + 3 + t, ip + 3 + t + f); * } * * [16] WHILE_NOT_ERROR b * * while(stack.top() !== FAILED) { * interpret(ip + 2, ip + 2 + b); * } * * Matching * -------- * * [17] MATCH_ANY a, f, ... * * if (input.length > currPos) { * interpret(ip + 3, ip + 3 + a); * } else { * interpret(ip + 3 + a, ip + 3 + a + f); * } * * [18] MATCH_STRING s, a, f, ... * * if (input.substr(currPos, consts[s].length) === consts[s]) { * interpret(ip + 4, ip + 4 + a); * } else { * interpret(ip + 4 + a, ip + 4 + a + f); * } * * [19] MATCH_STRING_IC s, a, f, ... * * if (input.substr(currPos, consts[s].length).toLowerCase() === consts[s]) { * interpret(ip + 4, ip + 4 + a); * } else { * interpret(ip + 4 + a, ip + 4 + a + f); * } * * [20] MATCH_REGEXP r, a, f, ... * * if (consts[r].test(input.charAt(currPos))) { * interpret(ip + 4, ip + 4 + a); * } else { * interpret(ip + 4 + a, ip + 4 + a + f); * } * * [21] ACCEPT_N n * * stack.push(input.substring(currPos, n)); * currPos += n; * * [22] ACCEPT_STRING s * * stack.push(consts[s]); * currPos += consts[s].length; * * [23] FAIL e * * stack.push(FAILED); * fail(consts[e]); * * Calls * ----- * * [24] LOAD_SAVED_POS p * * savedPos = stack[p]; * * [25] UPDATE_SAVED_POS * * savedPos = currPos; * * [26] CALL f, n, pc, p1, p2, ..., pN * * value = consts[f](stack[p1], ..., stack[pN]); * stack.pop(n); * stack.push(value); * * Rules * ----- * * [27] RULE r * * stack.push(parseRule(r)); * * Failure Reporting * ----------------- * * [28] SILENT_FAILS_ON * * silentFails++; * * [29] SILENT_FAILS_OFF * * silentFails--; */ function generateBytecode(ast) { var consts = []; function addConst(value) { var index = arrays.indexOf(consts, value); return index === -1 ? consts.push(value) - 1 : index; } function addFunctionConst(params, code) { return addConst( "function(" + params.join(", ") + ") {" + code + "}" ); } function buildSequence() { return Array.prototype.concat.apply([], arguments); } function buildCondition(condCode, thenCode, elseCode) { return condCode.concat( [thenCode.length, elseCode.length], thenCode, elseCode ); } function buildLoop(condCode, bodyCode) { return condCode.concat([bodyCode.length], bodyCode); } function buildCall(functionIndex, delta, env, sp) { var params = arrays.map(objects.values(env), function(p) { return sp - p; }); return [op.CALL, functionIndex, delta, params.length].concat(params); } function buildSimplePredicate(expression, negative, context) { return buildSequence( [op.PUSH_CURR_POS], [op.SILENT_FAILS_ON], generate(expression, { sp: context.sp + 1, env: objects.clone(context.env), action: null }), [op.SILENT_FAILS_OFF], buildCondition( [negative ? op.IF_ERROR : op.IF_NOT_ERROR], buildSequence( [op.POP], [negative ? op.POP : op.POP_CURR_POS], [op.PUSH_UNDEFINED] ), buildSequence( [op.POP], [negative ? op.POP_CURR_POS : op.POP], [op.PUSH_FAILED] ) ) ); } function buildSemanticPredicate(code, negative, context) { var functionIndex = addFunctionConst(objects.keys(context.env), code); return buildSequence( [op.UPDATE_SAVED_POS], buildCall(functionIndex, 0, context.env, context.sp), buildCondition( [op.IF], buildSequence( [op.POP], negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED] ), buildSequence( [op.POP], negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED] ) ) ); } function buildAppendLoop(expressionCode) { return buildLoop( [op.WHILE_NOT_ERROR], buildSequence([op.APPEND], expressionCode) ); } var generate = visitor.build({ grammar: function(node) { arrays.each(node.rules, generate); node.consts = consts; }, rule: function(node) { node.bytecode = generate(node.expression, { sp: -1, // stack pointer env: { }, // mapping of label names to stack positions action: null // action nodes pass themselves to children here }); }, named: function(node, context) { var nameIndex = addConst( 'peg$otherExpectation("' + js.stringEscape(node.name) + '")' ); /* * The code generated below is slightly suboptimal because |FAIL| pushes * to the stack, so we need to stick a |POP| in front of it. We lack a * dedicated instruction that would just report the failure and not touch * the stack. */ return buildSequence( [op.SILENT_FAILS_ON], generate(node.expression, context), [op.SILENT_FAILS_OFF], buildCondition([op.IF_ERROR], [op.FAIL, nameIndex], []) ); }, choice: function(node, context) { function buildAlternativesCode(alternatives, context) { return buildSequence( generate(alternatives[0], { sp: context.sp, env: objects.clone(context.env), action: null }), alternatives.length > 1 ? buildCondition( [op.IF_ERROR], buildSequence( [op.POP], buildAlternativesCode(alternatives.slice(1), context) ), [] ) : [] ); } return buildAlternativesCode(node.alternatives, context); }, action: function(node, context) { var env = objects.clone(context.env), emitCall = node.expression.type !== "sequence" || node.expression.elements.length === 0, expressionCode = generate(node.expression, { sp: context.sp + (emitCall ? 1 : 0), env: env, action: node }), functionIndex = addFunctionConst(objects.keys(env), node.code); return emitCall ? buildSequence( [op.PUSH_CURR_POS], expressionCode, buildCondition( [op.IF_NOT_ERROR], buildSequence( [op.LOAD_SAVED_POS, 1], buildCall(functionIndex, 1, env, context.sp + 2) ), [] ), [op.NIP] ) : expressionCode; }, sequence: function(node, context) { function buildElementsCode(elements, context) { var processedCount, functionIndex; if (elements.length > 0) { processedCount = node.elements.length - elements.slice(1).length; return buildSequence( generate(elements[0], { sp: context.sp, env: context.env, action: null }), buildCondition( [op.IF_NOT_ERROR], buildElementsCode(elements.slice(1), { sp: context.sp + 1, env: context.env, action: context.action }), buildSequence( processedCount > 1 ? [op.POP_N, processedCount] : [op.POP], [op.POP_CURR_POS], [op.PUSH_FAILED] ) ) ); } else { if (context.action) { functionIndex = addFunctionConst( objects.keys(context.env), context.action.code ); return buildSequence( [op.LOAD_SAVED_POS, node.elements.length], buildCall( functionIndex, node.elements.length, context.env, context.sp ), [op.NIP] ); } else { return buildSequence([op.WRAP, node.elements.length], [op.NIP]); } } } return buildSequence( [op.PUSH_CURR_POS], buildElementsCode(node.elements, { sp: context.sp + 1, env: context.env, action: context.action }) ); }, labeled: function(node, context) { var env = objects.clone(context.env); context.env[node.label] = context.sp + 1; return generate(node.expression, { sp: context.sp, env: env, action: null }); }, text: function(node, context) { return buildSequence( [op.PUSH_CURR_POS], generate(node.expression, { sp: context.sp + 1, env: objects.clone(context.env), action: null }), buildCondition( [op.IF_NOT_ERROR], buildSequence([op.POP], [op.TEXT]), [op.NIP] ) ); }, simple_and: function(node, context) { return buildSimplePredicate(node.expression, false, context); }, simple_not: function(node, context) { return buildSimplePredicate(node.expression, true, context); }, optional: function(node, context) { return buildSequence( generate(node.expression, { sp: context.sp, env: objects.clone(context.env), action: null }), buildCondition( [op.IF_ERROR], buildSequence([op.POP], [op.PUSH_NULL]), [] ) ); }, zero_or_more: function(node, context) { var expressionCode = generate(node.expression, { sp: context.sp + 1, env: objects.clone(context.env), action: null }); return buildSequence( [op.PUSH_EMPTY_ARRAY], expressionCode, buildAppendLoop(expressionCode), [op.POP] ); }, one_or_more: function(node, context) { var expressionCode = generate(node.expression, { sp: context.sp + 1, env: objects.clone(context.env), action: null }); return buildSequence( [op.PUSH_EMPTY_ARRAY], expressionCode, buildCondition( [op.IF_NOT_ERROR], buildSequence(buildAppendLoop(expressionCode), [op.POP]), buildSequence([op.POP], [op.POP], [op.PUSH_FAILED]) ) ); }, group: function(node, context) { return generate(node.expression, { sp: context.sp, env: objects.clone(context.env), action: null }); }, semantic_and: function(node, context) { return buildSemanticPredicate(node.code, false, context); }, semantic_not: function(node, context) { return buildSemanticPredicate(node.code, true, context); }, rule_ref: function(node) { return [op.RULE, asts.indexOfRule(ast, node.name)]; }, literal: function(node) { var stringIndex, expectedIndex; if (node.value.length > 0) { stringIndex = addConst('"' + js.stringEscape( node.ignoreCase ? node.value.toLowerCase() : node.value ) + '"' ); expectedIndex = addConst( 'peg$literalExpectation(' + '"' + js.stringEscape(node.value) + '", ' + node.ignoreCase + ')' ); /* * For case-sensitive strings the value must match the beginning of the * remaining input exactly. As a result, we can use |ACCEPT_STRING| and * save one |substr| call that would be needed if we used |ACCEPT_N|. */ return buildCondition( node.ignoreCase ? [op.MATCH_STRING_IC, stringIndex] : [op.MATCH_STRING, stringIndex], node.ignoreCase ? [op.ACCEPT_N, node.value.length] : [op.ACCEPT_STRING, stringIndex], [op.FAIL, expectedIndex] ); } else { stringIndex = addConst('""'); return [op.PUSH, stringIndex]; } }, "class": function(node) { var regexp, parts, regexpIndex, expectedIndex; if (node.parts.length > 0) { regexp = '/^[' + (node.inverted ? '^' : '') + arrays.map(node.parts, function(part) { return part instanceof Array ? js.regexpClassEscape(part[0]) + '-' + js.regexpClassEscape(part[1]) : js.regexpClassEscape(part); }).join('') + ']/' + (node.ignoreCase ? 'i' : ''); } else { /* * IE considers regexps /[]/ and /[^]/ as syntactically invalid, so we * translate them into equivalents it can handle. */ regexp = node.inverted ? '/^[\\S\\s]/' : '/^(?!)/'; } parts = '[' + arrays.map(node.parts, function(part) { return part instanceof Array ? '["' + js.stringEscape(part[0]) + '", "' + js.stringEscape(part[1]) + '"]' : '"' + js.stringEscape(part) + '"'; }).join(', ') + ']'; regexpIndex = addConst(regexp); expectedIndex = addConst( 'peg$classExpectation(' + parts + ', ' + node.inverted + ', ' + node.ignoreCase + ')' ); return buildCondition( [op.MATCH_REGEXP, regexpIndex], [op.ACCEPT_N, 1], [op.FAIL, expectedIndex] ); }, any: function() { var expectedIndex = addConst('peg$anyExpectation()'); return buildCondition( [op.MATCH_ANY], [op.ACCEPT_N, 1], [op.FAIL, expectedIndex] ); } }); generate(ast); } module.exports = generateBytecode; /***/ }), /***/ "./node_modules/pegjs/lib/compiler/passes/generate-js.js": /*!***************************************************************!*\ !*** ./node_modules/pegjs/lib/compiler/passes/generate-js.js ***! \***************************************************************/ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; var arrays = __webpack_require__(/*! ../../utils/arrays */ "./node_modules/pegjs/lib/utils/arrays.js"), objects = __webpack_require__(/*! ../../utils/objects */ "./node_modules/pegjs/lib/utils/objects.js"), asts = __webpack_require__(/*! ../asts */ "./node_modules/pegjs/lib/compiler/asts.js"), op = __webpack_require__(/*! ../opcodes */ "./node_modules/pegjs/lib/compiler/opcodes.js"), js = __webpack_require__(/*! ../js */ "./node_modules/pegjs/lib/compiler/js.js"); /* Generates parser JavaScript code. */ function generateJS(ast, options) { /* These only indent non-empty lines to avoid trailing whitespace. */ function indent2(code) { return code.replace(/^(.+)$/gm, ' $1'); } function indent6(code) { return code.replace(/^(.+)$/gm, ' $1'); } function indent10(code) { return code.replace(/^(.+)$/gm, ' $1'); } function generateTables() { if (options.optimize === "size") { return [ 'peg$consts = [', indent2(ast.consts.join(',\n')), '],', '', 'peg$bytecode = [', indent2(arrays.map(ast.rules, function(rule) { return 'peg$decode("' + js.stringEscape(arrays.map( rule.bytecode, function(b) { return String.fromCharCode(b + 32); } ).join('')) + '")'; }).join(',\n')), '],' ].join('\n'); } else { return arrays.map( ast.consts, function(c, i) { return 'peg$c' + i + ' = ' + c + ','; } ).join('\n'); } } function generateRuleHeader(ruleNameCode, ruleIndexCode) { var parts = []; parts.push(''); if (options.trace) { parts.push([ 'peg$tracer.trace({', ' type: "rule.enter",', ' rule: ' + ruleNameCode + ',', ' location: peg$computeLocation(startPos, startPos)', '});', '' ].join('\n')); } if (options.cache) { parts.push([ 'var key = peg$currPos * ' + ast.rules.length + ' + ' + ruleIndexCode + ',', ' cached = peg$resultsCache[key];', '', 'if (cached) {', ' peg$currPos = cached.nextPos;', '' ].join('\n')); if (options.trace) { parts.push([ 'if (cached.result !== peg$FAILED) {', ' peg$tracer.trace({', ' type: "rule.match",', ' rule: ' + ruleNameCode + ',', ' result: cached.result,', ' location: peg$computeLocation(startPos, peg$currPos)', ' });', '} else {', ' peg$tracer.trace({', ' type: "rule.fail",', ' rule: ' + ruleNameCode + ',', ' location: peg$computeLocation(startPos, startPos)', ' });', '}', '' ].join('\n')); } parts.push([ ' return cached.result;', '}', '' ].join('\n')); } return parts.join('\n'); } function generateRuleFooter(ruleNameCode, resultCode) { var parts = []; if (options.cache) { parts.push([ '', 'peg$resultsCache[key] = { nextPos: peg$currPos, result: ' + resultCode + ' };' ].join('\n')); } if (options.trace) { parts.push([ '', 'if (' + resultCode + ' !== peg$FAILED) {', ' peg$tracer.trace({', ' type: "rule.match",', ' rule: ' + ruleNameCode + ',', ' result: ' + resultCode + ',', ' location: peg$computeLocation(startPos, peg$currPos)', ' });', '} else {', ' peg$tracer.trace({', ' type: "rule.fail",', ' rule: ' + ruleNameCode + ',', ' location: peg$computeLocation(startPos, startPos)', ' });', '}' ].join('\n')); } parts.push([ '', 'return ' + resultCode + ';' ].join('\n')); return parts.join('\n'); } function generateInterpreter() { var parts = []; function generateCondition(cond, argsLength) { var baseLength = argsLength + 3, thenLengthCode = 'bc[ip + ' + (baseLength - 2) + ']', elseLengthCode = 'bc[ip + ' + (baseLength - 1) + ']'; return [ 'ends.push(end);', 'ips.push(ip + ' + baseLength + ' + ' + thenLengthCode + ' + ' + elseLengthCode + ');', '', 'if (' + cond + ') {', ' end = ip + ' + baseLength + ' + ' + thenLengthCode + ';', ' ip += ' + baseLength + ';', '} else {', ' end = ip + ' + baseLength + ' + ' + thenLengthCode + ' + ' + elseLengthCode + ';', ' ip += ' + baseLength + ' + ' + thenLengthCode + ';', '}', '', 'break;' ].join('\n'); } function generateLoop(cond) { var baseLength = 2, bodyLengthCode = 'bc[ip + ' + (baseLength - 1) + ']'; return [ 'if (' + cond + ') {', ' ends.push(end);', ' ips.push(ip);', '', ' end = ip + ' + baseLength + ' + ' + bodyLengthCode + ';', ' ip += ' + baseLength + ';', '} else {', ' ip += ' + baseLength + ' + ' + bodyLengthCode + ';', '}', '', 'break;' ].join('\n'); } function generateCall() { var baseLength = 4, paramsLengthCode = 'bc[ip + ' + (baseLength - 1) + ']'; return [ 'params = bc.slice(ip + ' + baseLength + ', ip + ' + baseLength + ' + ' + paramsLengthCode + ');', 'for (i = 0; i < ' + paramsLengthCode + '; i++) {', ' params[i] = stack[stack.length - 1 - params[i]];', '}', '', 'stack.splice(', ' stack.length - bc[ip + 2],', ' bc[ip + 2],', ' peg$consts[bc[ip + 1]].apply(null, params)', ');', '', 'ip += ' + baseLength + ' + ' + paramsLengthCode + ';', 'break;' ].join('\n'); } parts.push([ 'function peg$decode(s) {', ' var bc = new Array(s.length), i;', '', ' for (i = 0; i < s.length; i++) {', ' bc[i] = s.charCodeAt(i) - 32;', ' }', '', ' return bc;', '}', '', 'function peg$parseRule(index) {' ].join('\n')); if (options.trace) { parts.push([ ' var bc = peg$bytecode[index],', ' ip = 0,', ' ips = [],', ' end = bc.length,', ' ends = [],', ' stack = [],', ' startPos = peg$currPos,', ' params, i;' ].join('\n')); } else { parts.push([ ' var bc = peg$bytecode[index],', ' ip = 0,', ' ips = [],', ' end = bc.length,', ' ends = [],', ' stack = [],', ' params, i;' ].join('\n')); } parts.push(indent2(generateRuleHeader('peg$ruleNames[index]', 'index'))); parts.push([ /* * The point of the outer loop and the |ips| & |ends| stacks is to avoid * recursive calls for interpreting parts of bytecode. In other words, we * implement the |interpret| operation of the abstract machine without * function calls. Such calls would likely slow the parser down and more * importantly cause stack overflows for complex grammars. */ ' while (true) {', ' while (ip < end) {', ' switch (bc[ip]) {', ' case ' + op.PUSH + ':', // PUSH c ' stack.push(peg$consts[bc[ip + 1]]);', ' ip += 2;', ' break;', '', ' case ' + op.PUSH_UNDEFINED + ':', // PUSH_UNDEFINED ' stack.push(void 0);', ' ip++;', ' break;', '', ' case ' + op.PUSH_NULL + ':', // PUSH_NULL ' stack.push(null);', ' ip++;', ' break;', '', ' case ' + op.PUSH_FAILED + ':', // PUSH_FAILED ' stack.push(peg$FAILED);', ' ip++;', ' break;', '', ' case ' + op.PUSH_EMPTY_ARRAY + ':', // PUSH_EMPTY_ARRAY ' stack.push([]);', ' ip++;', ' break;', '', ' case ' + op.PUSH_CURR_POS + ':', // PUSH_CURR_POS ' stack.push(peg$currPos);', ' ip++;', ' break;', '', ' case ' + op.POP + ':', // POP ' stack.pop();', ' ip++;', ' break;', '', ' case ' + op.POP_CURR_POS + ':', // POP_CURR_POS ' peg$currPos = stack.pop();', ' ip++;', ' break;', '', ' case ' + op.POP_N + ':', // POP_N n ' stack.length -= bc[ip + 1];', ' ip += 2;', ' break;', '', ' case ' + op.NIP + ':', // NIP ' stack.splice(-2, 1);', ' ip++;', ' break;', '', ' case ' + op.APPEND + ':', // APPEND ' stack[stack.length - 2].push(stack.pop());', ' ip++;', ' break;', '', ' case ' + op.WRAP + ':', // WRAP n ' stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1]));', ' ip += 2;', ' break;', '', ' case ' + op.TEXT + ':', // TEXT ' stack.push(input.substring(stack.pop(), peg$currPos));', ' ip++;', ' break;', '', ' case ' + op.IF + ':', // IF t, f indent10(generateCondition('stack[stack.length - 1]', 0)), '', ' case ' + op.IF_ERROR + ':', // IF_ERROR t, f indent10(generateCondition( 'stack[stack.length - 1] === peg$FAILED', 0 )), '', ' case ' + op.IF_NOT_ERROR + ':', // IF_NOT_ERROR t, f indent10( generateCondition('stack[stack.length - 1] !== peg$FAILED', 0 )), '', ' case ' + op.WHILE_NOT_ERROR + ':', // WHILE_NOT_ERROR b indent10(generateLoop('stack[stack.length - 1] !== peg$FAILED')), '', ' case ' + op.MATCH_ANY + ':', // MATCH_ANY a, f, ... indent10(generateCondition('input.length > peg$currPos', 0)), '', ' case ' + op.MATCH_STRING + ':', // MATCH_STRING s, a, f, ... indent10(generateCondition( 'input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]', 1 )), '', ' case ' + op.MATCH_STRING_IC + ':', // MATCH_STRING_IC s, a, f, ... indent10(generateCondition( 'input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]', 1 )), '', ' case ' + op.MATCH_REGEXP + ':', // MATCH_REGEXP r, a, f, ... indent10(generateCondition( 'peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))', 1 )), '', ' case ' + op.ACCEPT_N + ':', // ACCEPT_N n ' stack.push(input.substr(peg$currPos, bc[ip + 1]));', ' peg$currPos += bc[ip + 1];', ' ip += 2;', ' break;', '', ' case ' + op.ACCEPT_STRING + ':', // ACCEPT_STRING s ' stack.push(peg$consts[bc[ip + 1]]);', ' peg$currPos += peg$consts[bc[ip + 1]].length;', ' ip += 2;', ' break;', '', ' case ' + op.FAIL + ':', // FAIL e ' stack.push(peg$FAILED);', ' if (peg$silentFails === 0) {', ' peg$fail(peg$consts[bc[ip + 1]]);', ' }', ' ip += 2;', ' break;', '', ' case ' + op.LOAD_SAVED_POS + ':', // LOAD_SAVED_POS p ' peg$savedPos = stack[stack.length - 1 - bc[ip + 1]];', ' ip += 2;', ' break;', '', ' case ' + op.UPDATE_SAVED_POS + ':', // UPDATE_SAVED_POS ' peg$savedPos = peg$currPos;', ' ip++;', ' break;', '', ' case ' + op.CALL + ':', // CALL f, n, pc, p1, p2, ..., pN indent10(generateCall()), '', ' case ' + op.RULE + ':', // RULE r ' stack.push(peg$parseRule(bc[ip + 1]));', ' ip += 2;', ' break;', '', ' case ' + op.SILENT_FAILS_ON + ':', // SILENT_FAILS_ON ' peg$silentFails++;', ' ip++;', ' break;', '', ' case ' + op.SILENT_FAILS_OFF + ':', // SILENT_FAILS_OFF ' peg$silentFails--;', ' ip++;', ' break;', '', ' default:', ' throw new Error("Invalid opcode: " + bc[ip] + ".");', ' }', ' }', '', ' if (ends.length > 0) {', ' end = ends.pop();', ' ip = ips.pop();', ' } else {', ' break;', ' }', ' }' ].join('\n')); parts.push(indent2(generateRuleFooter('peg$ruleNames[index]', 'stack[0]'))); parts.push('}'); return parts.join('\n'); } function generateRuleFunction(rule) { var parts = [], code; function c(i) { return "peg$c" + i; } // |consts[i]| of the abstract machine function s(i) { return "s" + i; } // |stack[i]| of the abstract machine var stack = { sp: -1, maxSp: -1, push: function(exprCode) { var code = s(++this.sp) + ' = ' + exprCode + ';'; if (this.sp > this.maxSp) { this.maxSp = this.sp; } return code; }, pop: function(n) { var values; if (n === void 0) { return s(this.sp--); } else { values = arrays.map(arrays.range(this.sp - n + 1, this.sp + 1), s); this.sp -= n; return values; } }, top: function() { return s(this.sp); }, index: function(i) { return s(this.sp - i); } }; function compile(bc) { var ip = 0, end = bc.length, parts = [], value; function compileCondition(cond, argCount) { var baseLength = argCount + 3, thenLength = bc[ip + baseLength - 2], elseLength = bc[ip + baseLength - 1], baseSp = stack.sp, thenCode, elseCode, thenSp, elseSp; ip += baseLength; thenCode = compile(bc.slice(ip, ip + thenLength)); thenSp = stack.sp; ip += thenLength; if (elseLength > 0) { stack.sp = baseSp; elseCode = compile(bc.slice(ip, ip + elseLength)); elseSp = stack.sp; ip += elseLength; if (thenSp !== elseSp) { throw new Error( "Branches of a condition must move the stack pointer in the same way." ); } } parts.push('if (' + cond + ') {'); parts.push(indent2(thenCode)); if (elseLength > 0) { parts.push('} else {'); parts.push(indent2(elseCode)); } parts.push('}'); } function compileLoop(cond) { var baseLength = 2, bodyLength = bc[ip + baseLength - 1], baseSp = stack.sp, bodyCode, bodySp; ip += baseLength; bodyCode = compile(bc.slice(ip, ip + bodyLength)); bodySp = stack.sp; ip += bodyLength; if (bodySp !== baseSp) { throw new Error("Body of a loop can't move the stack pointer."); } parts.push('while (' + cond + ') {'); parts.push(indent2(bodyCode)); parts.push('}'); } function compileCall() { var baseLength = 4, paramsLength = bc[ip + baseLength - 1]; var value = c(bc[ip + 1]) + '(' + arrays.map( bc.slice(ip + baseLength, ip + baseLength + paramsLength), function(p) { return stack.index(p); } ).join(', ') + ')'; stack.pop(bc[ip + 2]); parts.push(stack.push(value)); ip += baseLength + paramsLength; } while (ip < end) { switch (bc[ip]) { case op.PUSH: // PUSH c parts.push(stack.push(c(bc[ip + 1]))); ip += 2; break; case op.PUSH_CURR_POS: // PUSH_CURR_POS parts.push(stack.push('peg$currPos')); ip++; break; case op.PUSH_UNDEFINED: // PUSH_UNDEFINED parts.push(stack.push('void 0')); ip++; break; case op.PUSH_NULL: // PUSH_NULL parts.push(stack.push('null')); ip++; break; case op.PUSH_FAILED: // PUSH_FAILED parts.push(stack.push('peg$FAILED')); ip++; break; case op.PUSH_EMPTY_ARRAY: // PUSH_EMPTY_ARRAY parts.push(stack.push('[]')); ip++; break; case op.POP: // POP stack.pop(); ip++; break; case op.POP_CURR_POS: // POP_CURR_POS parts.push('peg$currPos = ' + stack.pop() + ';'); ip++; break; case op.POP_N: // POP_N n stack.pop(bc[ip + 1]); ip += 2; break; case op.NIP: // NIP value = stack.pop(); stack.pop(); parts.push(stack.push(value)); ip++; break; case op.APPEND: // APPEND value = stack.pop(); parts.push(stack.top() + '.push(' + value + ');'); ip++; break; case op.WRAP: // WRAP n parts.push( stack.push('[' + stack.pop(bc[ip + 1]).join(', ') + ']') ); ip += 2; break; case op.TEXT: // TEXT parts.push( stack.push('input.substring(' + stack.pop() + ', peg$currPos)') ); ip++; break; case op.IF: // IF t, f compileCondition(stack.top(), 0); break; case op.IF_ERROR: // IF_ERROR t, f compileCondition(stack.top() + ' === peg$FAILED', 0); break; case op.IF_NOT_ERROR: // IF_NOT_ERROR t, f compileCondition(stack.top() + ' !== peg$FAILED', 0); break; case op.WHILE_NOT_ERROR: // WHILE_NOT_ERROR b compileLoop(stack.top() + ' !== peg$FAILED', 0); break; case op.MATCH_ANY: // MATCH_ANY a, f, ... compileCondition('input.length > peg$currPos', 0); break; case op.MATCH_STRING: // MATCH_STRING s, a, f, ... compileCondition( eval(ast.consts[bc[ip + 1]]).length > 1 ? 'input.substr(peg$currPos, ' + eval(ast.consts[bc[ip + 1]]).length + ') === ' + c(bc[ip + 1]) : 'input.charCodeAt(peg$currPos) === ' + eval(ast.consts[bc[ip + 1]]).charCodeAt(0), 1 ); break; case op.MATCH_STRING_IC: // MATCH_STRING_IC s, a, f, ... compileCondition( 'input.substr(peg$currPos, ' + eval(ast.consts[bc[ip + 1]]).length + ').toLowerCase() === ' + c(bc[ip + 1]), 1 ); break; case op.MATCH_REGEXP: // MATCH_REGEXP r, a, f, ... compileCondition( c(bc[ip + 1]) + '.test(input.charAt(peg$currPos))', 1 ); break; case op.ACCEPT_N: // ACCEPT_N n parts.push(stack.push( bc[ip + 1] > 1 ? 'input.substr(peg$currPos, ' + bc[ip + 1] + ')' : 'input.charAt(peg$currPos)' )); parts.push( bc[ip + 1] > 1 ? 'peg$currPos += ' + bc[ip + 1] + ';' : 'peg$currPos++;' ); ip += 2; break; case op.ACCEPT_STRING: // ACCEPT_STRING s parts.push(stack.push(c(bc[ip + 1]))); parts.push( eval(ast.consts[bc[ip + 1]]).length > 1 ? 'peg$currPos += ' + eval(ast.consts[bc[ip + 1]]).length + ';' : 'peg$currPos++;' ); ip += 2; break; case op.FAIL: // FAIL e parts.push(stack.push('peg$FAILED')); parts.push('if (peg$silentFails === 0) { peg$fail(' + c(bc[ip + 1]) + '); }'); ip += 2; break; case op.LOAD_SAVED_POS: // LOAD_SAVED_POS p parts.push('peg$savedPos = ' + stack.index(bc[ip + 1]) + ';'); ip += 2; break; case op.UPDATE_SAVED_POS: // UPDATE_SAVED_POS parts.push('peg$savedPos = peg$currPos;'); ip++; break; case op.CALL: // CALL f, n, pc, p1, p2, ..., pN compileCall(); break; case op.RULE: // RULE r parts.push(stack.push("peg$parse" + ast.rules[bc[ip + 1]].name + "()")); ip += 2; break; case op.SILENT_FAILS_ON: // SILENT_FAILS_ON parts.push('peg$silentFails++;'); ip++; break; case op.SILENT_FAILS_OFF: // SILENT_FAILS_OFF