/** * {@inheritDoc} */ public function parse(TokenStream $stream) { $stateStack = array($currentState = 0); $args = array(); foreach ($stream as $token) { while (true) { $type = $token->getType(); if (!isset($this->parseTable['action'][$currentState][$type])) { // unexpected token throw new UnexpectedTokenException($token, array_keys($this->parseTable['action'][$currentState])); } $action = $this->parseTable['action'][$currentState][$type]; if ($action > 0) { // shift $args[] = $token; $stateStack[] = $currentState = $action; break; } elseif ($action < 0) { // reduce $rule = $this->grammar->getRule(-$action); $popCount = count($rule->getComponents()); array_splice($stateStack, -$popCount); $newArgs = array_splice($args, -$popCount); if ($callback = $rule->getCallback()) { $args[] = call_user_func_array($callback, $newArgs); } else { $args[] = $newArgs[0]; } $state = $stateStack[count($stateStack) - 1]; $stateStack[] = $currentState = $this->parseTable['goto'][$state][$rule->getName()]; } else { // accept return $args[0]; } } } }
protected function writeAction($trigger, $action) { if ($action > 0) { $this->writer->writeLine(sprintf('// on %s shift and go to state %d', $trigger, $action)); } elseif ($action < 0) { $rule = $this->grammar->getRule(-$action); $components = $rule->getComponents(); if (empty($components)) { $rhs = '/* empty */'; } else { $rhs = implode(' ', $components); } $this->writer->writeLine(sprintf('// on %s reduce by rule %s -> %s', $trigger, $rule->getName(), $rhs)); } else { $this->writer->writeLine(sprintf('// on %s accept the input', $trigger)); } $this->writer->writeLine(sprintf("'%s' => %d,", $trigger, $action)); }
/** * Encodes the handle-finding FSA as a LR parse table. * * @param \Dissect\Parser\LALR1\Analysis\Automaton $automaton * * @return array The parse table. */ protected function buildParseTable(Automaton $automaton, Grammar $grammar) { $nonterminals = array_keys($grammar->getGroupedRules()); $conflictsMode = $grammar->getConflictsMode(); $conflicts = array(); // initialize the table $table = array('action' => array(), 'goto' => array()); foreach ($automaton->getTransitionTable() as $num => $transitions) { foreach ($transitions as $trigger => $destination) { if (!in_array($trigger, $nonterminals)) { // terminal implies shift $table['action'][$num][$trigger] = $destination; } else { // nonterminal goes in the goto table $table['goto'][$num][$trigger] = $destination; } } } foreach ($automaton->getStates() as $num => $state) { if (!isset($table['action'][$num])) { $table['action'][$num] = array(); } foreach ($state->getItems() as $item) { if ($item->isReduceItem()) { $ruleNumber = $item->getRule()->getNumber(); foreach ($item->getLookahead() as $token) { if (array_key_exists($token, $table['action'][$num])) { // conflict $instruction = $table['action'][$num][$token]; if ($instruction > 0) { // s/r if ($conflictsMode & Grammar::SHIFT) { $conflicts[] = array('state' => $num, 'lookahead' => $token, 'rule' => $item->getRule(), 'resolution' => Grammar::SHIFT); continue; } else { throw new ShiftReduceConflictException($num, $item->getRule(), $token, $automaton); } } else { // r/r $originalRule = $grammar->getRule(-$instruction); $newRule = $item->getRule(); if ($conflictsMode & Grammar::LONGER_REDUCE) { $count1 = count($originalRule->getComponents()); $count2 = count($newRule->getComponents()); if ($count1 > $count2) { // original rule is longer $resolvedRules = array($originalRule, $newRule); $conflicts[] = array('state' => $num, 'lookahead' => $token, 'rules' => $resolvedRules, 'resolution' => Grammar::LONGER_REDUCE); continue; } elseif ($count2 > $count1) { // new rule is longer $table['action'][$num][$token] = -$ruleNumber; $resolvedRules = array($newRule, $originalRule); $conflicts[] = array('state' => $num, 'lookahead' => $token, 'rules' => $resolvedRules, 'resolution' => Grammar::LONGER_REDUCE); continue; } } if ($conflictsMode & Grammar::EARLIER_REDUCE) { if (-$instruction < $ruleNumber) { // original rule was earlier $resolvedRules = array($originalRule, $newRule); $conflicts[] = array('state' => $num, 'lookahead' => $token, 'rules' => $resolvedRules, 'resolution' => Grammar::EARLIER_REDUCE); continue; } else { // new rule was earlier $table['action'][$num][$token] = -$ruleNumber; $conflicts[] = array('state' => $num, 'lookahead' => $token, 'rules' => $resolvedRules, 'resolution' => Grammar::EARLIER_REDUCE); $resolvedRules = array($newRule, $originalRule); continue; } } // everything failed, throw an exception throw new ReduceReduceConflictException($num, $originalRule, $newRule, $token, $automaton); } } $table['action'][$num][$token] = -$ruleNumber; } } } } return array($table, $conflicts); }