packages/sql-faker/src/Compiler/Bison/Lexer/BisonScannerChain.php
1<?php
2
3declare(strict_types=1);
4
5namespace SqlFaker\Compiler\Bison\Lexer;
6
7/**
8 * Chooses which scanner owns the character under the cursor.
9 *
10 * Holding the order in one place is what makes the lexer's dispatch a lookup
11 * rather than a decision: no scanner needs to know which others exist, and the
12 * grammar language can gain a lexeme by adding a scanner to the chain.
13 *
14 * @visibility root
15 */
16final class BisonScannerChain
17{
18 /**
19 * @param list<BisonScanner> $scanners Scanners in the order they are consulted
20 */
21 public function __construct(private readonly array $scanners)
22 {
23 }
24
25 /**
26 * Builds the chain that reads GNU Bison and Yacc grammar files.
27 *
28 * The claimed characters do not overlap, so the order is documentation
29 * rather than precedence.
30 *
31 * @return self A chain covering every lexeme of the grammar language
32 */
33 public static function forBisonGrammar(): self
34 {
35 return new self([
36 new DirectiveScanner(),
37 new ActionScanner(),
38 new TypeTagScanner(),
39 new QuotedLiteralScanner(),
40 new NumberScanner(),
41 new IdentifierScanner(),
42 new PunctuationScanner(),
43 ]);
44 }
45
46 /**
47 * Finds the scanner that recognises a lexeme starting at the character.
48 *
49 * @param string $character A single character; never the empty string
50 *
51 * @return BisonScanner|null The first scanner that claims it, or null when none does
52 */
53 public function scannerFor(string $character): ?BisonScanner
54 {
55 foreach ($this->scanners as $scanner) {
56 if ($scanner->handles($character)) {
57 return $scanner;
58 }
59 }
60
61 return null;
62 }
63}
64