classDerivation
final class Derivation
Derives terminals from a common grammar while reserving enough steps to finish the remaining form.
Properties§
public ?DerivationTrace $trace = nullOccurrences from the most recent derivation, including empty productions.
Methods§
public function __construct(
private Grammar $grammar,
private Generator $faker,
private TerminationAnalyzer $analyzer,
?CompletionCosts $completion = null,
private Closure(int): ?int|null $choose = null,
)Parameters
$grammar | Grammar | Grammar being walked |
$faker | Generator | Source of the choices the walk makes freely |
$analyzer | TerminationAnalyzer | Answers what a production still costs to finish |
$completion | ?CompletionCosts | |
$choose | Closure(int): ?int|null | Optional choice policy; null results choose the shortest completion |
Test cases 18
DerivationTest::testAffordableAcceptsAnAlternativeThatExactlyUsesTheBudgetcoversDerivationTest::testAffordableAllowsATerminalOnlyProductionWithNoExpansionBudgetLeftcoversDerivationTest::testAffordableCompletionRetainsOnlyProductionsWithAnAffordableNonEmptyContinuationcoversDerivationTest::testAffordableKeepsAnAlternativeThatStillLeavesRoomToFinishcoversDerivationTest::testAffordableRejectsAnAlternativeThatCannotFitAfterTheRemaindercoversDerivationTest::testAffordableRejectsARemainderThatExceedsTheWholeBudgetcoversDerivationTest::testAffordableReturnsAListAfterDiscardingAnOverBudgetAlternativecoversDerivationTest::testAlternativesRespectsTheOccurrenceAndRejectsUnrealizableProductionscoversDerivationTest::testCompletableKeepsAnEmptyAlternativeWhenTheRemainingSiblingSatisfiesNonEmptycoversDerivationTest::testFirstNonTerminalAnswersNothingWhenOnlyTerminalsAreLeftcoversDerivationTest::testFirstNonTerminalAnswersTheLeftmostPositionTheWalkActsOncoversDerivationTest::testOfReportsAPlanNoAlternativeCanSatisfycoversDerivationTest::testOfReportsASymbolTheGrammarDeclaresNoRuleForcoversDerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcoversDerivationTest::testOfSelectsTerminationPolicyFromThePlancoversDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycoversDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcoversDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcovers
Calls 2
public function of(string $startSymbol, GenerationPlan<bool> $plan): list<Terminal>Walks from one symbol until nothing but terminals is left.
Parameters
$startSymbol | string | Symbol the walk begins at |
$plan | GenerationPlan<bool> | Plan directing the walk |
Returns
list<Terminal> Terminals the walk arrived atThrows
GenerationException When the grammar, the plan, or the step budget leaves no production to takeTest cases 7
DerivationTest::testOfReportsAPlanNoAlternativeCanSatisfycovers and callsDerivationTest::testOfReportsASymbolTheGrammarDeclaresNoRuleForcovers and callsDerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcovers and callsDerivationTest::testOfSelectsTerminationPolicyFromThePlancovers and callsDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycovers and callsDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcovers and callsDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcovers and calls
Called from 1
Calls 13
- new DerivationTrace line 66
- new NonTerminal line 68
- method-call Derivation::firstNonTerminal() line 73
- method-call GenerationPlan::expansionBudget() line 79
- class-const Derivation::STEP_LIMIT() line 79
- static-call GenerationException::derivationLimitExceeded() line 80
- method-call Derivation::alternatives() line 87
- function-call
countline 89 - method-call Derivation::affordableCompletion() line 90
- method-call Derivation::completable() line 91
- method-call Derivation::selectProduction() line 92
- function-call
array_searchline 93 - function-call
array_sliceline 97
public function firstNonTerminal(list<Symbol> $form): int|nullAnswers where in the sentential form the walk acts next.
Parameters
$form | list<Symbol> | Sentential form the walk has reached |
Returns
int|null Position of the leftmost non-terminal, or null when only terminals are leftTest cases 9
DerivationTest::testFirstNonTerminalAnswersNothingWhenOnlyTerminalsAreLeftcovers and callsDerivationTest::testFirstNonTerminalAnswersTheLeftmostPositionTheWalkActsOncovers and callsDerivationTest::testOfReportsAPlanNoAlternativeCanSatisfycoversDerivationTest::testOfReportsASymbolTheGrammarDeclaresNoRuleForcoversDerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcoversDerivationTest::testOfSelectsTerminationPolicyFromThePlancoversDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycoversDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcoversDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcovers
public function affordable(
non-empty-list<Production> $alternatives,
Production $remainder,
): non-empty-list<Production>Keeps only the alternatives the walk can still afford to finish.
What is left of the form behind the symbol being rewritten has to be derived too, so the budget an alternative may spend is the step limit less what has been spent and less what the remainder will cost.
Parameters
$alternatives | non-empty-list<Production> | Alternatives the grammar and the plan both allow |
$remainder | Production | What the walk still has to derive behind this symbol |
Returns
non-empty-list<Production> Alternatives that still leave room to finishThrows
GenerationException When none of them fits in the remaining budgetTest cases 6
DerivationTest::testAffordableAcceptsAnAlternativeThatExactlyUsesTheBudgetcovers and callsDerivationTest::testAffordableAllowsATerminalOnlyProductionWithNoExpansionBudgetLeftcovers and callsDerivationTest::testAffordableKeepsAnAlternativeThatStillLeavesRoomToFinishcovers and callsDerivationTest::testAffordableRejectsAnAlternativeThatCannotFitAfterTheRemaindercovers and callsDerivationTest::testAffordableRejectsARemainderThatExceedsTheWholeBudgetcovers and callsDerivationTest::testAffordableReturnsAListAfterDiscardingAnOverBudgetAlternativecovers and calls
Calls 5
- class-const Derivation::STEP_LIMIT() line 141
- method-call TerminationAnalyzer::estimateProductionSteps() line 141
- static-call GenerationException::derivationLimitExceeded() line 143
- function-call
array_valuesline 146 - function-call
array_filterline 146
public function selectProduction(
non-empty-list<Production> $alternatives,
GenerationPlan<bool> $plan,
): ProductionAnswers which of the alternatives the walk takes.
Up to the plan's depth the choice is free. Past it the walk is trying to finish, so it takes whichever alternative gets there in the fewest steps, and among equals whichever writes the least.
Parameters
$alternatives | non-empty-list<Production> | Alternatives the walk may still take |
$plan | GenerationPlan<bool> | Plan directing the walk |
Returns
Production Alternative to rewrite withTest cases 5
DerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcoversDerivationTest::testOfSelectsTerminationPolicyFromThePlancoversDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycoversDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcoversDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcovers
Called from 1
Calls 6
- method-call GenerationPlan::maxDepth() line 171
- method-call
Generator::numberBetween()line 173 - function-call
countline 173 - method-call GenerationPlan::usesStepBudget() line 184
- method-call TerminationAnalyzer::estimateProductionSteps() line 184
- method-call TerminationAnalyzer::estimateProductionLength() line 185
public function alternatives(
NonTerminal $nonTerminal,
GenerationPlan<bool> $plan,
int $occurrence,
): non-empty-list<Production>Finds realizable alternatives satisfying the plan at this occurrence.
Parameters
$nonTerminal | NonTerminal | |
$plan | GenerationPlan<bool> | Plan directing the walk |
$occurrence | int |
Returns
non-empty-list<Production>Throws
GenerationException When no alternative satisfies the grammar and planTest cases 8
DerivationTest::testAlternativesRespectsTheOccurrenceAndRejectsUnrealizableProductionscovers and callsDerivationTest::testOfReportsAPlanNoAlternativeCanSatisfycoversDerivationTest::testOfReportsASymbolTheGrammarDeclaresNoRuleForcoversDerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcoversDerivationTest::testOfSelectsTerminationPolicyFromThePlancoversDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycoversDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcoversDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcovers
Called from 1
Calls 10
- static-call GenerationException::unknownRule() line 207
- static-call GenerationException::ruleHasNoAlternatives() line 209
- function-call
array_filterline 211 - method-call TerminationAnalyzer::isProductionViable() line 211
- static-call GenerationException::noRealizableAlternative() line 213
- method-call GenerationPlan::patternAt() line 215
- function-call
array_valuesline 217 - function-call
array_mapline 219 - method-call Symbol::value() line 220
- static-call GenerationException::noAlternativeMatchingPlan() line 226
public function completable(
non-empty-list<Production> $alternatives,
list<Symbol> $form,
int $index,
GenerationPlan<bool> $plan,
array<string, int> $occurrences = [],
): non-empty-list<Production>Reserves a complete non-empty continuation when required, independently of lexical handler availability.
Parameters
$alternatives | non-empty-list<Production> | |
$form | list<Symbol> | |
$index | int | |
$plan | GenerationPlan<bool> | |
$occurrences | array<string, int> | Occurrences already selected before the pending continuation |
Returns
non-empty-list<Production>Throws
GenerationException When the explicit plan has no affordable completionTest cases 3
Called from 1
Calls 11
- method-call GenerationPlan::requiresNonEmpty() line 244
- method-call CompletionCosts::hasTerminalOutput() line 244
- function-call
array_sliceline 244 - method-call Derivation::affordableCompletion() line 245
- method-call GenerationPlan::hasRemainingPatterns() line 246
- method-call GenerationPlan::expansionBudget() line 247
- class-const Derivation::STEP_LIMIT() line 247
- function-call
array_valuesline 248 - function-call
array_filterline 248 - method-call ConstrainedCompletion::within() line 249
- static-call GenerationException::derivationLimitExceeded() line 252
public function affordableCompletion(
non-empty-list<Production> $alternatives,
list<Symbol> $form,
int $index,
GenerationPlan<bool> $plan,
): non-empty-list<Production>Checks budget/output lower bounds when the next production is forced; the walk validates subsequent forced steps directly. Looking ahead cannot select a different production here, so it would only repeat the frozen plan's remaining walk.
Parameters
$alternatives | non-empty-list<Production> | |
$form | list<Symbol> | |
$index | int | |
$plan | GenerationPlan<bool> |
Returns
non-empty-list<Production>Throws
GenerationException When no production can satisfy the remaining budget and output requirementTest cases 8
DerivationTest::testAffordableCompletionRetainsOnlyProductionsWithAnAffordableNonEmptyContinuationcovers and callsDerivationTest::testCompletableKeepsAnEmptyAlternativeWhenTheRemainingSiblingSatisfiesNonEmptycoversDerivationTest::testOfReportsASymbolTheGrammarDeclaresNoRuleForcoversDerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcoversDerivationTest::testOfSelectsTerminationPolicyFromThePlancoversDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycoversDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcoversDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcovers
Called from 2
Calls 7
- method-call GenerationPlan::requiresNonEmpty() line 268
- method-call CompletionCosts::hasTerminalOutput() line 268
- function-call
array_sliceline 268 - method-call CompletionCosts::affordable() line 269
- method-call GenerationPlan::expansionBudget() line 269
- class-const Derivation::STEP_LIMIT() line 269
- static-call GenerationException::derivationLimitExceeded() line 270
Private surface 8§
Implementation details, listed for orientation only.
private const STEP_LIMIT = 5000private int $steps = 0private CompletionCosts $completionprivate ConstrainedCompletion $constrainedCompletionprivate Grammar $grammarprivate Generator $fakerprivate TerminationAnalyzer $analyzerprivate Closure(int): ?int|null $choose = nullTest cases 33§
Test cases that cover or call this symbol, from the coverage report and from the analyzed test sources.
Dedicated tests 18
DerivationTest::testAffordableAcceptsAnAlternativeThatExactlyUsesTheBudgetcovers and callsDerivationTest::testAffordableAllowsATerminalOnlyProductionWithNoExpansionBudgetLeftcovers and callsDerivationTest::testAffordableCompletionRetainsOnlyProductionsWithAnAffordableNonEmptyContinuationcovers and callsDerivationTest::testAffordableKeepsAnAlternativeThatStillLeavesRoomToFinishcovers and callsDerivationTest::testAffordableRejectsAnAlternativeThatCannotFitAfterTheRemaindercovers and callsDerivationTest::testAffordableRejectsARemainderThatExceedsTheWholeBudgetcovers and callsDerivationTest::testAffordableReturnsAListAfterDiscardingAnOverBudgetAlternativecovers and callsDerivationTest::testAlternativesRespectsTheOccurrenceAndRejectsUnrealizableProductionscovers and callsDerivationTest::testCompletableKeepsAnEmptyAlternativeWhenTheRemainingSiblingSatisfiesNonEmptycovers and callsDerivationTest::testFirstNonTerminalAnswersNothingWhenOnlyTerminalsAreLeftcovers and callsDerivationTest::testFirstNonTerminalAnswersTheLeftmostPositionTheWalkActsOncovers and callsDerivationTest::testOfReportsAPlanNoAlternativeCanSatisfycovers and callsDerivationTest::testOfReportsASymbolTheGrammarDeclaresNoRuleForcovers and callsDerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcovers and callsDerivationTest::testOfSelectsTerminationPolicyFromThePlancovers and callsDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycovers and callsDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcovers and callsDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcovers and calls
Other tests reaching this symbol 15
BytePlanCompilerTestcallsPlanBuilderTestcallsDerivationTraceTestcallsTokenGeneratorTestcallsLexemeSequenceTestcallsCandidateResolverTestcallsSqlGeneratorTestcallsTerminalSequenceTestcallsGenerationContextTestcallsMySqlProviderTestcallsGenerationContextTestcallsPostgreSqlProviderTestcallsSqlGeneratorFactoryTestcallsGenerationContextTestcallsSqliteProviderTestcalls