Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: "PHPStan analysis"

permissions:
contents: read

on:
pull_request:
push:
branches:
- master

jobs:
build:
name: "PHPStan analysis - PHP8.4"
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v4
- name: "Install PHP"
uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
ini-values: memory_limit=-1
tools: composer:v2
- name: "Cache dependencies"
uses: actions/cache@v4
with:
path: |
~/.composer/cache
vendor
key: "php-8.4"
restore-keys: "php-8.4"
- name: "Install dependencies"
run: "composer install --no-interaction --no-progress"
- name: "Static analysis"
run: "vendor/bin/phpstan analyze --memory-limit=1G"
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
Changelog
=========

4.0.0 (2026-03-25)
------------------

- Modernized entire codebase for PHP 8.4+: readonly constructor-promoted properties, union types, first-class callable syntax, and modern array destructuring throughout
- Refactored `Recognizer::match()` API from by-reference output parameter to clean `?string` return value
- Replaced `static $regex` local variable in `RegexLexer` with a typed class property; added explicit `preg_split` false-return guard
- Decomposed `StatefulLexer` nested array shape into three separately-typed arrays (`stateRecognizers`, `stateActions`, `stateSkipTokens`) for correctness and clarity
- Narrowed `Token::getType()` return type from `mixed` to `string`
- Added precise generic type annotations throughout: `array{action:..., goto:...}` parse table shapes, `ArrayIterator<int, Token>`, `IteratorAggregate<int, Token>`, `SplQueue<State>`, and `@template T of string` constraints on `Util`
- Fixed undefined variable bug in `Analyzer::buildParseTable()` (`$resolvedRules` used before assignment)
- Added `phpstan/phpstan` (level 10) and `phpstan/phpstan-phpunit` as dev dependencies; all 57 tests pass with zero static analysis errors

3.0.0 (2024-02-18)
------------------

- Forked package to the `goaop` organization to keep all dependencies under control
- Migrated PHPUnit configuration to the newer XML scheme
- Removed deprecated `utf8_decode()` call, replaced with `mb_convert_encoding()`
- Applied Rector ruleset: `DeclareStrictTypesRector`, PSR-4 autoloading, `AddVoidReturnTypeWhereNoReturnRector`, `ClosureToArrowFunctionRector`, `PublicConstantVisibilityRector`, removed useless PHPDoc tags
- Modernized test suite via `PHPUnitSetList` and `PHPUnitSetList::PHPUNIT_CODE_QUALITY` Rector rules
- Used variadic syntax for `ArrayTokenStream` constructor to enforce type checks
- Dropped PHP 8.1 and 8.2 from build matrix; added PHP 8.3
- Added Dependabot for automated dependency updates
- Updated license to MIT

1.0.1 (2013-01-29)
------------------

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2024 Lisachenko Alexander <lisachenko.it@gmail.com>
Copyright (c) 2026 Lisachenko Alexander <lisachenko.it@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
135 changes: 117 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,143 @@
Dissect
-----------------
This library is based on @jakubledl and @WalterWoshid work.
# 🔬 Dissect

Dissect library provides a set of tools to create and use lexical parsers. Read more at /docs folder.
For the goaop/framework it is responsible for parsing pointcut DSL expressions into AST tree, which might be then
processed.
> A pure-PHP toolkit for building custom **lexers** and **LALR(1) parsers** — fast, type-safe, and dependency-free.

![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/goaop/dissect/php-tests.yml?branch=master)
![PHPStan Badge](https://img.shields.io/badge/PHPStan-level%2010-brightgreen.svg?style=flat&link=https%3A%2F%2Fphpstan.org%2Fuser-guide%2Frule-levels)
[![Total Downloads](https://img.shields.io/packagist/dt/goaop/dissect.svg)](https://packagist.org/packages/goaop/dissect)
[![Daily Downloads](https://img.shields.io/packagist/dd/goaop/dissect.svg)](https://packagist.org/packages/goaop/dissect)
[![PHP Version](https://img.shields.io/badge/php-%3E%3D%208.2-8892BF.svg)](https://php.net/)
[![PHP Version](https://img.shields.io/badge/php-%3E%3D%208.4-8892BF.svg)](https://php.net/)
![GitHub License](https://img.shields.io/github/license/goaop/dissect)
[![Sponsor](https://img.shields.io/badge/Sponsor-❤️-lightgray?style=flat&logo=github)](https://github.com/sponsors/lisachenko)

---

## Installation
## ✨ What is Dissect?

Dissect is a pure-PHP library for **lexical and syntactical analysis** — the foundational building blocks for any language tooling: expression evaluators, template engines, DSL interpreters, query parsers, and more.

It powers the [GoAOP framework](https://github.com/goaop/framework), where it parses pointcut DSL expressions into an AST for aspect-oriented programming.

### Data flow

```
Input String
┌─────────┐ ┌──────────────┐ ┌──────────────────┐
│ Lexer │ ───▶ │ TokenStream │ ───▶ │ LALR(1) Parser │ ───▶ Result / AST
└─────────┘ └──────────────┘ └──────────────────┘
Grammar (rules
+ callbacks)
```

---

## 🚀 Key Features

### 🔤 Flexible Lexers
| Lexer | Description |
|---------------------|--------------------------------------------------------------------------------------------|
| **`SimpleLexer`** | Fluent builder API — define tokens with strings or regex, mark skippable tokens |
| **`StatefulLexer`** | Context-aware tokenization with explicit state transitions (e.g. for string interpolation) |
| **`RegexLexer`** | Abstract base class adapted from Doctrine — ultra-fast single-pass regex lexing |

### 📐 LALR(1) Parser

- **Full LALR(1) grammar support** — handles the vast majority of real-world grammars
- **Fluent grammar API** — define productions and semantic actions with readable PHP closures
- **Operator precedence & associativity** — built-in `left()`, `right()`, `nonassoc()` declarations
- **Conflict resolution** — configurable strategies: shift-wins, longer-reduce, earlier-reduce
- **Precomputed parse tables** — analyze once, serialize to PHP file, load instantly in production

### 🌳 AST Construction

- **`CommonNode`** — ready-to-use tree node with named children and arbitrary attributes
- **Countable & iterable** — traverse subtrees with standard PHP constructs

### 🛠 Developer Experience

- **Zero runtime dependencies** — only Symfony Console as an optional CLI dep
- **PHPStan level 10** — fully typed with generics, array shapes, and readonly properties
- **CLI tool** — dump parse tables and visualize automaton states as Graphviz graphs

---

## 📦 Installation

```bash
composer require goaop/dissect
```

---

## ⚡ Quick Example

# Documentation?
```php
use Dissect\Lexer\SimpleLexer;
use Dissect\Parser\Grammar;
use Dissect\Parser\LALR1\Parser;

[Here](docs/index.md).
// 1. Define a lexer
$lexer = new SimpleLexer();
$lexer->regex('INT', '/[0-9]+/')
->token('PLUS', '+')
->token('MINUS', '-')
->regex('WS', '/\s+/')
->skip('WS');

// 2. Define a grammar
$grammar = new Grammar();
$grammar('Expr')
->is('Expr', 'PLUS', 'Expr')
->call(fn($l, $_, $r) => $l + $r)

->is('Expr', 'MINUS', 'Expr')
->call(fn($l, $_, $r) => $l - $r)

## Testing
->is('INT')
->call(fn($t) => (int) $t->getValue());

$grammar->operators('PLUS', 'MINUS')->left()->prec(1);
$grammar->start('Expr');

// 3. Parse!
$parser = new Parser($grammar);
$result = $parser->parse($lexer->lex('3 + 5 - 2')); // → 6
```

- Run `composer run-script test`<br>
or
- Run `composer run-script test-coverage`
---

## 📖 Documentation

| Topic | Description |
|--------------------------------------|------------------------------------------------------------------|
| [Lexical analysis](docs/lexing.md) | `SimpleLexer`, `StatefulLexer`, `RegexLexer`, performance tips |
| [Writing a grammar](docs/parsing.md) | Productions, callbacks, operator precedence, conflict resolution |
| [Building an AST](docs/ast.md) | `CommonNode`, tree traversal |
| [Common patterns](docs/common.md) | Lists, comma-separated sequences, expression grammars |
| [CLI tool](docs/cli.md) | Precomputing parse tables, exporting automaton graphs |

## Show your support
---

## 🧪 Testing & Quality

```bash
# Run tests
composer test

# Run tests with coverage
composer test-coverage

# Static analysis (PHPStan level 10)
composer phpstan
```

Give a ⭐ if this project helped you!
---

## 🙏 Credits

## 📝 License
Originally created by [@jakubledl](https://github.com/jakubledl), extended by [@WalterWoshid](https://github.com/WalterWoshid), maintained by the [GoAOP](https://github.com/goaop) team.

This project is [MIT](https://opensource.org/licenses/MIT) licensed.
Give a ⭐ if Dissect saved you from writing a parser by hand!
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
],
"scripts": {
"test": "phpunit",
"test-coverage": "phpunit --coverage-html tests/coverage"
"test-coverage": "phpunit --coverage-html tests/coverage",
"phpstan": "phpstan analyse"
},
"require": {
"php": "^8.4.0"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^13.0",
"rector/rector": "^2.0",
"symfony/console": "^7.4 || ^8.0"
Expand Down
8 changes: 8 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon

parameters:
level: 10
paths:
- src
- tests
2 changes: 1 addition & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd" bootstrap="./tests/bootstrap.php" colors="true">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/13.0/phpunit.xsd" bootstrap="./tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Dissect Tests">
<directory>tests/</directory>
Expand Down
2 changes: 1 addition & 1 deletion src/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Application extends BaseApplication
// credit goes to everzet & kostiklv, since
// I copied the BehatApplication class when
// dealing with some CLI problems.
public function __construct($version)
public function __construct(string $version)
{
parent::__construct('Dissect', $version);
}
Expand Down
Loading
Loading