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
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ runtime helpers, and standard-library surfaces.
- [x] Mixed indexed/associative array union — model `array + array` across indexed/hash representations while preserving PHP's shared int/string key space and left-key precedence
- [X] Callable parity follow-up — support captured method/static first-class callables in the remaining callback runtimes (`array_reduce()`, `array_walk()`, `usort()`, `uksort()`, `uasort()`), direct callable expression calls such as `($obj->method(...))()`, non-local method receivers such as `(new Foo())->method(...)`, nullsafe first-class callables, broader builtin first-class callable wrappers, and the remaining `call_user_func_array()` by-reference callback gaps
- [ ] Runtime-value compatibility polishing v2 — continue with PHP's uninitialized typed-property state, integer overflow promotion, broader loose-comparison semantics, and future warning/notice sites as they are added
- [ ] Broader date and regex PHP parity — expand `strtotime()` relative formats and PCRE-compatible regex features/captures/backreferences (JSON parity now closed: see v0.8.x base + v0.20.x polish)
- [x] Broader date and regex PHP parity — expand `strtotime()` relative formats with `a/an <unit>` article offsets and add `preg_replace()` capture backreference expansion (`$0`..`$9`, `\0`..`\9`) over the POSIX bridge (JSON parity now closed: see v0.8.x base + v0.20.x polish)
- [x] JSON encoder optimization — folded `__rt_json_assoc_is_list_shape` into the main associative-array encoding walk. `__rt_json_encode_assoc` now emits a provisional object form, tracks whether keys remain `0..count-1` while iterating the hash once, and compacts the finished buffer in-place to `[...]` only for real list-shape payloads. Object-shape inputs still stay object form, and `JSON_FORCE_OBJECT` disables compaction.
- [x] JSON decoder optimization — fused the `__rt_json_validate` pre-pass into `__rt_json_decode_mixed` for `json_decode()`. The wrapper now calls the checked structural decoder directly; the decoder trims the input once, validates scalar strings/numbers at the point where they are decoded, enforces depth around containers, records syntax/depth/UTF-16 errors internally, and returns null-on-error for the PHP-facing wrapper. `json_validate()` keeps the standalone RFC 8259 validator surface.
- [x] JSON encoder optimization — extended the `_json_active_flags` callee-saved-register cache to `__rt_json_encode_assoc` and `__rt_json_encode_array_dynamic` (`x19` ARM64 / `r15` x86_64). The recursive encoder chain now preserves that cache: `__rt_json_encode_object` no longer clobbers ARM64 `x19`, and the x86_64 string encoder keeps `r15` dedicated to cached flags during UTF-8 decoding.
Expand Down
4 changes: 2 additions & 2 deletions docs/internals/the-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ The fatal uncaught-exception path writes `Fatal error: uncaught exception` to st
|---|---|---|---|
| `__rt_date` | Format a Unix timestamp using PHP date format characters (Y, m, d, H, i, s, l, F, etc.). Uses `localtime_r()` from libc and static lookup tables (`_day_names`, `_month_names`) for day/month names | `x1`/`x2` = format string, `x0` = timestamp | `x1`/`x2` = formatted string |
| `__rt_mktime` | Create a Unix timestamp from date components (hour, minute, second, month, day, year). Populates a `tm` struct on the stack and calls libc `mktime()` | `x0`-`x5` = h, m, s, mon, day, year | `x0` = Unix timestamp |
| `__rt_strtotime` | Parse trimmed date/time strings through strategy emitters: ISO dates/datetimes, time-only forms, bare keywords (`now`, `today`, `tomorrow`, `yesterday`, `midnight`, `noon`), relative offsets (`+1 day`, `3 months ago`), and named weekdays with `next` / `last` / `this`. Successful paths populate a `tm` struct and call libc `mktime()`; malformed input returns `-1`. | `x1`/`x2` = date string | `x0` = Unix timestamp or `-1` |
| `__rt_strtotime` | Parse trimmed date/time strings through strategy emitters: ISO dates/datetimes, time-only forms, bare keywords (`now`, `today`, `tomorrow`, `yesterday`, `midnight`, `noon`), relative offsets (`+1 day`, `3 months ago`, `a/an <unit>` article forms), and named weekdays with `next` / `last` / `this`. Successful paths populate a `tm` struct and call libc `mktime()`; malformed input returns `-1`. | `x1`/`x2` = date string | `x0` = Unix timestamp or `-1` |

### JSON routines

Expand Down Expand Up @@ -383,7 +383,7 @@ All regex routines use **POSIX extended regular expressions** via libc's `regcom
|---|---|---|---|
| `__rt_preg_match` | Test if a regex matches the subject string. Compiles the pattern, executes once, frees | pattern + subject strings | `x0` = 1 (match) or 0 (no match) |
| `__rt_preg_match_all` | Count all non-overlapping matches by repeatedly executing the regex with advancing offsets | pattern + subject strings | `x0` = match count |
| `__rt_preg_replace` | Replace all regex matches with a replacement string. Builds the result incrementally in the concat buffer | pattern + replacement + subject | `x1`/`x2` = result string |
| `__rt_preg_replace` | Replace all regex matches with a replacement string. Builds the result incrementally in the concat buffer and expands `$0`..`$9` / `\0`..`\9` from the `regexec()` capture vector | pattern + replacement + subject | `x1`/`x2` = result string |
| `__rt_preg_split` | Split the subject string at regex match boundaries. Returns a string array of the non-matching segments | pattern + subject strings | `x0` = array pointer |

## I/O routines
Expand Down
4 changes: 2 additions & 2 deletions docs/php/strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ Read-only. Negative indices count from end. Out-of-bounds returns empty string.
| `ctype_space()` | `ctype_space($str): bool` | All chars are whitespace |
| `preg_match()` | `preg_match($pattern, $subject): int` | Test if regex matches (1 or 0). Uses POSIX extended regex. |
| `preg_match_all()` | `preg_match_all($pattern, $subject): int` | Count all non-overlapping matches |
| `preg_replace()` | `preg_replace($pattern, $replacement, $subject): string` | Replace all regex matches |
| `preg_replace()` | `preg_replace($pattern, $replacement, $subject): string` | Replace all regex matches; supports `$0`..`$9` and `\0`..`\9` replacement backreferences |
| `preg_split()` | `preg_split($pattern, $subject): array` | Split string by regex pattern |

### Regex limitations

- Uses POSIX extended regex via libc, with translation of common PCRE shorthands (`\s`, `\d`, `\w`)
- `preg_replace()` expands `$0`..`$9` and `\0`..`\9` to captured groups; unmatched optional groups expand to an empty string
- Lookahead, lookbehind, non-greedy quantifiers are not supported
- `preg_match()` does not support `$matches` capture parameter
- `preg_replace()` does not support backreferences like `$1`
6 changes: 3 additions & 3 deletions docs/php/system-and-io.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ sidebar:
- **ISO date / datetime** — `YYYY-MM-DD`, `YYYY-MM-DD HH:MM`, `YYYY-MM-DD HH:MM:SS`, `YYYY-MM-DDTHH:MM`, or `YYYY-MM-DDTHH:MM:SS`. Lowercase `t` is also accepted as the date/time separator.
- **Bare keywords** — `now`, `today`, `tomorrow`, `yesterday`, `midnight`, `noon`. (`midnight` is an alias for `today`.)
- **Time-only** — `H:MM`, `HH:MM`, `H:MM:SS`, `HH:MM:SS` — combined with today's date.
- **Relative offsets** — `[+-]?N unit [N unit ...]` and `N unit ago` (negates the whole expression). Units: `sec(s)`, `second(s)`, `min(s)`, `minute(s)`, `hour(s)`, `day(s)`, `week(s)`, `month(s)`, `year(s)`. Composite forms like `"+1 day 2 hours"` and `"3 months ago"` are supported. Day/week offsets honor DST through libc `mktime` normalization.
- **Relative offsets** — `[+-]?N unit [N unit ...]`, `a/an unit`, and `N unit ago` / `a/an unit ago` (negates the whole expression). Units: `sec(s)`, `second(s)`, `min(s)`, `minute(s)`, `hour(s)`, `day(s)`, `week(s)`, `month(s)`, `year(s)`. Composite forms like `"+1 day 2 hours"`, `"an hour"`, and `"a day ago"` are supported. Day/week offsets honor DST through libc `mktime` normalization.
- **Named weekdays** — `Monday`..`Sunday` and 3-letter abbreviations `Mon`..`Sun`. Modifiers: `next <weekday>` (next future occurrence; today + 7 if today matches), `last <weekday>` (most recent past; today - 7 if today matches), `this <weekday>` (delta may be zero when today matches). Result is midnight of the target day.

Currently out of scope (not accepted): timezone offsets (`+0200`, `UTC`, ...), `@unix_timestamp` form, `first/last day of` patterns, `MM/DD/YYYY` and `DD-Mon-YYYY` alternative date shapes, `nth <weekday> of <month>` patterns. Malformed input returns `-1`.
Expand Down Expand Up @@ -131,10 +131,10 @@ Encoding rules for objects:
|---|---|---|
| `preg_match()` | `preg_match($pattern, $subject): int` | Test regex match (1 or 0) |
| `preg_match_all()` | `preg_match_all($pattern, $subject): int` | Count all non-overlapping matches |
| `preg_replace()` | `preg_replace($pattern, $replacement, $subject): string` | Replace all regex matches |
| `preg_replace()` | `preg_replace($pattern, $replacement, $subject): string` | Replace all regex matches; `$0`..`$9` and `\0`..`\9` replacement backreferences expand captured groups |
| `preg_split()` | `preg_split($pattern, $subject): array` | Split string by regex |

Uses POSIX extended regex with common PCRE shorthand translation (`\s`, `\d`, `\w`). Lookahead, lookbehind, non-greedy quantifiers not supported.
Uses POSIX extended regex with common PCRE shorthand translation (`\s`, `\d`, `\w`). Replacement backreferences `$0`..`$9` and `\0`..`\9` are expanded by `preg_replace()`. Lookahead, lookbehind, non-greedy quantifiers, and the `$matches` output parameter for `preg_match()` are not supported.

## File I/O

Expand Down
3 changes: 3 additions & 0 deletions examples/date-json-regex/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
$cleaned = preg_replace("/[ ]+/", " ", "hello world test");
echo "Cleaned: " . $cleaned . "\n";

$name = preg_replace("/([a-z]+) ([a-z]+)/", '$2, $1', "ada lovelace");
echo "Name swap: " . $name . "\n";

// Split
$parts = preg_split("/[,;]+/", "one,two;;three,four");
echo "Parts: " . count($parts) . "\n";
Expand Down
6 changes: 6 additions & 0 deletions examples/strtotime-relative/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
$past = strtotime("3 days ago");
echo "3 days ago = " . date("Y-m-d", $past) . "\n";

$article = strtotime("a day ago");
echo "a day ago = " . date("Y-m-d H:i", $article) . "\n";

$hour = strtotime("an hour");
echo "an hour = " . date("H:i", $hour) . "\n";

$weekday = strtotime("next Monday");
echo "next Monday = " . date("Y-m-d (D)", $weekday) . "\n";

Expand Down
Loading
Loading