phpegjs
Version:
PHP target for PEG.js parser generator
892 lines (799 loc) • 33.9 kB
JavaScript
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 Elantcev Mikhail
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var arrayUtils = require("pegjs/lib/utils/arrays"),
asts = require("pegjs/lib/compiler/asts"),
op = require("pegjs/lib/compiler/opcodes"),
internalUtils = require("../utils");
/* Generates parser PHP code. */
module.exports = function(ast, options) {
var phpGlobalNamePrefix, phpGlobalNamespacePrefix, phpGlobalNamePrefixOrNamespaceEscaped;
var phpNamespace = options.phpegjs.parserNamespace;
var phpParserClass = options.phpegjs.parserClassName;
if (phpNamespace) {
phpGlobalNamePrefix = '';
phpGlobalNamespacePrefix = '\\';
// For use within strings inside generated code
phpGlobalNamePrefixOrNamespaceEscaped = phpNamespace + '\\\\';
} else if (options.phpegjs.parserGlobalNamePrefix) {
phpGlobalNamePrefix = options.phpegjs.parserGlobalNamePrefix;
phpGlobalNamespacePrefix = '';
phpGlobalNamePrefixOrNamespaceEscaped = phpGlobalNamePrefix;
phpParserClass = phpGlobalNamePrefix + phpParserClass;
} else {
phpGlobalNamePrefix = '';
phpGlobalNamespacePrefix = '';
phpGlobalNamePrefixOrNamespaceEscaped = '';
}
var mbstringAllowed = (
typeof options.phpegjs.mbstringAllowed === 'undefined'
? true
: options.phpegjs.mbstringAllowed
);
/* These only indent non-empty lines to avoid trailing whitespace. */
function indent2(code) {
return code.replace(/^(.+)$/gm, ' $1');
}
function indent4(code) {
return code.replace(/^(.+)$/gm, ' $1');
}
function indent8(code) {
return code.replace(/^(.+)$/gm, ' $1');
}
function indent10(code) {
return code.replace(/^(.+)$/gm, ' $1');
}
function generateTablesDeclaration() {
return arrayUtils.map(
ast.consts,
function(c, i) {
return 'private $peg_c' + i + ';';
}
).join('\n');
}
function generateTablesDefinition() {
return arrayUtils.map(
ast.consts,
function(c, i) {
return '$this->peg_c' + i + ' = ' + c + ';';
}
).join('\n');
}
function generateFunctions() {
return arrayUtils.map(
ast.functions,
function( c, i ) {
return 'private function peg_f' + i
+ '(' + c.params + ') {'
+ c.code
+ '}';
}
).join('\n');
}
function generateCacheHeader(ruleIndexCode) {
return [
'$key = $this->peg_currPos * ' + ast.rules.length + ' + ' + ruleIndexCode + ';',
' $cached = isset($this->peg_cache[$key]) ? $this->peg_cache[$key] : null;',
'',
'if ($cached) {',
' $this->peg_currPos = $cached["nextPos"];',
' return $cached["result"];',
'}',
''
].join('\n');
}
function generateCacheFooter(resultCode) {
return [
'',
'$this->peg_cache[$key] = array ("nextPos" => $this->peg_currPos, "result" => ' + resultCode + ' );'
].join('\n');
}
function generateRuleFunction(rule) {
var parts = [], code;
function c(i) {
return "$this->peg_c" + i;
} // |consts[i]| of the abstract machine
function f(i) {
return "$this->peg_f" + i;
} // |functions[i]| of the abstract machine
function s(i) {
return "$s" + i;
} // |stack[i]| of the abstract machine
function inputSubstr(start, len) {
// TODO If we can guarantee that `start` is within the bounds of
// the array, replace this with a direct array access when
// `len === 1`. Currently we cannot guarantee this.
return "$this->input_substr(" + start + ", " + len + ")";
}
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() {
var n, values;
if (arguments.length === 0) {
return s(this.sp--);
} else {
n = arguments[0];
values = arrayUtils.map(arrayUtils.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 = [],
stackTop,
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 params = bc.slice(ip + baseLength, ip + baseLength + paramsLength);
var value = f(bc[ip + 1]) + '(';
if (params.length > 0) {
value += arrayUtils.map(
params,
stackIndex
).join(', ');
}
value += ')';
stack.pop(bc[ip + 2]);
parts.push(stack.push(value));
ip += baseLength + paramsLength;
}
/*
* Extracted into a function just to silence JSHint complaining about
* creating functions in a loop.
*/
function stackIndex(p) {
return stack.index(p);
}
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_UNDEFINED: // PUSH_UNDEFINED
parts.push(stack.push('null'));
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('$this->peg_FAILED'));
ip++;
break;
case op.PUSH_EMPTY_ARRAY: // PUSH_EMPTY_ARRAY
parts.push(stack.push('array()'));
ip++;
break;
case op.PUSH_CURR_POS: // PUSH_CURR_POS
parts.push(stack.push('$this->peg_currPos'));
ip++;
break;
case op.POP: // POP
stack.pop();
ip++;
break;
case op.POP_CURR_POS: // POP_CURR_POS
parts.push('$this->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() + '[] = ' + value + ';');
ip++;
break;
case op.WRAP: // WRAP n
parts.push(
stack.push('array(' + stack.pop(bc[ip + 1]).join(', ') + ')')
);
ip += 2;
break;
case op.TEXT: // TEXT
stackTop = stack.pop();
parts.push(stack.push(
inputSubstr(
stackTop,
'$this->peg_currPos - ' + stackTop
)
));
ip++;
break;
case op.IF: // IF t, f
compileCondition(stack.top(), 0);
break;
case op.IF_ERROR: // IF_ERROR t, f
compileCondition(stack.top() + ' === $this->peg_FAILED', 0);
break;
case op.IF_NOT_ERROR: // IF_NOT_ERROR t, f
compileCondition(stack.top() + ' !== $this->peg_FAILED', 0);
break;
case op.WHILE_NOT_ERROR: // WHILE_NOT_ERROR b
compileLoop(stack.top() + ' !== $this->peg_FAILED', 0);
break;
case op.MATCH_ANY: // MATCH_ANY a, f, ...
compileCondition('$this->input_length > $this->peg_currPos', 0);
break;
case op.MATCH_STRING: // MATCH_STRING s, a, f, ...
compileCondition(
inputSubstr(
'$this->peg_currPos',
eval(ast.consts[bc[ip + 1]]).length
) + ' === ' + c(bc[ip + 1]),
1
);
break;
case op.MATCH_STRING_IC: // MATCH_STRING_IC s, a, f, ...
// Disallowed in `generate-bytecode-php.js` if the
// `mbstringAllowed` option is set to false.
compileCondition(
'mb_strtolower(' + inputSubstr(
'$this->peg_currPos',
eval(ast.consts[bc[ip + 1]]).length
) + ', "UTF-8") === ' + c(bc[ip + 1]),
1
);
break;
case op.MATCH_REGEXP: // MATCH_REGEXP r, a, f, ...
if (mbstringAllowed) {
compileCondition(
phpGlobalNamePrefix + 'peg_regex_test('
+ c(bc[ip + 1]) + ', '
+ inputSubstr('$this->peg_currPos', 1)
+ ')',
1
);
} else {
compileCondition(
phpGlobalNamePrefix + 'peg_char_class_test('
+ c(bc[ip + 1]) + ', '
+ inputSubstr('$this->peg_currPos', 1)
+ ')',
1
);
}
break;
case op.ACCEPT_N: // ACCEPT_N n
parts.push(stack.push(
inputSubstr('$this->peg_currPos', bc[ip + 1])
));
parts.push(
bc[ip + 1] > 1
? '$this->peg_currPos += ' + bc[ip + 1] + ';'
: '$this->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
? '$this->peg_currPos += ' + eval(ast.consts[bc[ip + 1]]).length + ';'
: '$this->peg_currPos++;'
);
ip += 2;
break;
case op.FAIL: // FAIL e
parts.push(stack.push('$this->peg_FAILED'));
parts.push('if ($this->peg_silentFails === 0) {');
parts.push(' $this->peg_fail(' + c(bc[ip + 1]) + ');');
parts.push('}');
ip += 2;
break;
case op.LOAD_SAVED_POS: // LOAD_SAVED_POS p
parts.push('$this->peg_reportedPos = ' + stack.index(bc[ip + 1]) + ';');
ip += 2;
break;
case op.UPDATE_SAVED_POS: // UPDATE_SAVED_POS
parts.push('$this->peg_reportedPos = $this->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("$this->peg_parse" + ast.rules[bc[ip + 1]].name + "()"));
ip += 2;
break;
case op.SILENT_FAILS_ON: // SILENT_FAILS_ON
parts.push('$this->peg_silentFails++;');
ip++;
break;
case op.SILENT_FAILS_OFF: // SILENT_FAILS_OFF
parts.push('$this->peg_silentFails--;');
ip++;
break;
default:
throw new Error("Invalid opcode: " + bc[ip] + ".");
}
}
return parts.join('\n');
}
code = compile(rule.bytecode);
parts.push([
'private function peg_parse' + rule.name + '() {',
''
].join('\n'));
if (options.cache) {
parts.push(indent2(
generateCacheHeader(asts.indexOfRule(ast, rule.name))
));
}
parts.push(indent2(code));
if (options.cache) {
parts.push(indent2(generateCacheFooter(s(0))));
}
parts.push([
'',
' return ' + s(0) + ';',
'}'
].join('\n'));
return parts.join('\n');
}
var parts = [], startRuleFunctions, startRuleFunction;
parts.push([
'<?php',
'/*',
' * Generated by PEG.js 0.10.x with phpegjs plugin',
' *',
' * http://pegjs.majda.cz/',
' */',
'',
].join('\n'));
if (phpNamespace) {
parts.push('namespace ' + phpNamespace + ';');
}
parts.push([
'',
'/* Useful functions: */',
'',
'/* ' + phpGlobalNamePrefix + 'chr_unicode - get unicode character from its char code */',
'if (!function_exists("' + phpGlobalNamePrefixOrNamespaceEscaped + 'chr_unicode")) {',
' function ' + phpGlobalNamePrefix + 'chr_unicode($code) {',
' return html_entity_decode("&#$code;", ENT_QUOTES, "UTF-8");',
' }',
'}',
'/* ' + phpGlobalNamePrefix + 'ord_unicode - get unicode char code from string */',
'if (!function_exists("' + phpGlobalNamePrefixOrNamespaceEscaped + 'ord_unicode")) {',
' function ' + phpGlobalNamePrefix + 'ord_unicode($character) {',
' if (strlen($character) === 1) {',
' return ord($character);',
' }',
' $json = json_encode($character);',
' $utf16_1 = hexdec(substr($json, 3, 4));',
// A character inside the BMP has a JSON representation like "\uXXXX".
// A character outside the BMP looks like "\uXXXX\uXXXX".
' if (substr($json, 7, 2) === "\\u") {',
// Outside the BMP. Math from https://stackoverflow.com/a/6240819
' $utf16_2 = hexdec(substr($json, 9, 4));',
' return 0x10000 + (($utf16_1 & 0x3ff) << 10) + ($utf16_2 & 0x3ff);',
' } else {',
' return $utf16_1;',
' }',
' }',
'}',
].join('\n'));
if (mbstringAllowed) {
parts.push([
'/* ' + phpGlobalNamePrefix + 'peg_regex_test - multibyte regex test */',
'if (!function_exists("' + phpGlobalNamePrefixOrNamespaceEscaped + 'peg_regex_test")) {',
' function ' + phpGlobalNamePrefix + 'peg_regex_test($pattern, $string) {',
' if (substr($pattern, -1) == "i") {',
' return mb_eregi(substr($pattern, 1, -2), $string);',
' } else {',
' return mb_ereg(substr($pattern, 1, -1), $string);',
' }',
' }',
'}',
'',
].join('\n'));
} else {
// Case-insensitive character classes are disallowed in
// `generate-bytecode-php.js` if the `mbstringAllowed` option is set to
// false.
parts.push([
'/* ' + phpGlobalNamePrefix + 'peg_char_class_test - simple character class test */',
'if (!function_exists("' + phpGlobalNamePrefixOrNamespaceEscaped + 'peg_char_class_test")) {',
' function ' + phpGlobalNamePrefix + 'peg_char_class_test($class, $character) {',
' $code = ' + phpGlobalNamePrefix + 'ord_unicode($character);',
' foreach ($class as $range) {',
' if ($code >= $range[0] && $code <= $range[1]) {',
' return true;',
' }',
' }',
' return false;',
' }',
'}',
'',
].join('\n'));
}
parts.push([
'/* Syntax error exception */',
'if (!class_exists("' + phpGlobalNamePrefixOrNamespaceEscaped + 'SyntaxError", false)) {',
' class ' + phpGlobalNamePrefix + 'SyntaxError extends ' + phpGlobalNamespacePrefix + 'Exception {',
' public $expected;',
' public $found;',
' public $grammarOffset;',
' public $grammarLine;',
' public $grammarColumn;',
' public $name;',
' public function __construct($message, $expected, $found, $offset, $line, $column) {',
' parent::__construct($message, 0);',
' $this->expected = $expected;',
' $this->found = $found;',
' $this->grammarOffset = $offset;',
' $this->grammarLine = $line;',
' $this->grammarColumn = $column;',
' $this->name = "' + phpGlobalNamePrefix + 'SyntaxError";',
' }',
' }',
'}',
'',
'class ' + phpParserClass + ' {',
].join('\n'));
parts.push([
' private $peg_currPos = 0;',
' private $peg_reportedPos = 0;',
' private $peg_cachedPos = 0;',
' private $peg_cachedPosDetails = array(\'line\' => 1, \'column\' => 1, \'seenCR\' => false );',
' private $peg_maxFailPos = 0;',
' private $peg_maxFailExpected = array();',
' private $peg_silentFails = 0;', // 0 = report failures, > 0 = silence failures
' private $input = array();',
' private $input_length = 0;',
].join('\n'));
if (options.cache) {
parts.push(' public $peg_cache = array();');
}
parts.push([
'',
' private function cleanup_state() {',
' $this->peg_currPos = 0;',
' $this->peg_reportedPos = 0;',
' $this->peg_cachedPos = 0;',
" $this->peg_cachedPosDetails = array('line' => 1, 'column' => 1, 'seenCR' => false );",
' $this->peg_maxFailPos = 0;',
' $this->peg_maxFailExpected = array();',
' $this->peg_silentFails = 0;',
' $this->input = array();',
' $this->input_length = 0;',
options.cache ?
' $this->peg_cache = array();' : '',
' }',
'',
' private function input_substr($start, $length) {',
' if ($length === 1 && $start < $this->input_length) {',
' return $this->input[$start];',
' }',
' $substr = \'\';',
' $max = min($start + $length, $this->input_length);',
' for ($i = $start; $i < $max; $i++) {',
' $substr .= $this->input[$i];',
' }',
' return $substr;',
' }',
''
].join('\n'));
parts.push([
'',
' private function text() {',
' return substr($this->input, $this->peg_reportedPos, $this->peg_reportedPos + $this->peg_currPos);',
' }',
'',
' private function offset() {',
' return $this->peg_reportedPos;',
' }',
'',
' private function line() {',
' $compute_pd = $this->peg_computePosDetails($this->peg_reportedPos);',
' return $compute_pd["line"];',
' }',
'',
' private function column() {',
' $compute_pd = $this->peg_computePosDetails($this->peg_reportedPos);',
' return $compute_pd["column"];',
' }',
'',
' private function expected($description) {',
' throw $this->peg_buildException(',
' null,',
' array(array("type" => "other", "description" => $description )),',
' $this->peg_reportedPos',
' );',
' }',
'',
' private function error($message) {',
' throw $this->peg_buildException($message, null, $this->peg_reportedPos);',
' }',
'',
' private function peg_advancePos(&$details, $startPos, $endPos) {',
' for ($p = $startPos; $p < $endPos; $p++) {',
' $ch = $this->input_substr($p, 1);',
' if ($ch === "\\n") {',
' if (!$details["seenCR"]) { $details["line"]++; }',
' $details["column"] = 1;',
' $details["seenCR"] = false;',
' } else if ($ch === "\\r" || $ch === "\\u2028" || $ch === "\\u2029") {',
' $details["line"]++;',
' $details["column"] = 1;',
' $details["seenCR"] = true;',
' } else {',
' $details["column"]++;',
' $details["seenCR"] = false;',
' }',
' }',
' }',
'',
' private function peg_computePosDetails($pos) {',
' if ($this->peg_cachedPos !== $pos) {',
' if ($this->peg_cachedPos > $pos) {',
' $this->peg_cachedPos = 0;',
' $this->peg_cachedPosDetails = array( "line" => 1, "column" => 1, "seenCR" => false );',
' }',
' $this->peg_advancePos($this->peg_cachedPosDetails, $this->peg_cachedPos, $pos);',
' $this->peg_cachedPos = $pos;',
' }',
'',
' return $this->peg_cachedPosDetails;',
' }',
'',
' private function peg_fail($expected) {',
' if ($this->peg_currPos < $this->peg_maxFailPos) { return; }',
'',
' if ($this->peg_currPos > $this->peg_maxFailPos) {',
' $this->peg_maxFailPos = $this->peg_currPos;',
' $this->peg_maxFailExpected = array();',
' }',
'',
' $this->peg_maxFailExpected[] = $expected;',
' }',
'',
' private function peg_buildException_expectedComparator($a, $b) {',
' if ($a["description"] < $b["description"]) {',
' return -1;',
' } else if ($a["description"] > $b["description"]) {',
' return 1;',
' } else {',
' return 0;',
' }',
' }',
'',
' private function peg_buildException($message, $expected, $pos) {',
' $posDetails = $this->peg_computePosDetails($pos);',
' $found = $pos < $this->input_length ? $this->input[$pos] : null;',
'',
' if ($expected !== null) {',
' usort($expected, array($this, "peg_buildException_expectedComparator"));',
' $i = 1;',
/*
* This works because the bytecode generator guarantees that every
* expectation object exists only once, so it's enough to use |===| instead
* of deeper structural comparison.
*/
' while ($i < count($expected)) {',
' if ($expected[$i - 1] === $expected[$i]) {',
' array_splice($expected, $i, 1);',
' } else {',
' $i++;',
' }',
' }',
' }',
'',
' if ($message === null) {',
' $expectedDescs = array_fill(0, count($expected), null);',
'',
' for ($i = 0; $i < count($expected); $i++) {',
' $expectedDescs[$i] = $expected[$i]["description"];',
' }',
'',
' $expectedDesc = count($expected) > 1',
' ? join(", ", array_slice($expectedDescs, 0, -1))',
' . " or "',
' . $expectedDescs[count($expected) - 1]',
' : $expectedDescs[0];',
'',
' $foundDesc = $found ? json_encode($found) : "end of input";',
'',
' $message = "Expected " . $expectedDesc . " but " . $foundDesc . " found.";',
' }',
'',
' return new ' + phpGlobalNamePrefix + 'SyntaxError(',
' $message,',
' $expected,',
' $found,',
' $pos,',
' $posDetails["line"],',
' $posDetails["column"]',
' );',
' }',
''
].join('\n'));
parts.push(' private $peg_FAILED;');
parts.push(indent4(generateTablesDeclaration()));
parts.push('');
parts.push(indent4(generateFunctions()));
parts.push('');
arrayUtils.each(ast.rules, function(rule) {
parts.push(indent4(generateRuleFunction(rule)));
parts.push('');
});
parts.push([
' public function parse($input) {',
' $arguments = func_get_args();',
' $options = count($arguments) > 1 ? $arguments[1] : array();',
' $this->cleanup_state();',
'',
' if (is_array($input)) {',
' $this->input = $input;',
' } else {',
' preg_match_all("/./us", $input, $match);',
' $this->input = $match[0];',
' }',
' $this->input_length = count($this->input);',
'',
].join('\n'));
if (mbstringAllowed) {
parts.push([
' $old_regex_encoding = mb_regex_encoding();',
' mb_regex_encoding("UTF-8");',
'',
].join('\n'));
}
parts.push(indent4('$this->peg_FAILED = new ' + phpGlobalNamespacePrefix + 'stdClass;'));
parts.push(indent4(generateTablesDefinition()));
parts.push('');
startRuleFunctions = 'array( '
+ arrayUtils.map(
options.allowedStartRules,
function(r) {
return '\'' + r + '\' => array($this, "peg_parse' + r + '")';
}
).join(', ')
+ ' )';
startRuleFunction = 'array($this, "peg_parse' + options.allowedStartRules[0] + '")';
parts.push([
' $peg_startRuleFunctions = ' + startRuleFunctions + ';',
' $peg_startRuleFunction = ' + startRuleFunction + ';'
].join('\n'));
parts.push([
' if (isset($options["startRule"])) {',
' if (!(isset($peg_startRuleFunctions[$options["startRule"]]))) {',
' throw new ' + phpGlobalNamespacePrefix + 'Exception("Can\'t start parsing from rule \\"" + $options["startRule"] + "\\".");',
' }',
'',
' $peg_startRuleFunction = $peg_startRuleFunctions[$options["startRule"]];',
' }'
].join('\n'));
if (ast.initializer) {
parts.push('');
parts.push(indent4('/* BEGIN initializer code */'));
parts.push(indent4(
internalUtils.extractPhpCode(ast.initializer.code)
));
parts.push(indent4('/* END initializer code */'));
parts.push('');
}
parts.push(' $peg_result = call_user_func($peg_startRuleFunction);');
if (options.cache) {
parts.push('');
parts.push(' $this->peg_cache = array();');
}
if (mbstringAllowed) {
parts.push([
'',
' mb_regex_encoding($old_regex_encoding);',
].join('\n'));
}
parts.push([
'',
' if ($peg_result !== $this->peg_FAILED && $this->peg_currPos === $this->input_length) {',
' $this->cleanup_state(); // Free up memory',
' return $peg_result;',
' } else {',
' if ($peg_result !== $this->peg_FAILED && $this->peg_currPos < $this->input_length) {',
' $this->peg_fail(array("type" => "end", "description" => "end of input" ));',
' }',
'',
' $exception = $this->peg_buildException(null, $this->peg_maxFailExpected, $this->peg_maxFailPos);',
' $this->cleanup_state(); // Free up memory',
' throw $exception;',
' }',
' }',
'',
'};'
].join('\n'));
ast.code = parts.join('\n');
};