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, non-empty-list<Production>): ?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, non-empty-list<Production>): ?int|null | Optional choice policy given the count and the candidates; null results choose the shortest completion |
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 9
DerivationTest::testOfCountsOccurrencesAcrossSpecializedCopiesOfTheSameRulecallsDerivationTest::testOfOffersTheCandidatesToTheChoicePolicycallsDerivationTest::testOfReportsAPlanNoAlternativeCanSatisfycallsDerivationTest::testOfReportsASymbolTheGrammarDeclaresNoRuleForcallsDerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcallsDerivationTest::testOfSelectsTerminationPolicyFromThePlancallsDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycallsDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcallsDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcalls
Called from 1
Calls 14
- 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 Grammar::sourceRule() line 85
- method-call Derivation::alternatives() line 88
- function-call
countline 90 - method-call Derivation::affordableCompletion() line 91
- method-call Derivation::completable() line 92
- method-call Derivation::selectProduction() line 93
- function-call
array_searchline 94 - function-call
array_sliceline 98
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 2
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::testAffordableAcceptsAnAlternativeThatExactlyUsesTheBudgetcallsDerivationTest::testAffordableAllowsATerminalOnlyProductionWithNoExpansionBudgetLeftcallsDerivationTest::testAffordableKeepsAnAlternativeThatStillLeavesRoomToFinishcallsDerivationTest::testAffordableRejectsAnAlternativeThatCannotFitAfterTheRemaindercallsDerivationTest::testAffordableRejectsARemainderThatExceedsTheWholeBudgetcallsDerivationTest::testAffordableReturnsAListAfterDiscardingAnOverBudgetAlternativecalls
Calls 5
- class-const Derivation::STEP_LIMIT() line 142
- method-call TerminationAnalyzer::estimateProductionSteps() line 142
- static-call GenerationException::derivationLimitExceeded() line 144
- function-call
array_valuesline 147 - function-call
array_filterline 147
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 1
Called from 1
Calls 6
- method-call GenerationPlan::maxDepth() line 172
- method-call
Generator::numberBetween()line 174 - function-call
countline 174 - method-call GenerationPlan::usesStepBudget() line 185
- method-call TerminationAnalyzer::estimateProductionSteps() line 185
- method-call TerminationAnalyzer::estimateProductionLength() line 186
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 1
Called from 1
Calls 12
- static-call GenerationException::unknownRule() line 208
- static-call GenerationException::ruleHasNoAlternatives() line 210
- function-call
array_filterline 212 - method-call TerminationAnalyzer::isProductionViable() line 212
- static-call GenerationException::noRealizableAlternative() line 214
- method-call GenerationPlan::patternAt() line 216
- method-call Grammar::sourceRule() line 216
- function-call
array_valuesline 218 - function-call
array_mapline 220 - method-call Symbol::value() line 221
- method-call Grammar::sourceOrdinal() line 223
- static-call GenerationException::noAlternativeMatchingPlan() line 227
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 1
Called from 1
Calls 11
- method-call GenerationPlan::requiresNonEmpty() line 245
- method-call CompletionCosts::hasTerminalOutput() line 245
- function-call
array_sliceline 245 - method-call Derivation::affordableCompletion() line 246
- method-call GenerationPlan::hasRemainingPatterns() line 247
- method-call GenerationPlan::expansionBudget() line 248
- class-const Derivation::STEP_LIMIT() line 248
- function-call
array_valuesline 249 - function-call
array_filterline 249 - method-call ConstrainedCompletion::within() line 250
- static-call GenerationException::derivationLimitExceeded() line 253
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 1
Called from 2
Calls 7
- method-call GenerationPlan::requiresNonEmpty() line 269
- method-call CompletionCosts::hasTerminalOutput() line 269
- function-call
array_sliceline 269 - method-call CompletionCosts::affordable() line 270
- method-call GenerationPlan::expansionBudget() line 270
- class-const Derivation::STEP_LIMIT() line 270
- static-call GenerationException::derivationLimitExceeded() line 271
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, non-empty-list<Production>): ?int|null $choose = nullTest cases 39§
Test cases that cover or call this symbol, from the coverage report and from the analyzed test sources.
Dedicated tests 21
DerivationTest::testAffordableAcceptsAnAlternativeThatExactlyUsesTheBudgetcallsDerivationTest::testAffordableAllowsATerminalOnlyProductionWithNoExpansionBudgetLeftcallsDerivationTest::testAffordableCompletionRetainsOnlyProductionsWithAnAffordableNonEmptyContinuationcallsDerivationTest::testAffordableKeepsAnAlternativeThatStillLeavesRoomToFinishcallsDerivationTest::testAffordableRejectsAnAlternativeThatCannotFitAfterTheRemaindercallsDerivationTest::testAffordableRejectsARemainderThatExceedsTheWholeBudgetcallsDerivationTest::testAffordableReturnsAListAfterDiscardingAnOverBudgetAlternativecallsDerivationTest::testAlternativesRespectsTheOccurrenceAndRejectsUnrealizableProductionscallsDerivationTest::testCompletableKeepsAnEmptyAlternativeWhenTheRemainingSiblingSatisfiesNonEmptycallsDerivationTest::testFirstNonTerminalAnswersNothingWhenOnlyTerminalsAreLeftcallsDerivationTest::testFirstNonTerminalAnswersTheLeftmostPositionTheWalkActsOncallsDerivationTest::testOfCountsOccurrencesAcrossSpecializedCopiesOfTheSameRulecallsDerivationTest::testOfOffersTheCandidatesToTheChoicePolicycallsDerivationTest::testOfReportsAPlanNoAlternativeCanSatisfycallsDerivationTest::testOfReportsASymbolTheGrammarDeclaresNoRuleForcallsDerivationTest::testOfRewritesTheStartSymbolUntilOnlyTerminalsAreLeftcallsDerivationTest::testOfSelectsTerminationPolicyFromThePlancallsDerivationTest::testOfStillRejectsAnEntirelyForcedPlanWhoseDescendantCannotSatisfyNonEmptycallsDerivationTest::testSelectProductionChoosesFreelyWhileThePlanStillAllowsDepthcallsDerivationTest::testSelectProductionDrawsUniformlyAmongTheEligibleAlternativesBeforeTheThresholdcallsDerivationTest::testSelectProductionTakesTheShortestAlternativeOncePastThePlanDepthcalls
Other tests reaching this symbol 18
BytePlanCompilerTestcallsBytePlanEncoderTestcallsPlanBuilderTestcallsDerivationTraceTestcallsTokenGeneratorTestcallsLexemeSequenceTestcallsCandidateResolverTestcallsSqlGeneratorTestcallsTerminalSequenceTestcallsGenerationContextTestcallsSqlGeneratorFactoryTestcallsMySqlProviderTestcallsGenerationContextTestcallsSqlGeneratorFactoryTestcallsPostgreSqlProviderTestcallsGenerationContextTestcallsSqlGeneratorFactoryTestcallsSqliteProviderTestcalls