.gitignore 0000644 00000000044 14476011371 0006536 0 ustar 00 vendor/
composer.phar
composer.lock
.travis.yml 0000644 00000000216 14476011371 0006660 0 ustar 00 language: php
php:
- 5.3
- 5.4
before_script:
- wget http://getcomposer.org/composer.phar
- php composer.phar dump-autoload
CHANGELOG.md 0000644 00000000243 14476011371 0006360 0 ustar 00 Changelog
=========
1.0.1 (2013-01-29)
------------------
- 2b40f94: Fixed an invalid format in the CLI
1.0.0 (2013-01-15)
------------------
- First release.
README.md 0000644 00000001227 14476011371 0006031 0 ustar 00 # Welcome to Dissect!
- [master](https://github.com/jakubledl/dissect/tree/master) [](https://travis-ci.org/jakubledl/dissect) - this branch always contains the last stable version.
- [develop](https://github.com/jakubledl/dissect) [](https://travis-ci.org/jakubledl/dissect) - the unstable development branch.
Dissect is a set of tools for lexical and syntactical analysis written
in pure PHP.
Documentation?
--------------
[Here][docs].
[docs]: https://github.com/jakubledl/dissect/blob/master/docs/index.md
TODO.md 0000644 00000000712 14476011371 0005637 0 ustar 00 Goals
=====
1.1
---
- Optional operator precedence support (Ã la *yacc*, *bison*).
1.0
---
- Compute reduction lookahead by the channel algorithm from *yacc*
instead of the current LALR-by-SLR algorithm - ✔
- Change the analyzer API to allow for grammar debugging
(provide access to resolved conflicts, dumping the automaton to DOT ...) - ✔
- Provide classes for dumping the parse table to PHP (both the dev & prod version) - ✔
UNLICENSE 0000644 00000002256 14476011371 0006025 0 ustar 00 This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author
of this software dedicates any and all copyright interest in the
software to the public domain. I make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. I intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to
bin/dissect 0000755 00000000074 14476011371 0006705 0 ustar 00 #!/usr/bin/env php
run();
composer.json 0000644 00000001223 14476011371 0007270 0 ustar 00 {
"name": "jakubledl/dissect",
"description": "Lexing and parsing in pure PHP",
"keywords": ["lexing", "parsing", "ast", "parser"],
"homepage": "https://github.com/jakubledl/dissect",
"license": "unlicense",
"authors": [
{
"name": "Jakub Lédl",
"email": "jakubledl@gmail.com"
}
],
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/console": "~2.1"
},
"suggest": {
"symfony/console": "for the command-line tool"
},
"bin": ["bin/dissect.php", "bin/dissect"],
"autoload": {
"psr-0": { "Dissect": ["src/"] }
}
}
docs/ast.md 0000644 00000007447 14476011371 0006625 0 ustar 00 Building an AST
===============
Often, when parsing a language that's more complex than
[mathematical expressions][prev], you will want to represent
the input as an *abstract syntax tree*, or AST (for a real-life
example, see [Twig][twig-ast] or [Gherkin][gherkin-ast]).
Getting the AST of the input with Dissect is nothing special; the
callbacks in your grammar can return anything, so they might as well
return AST nodes. Dissect however helps you by providing a simple base
class for the different node types: `Dissect\Node\CommonNode`.
Let's say we want to create an AST for the mathematical expressions from
the previous chapter. Since the input can consist of binary operations
and integers, let's create a subclass for each case:
```php
use Dissect\Node\CommonNode;
use Dissect\Node\Node;
class BinaryExpressionNode extends CommonNode
{
const PLUS = 1;
const TIMES = 2;
const POWER = 3;
public function __construct(Node $left, $op, Node $right)
{
parent::__construct(['operator' => $op], [
'left' => $left,
'right' => $right,
]);
}
public function getLeft()
{
return $this->getNode('left');
}
public function getRight()
{
return $this->getNode('right');
}
public function getOperator()
{
return $this->getAttribute('operator');
}
}
class IntNode extends CommonNode
{
public function __construct($value)
{
parent::__construct(['value' => $value]);
}
public function getValue()
{
return $this->getAttribute('value');
}
}
```
The original constructor has two parameters, an array of child nodes and
an array of node attributes. `Dissect\Node\Node` is an interface
describing common operations for an AST node.
We can now easily modify the original grammar to build the AST:
```php
$this('Additive')
->is('Additive', '+', 'Multiplicative')
->call(function ($l, $_, $r) {
return new BinaryExpressionNode($l, BinaryExpressionNode::PLUS, $r);
})
->is('Multiplicative');
$this('Multiplicative')
->is('Multiplicative', '*', 'Power')
->call(function ($l, $_, $r) {
return new BinaryExpressionNode($l, BinaryExpressionNode::TIMES, $r);
})
->is('Power');
$this('Power')
->is('Primary', '**', 'Power')
->call(function ($l, $_, $r) {
return new BinaryExpressionNode($l, BinaryExpressionNode::POWER, $r);
})
->is('Primary');
$this('Primary')
->is('(', 'Additive', ')')
->call(function ($_, $e, $_) {
return $e;
})
->is('INT')
->call(function ($int) {
return new IntNode((int)$int->getValue());
});
```
Traversing the AST
------------------
When we have the AST of our input, we want to interpret it somehow.
The most common way to do this is to create a *node visitor* (sometimes
called a *tree walker*). A trivial node visitor for our example could be
the following recursive function:
```php
function visit(Node $node)
{
if ($node instanceof BinaryExpressionNode) {
switch ($node->getOperator()) {
case BinaryExpressionNode::PLUS:
return visit($node->getLeft()) + visit($node->getRight());
case BinaryExpressionNode::TIMES:
return visit($node->getLeft()) * visit($node->getRight());
case BinaryExpressionNode::POWER:
return pow(visit($node->getLeft()), visit($node->getRight());
}
} elseif ($node instanceof IntNode) {
return $node->getValue();
} else {
throw new \Exception("Unknown node type.");
}
}
echo visit($parser->parse(...));
```
[prev]: parsing.md#example-parsing-mathematical-expressions
[twig-ast]: https://github.com/fabpot/Twig/tree/master/lib/Twig/Node
[gherkin-ast]: https://github.com/Behat/Gherkin/tree/master/src/Behat/Gherkin/Node
docs/cli.md 0000644 00000005620 14476011371 0006574 0 ustar 00 The command-line interface
==========================
Dissect provides you with a command-line tool for processing and
debugging your grammars. This chapted describes the tool and its
options.
Running the tool
----------------
Let's assume that the executable is located in a folder called `bin`.
The most basic way to invoke it is
$ bin/dissect
This will analyze the given grammar and, if successful, save the parse
table in a file `parse_table.php` in the same folder where you've
defined your grammar. You can use `/` instead of `\` as the namespace
separator or enclose the class name in quotes.
To change the directory in which the parse table will be saved, use the
`--output-dir` (or `-o`) option:
$ bin/dissect --output-dir=../dir
Dumping the parse table in the debug format
-------------------------------------------
By default, the parse table will be saved as a single line of PHP code,
with minimal whitespace. If you want to inspect the generated table
manually, you can use the `--debug` (or `-d`) option:
$ bin/dissect --debug
The parse table will then be written in a human-readable way and with
comments explaining the steps of the parser.
Dumping the handle-finding automaton
------------------------------------
If you have an understanding of the LR parsing process, being able to
inspect the LR automaton visually could be an aid in resolving potential
grammar conflicts. In order to dump the automaton as a Graphviz graph,
use the `--dfa` (or `-D`) option:
$ bin/dissect --dfa
This will create a file called `automaton.dot` in the output directory.
You can then run something like
dot -Tpng automaton.dot > automaton.png
to render it as a PNG image.
Of course, for more complex grammars, the automaton will quickly become rather large
and unwieldy. You can then use the `--state` (or `-s`) option to dump
only the specified state:
$ bin/dissect --dfa --state=5
As an example, let's say we use the following grammar:
```php
class PalindromeGrammar extends Grammar
{
public function __construct()
{
$this('S')
->is('a', 'S', 'a')
->is('b', 'S', 'b')
->is(/* empty */);
$this->start('S');
}
}
```
When running the command-line tool, we'll notice a list of resolved
conflicts in the output:
Resolved a shift/reduce conflict in state 2 on lookahead a
Resolved a shift/reduce conflict in state 3 on lookahead b
If we wanted to examine the conflict in state 3, we could run
$ bin/dissect PalindromeGrammar --dfa --state=3
and then
$ dot -Tpng state_3.dot > state_3.png
The result will be the following image:

in which we can clearly see how the conflict arose: the state #3 calls
both for a shift and a reduction by the rule `S -> ` on
lookahead `b`.
docs/common.md 0000644 00000005141 14476011371 0007313 0 ustar 00 Describing common syntactic structures
======================================
This chapter of the documentation shows how to implement common
grammar patterns like lists & repetitions in a way that's most efficient
for a LALR(1) parser like Dissect.
List of 1 or more `Foo`s
------------------------
```php
$this('Foo+')
->is('Foo+', 'Foo')
->call(function ($list, $foo) {
$list[] = $foo;
return $list;
})
->is('Foo')
->call(function ($foo) {
return [$foo];
});
```
With some practice, it's very easy to see how this works: when the
parser recognizes the first `Foo`, it reduces it to a single-item array
and for each following `Foo`, it just pushes it onto the array.
Note that `Foo+` is just a rule name, it could be equally well called
`Foos`, `ListOfFoo` or anything else you feel like.
List of 0 or more `Foo`s
------------------------
```php
$this('Foo*')
->is('Foo*', 'Foo')
->call(function ($list, $foo) {
$list[] = $foo;
return $list;
})
->is(/* empty */)
->call(function () {
return [];
});
```
This works pretty much the same like the previous example, the only
difference being that we allow `Foo*` to match nothing.
A comma separated list
----------------------
The first example of this chapter is trivial to modify to include
commas between the `Foo`s. Just change the second line to:
```php
$this('Foo+')
->is('Foo+', ',', 'Foo')
...
```
The second example, however, cannot be modified so easily. We cannot
just put a comma in the first alternative:
```php
$this('Foo*')
->is('Foo*', ',', 'Foo')
...
```
since that would allow the list to start with a comma:
, Foo , Foo , Foo
Instead, we say that a "list of zero or more `Foo`s
separated by commas" is actually "a list of one or more `Foo`s separated
by commas or nothing at all". So our rule now becomes:
```php
$this('Foo*')
->is('Foo+')
->is(/* empty */)
->call(function () {
return [];
});
$this('Foo+')
->is('Foo+', ',', 'Foo')
...
```
Expressions
-----------
A grammar for very basic mathematical expressions is described in the
[chapter on parsing][arith]. It would require extensive modifications to allow
for other operators, function calls, unary operators, ternary
operator(s), but there's a lot of grammars for practical programming
languages on the internet that you can take inspiration from.
For starters, take a look at [this grammar][php-grammar] for PHP itself.
[php-grammar]: https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y
[arith]: parsing.md#example-parsing-mathematical-expressions
docs/index.md 0000644 00000003361 14476011371 0007134 0 ustar 00 Welcome to Dissect!
===================
Dissect is a set of tools for lexical and syntactical analysis
written in pure PHP.
This guide assumes that you're already familiar with basic concepts
of parsing. Explaining them is beyond the scope of this simple guide,
so if you're not, see, for example, [this article][parsing].
This page serves as an index for individual documentation pages.
1. [Lexical analysis with Dissect](lexing.md)
1. [SimpleLexer](lexing.md#simplelexer)
2. [StatefulLexer](lexing.md#statefullexer)
3. [Improving lexer performance](lexing.md#improving-lexer-performance)
2. [Parsing with Dissect](parsing.md)
1. [Why an LALR(1) parser?](parsing.md#why-an-lalr1-parser)
2. [Writing a grammar](parsing.md#writing-a-grammar)
3. [Example: Parsing mathematical expressions](parsing.md#example-parsing-mathematical-expressions)
4. [Invalid input](parsing.md#invalid-input)
5. [Precomputing the parse table](parsing.md#precomputing-the-parse-table)
6. [Resolving conflicts](parsing.md#resolving-conflicts)
3. [Building an AST](ast.md)
1. [Travesing the AST](ast.md#traversing-the-ast)
4. [Describing common syntactic structures](common.md)
1. [List of 1 or more `Foo`s](common.md#list-of-1-or-more-foos)
2. [List of 0 or more `Foo`s](common.md#list-of-0-or-more-foos)
3. [A comma separated list](common.md#a-comma-separated-list)
4. [Expressions](common.md#expressions)
5. [The command-line interface](cli.md)
1. [Running the tool](cli.md#running-the-tool)
2. [Dumping the parse table in the debug format](cli.md#dumping-the-parse-table-in-the-debug-format)
3. [Dumping the handle-finding automaton](cli.md#dumping-the-handle-finding-automaton)
[parsing]: http://en.wikipedia.org/wiki/Parsing
docs/lexing.md 0000644 00000012766 14476011371 0007324 0 ustar 00 Lexical analysis with Dissect
=============================
There are two classes for lexical analysis in Dissect, both under the
namespace `Dissect\Lexer`: `SimpleLexer` and `StatefulLexer`.
SimpleLexer
-----------
`SimpleLexer` simply accepts some token definitions and applies them on
the input. Let's create a subclass for this chapter:
```php
use Dissect\Lexer\SimpleLexer;
class ArithLexer extends SimpleLexer
{
public function __construct()
{
// token definitions
}
}
```
### Defining tokens
There are 3 ways to define a token. The simplest one looks like this:
```php
$this->token('+');
```
This definition will simply match a plus symbol, using `+` both as the
name and value of the token. You can use 2 arguments:
```php
$this->token('CLASS', 'class');
```
if you want the token name (first argument) to differ from what will actually be
recognized (second argument).
The final way defines a token by a regular expression:
```php
$this->regex('INT', '/^[1-9][0-9]*/');
```
Let's now define some tokens we will use in the next chapter:
```php
class ArithLexer extends SimpleLexer
{
public function __construct()
{
$this->regex('INT', '/^[1-9][0-9]*/');
$this->token('(');
$this->token(')');
$this->token('+');
$this->token('*');
$this->token('**');
}
}
```
> **Tip**: You can also chain the method calls using a fluent interface.
### Skipping tokens
Some tokens have to be recognized, but we don't want them cluttering the
output. The best example are probably whitespace tokens: the lexer has
to recognize them, but they carry no meaning or value, so we can tell
the lexer to `skip` them:
```php
class ArithLexer extends SimpleLexer
{
public function __construct()
{
$this->regex('INT', '/[1-9][0-9]*/');
$this->token('(');
$this->token(')');
$this->token('+');
$this->token('*');
$this->token('**');
$this->regex('WSP', "/^[ \r\n\t]+/");
$this->skip('WSP');
}
}
```
> You can pass any number of token names to the `skip` method.
### Lexing
Now that we've defined our tokens, we can simply call:
```php
$lexer = new ArithLexer();
$stream = $lexer->lex($input);
```
The return value is an object implementing the
`Dissect\Lexer\TokenStream\TokenStream` interface. The interface defines
several methods you can use to inspect and move through the token
stream. See [TokenStream.php][tokenstream] for all the methods you can
use.
> If you `count` the token stream, you may be surprised to find out that
> for input like `5 + 3`, it actually contains 4 tokens. That's because,
> as the last step of lexing, a special token called `$eof` is appended
> to mark the end of input. This is crucial to the parsing process, so
> please, never define a token called `$eof` yourself. It could lead to
> some pretty strange errors. Another forbidden token names are `$start`
> and `$epsilon`.
StatefulLexer
-------------
`SimpleLexer` should work fine for general use cases. However, let's
imagine we're lexing a very simple templating language:
Outer content, {{ variable_name }}, other outer content
`SimpleLexer` falls short here, because the outer content can be pretty
much anything, while the content inside the tags has to be strictly
intepreted. Furthermore, if we were to work with this template, we'd
want to skip the whitespace inside tags, but keep it in the outer
content.
That's where `StatefulLexer` comes in; during lexing, it maintains a
stack of states with the top one being the current one, and for each
token, you can define the action the lexer should take after recognizing
it. Let's see an example for our templating language:
```php
use Dissect\Lexer\StatefulLexer;
class TemplateLexer extends StatefulLexer
{
public function __construct()
{
$lexer->state('outside')
->regex('CONTENT', '/^[^"{{"]*/')
->token('{{')->action('tag');
$lexer->state('tag')
->regex('WSP', "/^[ \r\n\t]+/")
->regex('VAR', '/^[a-zA-Z_]+/')
->token('}}')->action(StatefulLexer::POP_STATE)
->skip('WSP');
$lexer->start('outside');
}
}
```
Please note that before defining any tokens, we have to define a state.
For the tokens that cause the state transition, we call `action` to
specify what should the lexer do. The action can be either a string, in
which case the lexer goes to the state specified by the string, or
`StatefulLexer::POP_STATE`, which causes the lexer to pop the current
state of the stack, essentialy going back to previous state.
Finally, we tell the lexer in which state to start by calling `start`.
Improving lexer performance
---------------------------
There's one important trick to improve the performance of your lexers.
The documentation uses it implicitly, but it requires an explicit mention:
When defining tokens using regular expressions, *always* anchor the
regex at the beginning using `^` like this:
```php
$this->regex('INT', '/^[1-9][0-9]*/');
```
This little optimization will lead to substantial performance gains on
any but the shortest input strings, since without anchoring, the PCRE
engine would always look for matches throughout the entire remaining
input string, which would be incredibly wasteful for long inputs.
Continue
--------
Now that we've demonstrated how to perform lexical analysis with
Dissect, we can move onto syntactical analysis, commonly known as
[parsing][parsing].
[tokenstream]: ../src/Dissect/Lexer/TokenStream/TokenStream.php
[parsing]: parsing.md
docs/parsing.md 0000644 00000024551 14476011371 0007474 0 ustar 00 Parsing with Dissect
====================
Why an LALR(1) parser?
----------------------
Parsing is a task that's needed more often than one would think;
for examples in some famous PHP projects, see [this parser][twigparser]
from [Twig][twig] and [these][annotationsparser] [two][dqlparser] from
[Doctrine][doctrine]. Chances are you've written one; if you did, it was
most likely a [recursive descent parser][rdparser], just like the
examples above. Now, such parsers have several disadvantages: first,
they obviously have to be manually written. Second, they're *recursive*,
which means one thing: nest the input deep enough (like an
annotation, which has another annotation as a parameter, that annotation
has another annotation as a parameter ...) and your PHP process blows up
because of stack overflow (to be fair, you'd have to nest pretty deep).
And third, such parsers belong to a class of parsers known as
[LL(k)][llk], which means they're generally not as powerful as [LR(k)][lrk]
parsers. For instance, they cannot handle left-recursive rules
(rules like `A -> A ...`), which are probably the only sane way of
expressing left-associative binary operators (like addition, for
example).
But let's get to actually parsing something.
Writing a grammar
-----------------
A grammar is represented by a subclass of `Dissect\Parser\Grammar`.
```php
use Dissect\Parser\Grammar;
class ArithGrammar extends Grammar
{
public function __construct()
{
// rule definitions
}
}
```
First, you tell Dissect what rule are you describing. Let's say we want
to describe a rule for a `Sum`:
```php
$this('Sum')
```
and then you specify what the rule actually `is`:
```php
$this('Sum')
->is('int', '+', 'int');
```
A rule can of course have many alternatives:
```php
$this('Sum')
->is('int', '+', 'int')
->is('string', '+', 'string');
```
and you will probably want to specify how to evalute the rule:
```php
$this('Sum')
->is('int', '+', 'int')
->call(function ($l, $_, $r) {
return $l + $r;
})
->is('string', '+', 'string')
->call(function ($l, $_, $r) {
return $l . $r;
});
```
> The number of arguments to the callback function is always equal
> to the length of the rule to which it belongs.
### Empty rules
A grammar can (and many times will) contain empty rules, that is, rules that
can match 0 tokens of the input. This is useful when, for example,
describing a list of function arguments, which can be either empty or a list of
values separated by commas.
An empty rule is defined simply by calling `is` with 0 arguments:
```php
$this('Empty')
->is();
```
If you find this notation unclear, you can explicitly mark empty rules
with a comment:
```php
$this('Empty')
->is(/* empty */);
```
> **Beware:** When you don't specify a callback for a rule, Dissect
> will default to returing the leftmost (first) component of the rule. You
> are, however, required to specify a callback for an empty rule, since
> in a rule with zero components, there is obviously no leftmost one.
Example: Parsing mathematical expressions
-----------------------------------------
In the chapter on lexing, we've created a lexer we will now use to
process our expressions:
```php
class ArithLexer extends SimpleLexer
{
public function __construct()
{
$this->regex('INT', '/^[1-9][0-9]*/');
$this->token('(');
$this->token(')');
$this->token('+');
$this->token('*');
$this->token('**');
$this->regex('WSP', "/^[ \r\n\t]+/");
$this->skip('WSP');
}
}
$lexer = new ArithLexer();
```
There's more to specifying mathematical expression than would seem,
because there are two concepts to consider:
1. Operator precedence
2. Operator associativity
The operator problem is usually solved in these steps:
1. Create a hierarchy of your operators.
2. Start creating rules from the lowest-precedence one to the
highest-precedence one, each "level" will reference rules
in the one above it.
3. The highest operator will reference an atomic, nondividable
expression, which in our case is an `INT` or a parenthesised
expression.
The lowest-precedence operator in our grammar is `+`, so we will start
with two rules for `Additive`:
```php
$this('Additive')
->is('Additive', '+', 'Multiplicative')
->call(function ($l, $_, $r) {
return $l + $r;
})
->is('Multiplicative');
```
Here we say "an additive expression is an additive expression plus a
multiplicative expression, or simply a multiplicative expression.
Note that we've taken care of associativity too: the first rule for
`Additive` is left-recursive, which means that an input like this:
2 + 7 + 3
will be interpreted as
(2 + 7) + 3
which is exactly what we want to achieve.
Let's take care of `Multiplicative` the same way:
```php
$this('Multiplicative')
->is('Multiplicative', '*', 'Power')
->call(function ($l, $_, $r) {
return $l * $r;
})
->is('Power');
```
Again, we'll do the same for `Power`, but notice that we've made it
right-recursive, since when we say
2 ** 3 ** 4
we want it to mean
2 ** (3 **Â 4)
```php
$this('Power')
->is('Primary', '**', 'Power')
->call(function ($l, $_, $r) {
return pow($left, $right);
})
->is('Primary');
```
We've reached the highest-precedence operator, so now we have to define
what a `Primary` expression is:
```php
$this('Primary')
->is('(', 'Additive', ')')
->call(function ($_, $e, $_) {
return $e;
})
->is('INT')
->call(function ($int) {
return (int)$int->getValue();
});
```
Note that the callback for the last rule recieves a token, that is, a
`Dissect\Lexer\Token` object, so we have to "unwrap" the value from it.
Now we just specify a start rule:
```php
$this->start('Additive');
```
and parse away:
```php
use Dissect\Parser\LALR1\Parser;
$parser = new Parser(new ArithGrammar());
$stream = $lexer->lex('6 ** (1 + 1) ** 2 * (5 + 4)');
echo $parser->parse($stream);
// => 11664
```
### Describing common syntactic structures
To see how to describe commonly used syntactic structures such as
repetitions and lists, see the [dedicated documentation section][common].
Invalid input
-------------
When the parser encounters a syntactical error, it stops dead and
throws a `Dissect\Parser\Exception\UnexpectedTokenException`.
The exception gives you programmatic access to information about the
problem: `getToken()` returns a `Dissect\Lexer\Token` representing the
invalid token and `getExpected()` returns an array of token types the parser
expected to encounter.
Precomputing the parse table
----------------------------
The parser needs a *parse table* to decide what to do based on given
input. That parse table is created from the grammar and, if we give the
parser only the grammar, needs to be computed every time we instantiate
the parser.
Grammar analysis is costly; if you need the speed, a far better choice
would be to precompute the table beforehand (perhaps as a part of your
build process) like this:
```php
use Dissect\Parser\LALR1\Analysis\Analyzer;
$analyzer = new Analyzer();
$parseTable = $analyzer->analyze($grammar)->getParseTable();
```
Now that we've got the parse table, we can dump it to a string which
we then save to a file. To do this, we can use either
`Dissect\Parser\LALR1\Dumper\ProductionTableDumper`:
```php
$dumper = new ProductionTableDumper();
$php = $dumper->dump($parseTable);
```
which produces very compact, whitespace-free and absolutely unreadable
code, or `Dissect\Parser\LALR1\Dumper\DebugTableDumper`:
```php
$dumper = new DebugTableDumper($grammar);
$php = $dumper->dump($parseTable);
```
which produces indented, readable representation with comments
explaining each step the parser takes when processing the input.
### Using the dumped parse table
To use the dumped parse table, just write
```php
$parser = new Parser($grammar, require $parseTableFile);
```
You still need to pass the grammar, since it contains the callbacks
used to evalute the input.
> If you intend to use Dissect more like a traditional parser generator,
> you don't actually need to do any of this, of course. Dissect provides a
> command-line interface you can use to process and debug your grammars.
> It's described in its own [documentation section][cli].
Resolving conflicts
-------------------
*Caution, this is advanced stuff. You probably won't ever need to worry
about this.*
LALR(1) is generally a very poweful parsing algorithm. However, there
are practical grammars that are, unfortunately, almost-but-not-quite
LALR(1). When running an LALR(1) analyzer on such grammars, one sees
that they contain 2 types of conflicts:
- **Shift/Reduce conflicts** - the parser doesn't know whether to shift
another token or reduce what's on the stack.
- **Reduce/Reduce conflicts** - the parser can reduce by multiple
grammar rules.
There are 3 commonly used ways of resolving such conflicts and Dissect allows you to
combine them any way you want:
1. On a shift/reduce conflict, always shift. This is represented by
the constant `Grammar::SHIFT` and is so common that Dissect enables
it by default.
2. On a reduce/reduce conflict, reduce using the longer rule.
Represented by `Grammar::LONGER_REDUCE`. Both this and the previous
way represent the same philosophy: take the largest bite possible.
This is usually what the user intended to express.
3. On a reduce/reduce conflict, reduce using the rule that was
declared earlier in the grammar. Represented by
`Grammar::EARLIER_REDUCE`.
To specify precisely how should Dissect resolve parse table conflicts,
call `resolve` on your grammar:
```php
$this->resolve(Grammar::SHIFT | Grammar::LONGER_REDUCE);
```
There are two other constants: `Grammar::NONE` that forbids any
conflicts in the grammar and `Grammar::ALL`, which is a combination
of all the 3 above methods defined simply for convenience.
[twigparser]: https://github.com/fabpot/Twig/blob/master/lib/Twig/Parser.php
[twig]: https://github.com/fabpot/Twig
[annotationsparser]: https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Annotations/DocParser.php
[dqlparser]: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/Query/Parser.php
[doctrine]: https://github.com/doctrine
[rdparser]: http://en.wikipedia.org/wiki/Recursive_descent_parser
[llk]: http://en.wikipedia.org/wiki/LL_parser
[lrk]: http://en.wikipedia.org/wiki/LR_parser
[cli]: cli.md
[common]: common.md
docs/state_3.png 0000644 00000036340 14476011371 0007556 0 ustar 00 ‰PNG
IHDR K ñ Íón bKGD ÿ ÿ ÿ ½§“ IDATxœí�w\S×ÿÿOv3B@ör ™"Se¨ÕBq€ -Ѝ8¾ŠýXë¨
®jëW+uUë*à(‚ZÅ�¢"•©€° )›��ß÷ûÉ!„Œ›ÜÎórsîû¼Or_÷Ü{Æû�ãr¹ �((x¬€@ R*Qd Â!E*Qd Â!E*Qd†£Âß¾}»gÏkkk¬�@¤n·S©Ô®®. À0l;d¸1 Àáp *2ŽOéÈðaX+üÎ�;ªªª›7oÆÚD*k…WTT<~üØÆÆf÷îÝIIIX»� ϰ~g³ÙáÒ¥K“'O¾ÿ>Ö~A (3¬Ž´½²²ÒÀÀ€Á`ÔÖÖbí‚2Pá »»›L&S(”ÎÎN¬ý‚@PfX¿‡#´¶¶ ÌḬ̀vA¨pðüùs @PPÖŽ@ è3LŸÒ‰D"‡ÃIIIqppøôéSVV–²²2Ö~A (3Lûð„„„‰'|ñÅcÇŽ½ÿ>”7D!¦}82L¦}82L€
‡@¨pD‘!bí 6477çææ644°X,sss"q˜~fx]ÓuuuqqqgÏžÍÍÍíéééý�NŸ>}z@@ÀìÙ³ñxøhQ†Ë¥ÜÖÖ¶eË33³={ö¸»»_¾|¹¼¼¼±±±±±±´´ôÉ“'7nüøñã¼yólmm¯]»†µ¿Jp‡™™™æææ#FŒØ¿GG‡€’yyysæÌÁáp‹-jkk“™‡ˆ”PüùðÄÄÄÐÐPOOÏÓ§Oëêê
sʵk×ÂÂÂôôônÞ¼©¯¯/m!é¡àOé.\^¾|ùõë×…”7 ÀÏÏïÕ«W\.×ÓÓ³²²RªB RE‘ûð[·nùùù}÷Ýw{÷îãôšš///‡“••¥¦¦†º{ˆPX…WVVÚÚÚNŸ>=>>Ù
.UUUvvvÞÞÞçÎ�C×=D6(¦Â¹\î´iÓ*++Ÿ?.á–’Û·oϘ1ãôéÓ.DË=Df(¦Âccc/^üôéSGGGÉ]»6..®¨¨HSSSrkˆ,Q@…777�5*$$$&&ƒ---³fÍ:rä*!™¡€cé{÷îe³ÙÑÑÑhTUUýå—_Ž?^XXˆ–MD6(Zþï¿ÿlݺuýúõ(šår¹¶¶¶¶¶¶±±±¼ƒÝÝÝ$ ÅZ ÔQ´>üÌ™3x<~åÊ•èšÅáp?þøã¹sç²³³/^¼¸dÉ]]]‹¶¶6t+‚@ÐE¡vžp¹Ü'NÌŸ?_EEE³ÝÝÝOž<ÉÉÉQVVž0a—Ë% €ü�È-
¥ð{÷î½yóÝüD‹-JHHèèèà©üWØAUUź ÔQ¨÷ðàààÊÊÊŒŒ´vwwkjj¶¶¶òý–ètzcc#ZuA Ò@qÞÃ;;;SRRÂÂÂP´I"‘bccº jhh X"
Gáiii~~~èš�={vHHßð/pDþQ…§¤¤8::jkk£nùèÑ£ƒ@ ô9Î`0P¯AQ8—˽víÚW_}%
ãêêêñññ}¢>¨pˆü£
ÏÍÍýøñã—_~)%ûÞÞÞ«Vêý¬N ètº”ªƒ@ÐBAž‘‘A§ÓQÙg2{÷îí�‡ÃA…CäQxVVÖ„ ¤#•J¥ž?ž7®ÎårGŒ!½ê TP�/ÏŸ?Ÿ;wî Ū««kkk9NKK›ÍFŽÓh4
…¢¤¤4räH===%%%¾§ÛÛÛGEEíØ±ƒÃá°Ùl¨pˆü£
onn...ž0aBïƒïß¿ÏËË+))))))..f2™555]]]ÂTSSÓ××7773f̘1cF�mgg‡<“oܸñÒ¥KoÞ¼éîî†OéùGÖ´Ý¿ßÓÓ³¸¸¸¨¨(###;;;;;»©©‰L&›™™YXXŒ3ÆÄÄDGG‡Á`hiiiii‰DÞΰ¶¶6‹ÕÞÞ^___SSSWWW[[[ü_*** fffŽŽŽŽŽŽ£G� éèèxúôéĉ1m:2C¾ÏÍÍ={ö,ƒÁ°²²êîî5j”‹‹Ëœ9s\]]¿øâ!)+++++Óét¾±“›››Ÿ={öäÉ“gÏžíÙ³çÓ§OÊÊÊJJJæææZZZh· A�¡Ú‡¿~ý:111))©¸¸ØÀÀÀÛÛÛËËËÛÛ[ÚáÍ{zz^½zuïÞ½»wïfdd°X¬)S¦Ì™3N�Cä�!¦ðššš?ÿüóôéÓÅÅÅcÇŽ
´²²ÂÄ™®®®Û·o'&&&''·´´xyyEDDøûû“ÉdLü�@ø Ó+ðâÅ‹o¾ù†J¥ªªª.^¼8##£§§k§þ�–––ØØØ)S¦àp8ƒ;vÔÕÕaíÂår¹ò®ðžžžëׯ{zz &L˜‹ìå”OÞ¿¿uëVƒA£ÑV®\YRR‚µG�áŽ\+üÖ[vvv8nÆŒéééX»#,íííG�577ÇãñaaaåååX{¾È©ÂŸ?îíí
ðóó{ýú5ÖÍ>w¹9•J]·n]CCÖA†#r·jµ¼¼ÜÏÏo„ #FŒÈËËKNN¶±±ÁÚ)q !!!ÅÅÅqqq—/_622Ú¶m‹ÅÂÚ/È0ë[Ìÿ§»»ûÀ�êêꦦ¦W¯^ÅÚ4immÝ´i™L¶³³{ôèÖî@†ò¢ðׯ_ÛÚÚR(”[·¶··cíŽTxûöíÔ©Sq8ÜòåË›››±v2,À^á,kûöí$ÉÃ㨨kw¤Îùóçµ´´LLLîÞ½‹µ/Åc…ÛÛÛ+++}:fÌggçÌÌL¬Ý�(2x×ß°a�@8qâ„êR ÚÚÚüüüTUUáúVˆäH]áëׯ'ýõ—´+R$º»»¡È!’#E…÷ôô,[¶ŒD"]ºtIzµ(*l6ûÛo¿¥ÑhiiiXûÂHQáÛ¶mÃãñqqqÒ«B±a±XþþþªªªÙÙÙXûªHk>üäÉ“GŽY±b…4ìX,ÖÌ™3
233‡çÊ?ˆ„HE቉‰ÁÁÁ;vìØ¼y3êÆ‡íííS§NªªÊÌÌÔÕÕÅÚÈ}…¿|ùÒÃÃcþüù'NœÀápèžÔÔÔ¸¹¹1Œôôt*•е;�¡Ê
¯®®vrr7nÜõë×…Ì7†ââbggg??¿ØØX¬}�%Ð\ñÒÞÞ>sæL
�¤¤$(ot3fLrrò_ýõÓO?aíd(�¦Â###ËÊÊUUUQ4Apwwÿù矣££oß¾�µ/�!jOé±±±‹-º~ýúŒ3P1áKhhèíÛ·_½z¥§§‡µ/�! :
õê•««ëºuëvîÜ)¹5ˆ :;;'NœH¥R322`‚4È ðööö‰'*++gddð2rC¤Çëׯ���øáx?•���eeeL&“ÉdVVVVWW×ÿ—ºº:'ò²Ó3SSSg@PPxddd\\Ü«W¯LMMQñ 2(‡þî»ï>|èêꊵ/C6›�ŸŸŸ——————““SXXXQQ�|¤©©©¯¯¯�¨—Á`4ÜÓÓƒÜêëëkjjêêê*** 8N__ßÊÊÊÆÆÆÚÚÚÚÚÚÊÊJfCÑ’*<55õ«¯¾:wîÜüùóÑòIRRRöìÙSPP€Çã�œœ&Ož|ùòåçÏŸË êG�ZµêÝ»w¶¶¶Ç�Ç*·±¯¯oAAAnn.וÖÖÖÇ�gfffdddeeµµµ‘Édkkk{{{{{{333CCCccc�&aEíííeeeÿüó“ÉÌÎÎ~ùòe~~>‹ÅRVVž8q¢»»»»»»‹‹‹ŠŠ
*íâ�$K^ôôôæÍ›'áÒYQùã�?BBBÞ¿ßÖÖ–��½råJdϹª.))ùå—_ššš6lØ °³³“A¥|ùøñ£¦¦æ²e˰r`ÈQZZzðàÁiÓ¦Q( À˜1c,XpôèÑ—/_²X,ÙøÐÕÕ•��}äÈ‘ÐÐÐQ£F (ÊôéÓ:Äd2¥Q£DªXºt)ƒÁ¨®®FË!111ùôéSï#ÿýwo…777�7nP;BëͶmÛ�x‰ÍÍÍ ƒ!Òéè‡Ãáþ¼œ$x¯=wî\PP�ºº:Òa8p ¦¦Å*ÄWø“'Oðxü¹sçPôFH¨TêÙ³gûœ5kò‹õõ×_Ú¥Y¬���ÈÊÊÊ(JBB‚H§£ÎÌ™3¬¬dÖ
!�Ìó¾¾¾D"QUU5<<<--Mn¿(‹•––¶hÑ"‰äçç—ššŠJì]1Îb±¬¬¬fΜ)¹b`gg‡ÇãW¬XññãÇþŸzyyõy
ÉÌÌô÷÷×ÑÑ!‘H#FŒ
ª®®î_ŒËå}ùå—JJJúúúG�È�îîî��Lîn}(//WVVþé§Ÿ°vDŽ`±X±±±ÖÖÖ �?ÿü³µµk§„¥¥¥åÌ™3nnn €ñãÇÇÅÅIxWSá¿þú+•J-))‘¤n±¹rå
ò*E¡P–.]Z\\ܧ@Ÿ!:�^VVÖÑÑqùòe À×_Ý¿XuuµŽŽNPPP}}ý–-[ ÉÉÉ|°··GÎ
Å<-ÑO?ý¤¤¤TZZŠò ‡Ã‰‹‹366&‰ÁÁÁÏŸ?ÇÚ#ñÉÊÊš7o‘H4119{ö¬Ø9¿ÄQxCC�N_¿~½xU¢Âû÷ï—.]Šèœ@ ¬X±¢�÷) ‡Ãñ=‘Ãá ÔÔÔú[µj //�Ëå"3Ÿ®®®|�tvvÞ»wYU¶nÝ:4&:æææóçÏÇÖ
̹sçŽ���@X¼x±”Fd“É'ööö⥲Gá«VÒÕÕ•‡'Ÿ�?~ÿý÷ȬƂxÇ x<¾OáOŸ>:tÈÖÖ–×u÷)fddÔg¢�N§¨ý¯¿þ hkk£Ú&qHNN Û n
¡¡¡ ÿ‚‚¬ÝAŸüü|___ @XXo$HHDVxaa!‘H