packages/lemon-parser/src/Parser.php
1<?php
2
3declare(strict_types=1);
4
5namespace LemonParser;
6
7use LemonParser\Ast\GrammarFile;
8use LemonParser\Preprocessor\Preprocessor;
9use LemonParser\Scanner\Scanner;
10use LemonParser\Syntax\GrammarReader;
11use LemonParser\Syntax\TokenStream;
12
13/**
14 * Reads a Lemon grammar file into a tree that keeps everything the file says.
15 *
16 * The file is read as Lemon reads it: `%ifdef` regions are settled with the
17 * names given, the text is split into tokens the way `lemon.c` splits it,
18 * and rules and declarations are accepted and rejected where Lemon accepts
19 * and rejects them.
20 *
21 * @visibility public
22 *
23 * @example Reading a grammar
24 * $file = (new \LemonParser\Parser())->parse("%token_prefix TK_\n%left PLUS MINUS.\nexpr(A) ::= expr(B) PLUS expr(C). { A = B + C; }\nexpr(A) ::= NUM(B). { A = B; }\n");
25 * [count($file->declarations()), count($file->rules()), $file->rules()[1]->items[0]->symbols[0]->name] // => [2, 2, 'NUM']
26 * @example Settling a conditional region
27 * $source = "%ifndef OMIT_WINDOW\nwindow ::= OVER.\n%endif\ncmd ::= SELECT.\n";
28 * $parser = new \LemonParser\Parser();
29 * [count($parser->parse($source)->rules()), count($parser->parse($source, ['OMIT_WINDOW'])->rules())] // => [2, 1]
30 * @example Rejecting what Lemon rejects
31 * try { (new \LemonParser\Parser())->parse("expr ::= expr ? expr.\n"); } catch (\LemonParser\SyntaxException $e) { $error = $e->getMessage(); }
32 * $error // => 'Illegal character on RHS of rule: "?". at 1:15'
33 */
34final class Parser
35{
36 /**
37 * @param Preprocessor $preprocessor Settles conditional regions
38 * @param Scanner $scanner Splits the text into tokens
39 * @param GrammarReader $reader Builds the tree
40 */
41 public function __construct(
42 private readonly Preprocessor $preprocessor = new Preprocessor(),
43 private readonly Scanner $scanner = new Scanner(),
44 private readonly GrammarReader $reader = new GrammarReader(),
45 ) {
46 }
47
48 /**
49 * Reads a grammar file.
50 *
51 * @param string $source The file's text
52 * @param list<string> $defines Names defined for `%ifdef`, as Lemon's `-D` option defines them
53 *
54 * @return GrammarFile The tree
55 *
56 * @throws SyntaxException Where Lemon reports an error
57 */
58 public function parse(string $source, array $defines = []): GrammarFile
59 {
60 $tokens = $this->scanner->scan($this->preprocessor->preprocess($source, $defines));
61
62 return $this->reader->read(new TokenStream($tokens));
63 }
64}
65