Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Compiler Laravel Package

hoa/compiler

Hoa\Compiler is a PHP library for building lexers and parsers from grammar definitions. It supports LL(1) and LR(1) analysis, AST manipulation, grammar validation, and compiler construction, making it easier to parse domain-specific languages and custom syntax.

View on GitHub
Deep Wiki
Context7

Getting Started

Hoa Compiler is a grammar-driven lexer/parser generator. You write a .pp grammar, load it with Hoa\Compiler\Llk\Llk, parse input into a generic TreeNode AST, then walk that AST with visitors. It is archived and unmaintained, so pin versions and verify PHP compatibility before committing.

Install:

composer require hoa/compiler

Minimal grammar, e.g. resources/grammars/arithmetic.pp:

%skip   \s+
%token  number  \d+
%token  plus    \+
%token  times   \*
%token  lparen  \(
%token  rparen  \)

expr:
    term ( plus term )*

term:
    factor ( times factor )*

factor:
    number
  | lparen expr rparen

Parse and dump the AST:

use Hoa\Compiler\Llk\Llk;
use Hoa\Compiler\Visitor\Dump;
use Hoa\File\Read;

$parser = Llk::load(new Read(__DIR__ . '/arithmetic.pp'));
$ast = $parser->parse('1 + 2 * (3 + 4)');

echo (new Dump())->visit($ast);

Look first at:

  • Hoa\Compiler\Llk\Llk::load() — entry point for loading a grammar.
  • Hoa\Compiler\Llk\Parser — the parser object returned by load().
  • Hoa\Compiler\Llk\TreeNode — the AST node type (getId(), getValue(), getChildren()).
  • Hoa\Compiler\Visitor\Dump — the fastest way to understand the tree your grammar produces.
  • The package README’s grammar syntax: %token, %skip, rule definitions, alternation, repetition, and optional groups.

A good first use case is a small internal DSL or expression/config language: define the grammar, parse a fixture, dump the AST, then replace Dump with a visitor that validates or evaluates the tree.

Implementation Patterns

Keep the parser as a long-lived service. Llk::load() reads and compiles the grammar each time it is called. Load it once per request/process and reuse it.

use Hoa\Compiler\Llk\Llk;
use Hoa\Compiler\Llk\Parser;
use Hoa\File\Read;

final class GrammarParserFactory
{
    public function __invoke(): Parser
    {
        return Llk::load(new Read(resource_path('grammars/arithmetic.pp')));
    }
}

In Laravel, bind it as a singleton in a service provider:

$this->app->singleton(Parser::class, function () {
    return Llk::load(new Read(resource_path('grammars/arithmetic.pp')));
});

Under PHP-FPM this is per request; under Octane the singleton can persist for the worker lifetime, which is usually preferable.

Wrap parsing in a domain service. Do not let controllers or jobs call Llk directly. Translate parser exceptions into domain/validation errors at the boundary.

final class ExpressionParser
{
    public function __construct(private Parser $parser) {}

    public function parse(string $input): ExpressionAst
    {
        try {
            $tree = $this->parser->parse($input);
        } catch (\Hoa\Compiler\Exception $e) {
            throw new InvalidExpression($e->getMessage(), previous: $e);
        }

        return new ExpressionAst($tree);
    }
}

Use visitors for every AST operation. The AST is generic; avoid scattering getChild(2) calls throughout the codebase. Create one visitor per concern: validation, evaluation, pretty-printing, code generation, metrics collection.

use Hoa\Visitor\Element;
use Hoa\Visitor\Visit;

final class AstToArray implements Visit
{
    public function visit(Element $element, &$handle = null, $eldnah = null)
    {
        $node = [
            'id' => $element->getId(),
            'value' => $element->getValue(),
            'children' => [],
        ];

        foreach ($element->getChildren() as $child) {
            $node['children'][] = $this->visit($child, $handle, $eldnah);
        }

        return $node;
    }
}

The visit() signature must match Hoa\Visitor\Visit; the reference parameters are part of the interface.

Normalize the tree shape in one place. Because nonterminals and tokens both appear as nodes, visitors often need to skip punctuation tokens (plus, lparen, etc.) or unwrap single-child wrapper rules. Put that logic in one traversal helper rather than repeating it.

Use grammar tests with AST snapshots. For each grammar rule, keep valid and invalid fixture inputs. Snapshot the Dump output for valid cases; assert exception type/message for invalid cases. This catches accidental grammar changes faster than end-to-end tests.

Design grammars for LL parsing. Refactor left-recursive rules into right-recursive or repetition forms. Keep token order deliberate: keywords before identifier patterns, punctuation before catch-all regexes.

Use %skip aggressively. Whitespace and comments should disappear at the lexer level. Otherwise every visitor must handle them.

Separate lexer concerns from parser concerns. If tokenization gets complicated, test the lexer indirectly through small parser fixtures. Hoa exposes the lexer/parser internals, but relying on internal APIs increases upgrade risk.

Gotchas and Tips

  • Archived and unmaintained. The last release was 2017. Expect no fixes for modern PHP. Test on your target PHP version immediately; if you are on PHP 8.x, expect deprecation notices or fatal errors from Hoa’s dependency stack (hoa/consistency, hoa/zformat, etc.). Pin exact versions and consider a maintained fork or an alternative parser generator if PHP 8 compatibility is required.
  • LL(k), not a general CFG/PEG parser. Left recursion and ambiguous grammars will not work as expected. Refactor expression grammars, and keep alternatives distinguishable by the next token.
  • Lexer token order matters. Define keywords and specific punctuation before broad identifier/number patterns. A misplaced %token identifier [a-zA-Z_]\w* can swallow keywords.
  • Always define %skip for whitespace. Otherwise spaces become AST nodes and visitors become noisy.
  • The AST includes tokens. A rule like expr: term ( plus term )* produces child nodes for plus as well as term. Visitors must either skip token nodes by ID or explicitly consume them.
  • Child indexes are brittle. getChild(0) is fine for tiny grammars, but long-term code should switch on getId() and use named helper methods. Grammar refactors silently change child positions.
  • Use Dump before writing visitors. Most confusion comes from guessing the AST shape. Dump a representative input first.
  • Parser errors are exceptions, not return values. Catch Hoa\Compiler\Exception subclasses and translate them. Do not expose raw parser messages to end users.
  • No error recovery. Parsing is fail-fast. If you need multi-error reporting, you will need to parse fragments or implement recovery outside the library.
  • Make sure the start rule covers the whole input. Trailing unexpected input may not be reported the way you expect. Add fixtures with trailing garbage and verify behavior.
  • Do not serialize the parser. Treat Llk::load() as a compilation step and cache the resulting parser object in the container. Rebuild it on deploy.
  • Use absolute paths with Hoa\File\Read. Relative paths and stream wrappers can behave differently across CLI, queue workers, and web requests.
  • Grammar files are not PHP. Keep them in resources/ or a dedicated grammars/ directory and load them explicitly.
  • Composer conflicts are possible. Hoa packages have tight inter-dependencies. Run composer why hoa/consistency and avoid mixing with newer Hoa components unless you have tested the combination.
  • Performance is usually fine for small DSLs, but the AST is fully in memory. For large inputs, measure memory and parse time. Avoid reparsing the same input repeatedly; parse once and reuse the AST or the derived domain object.
  • Debugging tip: temporarily add a visitor that returns [$element->getId(), $element->getValue()] for every node. This gives a compact trace without the full Dump output.
  • Extension points: custom visitors are the safest extension mechanism. Subclassing parser/lexer internals is possible but fragile because the package is unmaintained.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource