Building JimPHP: Architecting a Lightweight, AST-Based PHP Interpreter in C++17

Published on November 27, 2024 - Visits: ...

The PHP programming language powers a massive percentage of the modern web, largely driven by the production-grade Zend Engine. However, understanding how PHP code translates from raw text into runtime execution requires peeling back layers of compilation theory, memory management, and tree evaluation. Inspired by Salvatore Sanfilippo’s minimalist Jim Tcl interpreter, JimPHP is a lightweight, low-footprint implementation of PHP built in modern C++17.

While Jim Tcl achieves simplicity through string-oriented, runtime command substitution, JimPHP adopts a clean compiler-frontend separation: a dedicated Lexer for lexical scanning, a Parser for formal Abstract Syntax Tree (AST) construction driven by EBNF grammar, and an AST-walking Interpreter operating over an encapsulated symbol table.

1. Architectural Comparison: Jim Tcl vs. Zend Engine vs. JimPHP

To contextualize JimPHP’s execution model, we can compare how different interpretable environments process source code into evaluation:

Architecture Dimension Jim Tcl (Salvatore Sanfilippo) Zend Engine (PHP 8+) JimPHP (C++17)
Primary Focus Embedded Tcl scripting with minimal footprint High-performance web production runtime Lightweight, transparent PHP subset execution
Intermediate Representation None (String substitution / runtime evaluation) AST $\rightarrow$ Zend Opcodes (Bytecode) Abstract Syntax Tree (AST)
Execution Engine Direct string-command dispatcher Virtual Machine (Zend VM) + JIT Compiler Recursive AST Tree-Walker
Type System Everything is a String (EAM) Dynamic `zval` union structures Polymorphic C++ Value objects
Memory & Dependencies ANSI C, zero dependencies C/C++, complex memory pools C++17 STL, lightweight & zero external libs

2. Three-Tier Modular System Architecture

JimPHP is structured into three discrete, decoupled object layers. Each stage acts as an isolated pipeline component communicating via structured data objects:

3. Layer 1: Lexical Analysis (The Lexer)

The Lexer reads raw PHP source strings and transforms them into a linear stream of strongly typed Token structs. Early lexical naive matching fails when encountering complex code containing punctuation, multi-character operators, variable sigils, and float literals.

Interactive PHP Lexer Stream

0 Tokens

Scrivi il tuo codice PHP per visualizzare lo stream sequenziale dei token generati dal Lexer:

Token Stream Output

JimPHP categorizes incoming characters into explicit lexical buckets:

Lexical Transformation Example

When processing a complex statement such as:

$hello_user = 5.5 + 10 * (3 - 1);

The Lexer emits the following structured token sequence:

[SCHAR: $] [CHAR: hello_user] [OPER: =] [NUM: 5.5] [OPER: +] [NUM: 10] [OPER: *] [LPAREN: (] [NUM: 3] [OPER: -] [NUM: 1] [RPAREN: )] [PUNCT: ;]

4. Layer 2: Syntactic Parsing & AST Construction

Once tokenized, the stream is ingested by the Parser. The parser enforces operator precedence and constructs an Abstract Syntax Tree (AST).

+ (BinaryOpNode: ADD) / \ / \ (NUM: 3) * (BinaryOpNode: MULTIPLY) / \ / \ (NUM: 5) (NUM: 2)

5. Layer 3: Evaluation & Symbol Management

JimPHP executes code via an AST-walking Interpreter, recursively evaluating ASTNode instances at runtime.

6. Conclusion

Building JimPHP demonstrates that writing an interpreter from scratch doesn't require massive computational frameworks. By combining a tokenizing Lexer, an EBNF-driven Parser, and an AST Interpreter in modern C++17, JimPHP achieves a modular implementation of core PHP syntax. JimPHP Readme

My Journey to Becoming a Better Programmer by Building a PHP Interpreter in C++
by u/Giuseppe_Puleri in PHP
← All articles