Skip to content

Repository files navigation

Phplrt

PHP 8.4+ Latest Stable Version Latest Unstable Version Total Downloads License MIT

Introduction

The phplrt is a set of tools for programming languages recognition. The library provides lexer, parser, grammar compiler, library for working with errors, text analysis and so on.

Installation

Phplrt is available as composer repository and can be installed using the following command in a root of your project:

composer require phplrt/phplrt

The grammar compiler only runs while you develop, so a typical project splits the dependency in two:

composer require phplrt/runtime          # lexer, parser and sources
composer require phplrt/compiler --dev   # reads grammars and generates code

More detailed installation instructions are here.

Documentation

Quick Start

A grammar says which words the text is made of, how they may be arranged, and what to build out of them. Here is one that adds numbers up:

// grammar.pp3

%skip  T_WHITESPACE  \s++

%token T_NUMBER  \d++
%token T_PLUS    \+

// Recognition starts from this rule
%pragma root Sum

Sum -> { return \is_array($children) ? \array_sum($children) : $children; }
  : Number() (::T_PLUS:: Number())*
  ;

Number -> { return (int) $children->value; }
  : <T_NUMBER>
  ;

<T_NUMBER> reads a token and keeps it, ::T_PLUS:: reads one and throws it away, and * means "zero or more times". The -> blocks are reducers - PHP that runs when the rule matches, turning what was read into a value.

The Quick Start builds a real configuration format step by step, and the grammar syntax is described in full.

Execution

Load the grammar and ask for a parser. Because the reducers above run as the rules match, what comes back is a number rather than a syntax tree:

<?php

use Phplrt\Compiler\Compiler;
use Phplrt\Source\FileSource;
use Phplrt\Source\StringSource;

$parser = new Compiler()
    ->load(FileSource::createFromPathname(__DIR__ . '/grammar.pp3'))
    ->getParser();

echo $parser->parse(StringSource::createFromString('2 + 2'));     // 4
echo $parser->parse(StringSource::createFromString('1 + 2 + 3')); // 6

There is also analyze(), which reports what it made of a source instead of throwing - for validating without building, or for reading a source the grammar is not meant to describe in full.

Errors

Anything that cannot be recognized points at the exact spot in the source:

use Phplrt\Parser\Exception\UnexpectedTokenException;
use Phplrt\Source\VirtualSource;

try {
    $parser->parse(VirtualSource::createFromString('expr.txt', "1 + 2\n3 + + 4\n"));
} catch (UnexpectedTokenException $e) {
    echo $e;
}
error[UnexpectedTokenException]: Syntax error, unexpected "3" (T_NUMBER), T_PLUS expected
 --> expr.txt:2:1
1 | 1 + 2
2 | 3 + + 4
  | ^
3 |

Rendering the snippet is the job of phplrt/exception. It comes with phplrt/phplrt; alongside the separate runtime packages it is a suggestion rather than a requirement, so add it if you install them by hand.

Compilation

Reading a grammar file costs time, and the grammar does not change between requests. Once it is ready and tested, compile it into a PHP file and commit that file - after which the phplrt/compiler dependency is no longer needed (see https://phplrt.org/docs/guide/installation#which-packages-do-i-actually-ship).

vendor/bin/phplrt compile grammar.pp3 \ 
    src/CalculatorParser.php \
    --namespace='App\Calculator' \
    --class=CalculatorParser

The same thing from PHP, if you would rather do it from a build script:

new Compiler()
    ->load(FileSource::createFromPathname(__DIR__ . '/grammar.pp3'))
    ->generate()
        ->withNamespaceName('App\Calculator')
        ->withClassName('CalculatorParser')
        ->save(__DIR__ . '/src/CalculatorParser.php');

What you get is an ordinary class: the whole lexer is one regular expression, each rule is an array entry, and every reducer is a real method you can step through in a debugger.

namespace App\Calculator;

readonly class CalculatorParser extends \Phplrt\Parser\Parser
{
    public const int T_WHITESPACE = 0;
    public const int T_NUMBER = 1;
    public const int T_PLUS = 2;

    public function __construct()
    {
        parent::__construct(/* the whole grammar, inlined */);
    }

    private static function reduceNumber(\Phplrt\Parser\Context $ctx, mixed $children): mixed
    {
        return (int) $children->value;
    }
}

Use it like any other class - no compiler, no grammar file, no build step at runtime:

$parser = new App\Calculator\CalculatorParser();

echo $parser->parse(StringSource::createFromString('2 + 2')); // 4

Packages

Every component is published on its own, so you can install only what you use.

Package What it does
phplrt/source Reads source code from files, strings and streams
phplrt/position Turns a byte offset into the line and column it points at
phplrt/lexer Splits source code into tokens
phplrt/parser Recognizes tokens against a grammar and builds a result
phplrt/exception Renders errors with a snippet of the code around them
phplrt/lexer-builder Describes a lexer in PHP and compiles it
phplrt/parser-builder Describes a grammar in PHP and compiles it
phplrt/compiler Reads *.pp2 or *.pp3 grammars and generates PHP code

LLM Usage Policy

To maintain code quality, readability, and complete comprehension in production, the following LLM usage rules apply:

  • Production code must be hand-written. LLMs should not be used to write production code. Code generated by LLMs is not accepted.
  • LLM-generated comments are allowed. However, comments describing contracts (classes, methods, interfaces, etc.) must answer the question "WHAT is this?", not "HOW does it work?". Comments should describe the purpose, responsibility, or contract of the code, not its implementation details.
  • LLM-generated tests are allowed. Each AI-generated test must treat the code under test as a "black box". Tests may rely on the public contract and observable behavior, but must not depend on or make assumptions about the internal implementation.

License

Phplrt is open-sourced software licensed under the MIT license.