APPENDIX B ■ A SIMPLE PARSER
class SequenceParse extends CollectionParse {
function trigger( Scanner $scanner ) {
if ( empty( $this->parsers ) ) {
return false;
}
return $this->parsers[0]->trigger( $scanner );
}
protected function doScan( Scanner $scanner ) {
$start_state = $scanner->getState();
foreach( $this->parsers as $parser ) {
if (! ( $parser->trigger( $scanner ) &&
$scan=$parser->scan( $scanner )) ) {
$scanner->setState( $start_state );
return false;
}
}
return true;
}
}
The abstract CollectionParse class simply implements an add() method that aggregates Parsers
and overrides term() to return false.
The SequenceParse::trigger() method tests only the first child Parser it contains, invoking its
trigger() method. The calling Parser will first call CollectionParse::trigger() to see if it is worth
calling CollectionParse::scan(). If CollectionParse::scan() is called, then doScan() is invoked and the
trigger() and scan() methods of all Parser children are called in turn. A single failure results in
CollectionParse::doScan() reporting failure.
One of the problems with parsing is the need to try stuff out. A SequenceParse object may contain an
entire tree of parsers within each of its aggregated parsers. These will push the Scanner on by a token or
more and cause results to be registered with the Context object. If the final child in the Parser list returns
false, what should SequenceParse do about the results lodged in Context by the child’s more successful
siblings? A sequence is all or nothing, so I have no choice but to roll back both the Context object and the
Scanner. I do this by saving state at the start of doScan() and calling setState() just before returning
false on failure. Of course, if I return true then there’s no need to roll back.
For the sake of completeness, here are all the remaining Parser classes:
namespace gi\parse;
// This matches if one or more subparsers match
class RepetitionParse extends CollectionParse {
private $min;
private $max;
function construct( $min=0, $max=0, $name=null, $options=null ) {
parent::construct( $name, $options );
if ( $max < $min && $max > 0 ) {
throw new Exception(
"maximum ( $max ) larger than minimum ( $min )");
}
$this->min = $min;
$this->max = $max;