public function parse($code, ErrorHandler $errorHandler = null) {
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
// Initialize the lexer
$this->lexer->startLexing($code, $this->errorHandler);
// We start off with no lookahead-token
$symbol = self::SYMBOL_NONE;
// The attributes for a node are taken from the first and last token of the node.
// From the first token only the startAttributes are taken and from the last only
// the endAttributes. Both are merged using the array union operator (+).
$startAttributes = '*POISON';
$endAttributes = '*POISON';
$this->endAttributes = $endAttributes;
// Keep stack of start and end attributes
$this->startAttributeStack = array();
$this->endAttributeStack = array($endAttributes);
// Start off in the initial state and keep a stack of previous states
$state = 0;
$stateStack = array($state);
// Semantic value stack (contains values of tokens and semantic action results)
$this->semStack = array();
// Current position in the stack(s)
$this->stackPos = 0;
$this->errorState = 0;
for (;;) {
//$this->traceNewState($state, $symbol);
if ($this->actionBase[$state] == 0) {
$rule = $this->actionDefault[$state];
} else {
if ($symbol === self::SYMBOL_NONE) {
// Fetch the next token id from the lexer and fetch additional info by-ref.
// The end attributes are fetched into a temporary variable and only set once the token is really
// shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
// reduced after a token was read but not yet shifted.
$tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);
// map the lexer token id to the internally used symbols
$symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
? $this->tokenToSymbol[$tokenId]
: $this->invalidSymbol;
if ($symbol === $this->invalidSymbol) {
throw new \RangeException(sprintf(
'The lexer returned an invalid token (id=%d, value=%s)',
$tokenId, $tokenValue
));
}
// This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
// the attributes of the next token, even though they don't contain it themselves.
$this->startAttributeStack[$this->stackPos+1] = $startAttributes;
$this->endAttributeStack[$this->stackPos+1] = $endAttributes;
$this->lookaheadStartAttributes = $startAttributes;
//$this->traceRead($symbol);
}
$idx = $this->actionBase[$state] + $symbol;
if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol)
|| ($state < $this->YY2TBLSTATE
&& ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol))
&& ($action = $this->action[$idx]) != $this->defaultAction) {
/*
* >= YYNLSTATES: shift and reduce
* > 0: shift
* = 0: accept
* < 0: reduce
* = -YYUNEXPECTED: error
*/
if ($action > 0) {
/* shift */
//$this->traceShift($symbol);
++$this->stackPos;
$stateStack[$this->stackPos] = $state = $action;
$this->semStack[$this->stackPos] = $tokenValue;
$this->startAttributeStack[$this->stackPos] = $startAttributes;
$this->endAttributeStack[$this->stackPos] = $endAttributes;
$this->endAttributes = $endAttributes;
$symbol = self::SYMBOL_NONE;
if ($this->errorState) {
--$this->errorState;
}
if ($action < $this->YYNLSTATES) {
continue;
}
/* $yyn >= YYNLSTATES means shift-and-reduce */
$rule = $action - $this->YYNLSTATES;
} else {
$rule = -$action;
}
} else {
$rule = $this->actionDefault[$state];
}
}
for (;;) {
if ($rule === 0) {
/* accept */
//$this->traceAccept();
return $this->semValue;
} elseif ($rule !== $this->unexpectedTokenRule) {
/* reduce */
//$this->traceReduce($rule);
try {
$this->{'reduceRule' . $rule}();
} catch (Error $e) {
if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
$e->setStartLine($startAttributes['startLine']);
}
$this->emitError($e);
// Can't recover from this type of error
return null;
}
/* Goto - shift nonterminal */
$lastEndAttributes = $this->endAttributeStack[$this->stackPos];
$this->stackPos -= $this->ruleToLength[$rule];
$nonTerminal = $this->ruleToNonTerminal[$rule];
$idx = $this->gotoBase[$nonTerminal] + $stateStack[$this->stackPos];
if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] == $nonTerminal) {
$state = $this->goto[$idx];
} else {
$state = $this->gotoDefault[$nonTerminal];
}
++$this->stackPos;
$stateStack[$this->stackPos] = $state;
$this->semStack[$this->stackPos] = $this->semValue;
$this->endAttributeStack[$this->stackPos] = $lastEndAttributes;
} else {
/* error */
switch ($this->errorState) {
case 0:
$msg = $this->getErrorMessage($symbol, $state);
$this->emitError(new Error($msg, $startAttributes + $endAttributes));
// Break missing intentionally
case 1:
case 2:
$this->errorState = 3;
// Pop until error-expecting state uncovered
while (!(
(($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
|| ($state < $this->YY2TBLSTATE
&& ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $this->errorSymbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
) || ($action = $this->action[$idx]) == $this->defaultAction) { // Not totally sure about this
if ($this->stackPos <= 0) {
// Could not recover from error
return null;
}
$state = $stateStack[--$this->stackPos];
//$this->tracePop($state);
}
//$this->traceShift($this->errorSymbol);
++$this->stackPos;
$stateStack[$this->stackPos] = $state = $action;
$this->endAttributes = $this->endAttributeStack[$this->stackPos];
break;
case 3:
if ($symbol === 0) {
// Reached EOF without recovering from error
return null;
}
//$this->traceDiscard($symbol);
$symbol = self::SYMBOL_NONE;
break 2;
}
}
if ($state < $this->YYNLSTATES) {
break;
}
/* >= YYNLSTATES means shift-and-reduce */
$rule = $state - $this->YYNLSTATES;
}
}
throw new \RuntimeException('Reached end of parser loop');
}