refactor: Cleanup git state - commit all staged changes

Major refactoring cleanup:
- Add new controller architecture (class-controller-*.php)
- Add new settings-v2 UI (views/settings-v2/)
- Add new CSS architecture (agentic-sidebar.css, tokens)
- Add esbuild build pipeline (scripts/build.js, package.json)
- Add composer dependencies (vendor/)
- Add frontend src directory (assets/js/src/index.jsx)
- Add documentation files
- Remove old/obsolete files (class-settings.php, old CSS)

This commits all pending changes from previous refactoring efforts.
This commit is contained in:
Dwindi Ramadhana
2026-06-17 05:27:58 +07:00
parent d3f142222c
commit 690991c526
7963 changed files with 941566 additions and 67372 deletions

View File

@@ -0,0 +1,108 @@
---
title: Recursion
---
Often we want to parse arbitrarily nested structures. Arrays, JSON, XML are such example. To do that, we need to be able to pass the parser to itself. Because of a limitation in PHP, we cannot pass a value around before it is created. The solution is to split this in two steps: create a placeholder for a recursive parser, and then define the parser in terms of itself.
## Example
We need to parse nested pairs such as `[1,[2,[3,4]]]`. The structure repeats itself, every item in the pair can be either a digit or another pair.
We cannot write this:
```
<?php
$pair = collect(
ignore(char('[')),
digit()->or($pair),
ignore(char(',')),
digit()->or($pair),
ignore(char(']')),
);
```
The above results in "Undefined variable: pair" because we're trying to use `$pair` before it's defined. Instead, we need to mark the parser as `recursive` in a first step, and then define how the parser should `recurse`:
```php
<?php
// Create a recursive parser first
$pair = recursive();
// Then define the parser
$pair->recurse(
between(
char('['),
char(']'),
collect(
digitChar()->or($pair)
->thenIgnore(char(',')),
digitChar()->or($pair)
)
),
);
$result = $pair->tryString("[1,[2,[3,4]]]");
assertSame(['1', ['2', ['3', '4']]], $result->output());
```
It's possible to nest multiple recursive parsers. Simply initialise them all first using `recursive()` and then define them in terms of each other:
```php
<?php
$curlyPair = recursive();
$squarePair = recursive();
$anyPair = $curlyPair->or($squarePair);
$inner = collect(
digitChar()->or($anyPair)
->thenIgnore(char(',')),
digitChar()->or($anyPair)
);
$curlyPair->recurse(
between(char('{'), char('}'), $inner),
);
$squarePair->recurse(
between(char('['), char(']'), $inner),
);
$mixed = "{1,[2,{3,4}]}";
$result = $anyPair->tryString($mixed);
assertSame(['1', ['2', ['3', '4']]], $result->output());
```
Note that when you initialize a parser with `recursive()`, it is in fact mutable, and the `recurse()` method mutates it. All parsers are immutable, and this is the only exception. After calling `recurse()`, the parser is immutable again and behaves just like any other parser.
## Using recusion to avoid loops
Let's say we want to parse the character `'a'` at least one time, so that `"aaab"` outputs `"aaa"`, but `"bbb"` fails. Imperatively, you could solve this by running the `char('a')` parser in a while loop, and stop on the first failure. We can express it more concisely with recursion though:
1. Start by parsing `char('a')`.
2. Append another `char('a')`, but this second one is `optional()`.
3. Append another `optional(char('a'))`
4. Notice the similarity between the first two steps. This suggest an opportunity for recursion.
5. Wrap our `char('a')->append(optional(char('a')))` in a `recurse()` parser.
6. Replace the second `char('a')` by the recursive parser.
The end result looks like this:
```php
<?php
$rec = recursive();
$rec->recurse(char('a')->append(optional($rec)));
$result = $rec->tryString("aaab");
assertEquals("aaa", $result->output());
```
In fact the code above is how the `atLeastOne()` combinator works, so you can simplify that code by writing this:
```php
<?php
$parser = atLeastOne(char('a'));
$result = $parser->tryString("aaab");
assertEquals("aaa", $result->output());
```