Parser Contracts
This document describes a common interface for syntax analysis - turning a source into whatever that source means - together with the interfaces for the errors raised along the way.
The goal is to let anything that uses a parser - a template engine, a configuration loader, a rule engine - depend on the ability to parse rather than on a particular parser, whether it is handwritten, assembled at runtime or generated from a grammar.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
1. Specification
1.1 Parser
A parser is an object implementing ParserInterface.
- The
parse()method takes a source implementingReadableInterfaceand returns the result of its syntax analysis. - The shape of that result is defined by the implementation, which MAY return any value the analyzed source is converted into - an abstract syntax tree, a list of nodes, an array of settings, a number.
- An implementation SHOULD declare which of those it returns through the
TResulttemplate parameter, so that the value is known statically.
1use Phplrt\Contracts\Parser\ParserInterface; 2 3/** 4 * @implements ParserInterface<Node> 5 */ 6final class ExpressionParser implements ParserInterface { /* ... */ }
1/** 2 * @param ParserInterface<Node> $parser 3 */ 4function walk(ParserInterface $parser, ReadableInterface $source): void 5{ 6 $node = $parser->parse($source); // Node, rather than mixed 7}
1.2 Errors
- Every exception thrown by a parser MUST implement
ParserExceptionInterface, internal failures included. - An error that occurs before the analysis begins - a source that cannot
be recognized, settings that contain errors - is a plain
ParserExceptionInterface. - An error that occurs after the analysis has begun, and therefore
indicates a problem in the analyzed source, MUST implement
RuntimeExceptionInterface.
RuntimeExceptionInterface carries the source the error occurred in, the
token it occurred on, and the size of the fragment it covers:
-
$lengthis counted in bytes and starts at the offset of$token. -
$lengthMAY be as large as the whole grammar rule the analysis failed on, since a parser fails on a token but the construct at fault is usually larger. -
$lengthisnullwhen the size is not known.
1use Phplrt\Contracts\Parser\Exception\RuntimeExceptionInterface; 2 3try { 4 $ast = $parser->parse($source); 5} catch (RuntimeExceptionInterface $e) { 6 \printf( 7 "%s at offset %d, %d bytes\n", 8 $e->getMessage(), 9 $e->token->offset, 10 $e->length ?? $e->token->size, 11 ); 12}
Rendering such an error as a snippet of the source is what phplrt/exception does with the same three members.
2. Package
The interfaces described here are provided as part of the phplrt/parser-contracts package:
composer require phplrt/parser-contracts
It contains no parser, and requires nothing but PHP, phplrt/lexer-contracts and phplrt/source-contracts - a syntax error has to name the token it happened on.
A package implementing these interfaces provides the virtual package
phplrt/parser-contracts-implementation:
composer require phplrt/parser-contracts-implementation
phplrt/parser provides it, and every parser the compiler generates extends
Phplrt\Parser\Parser.
Note that analyze(), which the phplrt parser also has, is deliberately not
part of this specification: it is a diagnostic tool rather than part of what
makes an object a parser. See Usage.
The package ships a test declaring an anonymous implementation of every interface in it. Changing that test is allowed only in a major release, so no member can be added to any of these interfaces in a minor version.
3. Interfaces
3.1 Phplrt\Contracts\Parser\ParserInterface
1<?php 2 3declare(strict_types=1); 4 5namespace Phplrt\Contracts\Parser; 6 7use Phplrt\Contracts\Parser\Exception\ParserExceptionInterface; 8use Phplrt\Contracts\Parser\Exception\RuntimeExceptionInterface; 9use Phplrt\Contracts\Source\ReadableInterface; 10 11/** 12 * Converts a source into the result of its syntax analysis. 13 * 14 * @template TResult of mixed = mixed 15 */ 16interface ParserInterface 17{ 18 /** 19 * Performs syntax analysis of the given source and returns its result. 20 * 21 * The shape of the result is defined by the implementation, which MAY 22 * return any value the analyzed source is converted into, like an 23 * abstract syntax tree (AST) or a list of its nodes. 24 * 25 * @return TResult the result of the analysis 26 * @throws ParserExceptionInterface if the given source cannot be 27 * recognized or the parser settings contain errors 28 * @throws RuntimeExceptionInterface if the analyzed source contains errors 29 */ 30 public function parse(ReadableInterface $source): mixed; 31}
3.2 Phplrt\Contracts\Parser\Exception\ParserExceptionInterface
1<?php 2 3declare(strict_types=1); 4 5namespace Phplrt\Contracts\Parser\Exception; 6 7/** 8 * An error of the syntax analysis, including an internal one. 9 * 10 * Every exception thrown by a parser MUST implement this interface. 11 */ 12interface ParserExceptionInterface extends \Throwable {}
3.3 Phplrt\Contracts\Parser\Exception\RuntimeExceptionInterface
1<?php 2 3declare(strict_types=1); 4 5namespace Phplrt\Contracts\Parser\Exception; 6 7use Phplrt\Contracts\Lexer\TokenInterface; 8use Phplrt\Contracts\Source\ReadableInterface; 9 10/** 11 * An error that occurs after the syntax analysis has been started and 12 * indicates a problem in the analyzed source. 13 */ 14interface RuntimeExceptionInterface extends ParserExceptionInterface 15{ 16 /** 17 * The source the error occurred in. 18 */ 19 public ReadableInterface $source { 20 get; 21 } 22 23 /** 24 * The token the error occurred on. 25 */ 26 public TokenInterface $token { 27 get; 28 } 29 30 /** 31 * The size of the source fragment the error occurred in, in bytes, or 32 * {@see null} in case the size is not known. 33 * 34 * The fragment starts at the offset of the token the error occurred on and 35 * MAY be as large as the whole grammar rule the analysis has failed on. 36 * 37 * @var int<0, max>|null 38 */ 39 public ?int $length { 40 get; 41 } 42}
4. See Also
- Usage - the phplrt parser itself.
- Results and Reducers - what a result is usually made of.
- Lexer Contracts - the tokens a parser reads.