packages/sql-faker/tests/Unit/Sqlite/Generation/Rewrite/FunctionArgumentRuleTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Sqlite\Generation\Rewrite;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFaker\Generation\Derivation\DerivationTrace;
12use SqlFaker\Generation\Token\ProductionOccurrence;
13use SqlFaker\Generation\Token\TerminalOccurrence;
14use SqlFaker\Generation\Token\TerminalSequence;
15use SqlFaker\Grammar\Model\NonTerminal;
16use SqlFaker\Grammar\Model\Production;
17use SqlFaker\Grammar\Model\Terminal;
18use SqlFaker\Sqlite\Generation\Rewrite\FunctionArgumentRule;
19
20#[CoversClass(FunctionArgumentRule::class)]
21#[UsesClass(DerivationTrace::class)]
22#[UsesClass(ProductionOccurrence::class)]
23#[UsesClass(TerminalOccurrence::class)]
24#[UsesClass(TerminalSequence::class)]
25#[UsesClass(NonTerminal::class)]
26#[UsesClass(Production::class)]
27#[UsesClass(Terminal::class)]
28final class FunctionArgumentRuleTest extends TestCase
29{
30 /**
31 * @param list<string> $expected
32 */
33 #[DataProvider('providerArgumentLists')]
34 public function testRewriteLimitsDirectFunctionArguments(TerminalSequence $input, array $expected): void
35 {
36 $rule = new FunctionArgumentRule();
37 $result = $rule->rewrite($input);
38 self::assertSame($expected, $result->names());
39 self::assertSame($input->original, $result->original);
40 self::assertSame($input->productions, $result->productions);
41 self::assertSame($input->terminals[2], $result->terminals[2]);
42 self::assertNotEmpty($input->terminals);
43 self::assertNotEmpty($result->terminals);
44 self::assertSame($input->terminals[array_key_last($input->terminals)], $result->terminals[array_key_last($result->terminals)]);
45 self::assertSame($result, $rule->rewrite($result));
46 }
47
48 /**
49 * @return iterable<array{TerminalSequence, list<string>}>
50 */
51 public static function providerArgumentLists(): iterable
52 {
53 foreach ([['expr', 'idj', true], ['expr', 'nm', false], ['ordinary', 'idj', false]] as [$scope, $name, $limited]) {
54 foreach ([1, 126, 127, 128, 130] as $count) {
55 $trace = new DerivationTrace($scope);
56 $trace->expand(0, new Production([new NonTerminal($name), new Terminal('LP'), new NonTerminal('exprlist'), new Terminal('RP'), new Terminal('TAIL')]), 0);
57 $trace->expand(0, new Production([new Terminal('NAME')]), 0);
58 $trace->expand(2, new Production([new NonTerminal('nexprlist')]), 0);
59 for ($remaining = $count; $remaining > 1; --$remaining) {
60 $trace->expand(2, new Production([new NonTerminal('nexprlist'), new Terminal('COMMA'), new NonTerminal('expr')]), 0);
61 }
62 $trace->expand(2, new Production([new NonTerminal('expr')]), 1);
63 $expected = ['NAME', 'LP'];
64 for ($index = 0; $index < $count; ++$index) {
65 $trace->expand(2 + 4 * $index, new Production([new NonTerminal('expr'), new Terminal('PLUS'), new Terminal('INTEGER')]), 0);
66 $trace->expand(2 + 4 * $index, new Production([new Terminal('ARG_' . $index)]), 0);
67 if (!$limited || $index < 127) {
68 if ($index !== 0) {
69 $expected[] = 'COMMA';
70 }
71 array_push($expected, 'ARG_' . $index, 'PLUS', 'INTEGER');
72 }
73 }
74 yield [$trace->terminals(), [...$expected, 'RP', 'TAIL']];
75 }
76 }
77 }
78
79 public function testRewritePreservesEmptyArgumentsAndStandaloneLists(): void
80 {
81 $trace = new DerivationTrace('expr');
82 $trace->expand(0, new Production([new NonTerminal('idj'), new Terminal('LP'), new NonTerminal('exprlist'), new Terminal('RP')]), 0);
83 $trace->expand(0, new Production([new Terminal('NAME')]), 0);
84 $trace->expand(2, new Production([]), 0);
85 $input = $trace->terminals();
86 self::assertSame($input, (new FunctionArgumentRule())->rewrite($input));
87 $standalone = new TerminalSequence([], [], [], [new ProductionOccurrence(0, null, 'exprlist', 0)]);
88 self::assertSame($standalone, (new FunctionArgumentRule())->rewrite($standalone));
89 }
90}
91