Compiler
This package is for development only:
composer require phplrt/compiler --dev
The compiler reads a grammar file - the tokens, the rules, the reducers - and gives you back a working parser. It is the friendly front end to everything the lexer builder and parser builder can do.
Reading A Grammar
1use Phplrt\Compiler\Compiler; 2use Phplrt\Source\File; 3use Phplrt\Source\Source; 4 5$parser = new Compiler() 6 ->load(new File(__DIR__ . '/grammar.pp3')) 7 ->getParser(); 8 9echo $parser->parse(new Source('2 + 2'));
load() reads the grammar (and everything it %includes), and getParser()
compiles it. That is the whole thing for a script or a prototype.
You can load several grammars into one compiler - they all end up in the same lexer and parser:
1$compiler = new Compiler(); 2$compiler->load(new File(__DIR__ . '/lexemes.pp3')); 3$compiler->load(new File(__DIR__ . '/expressions.pp3')); 4 5$parser = $compiler->getParser();
Generating Code
Reading a grammar takes real time, and the grammar does not change between requests. So: do it once, write the result to a file, and commit the file.
1new Compiler() 2 ->load(new File(__DIR__ . '/grammar.pp3')) 3 ->generate() 4 ->withNamespaceName('App\Language') 5 ->withClassName('LanguageParser') 6 ->save(__DIR__ . '/Parser.php');
Now production never sees the compiler at all:
$parser = new App\Language\LanguageParser();
Code Generation covers this in full.
Grammar Formats
The format is decided by the file extension:
| Extension | Format |
|---|---|
.pp |
The legacy Hoa format - no longer supported |
.pp2 |
The older format, described in Legacy Grammar |
.pp3 |
The current format, described in Grammar |
Write .pp3 for anything new. A .pp2 file keeps being read the way it always
was, so an existing grammar needs no attention.
A grammar that did not come from a file (a Source, a string) is read as the
newest format, since there is no extension to go by:
$compiler->load(new Source('%token T_DIGIT \d++ Num : <T_DIGIT> ;'));
Reading a .pp file gives you a clear error rather than a confusing one:
1error[UnsupportedFormatException]: Grammar files written in the "pp" format 2are not supported 3 --> /app/grammar.pp:1:1
Splitting A Grammar Up
Real grammars get long. %include pulls in another file, and the
declarations land exactly where to include is written:
1%include grammar/lexemes 2%include grammar/literals 3%include grammar/expressions 4 5%pragma root Expression
A few useful details:
- the path is relative to the file to include is written in;
- the extension may be omitted - every known format is tried in turn;
- a grammar reached from several places is read once, so a shared
lexemes.pp3can be included by every file that needs it.
If the file is missing, the error names both the include and the file that wanted it:
1error[GrammarNotFoundException]: grammar/missing: failed to open stream: 2No such file or directory 3 --> /app/grammar.pp3:1:1 4 | 51 | %include grammar/missing 6 | ^^^^^^^^^^^^^^^^^^^^^^^^
Errors inside an included grammar are reported the same way, with the chain of includes that led there.
Getting At The Pieces
The compiler is a thin layer over the two builders, and both are public:
1$compiler = new Compiler(); 2$compiler->load(new File(__DIR__ . '/grammar.pp3')); 3 4// Add a token the grammar file does not mention 5$compiler->lexer->addPattern('#[^\n]*+') 6 ->hide(); 7 8// Add a compiler pass of your own 9$compiler->parser->addCompilerPass(new MyValidationPass()); 10 11$parser = $compiler->getParser();
build() gives you the compiled description instead of a ready parser -
which is what the generator works from:
1$result = $compiler->build(); 2 3$result->lexer; // LexerBuilderResult 4$result->parser; // ParserBuilderResult
Errors
Everything that can go wrong points at the exact spot in the grammar:
1error[UnsupportedPragmaException]: Unrecognized pragma "unknown" 2 --> /app/grammar.pp3:2:1 3 | 41 | %token T_A a 52 | %pragma unknown value 6 | ^^^^^^^^^^^^^^^^^^^^^ 73 | A : <T_A> ;
The kinds you are likely to meet:
| Exception | Cause |
|---|---|
UnexpectedTokenException |
The grammar file itself is malformed |
GrammarNotFoundException |
%include points at a file that is not there |
UnsupportedPragmaException |
An unknown %pragma |
UnsupportedFormatException |
A legacy .pp file, or an unknown extension |
UnsupportedTransitionException |
A token switching between two named states |
CompilationFailedException |
The grammar is well-formed but wrong: left recursion, an undefined rule, a broken regex |
Whats Next?
-
Grammar Syntax - everything a
.pp3file can say. -
Legacy Grammar Syntax - everything a
.pp2file can say. - PHP in a Grammar - reducers and the variables they get.
- Code Generation - namespaces, class names, and what the output looks like.