packages/sql-faker/tests/Unit/Generation/Lexeme/LexemeSequenceTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Generation\Lexeme;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Generation\Candidate\ChoiceLexemeGenerator;
11use SqlFaker\Generation\Candidate\FixedLexemeGenerator;
12use SqlFaker\Generation\Candidate\MatchingLexemeGenerator;
13use SqlFaker\Generation\Candidate\RegisteredLexemeGenerator;
14use SqlFaker\Generation\Candidate\SequenceLexemeGenerator;
15use SqlFaker\Generation\Candidate\ValueLexemeGenerator;
16use SqlFaker\Generation\Candidate\VersionCase;
17use SqlFaker\Generation\Candidate\VersionedLexemeGenerator;
18use SqlFaker\Generation\Derivation\CompletionCosts;
19use SqlFaker\Generation\Derivation\Derivation;
20use SqlFaker\Generation\Derivation\DerivationTrace;
21use SqlFaker\Generation\Derivation\TerminationAnalyzer;
22use SqlFaker\Generation\Derivation\TerminationCost;
23use SqlFaker\Generation\Derivation\TokenGenerator;
24use SqlFaker\Generation\Exception\GenerationException;
25use SqlFaker\Generation\Exception\LexicalException;
26use SqlFaker\Generation\Lexeme\Lexeme;
27use SqlFaker\Generation\Lexeme\LexemeBoundary;
28use SqlFaker\Generation\Lexeme\LexemeCandidates;
29use SqlFaker\Generation\Lexeme\LexemeGenerator;
30use SqlFaker\Generation\Lexeme\LexemeInput;
31use SqlFaker\Generation\Lexeme\LexemeSequence;
32use SqlFaker\Generation\Lexeme\OutputPart;
33use SqlFaker\Generation\Lexeme\ResolvedOutput;
34use SqlFaker\Generation\Lexeme\SpacingConstraint;
35use SqlFaker\Generation\Lexeme\SpacingRule;
36use SqlFaker\Generation\Output\CandidateResolver;
37use SqlFaker\Generation\Output\CombinedSpacingRule;
38use SqlFaker\Generation\Output\ReverseLexemeGenerator;
39use SqlFaker\Generation\Output\SqlSerializer;
40use SqlFaker\Generation\Plan\GenerationPlan;
41use SqlFaker\Generation\Token\ProductionOccurrence;
42use SqlFaker\Generation\Token\RewriteRule;
43use SqlFaker\Generation\Token\TerminalOccurrence;
44use SqlFaker\Generation\Token\TerminalSequence;
45use SqlFaker\Generation\Token\TokenRewriter;
46use SqlFaker\Grammar\Model\Grammar;
47use SqlFaker\Grammar\Model\NonTerminal;
48use SqlFaker\Grammar\Model\Production;
49use SqlFaker\Grammar\Model\ProductionRule;
50use SqlFaker\Grammar\Model\Terminal;
51use SqlFaker\MySql\Generation\Spacing\KeywordPhraseSpacingRule;
52
53#[CoversClass(LexemeSequence::class)]
54#[UsesClass(RewriteRule::class)]
55#[UsesClass(TokenGenerator::class)]
56#[UsesClass(ProductionOccurrence::class)]
57#[UsesClass(TerminalSequence::class)]
58#[UsesClass(TokenRewriter::class)]
59#[UsesClass(TerminalOccurrence::class)]
60#[UsesClass(CandidateResolver::class)]
61#[UsesClass(ReverseLexemeGenerator::class)]
62#[UsesClass(ResolvedOutput::class)]
63#[UsesClass(OutputPart::class)]
64#[UsesClass(SqlSerializer::class)]
65#[UsesClass(VersionCase::class)]
66#[UsesClass(VersionedLexemeGenerator::class)]
67#[UsesClass(KeywordPhraseSpacingRule::class)]
68#[UsesClass(CombinedSpacingRule::class)]
69#[UsesClass(LexemeBoundary::class)]
70#[UsesClass(SpacingRule::class)]
71#[UsesClass(SpacingConstraint::class)]
72#[UsesClass(Lexeme::class)]
73#[UsesClass(LexemeGenerator::class)]
74#[UsesClass(ValueLexemeGenerator::class)]
75#[UsesClass(RegisteredLexemeGenerator::class)]
76#[UsesClass(LexemeCandidates::class)]
77#[UsesClass(SequenceLexemeGenerator::class)]
78#[UsesClass(ChoiceLexemeGenerator::class)]
79#[UsesClass(FixedLexemeGenerator::class)]
80#[UsesClass(MatchingLexemeGenerator::class)]
81#[UsesClass(LexemeInput::class)]
82#[UsesClass(CompletionCosts::class)]
83#[UsesClass(Derivation::class)]
84#[UsesClass(DerivationTrace::class)]
85#[UsesClass(GenerationPlan::class)]
86#[UsesClass(TerminationAnalyzer::class)]
87#[UsesClass(TerminationCost::class)]
88#[UsesClass(Grammar::class)]
89#[UsesClass(NonTerminal::class)]
90#[UsesClass(Production::class)]
91#[UsesClass(ProductionRule::class)]
92#[UsesClass(Terminal::class)]
93#[UsesClass(GenerationException::class)]
94#[UsesClass(LexicalException::class)]
95#[UsesClass(\SqlFaker\Generation\Plan\ProductionPattern::class)]
96#[UsesClass(\SqlFaker\Generation\Output\BoundaryCompletion::class)]
97#[UsesClass(\SqlFaker\Generation\Derivation\CompletionState::class)]
98#[UsesClass(\SqlFaker\Generation\Derivation\CompletionFrontier::class)]
99#[UsesClass(\SqlFaker\Generation\Derivation\ConstrainedCompletion::class)]
100#[UsesClass(\SqlFaker\Generation\Value\ValueChoices::class)]
101#[UsesClass(\SqlFaker\Generation\Derivation\CompletionMemo::class)]
102#[UsesClass(\SqlFaker\Generation\Derivation\CompletionReduction::class)]
103#[UsesClass(\SqlFaker\Generation\Derivation\ConstraintDependencies::class)]
104#[UsesClass(\SqlFaker\Generation\Choice\BytePlanCompiler::class)]
105#[UsesClass(\SqlFaker\Generation\Derivation\Completion\PatternProductions::class)]
106#[UsesClass(\SqlFaker\Generation\Derivation\Completion\CompletionWitness::class)]
107final class LexemeSequenceTest extends TestCase
108{
109    public function testKeyDistinguishesOutputAndBoundarySemanticsButNotDefinitionIds(): void
110    {
111        $origin = new TerminalOccurrence('X', 4);
112        $a = new LexemeSequence([new Lexeme('x', 'identifier', $origin, 'one')], 'one');
113        $b = new LexemeSequence([new Lexeme('x', 'identifier', $origin, 'two')], 'two');
114        $c = new LexemeSequence($a->lexemes, 'one', new SpacingConstraint(SpacingConstraint::JOIN));
115        $d = new LexemeSequence([new Lexeme('x', 'function', $origin, 'one')], 'one');
116        self::assertSame($a->key(), $b->key());
117        self::assertNotSame($a->key(), $c->key());
118        self::assertNotSame($a->key(), $d->key());
119    }
120
121    public function testSourcesRetainsAlternativeDefinitionsLazily(): void
122    {
123        $choice = new ChoiceLexemeGenerator(
124            new FixedLexemeGenerator('x', 'identifier', 'first'),
125            new FixedLexemeGenerator('x', 'identifier', 'second'),
126            new FixedLexemeGenerator('y', 'identifier', 'third'),
127        );
128        $result = $choice->generate(new LexemeInput(TerminalSequence::fromNames(['X']), 0, new ResolvedOutput()));
129        self::assertNotNull($result);
130        $candidates = [...$result->sequences()];
131        self::assertCount(2, $candidates);
132        self::assertSame(['first:x', 'second:x'], [...$candidates[0]->sources()]);
133        self::assertSame(['third:y'], [...$candidates[1]->sources()]);
134    }
135    public function testKeyTreatsAnExplicitUnrestrictedBoundaryAsEquivalentToAnOmittedOne(): void
136    {
137        $origin = new TerminalOccurrence('X', 1);
138        $lexeme = new Lexeme('X', 'keyword', $origin, 'source');
139        $plain = new LexemeSequence([$lexeme], 'plain');
140        $unrestricted = new LexemeSequence([$lexeme], 'explicit', new SpacingConstraint(), [1 => new SpacingConstraint()]);
141        self::assertSame($plain->key(), $unrestricted->key());
142        $left = new LexemeSequence([$lexeme], 'left', new SpacingConstraint(SpacingConstraint::JOIN));
143        $boundary = new LexemeSequence([$lexeme], 'zero', null, [0 => new SpacingConstraint(SpacingConstraint::JOIN)]);
144        self::assertSame($left->key(), $boundary->key());
145        self::assertNotSame($plain->key(), $left->key());
146    }
147}
148