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
7 changes: 5 additions & 2 deletions .github/workflows/php-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ name: PHP Tests

on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master

permissions:
contents: read
Expand All @@ -14,7 +17,7 @@ jobs:
strategy:
matrix:
operating-system: [ ubuntu-latest ]
php-version: ['8.2', '8.3']
php-version: ['8.4', '8.5']

name: PHP ${{ matrix.php-version }} test on ${{ matrix.operating-system }}

Expand Down Expand Up @@ -45,7 +48,7 @@ jobs:
run: composer install --prefer-dist --no-progress

- name: PHPUnit Tests
run: vendor/bin/phpunit --coverage-clover ./tests/coverage.xml
run: vendor/bin/phpunit --display-all-issues --coverage-clover ./tests/coverage.xml

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
Expand Down
64 changes: 64 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
# Run tests
vendor/bin/phpunit

# Run tests with coverage
composer run-script test-coverage

# Apply Rector refactoring rules
vendor/bin/rector process

# CLI tool
php bin/dissect.php
```

To run a single test file: `vendor/bin/phpunit tests/Path/To/SomeTest.php`

## Architecture

Dissect is a pure PHP library for lexical and syntactical analysis (building custom lexers and LALR(1) parsers). It is used by the GoAOP framework to parse pointcut DSL expressions.

### Data Flow

```
Input String → Lexer → TokenStream → Parser → AST / semantic result
Grammar (rules + callbacks)
```

### Core Modules

**`src/Lexer/`** — Converts strings into token streams.
- `AbstractLexer` — Base class handling line tracking, token filtering, EOF insertion. Subclasses implement `extractToken()` and `shouldSkipToken()`.
- `SimpleLexer` / `StatefulLexer` — Fluent builders (`token()`, `regex()`, `skip()`, `state()`). `StatefulLexer` supports context-dependent tokenization via explicit state transitions.
- `RegexLexer` — Abstract base for custom regex-based lexers.
- Recognizers: `SimpleRecognizer` (string match) and `RegexRecognizer` (regex match) are tried in sequence until one succeeds.

**`src/Parser/`** — Converts token streams into results using LALR(1) parsing.
- `Grammar` — Defines rules with a fluent API: `$grammar('NonTerminal')->is('A', 'B')->call(fn(...) => ...)`. Also handles operator precedence and associativity.
- `Rule` — A single grammar production with optional semantic action callback.
- `src/Parser/LALR1/` — Core LALR(1) engine:
- `Analyzer` — Computes FIRST/FOLLOW sets and builds the parse table (shift/reduce/accept actions) from a `Grammar`.
- `Automaton` / `State` / `Item` — LR(0) items and states forming the state machine.
- `Parser` — Drives the parse loop using the generated table; executes rule callbacks on reduction.
- Dumpers — Debug utilities to print parse tables and automaton graphs.

**`src/Node/`** — AST representation.
- `Node` interface — Countable and IteratorAggregate for tree traversal.
- `CommonNode` — Default implementation with parent/child relationships and arbitrary attribute storage.

**`src/Console/`** — CLI via Symfony Console (`bin/dissect`).

**`src/Util/`** — Unicode-aware string utilities.

### Key Patterns

- Grammar rules are defined as callbacks: reductions transform matched symbols into AST nodes or computed values.
- The `Analyzer` is run once per grammar to build the parse table; the resulting `Automaton` is passed to `Parser` for repeated use.
- Conflict resolution (shift/reduce, reduce/reduce) is handled during grammar analysis using operator precedence declarations.
10 changes: 5 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
"test-coverage": "phpunit --coverage-html tests/coverage"
},
"require": {
"php": "^8.2.0"
"php": "^8.4.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0.3",
"rector/rector": "^1.0",
"symfony/console": ">=6.0"
"phpunit/phpunit": "^13.0",
"rector/rector": "^2.0",
"symfony/console": "^7.4 || ^8.0"
},
"suggest": {
"symfony/console": "for the command-line tool"
Expand All @@ -59,7 +59,7 @@
"minimum-stability": "stable",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "4.0-dev"
}
},
"config": {
Expand Down
2 changes: 1 addition & 1 deletion src/Lexer/SimpleLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class SimpleLexer extends AbstractLexer
* @param string $type The token type.
* @param string|null $value The value to be recognized.
*/
public function token(string $type, string $value = null): self
public function token(string $type, ?string $value = null): self
{
if ($value) {
$this->recognizers[$type] = new SimpleRecognizer($value);
Expand Down
2 changes: 1 addition & 1 deletion src/Lexer/StatefulLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class StatefulLexer extends AbstractLexer
* @param string $type The token type.
* @param string|null $value The value to be recognized.
*/
public function token(string $type, string $value = null): StatefulLexer
public function token(string $type, ?string $value = null): StatefulLexer
{
if ($this->stateBeingBuilt === null) {
throw new LogicException("Define a lexer state first.");
Expand Down
2 changes: 1 addition & 1 deletion src/Parser/LALR1/Dumper/StringWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function outdent(): void
*
* @param string|null $string The string to write.
*/
public function writeLine(string $string = null): void
public function writeLine(?string $string = null): void
{
if ($string) {
$this->write(sprintf(
Expand Down
2 changes: 1 addition & 1 deletion src/Parser/LALR1/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Parser implements P\Parser
* @param Grammar $grammar The grammar.
* @param array|null $parseTable If given, the parser doesn't have to analyze the grammar.
*/
public function __construct(Grammar $grammar, array $parseTable = null)
public function __construct(Grammar $grammar, ?array $parseTable = null)
{
$this->grammar = $grammar;

Expand Down
2 changes: 1 addition & 1 deletion src/Util/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static function stringLength(string $str): int
*
* @return string The substring.
*/
public static function substring(string $str, int $position, int $length = null): string
public static function substring(string $str, int $position, ?int $length = null): string
{
static $lengthFunc = null;

Expand Down
Loading