_current; } /** * Get the current iterator key. * * @return int */ public function key() { return $this->_key; } /** * Useless here. * * @return void */ public function next() { return; } /** * Rewind the internal iterator pointer. * * @return void */ public function rewind() { $this->_key = -1; $this->_current = null; $this->_tests = []; $this->_coveredRules = []; foreach ($this->_rules as $name => $rule) { $this->_coveredRules[$name] = []; if ($rule instanceof Compiler\Llk\Rule\Repetition) { $min = $rule->getMin(); $min1 = $min + 1; $max = -1 == $rule->getMax() ? 2 : $rule->getMax(); $max1 = $max - 1; if ($min == $max) { $this->_coveredRules[$name][$min] = 0; } else { $this->_coveredRules[$name][$min] = 0; $this->_coveredRules[$name][$min1] = 0; $this->_coveredRules[$name][$max1] = 0; $this->_coveredRules[$name][$max] = 0; } } elseif ($rule instanceof Compiler\Llk\Rule\Choice) { for ($i = 0, $max = count($rule->getChildren()); $i < $max; ++$i) { $this->_coveredRules[$name][$i] = 0; } } else { $this->_coveredRules[$name][0] = 0; } } return; } /** * Compute the current iterator value, i.e. generate a new solution. * * @return bool */ public function valid() { $ruleName = $this->_rootRuleName; if (true !== in_array(0, $this->_coveredRules[$ruleName]) && true !== in_array(.5, $this->_coveredRules[$ruleName])) { return false; } $this->_trace = []; $this->_todo = [new Compiler\Llk\Rule\Entry( $ruleName, $this->_coveredRules )]; $result = $this->unfold(); if (true !== $result) { return false; } $handle = null; foreach ($this->_trace as $trace) { if ($trace instanceof Compiler\Llk\Rule\Token) { $handle .= $this->generateToken($trace); } } ++$this->_key; $this->_current = $handle; $this->_tests[] = $this->_trace; foreach ($this->_coveredRules as $key => $value) { foreach ($value as $k => $v) { if (-1 == $v) { $this->_coveredRules[$key][$k] = 0; } } } return true; } /** * Unfold rules from the todo stack. * * @return bool */ protected function unfold() { while (0 < count($this->_todo)) { $pop = array_pop($this->_todo); if ($pop instanceof Compiler\Llk\Rule\Ekzit) { $this->_trace[] = $pop; $this->updateCoverage($pop); } else { $out = $this->coverage($this->_rules[$pop->getRule()]); if (true !== $out && true !== $this->backtrack()) { return false; } } } return true; } /** * The coverage algorithm. * * @param \Hoa\Compiler\Llk\Rule $rule Rule to cover. * @return bool */ protected function coverage(Compiler\Llk\Rule $rule) { $children = $rule->getChildren(); if ($rule instanceof Compiler\Llk\Rule\Repetition) { $uncovered = []; $inprogress = []; $already = []; foreach ($this->_coveredRules[$rule->getName()] as $child => $value) { if (0 == $value || .5 == $value) { $uncovered[] = $child; } elseif (-1 == $value) { $inprogress[] = $child; } else { $already[] = $child; } } if (empty($uncovered)) { if (empty($already)) { $rand = $inprogress[rand( 0, count($inprogress) - 1 )]; } else { $rand = $already[rand( 0, count($already) - 1 )]; } $this->_trace[] = new Compiler\Llk\Rule\Entry( $rule->getName(), $this->_coveredRules, $this->_todo ); $this->_todo[] = new Compiler\Llk\Rule\Ekzit( $rule->getName(), $rand ); if ($this->_rules[$children] instanceof Compiler\Llk\Rule\Token) { for ($i = 0; $i < $rand; ++$i) { $this->_todo[] = new Compiler\Llk\Rule\Entry( $children, $this->_coveredRules, $this->_todo ); } } else { $sequence = $this->extract([$children]); if (null === $sequence) { return null; } for ($i = 0; $i < $rand; ++$i) { foreach ($sequence as $seq) { $this->_trace[] = $seq; if ($seq instanceof Compiler\Llk\Rule\Ekzit) { $this->updateCoverage($seq); } } } } } else { $rand = $uncovered[rand(0, count($uncovered) - 1)]; $this->_coveredRules[$rule->getName()][$rand] = -1; $this->_trace[] = new Compiler\Llk\Rule\Entry( $rule->getName(), $this->_coveredRules, $this->_todo ); $this->_todo[] = new Compiler\Llk\Rule\Ekzit( $rule->getName(), $rand ); for ($i= 0 ; $i < $rand; ++$i) { $this->_todo[] = new Compiler\Llk\Rule\Entry( $children, $this->_coveredRules, $this->_todo ); } } return true; } elseif ($rule instanceof Compiler\Llk\Rule\Choice) { $uncovered = []; $inprogress = []; $already = []; foreach ($this->_coveredRules[$rule->getName()] as $child => $value) { if (0 == $value || .5 == $value) { $uncovered[] = $child; } elseif (-1 == $value) { $inprogress[] = $child; } else { $already[] = $child; } } if (empty($uncovered)) { $this->_trace[] = new Compiler\Llk\Rule\Entry( $rule->getName(), $this->_coveredRules, $this->_todo ); $sequence = $this->extract($children); if (null === $sequence) { return null; } foreach ($sequence as $seq) { $this->_trace[] = $seq; if ($seq instanceof Compiler\Llk\Rule\Ekzit) { $this->updateCoverage($seq); } } if (empty($already)) { $rand = $inprogress[rand( 0, count($inprogress) - 1 )]; } else { $rand = $already[rand( 0, count($already) - 1 )]; } $this->_todo[] = new Compiler\Llk\Rule\Ekzit( $rule->getName(), $rand ); } else { $rand = $uncovered[rand(0, count($uncovered) - 1)]; $this->_trace[] = new Compiler\Llk\Rule\Entry( $rule->getName(), $this->_coveredRules, $this->_todo ); $this->_coveredRules[$rule->getName()][$rand] = -1; $this->_todo[] = new Compiler\Llk\Rule\Ekzit( $rule->getName(), $rand ); $this->_todo[] = new Compiler\Llk\Rule\Entry( $children[$rand], $this->_coveredRules, $this->_todo ); } return true; } elseif ($rule instanceof Compiler\Llk\Rule\Concatenation) { $this->_coveredRules[$rule->getName()][0] = -1; $this->_trace[] = new Compiler\Llk\Rule\Entry( $rule->getName(), false ); $this->_todo[] = new Compiler\Llk\Rule\Ekzit( $rule->getName(), false ); for ($i = count($children) - 1; $i >= 0; --$i) { $this->_todo[] = new Compiler\Llk\Rule\Entry( $children[$i], false, $this->_todo ); } return true; } elseif ($rule instanceof Compiler\Llk\Rule\Token) { $this->_trace[] = new Compiler\Llk\Rule\Entry( $rule->getName(), false ); $this->_trace[] = $rule; $this->_todo[] = new Compiler\Llk\Rule\Ekzit( $rule->getName(), false ); return true; } return false; } /** * Extract a given sequence from existing traces. * * @param array $rules Rules to consider. * @return array */ protected function extract(array $rules) { $out = []; foreach ($rules as $rule) { foreach ($this->_tests as $test) { $opened = 0; foreach ($test as $t) { if ($t instanceof Compiler\Llk\Rule\Entry && $t->getRule() == $rule) { ++$opened; } if (0 < $opened) { $out[] = $t; if ($t instanceof Compiler\Llk\Rule\Ekzit && $t->getRule() == $rule) { --$opened; if (0 === $opened) { return $out; } } } } } } foreach ($rules as $rule) { $out = []; $closed = 0; foreach ($this->_trace as $t) { if ($t instanceof Compiler\Llk\Rule\Ekzit && $t->getRule() == $rule) { ++$closed; } if (0 < $closed) { $out[] = $t; if ($t instanceof Compiler\Llk\Rule\Ekzit && $t->getRule() == $rule) { --$closed; if (0 === $closed) { return array_reverse($out); } } } } } return null; } /** * Backtrack to the previous choice-point. * * @return bool */ protected function backtrack() { $found = false; do { $pop = array_pop($this->_trace); if ($pop instanceof Compiler\Llk\Rule\Entry) { $rule = $this->_rules[$pop->getRule()]; $found = $rule instanceof Compiler\Llk\Rule\Choice || $rule instanceof Compiler\Llk\Rule\Repetition; } } while (0 < count($this->_trace) && false === $found); if (false === $found) { return false; } $ruleName = $pop->getRule(); $this->_covered = $pop->getData(); $this->_todo = $pop->getTodo(); $this->_todo[] = new Compiler\Llk\Rule\Entry( $ruleName, $this->_covered, $this->_todo ); return true; } /** * Update coverage of a rule. * * @param \Hoa\Compiler\Llk\Rule\Ekzit $rule Rule to consider. * @return void */ protected function updateCoverage(Compiler\Llk\Rule\Ekzit $Rule) { $ruleName = $Rule->getRule(); $child = $Rule->getData(); $rule = $this->_rules[$ruleName]; $children = $rule->getChildren(); if ($rule instanceof Compiler\Llk\Rule\Repetition) { if (0 === $child) { $this->_coveredRules[$ruleName][$child] = 1; } else { if (true === $this->allCovered($children) || true === $this->checkRuleRoot($children)) { $this->_coveredRules[$ruleName][$child] = 1; foreach ($this->_coveredRules[$ruleName] as $child => $value) { if (.5 == $value) { $this->_coveredRules[$ruleName][$child] = 1; } } } else { $this->_coveredRules[$ruleName][$child] = .5; } } } elseif ($rule instanceof Compiler\Llk\Rule\Choice) { if (true === $this->allCovered($children[$child]) || true === $this->checkRuleRoot($children[$child])) { $this->_coveredRules[$ruleName][$child] = 1; } else { $this->_coveredRules[$ruleName][$child] = .5; } } elseif ($rule instanceof Compiler\Llk\Rule\Concatenation) { $isCovered = true; for ($i = count($children) - 1; $i >= 0 && true === $isCovered; --$i) { if (false === $this->allCovered($children[$i]) && false === $this->checkRuleRoot($children[$i])) { $isCovered = false; } } $this->_coveredRules[$ruleName][0] = true === $isCovered ? 1 : .5; } elseif ($rule instanceof Compiler\Llk\Rule\Token) { $this->_coveredRules[$ruleName][0] = 1; } return; } /** * Check if all rules have been entirely covered. * * @param string $ruleName Rule name. * @return bool */ protected function allCovered($ruleName) { foreach ($this->_coveredRules[$ruleName] as $value) { if (1 !== $value) { return false; } } return true; } /** * Check if a rule is a root rule that is currently being processed. * * @param string $ruleName Rule name. * @return bool */ protected function checkRuleRoot($ruleName) { if (true === $this->_rules[$ruleName]->isTransitional()) { return false; } $i = count($this->_trace) - 1; $nb = 0; while ($i >= 0) { $lastRule = $this->_trace[$i]; if ($lastRule instanceof Compiler\Llk\Rule\Entry) { if ($lastRule->getRule() == $ruleName) { ++$nb; } } elseif ($lastRule instanceof Compiler\Llk\Rule\Ekzit) { if ($lastRule->getRule() == $ruleName) { --$nb; } } --$i; } return 0 < $nb; } }