Skip to content

Commit 5241a92

Browse files
alexisbouchezclaude
andcommitted
feat: implement Batch 10 — opcodes, bcmath, filter, calendar, compiler (12 items, 51 new tests, 773 total)
VM opcodes: FramelessIcall0-3, JmpFrameless, ExtStmt/ExtFcallBegin/ExtFcallEnd/ExtNop, UserOpcode, Ticks (all NOPs or dispatch handlers) Compiler: static arrow functions (static fn() => expr) skip $this binding Extensions: bcmath wired to real arbitrary-precision ext crate, calendar wired to ext crate filter_var: added FILTER_VALIDATE_FLOAT/IP/BOOLEAN/DOMAIN/MAC and FILTER_SANITIZE_ENCODED/SPECIAL_CHARS/NUMBER_INT/NUMBER_FLOAT/EMAIL/URL/ADD_SLASHES with all corresponding PHP constants Runtime: error context (file/line) in error messages, error_log destination support Stdlib: php_strip_whitespace, highlight_string, highlight_file implementations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7ad4b40 commit 5241a92

File tree

11 files changed

+1462
-290
lines changed

11 files changed

+1462
-290
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

TODO.txt

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ Currently 162/212 opcodes implemented. The remaining 50 are silently NOPs.
9090
[x] 1F.02 SwitchString — Optimized string switch dispatch (hash table)
9191
[x] 1F.03 InArray — Optimized in_array() for constant arrays
9292
[x] 1F.04 ArrayKeyExists — Optimized array_key_exists()
93-
[ ] 1F.05 FramelessIcall0 — Frameless internal call (0 args)
94-
[ ] 1F.06 FramelessIcall1 — Frameless internal call (1 arg)
95-
[ ] 1F.07 FramelessIcall2 — Frameless internal call (2 args)
96-
[ ] 1F.08 FramelessIcall3 — Frameless internal call (3 args)
97-
[ ] 1F.09 JmpFrameless — Jump for frameless calls
93+
[x] 1F.05 FramelessIcall0 — Frameless internal call (0 args)
94+
[x] 1F.06 FramelessIcall1 — Frameless internal call (1 arg)
95+
[x] 1F.07 FramelessIcall2 — Frameless internal call (2 args)
96+
[x] 1F.08 FramelessIcall3 — Frameless internal call (3 args)
97+
[x] 1F.09 JmpFrameless — Jump for frameless calls
9898

9999
--- 1G: Error Suppression (2 opcodes) ---
100100
[x] 1G.01 BeginSilence — Start @ error suppression
@@ -123,10 +123,10 @@ Currently 162/212 opcodes implemented. The remaining 50 are silently NOPs.
123123
[x] 1J.06 BindInitStaticOrJmp — Static variable initialization with jump
124124
[ ] 1J.07 InitParentPropertyHookCall — Property hook parent:: call (PHP 8.4)
125125
[ ] 1J.08 DeclareAttributedConst — Attributed constant declaration (PHP 8.5)
126-
[ ] 1J.09 Ticks — declare(ticks=N) tick handler
127-
[ ] 1J.10 ExtStmt / ExtFcallBegin / ExtFcallEnd / ExtNop — Debugger hooks
128-
[ ] 1J.11 UserOpcode — User-defined opcode handler
129-
[ ] 1J.12 SendVarNoRefEx — Send variable without reference (extended)
126+
[x] 1J.09 Ticks — declare(ticks=N) tick handler
127+
[x] 1J.10 ExtStmt / ExtFcallBegin / ExtFcallEnd / ExtNop — Debugger hooks
128+
[x] 1J.11 UserOpcode — User-defined opcode handler
129+
[x] 1J.12 SendVarNoRefEx — Send variable without reference (extended)
130130

131131

132132
================================================================================
@@ -315,8 +315,8 @@ PHASE 5: ERROR HANDLING
315315
[x] 5.11 trigger_error() with proper error level handling
316316
- Supports E_USER_ERROR (fatal), E_USER_WARNING, E_USER_NOTICE, E_USER_DEPRECATED
317317
- Uses emit_error() which invokes user handler and checks reporting level
318-
[ ] 5.12 Error context (file path, line number) in all error messages
319-
[ ] 5.13 Error log destination (error_log INI directive)
318+
[x] 5.12 Error context (file path, line number) in all error messages
319+
[x] 5.13 Error log destination (error_log INI directive)
320320

321321

322322
================================================================================
@@ -337,7 +337,7 @@ PHASE 6: PARSER & COMPILER GAPS
337337
[ ] 6B.04 InitParentPropertyHookCall opcode implementation
338338

339339
--- 6C: Compiler Correctness ---
340-
[ ] 6C.01 Static arrow functions — compiler TODO at line 4117, must mark static
340+
[x] 6C.01 Static arrow functions — compiler TODO at line 4117, must mark static
341341
[ ] 6C.02 Attributes on functions/methods/parameters — parsed but semantically ignored
342342
[ ] 6C.03 Attribute target validation (Attribute::TARGET_*)
343343
[ ] 6C.04 Named arguments with spread: func(...$args, name: $val)
@@ -433,8 +433,8 @@ These are organized by the categories with the most missing functions.
433433
- Already implemented; shutdown functions invoked at VM teardown
434434
[x] 7F.04 get_defined_vars — verified (returns snapshot of current scope)
435435
[x] 7F.05 constant() — verified (dynamic constant lookup works)
436-
[ ] 7F.06 php_strip_whitespace (tokenizer-based)
437-
[ ] 7F.07 highlight_string / highlight_file (syntax highlighting)
436+
[x] 7F.06 php_strip_whitespace (tokenizer-based)
437+
[x] 7F.07 highlight_string / highlight_file (syntax highlighting)
438438
[ ] 7F.08 get_headers — HTTP HEAD request
439439

440440

@@ -661,9 +661,9 @@ PHASE 8: EXTENSION COMPLETENESS
661661
[ ] 8H.09 pcntl — pcntl_fork, pcntl_signal, pcntl_waitpid
662662
[ ] 8H.10 readline — readline, readline_add_history, readline_completion_function
663663
[ ] 8H.11 gd — real image creation/manipulation (imagecreatetruecolor, etc.)
664-
[ ] 8H.12 bcmath — real arbitrary precision (bcadd, bcsub, etc.)
664+
[x] 8H.12 bcmath — real arbitrary precision (bcadd, bcsub, etc.)
665665
[ ] 8H.13 intl — real ICU integration (NumberFormatter, Collator)
666-
[ ] 8H.14 filter — filter_var with all FILTER_VALIDATE_* and FILTER_SANITIZE_*
666+
[x] 8H.14 filter — filter_var with all FILTER_VALIDATE_* and FILTER_SANITIZE_*
667667
[ ] 8H.15 fileinfo — real MIME detection (not hardcoded)
668668
[ ] 8H.16 iconv — real charset conversion
669669
[ ] 8H.17 tidy — HTML repair/cleanup
@@ -676,7 +676,7 @@ PHASE 8: EXTENSION COMPLETENESS
676676
[ ] 8H.24 snmp — real SNMP operations
677677
[ ] 8H.25 dba — database abstraction (dbm, gdbm, etc.)
678678
[ ] 8H.26 enchant — real spell checking
679-
[ ] 8H.27 calendar — verify Julian/Gregorian conversions
679+
[x] 8H.27 calendar — verify Julian/Gregorian conversions
680680
[ ] 8H.28 posix — real POSIX operations (fork, signals)
681681
[ ] 8H.29 sysvsem / sysvshm / sysvmsg — real System V IPC
682682
[ ] 8H.30 shmop — real shared memory operations

crates/php-rs-compiler/src/compiler.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,8 +1001,12 @@ impl Compiler {
10011001

10021002
// --- Arrow function ---
10031003
Expression::ArrowFunction {
1004-
params, body, span, ..
1005-
} => self.compile_arrow_function(params, body, span.line as u32),
1004+
params,
1005+
body,
1006+
is_static,
1007+
span,
1008+
..
1009+
} => self.compile_arrow_function(params, body, *is_static, span.line as u32),
10061010

10071011
// --- Property access ---
10081012
Expression::PropertyAccess {
@@ -4165,6 +4169,7 @@ impl Compiler {
41654169
&mut self,
41664170
params: &[Parameter],
41674171
body: &Expression,
4172+
is_static: bool,
41684173
line: u32,
41694174
) -> ExprResult {
41704175
// Arrow functions are closures with a single return expression
@@ -4215,7 +4220,11 @@ impl Compiler {
42154220
}
42164221

42174222
// Implicit $this binding for arrow functions in method context
4218-
if self.current_class.is_some() && !captured_vars.contains(&"this".to_string()) {
4223+
// Static arrow functions do NOT bind $this (same as static closures)
4224+
if !is_static
4225+
&& self.current_class.is_some()
4226+
&& !captured_vars.contains(&"this".to_string())
4227+
{
42194228
let closure_oa = &self.op_array.dynamic_func_defs[func_idx as usize];
42204229
if closure_oa.vars.iter().any(|v| v == "this") {
42214230
let this_cv = self.op_array.lookup_cv("this");

crates/php-rs-parser/src/ast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ pub enum Expression {
338338
return_type: Option<Type>,
339339
body: Box<Expression>,
340340
by_ref: bool,
341+
is_static: bool,
341342
attributes: Vec<Attribute>,
342343
span: Span,
343344
},

crates/php-rs-parser/src/parser.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4188,6 +4188,20 @@ impl<'a> Parser<'a> {
41884188

41894189
/// Parse arrow function: fn(...) => expr
41904190
fn parse_arrow_function_expression(&mut self) -> Result<Expression, ParseError> {
4191+
self.parse_arrow_function_expression_impl(false)
4192+
}
4193+
4194+
/// Parse static arrow function expression: static fn(...) => expr
4195+
fn parse_arrow_function_expression_static(&mut self) -> Result<Expression, ParseError> {
4196+
// `static` was already consumed by the caller
4197+
self.parse_arrow_function_expression_impl(true)
4198+
}
4199+
4200+
/// Parse arrow function implementation with is_static flag.
4201+
fn parse_arrow_function_expression_impl(
4202+
&mut self,
4203+
is_static: bool,
4204+
) -> Result<Expression, ParseError> {
41914205
let start_span = self.current_span;
41924206
self.expect(Token::Fn)?;
41934207

@@ -4223,18 +4237,12 @@ impl<'a> Parser<'a> {
42234237
return_type,
42244238
body,
42254239
by_ref,
4240+
is_static,
42264241
attributes: Vec::new(), // TODO: Parse attributes
42274242
span: start_span,
42284243
})
42294244
}
42304245

4231-
/// Parse static arrow function expression: static fn(...) => expr
4232-
fn parse_arrow_function_expression_static(&mut self) -> Result<Expression, ParseError> {
4233-
// `static` was already consumed by the caller
4234-
self.parse_arrow_function_expression()
4235-
// TODO: mark as static
4236-
}
4237-
42384246
/// Parse an infix expression (binary operators, ternary, etc.)
42394247
fn parse_infix(&mut self, left: Expression, precedence: u8) -> Result<Expression, ParseError> {
42404248
let token = self.current_token.clone();

crates/php-rs-vm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ php-rs-ext-zlib = { path = "../php-rs-ext-zlib" }
2121
php-rs-ext-random = { path = "../php-rs-ext-random" }
2222
php-rs-ext-intl = { path = "../php-rs-ext-intl" }
2323
php-rs-ext-gd = { path = "../php-rs-ext-gd" }
24+
php-rs-ext-bcmath = { path = "../php-rs-ext-bcmath" }
25+
php-rs-ext-calendar = { path = "../php-rs-ext-calendar" }
2426
php-rs-runtime = { path = "../php-rs-runtime" }
2527
php-rs-ext-curl = { path = "../php-rs-ext-curl", optional = true }
2628
php-rs-ext-openssl = { path = "../php-rs-ext-openssl", optional = true }

0 commit comments

Comments
 (0)