Rust PHP Parser
A fast, fault-tolerant PHP parser written in Rust. Produces a full typed AST with source spans, recovers from syntax errors, and covers PHP 7.4–8.5 syntax.
Try the interactive playground → · AST Node Reference →
Installation
[]
= "*"
= "*" # AST types and Visitor trait
= "*" # arena allocator
# Optional
= "*" # pretty-print AST back to PHP source
Quick Start
use parse;
let arena = new;
let result = parse;
println!;
for err in &result.errors
// Resolve byte offsets to line/column
let pos = result.source_map.offset_to_line_col;
Arena lifetime
Every AST node is bump-allocated into bumpalo::Bump. The 'arena lifetime on ParseResult borrows from the arena, so the arena must outlive any reference into the AST. Drop the arena to free all nodes at once:
let arena = new;
let result = parse;
// use result...
drop;
drop; // frees all AST memory in one shot
API Reference
parse()/parse_versioned()— main parser entry points; seedocs.rs/php-rs-parserVisitor/ScopeVisitor— AST traversal traits; seedocs.rs/php-astfor the visitor infrastructureParseErrorvariants — seecrates/php-parser/src/diagnostics.rsfor all variants and recovery behavior- AST node types — see
docs.rs/php-ast/astfor the full set of statement, expression, and declaration nodes
Usage
Version-aware parsing
The parser targets PHP 8.5 by default. Use parse_versioned() to target an earlier version:
use ;
let arena = new;
let result = parse_versioned;
// Enums require PHP 8.1 — a VersionTooLow diagnostic is emitted.
assert!;
Supported versions: Php74, Php80, Php81, Php82, Php83, Php84, Php85.
ParseResult fields
| Field | Type | Description |
|---|---|---|
program |
Program |
The parsed AST. Always present, even when errors exist. |
errors |
Vec<ParseError> |
Parse errors and diagnostics. Empty on success. |
errors_truncated |
bool |
true when the error list was capped. Treat the result as incomplete (relevant for linters). |
source |
&str |
The original source text. Slice spans directly: &result.source[span.start as usize..span.end as usize]. |
comments |
Vec<Comment> |
All comments in source order. Comments are not attached to AST nodes — map them to adjacent nodes by comparing spans. |
source_map |
SourceMap |
Pre-computed line index. Use offset_to_line_col(offset) to convert byte offsets to (line, col). |
Error recovery
The parser never fails — it always produces a complete AST. When it cannot parse a statement, it emits a ParseError and inserts a StmtKind::Error node as a placeholder so the tree is structurally intact:
let arena = new;
let result = parse;
assert!; // parse error reported
assert!; // AST still produced
// result.program.stmts contains a FunctionDecl whose body has a StmtKind::Error node
Re-parsing (LSP / editor use)
Use ParserContext when parsing the same document repeatedly (e.g. on every keystroke). It reuses the backing arena memory in O(1), avoiding allocator churn:
let mut ctx = new;
let result = ctx.reparse;
assert!;
drop; // must be dropped before the next reparse
let result = ctx.reparse;
assert!;
reparse_versioned is also available for targeting a specific PHP version.
Visitor API
Implement Visitor to walk the AST depth-first. Override only the node types you care about; the default implementations recurse into children automatically.
use ;
use *;
use ControlFlow;
Return ControlFlow::Break(()) to stop traversal early. Return ControlFlow::Continue(()) without calling walk_* to skip a subtree.
Scope-aware traversal
Use ScopeVisitor + ScopeWalker when your visitor needs to know which namespace, class, or function it is currently inside. Every visit method receives a Scope with that context:
use ;
use *;
use ControlFlow;
let arena = new;
let result = parse;
let mut walker = new;
walker.walk;
// walker.into_inner().methods == ["Foo::bar"]
Use plain Visitor when you don't need namespace/class/function context.
PHPDoc parser
PHPDoc comments are parsed into a structured AST via php_rs_parser::phpdoc::parse(). Tag bodies are exposed as raw text — the parser does not interpret type expressions, letting you apply your own type parser:
use ;
let doc = parse;
for param in find_tags
Pretty printer
let arena = new;
let result = parse;
let output = pretty_print;
// output == "echo 1 + 2;"
Use pretty_print_file to produce a complete file with a <?php\n\n prefix and trailing newline.
To preserve comments in the output, use pretty_print_with_comments:
let output = pretty_print_with_comments;
To customise indentation or newlines, pass a PrinterConfig:
use ;
let config = PrinterConfig ;
let output = pretty_print_with_config;
Architecture
Four crates, one workspace:
Source flows through Lexer → Parser → arena-allocated AST nodes. The lexer is lazy (tokens produced on demand with peeking slots); the parser is Pratt-based recursive descent with panic-mode error recovery.
Performance
The fastest full-featured PHP parser. Optimised for modern PHP applications with full typing (PHP 7.4+, 8.x). For comparative benchmarks against other PHP parsers see php-parser-benchmark.
Contributing
See CONTRIBUTING.md for build instructions, testing, and contributor guides.
Acknowledgements
Inspired by and indebted to nikic/PHP-Parser — test corpus fixtures were adapted from its test suite. Thanks to the PHP community contributors.
License
BSD 3-Clause