
# Arkkitehtuuri

Phasis on nelivaiheinen puukävelijä, jossa on opportunistinen tavukoodikäyttöympäristö. Tietopolku on seuraava:

```
JS text
  │
  ▼
┌──────────┐
│  Lexer   │  src/Lexer/
└────┬─────┘
     ▼ tokens
┌──────────┐
│  Parser  │  src/Parser/
└────┬─────┘
     ▼ AST nodes
┌──────────────┐
│ Interpreter  │  src/Runtime/
│  + Bytecode  │  src/Bytecode/
│      VM      │
└────┬─────────┘
     ▼ JsValue
PHP host
```

Jokainen vaihe on PHP-luokka, jonka suppea rajapinta on seuraavan vaiheen käytössä. Linkitysvaihetta ei ole — jäsennys tuottaa AST:n, jota tulkki käy suoraan läpi.

## Leksikaattori

`src/Lexer/Lexer.php` muuntaa lähdetekstin `Token`-instanssien virraksi. Jokainen token on vain luku -rakenteinen: tyyppi (`TokenType`-enumeraatio, noin 80 tapausta), arvo, rivi ja sarake.

Leksikaattori käsittelee seuraavat:

- Numeroliteraalit — desimaali, heksadesimaali (`0x`), oktaali (`0o`), binääri (`0b`), eksponenttimuoto ja numeroryhmien erottimet.
- Merkkijonoliteraalit — yksinkertaiset ja kaksinkertaiset lainausmerkit, kaikki escape-sekvenssit sekä sisäkkäisiä lausekkeita sisältävät mallimerkkijonot.
- Säännöllisten lausekkeiden literaalit — täydellinen `/pattern/flags`-tokenisointi, jossa huomioidaan, onko `/` säännöllinen lauseke vai jakolasku.
- Tunnisteet, avainsanat ja välimerkit.
- Kommentit (rivi- ja lohkokommentit), jotka säilytetään triviana.
- Unicode-lähde — UTF-8-tavut tulkitaan JS UTF-16-lähteeksi.

Tuloste on generaattori, joka tuottaa tokeneita laiskasti. Jäsennin kuluttaa niitä yhden tokenin ennakoinnilla.

## Jäsennin

`src/Parser/Parser.php` on käsin kirjoitettu Pratt-jäsennin. Lausekkeiden etusijajärjestys määritellään kohteessa `src/Parser/Precedence.php`. Lausekkeilla on oma rekursiivinen laskeutumisjäsennyksensä.

Se tuottaa muuttumattomia AST-solmuja kohteesta `src/Ast/`. Jokaisessa solmussa on lähdesijainti pinon jäljityksiä ja virheraportointia varten.

Huomionarvoisia rakenteita:

- **Lauseke**: `Literal`, `Identifier`, `BinaryExpression`, `UnaryExpression`, `AssignmentExpression`, `CallExpression`, `MemberExpression`, `ArrowFunction`, `ObjectExpression`, `ArrayExpression`, `ConditionalExpression`, `TemplateLiteral`, `SpreadElement`, `NewExpression`, `ThisExpression`, `SequenceExpression`, `TaggedTemplate`, `ClassExpression`, `YieldExpression`, `AwaitExpression`.
- **Lause**: `BlockStatement`, `IfStatement`, `ForStatement`, `ForInStatement`, `ForOfStatement`, `WhileStatement`, `DoWhileStatement`, `SwitchStatement`, `ReturnStatement`, `ThrowStatement`, `TryStatement`, `BreakStatement`, `ContinueStatement`, `ExpressionStatement`.
- **Määrittely**: `VariableDeclaration`, `FunctionDeclaration`, `ClassDeclaration`, `ImportDeclaration`.
- **Malli** (hajautus): `ArrayPattern`, `ObjectPattern`, `RestElement`, `AssignmentPattern`.

Automaattinen puolipisteen lisäys toteutetaan määrityksen mukaisesti jäsentimessä (ei leksikaattorissa).

## Tulkki

`src/Runtime/Interpreter.php` käy AST:n läpi. Se vierailee jokaisessa solmussa ja palauttaa `JsValue`-arvon (tai `Completion`-arvon lauseille, jotka muuttavat ohjausvirtaa).

Keskeiset tukiluokat:

- **`Environment`** (`src/Runtime/Environment.php`) — leksikaalisten näkyvyysalueiden ketju. Jokainen funktiokutsu luo uuden ympäristön, joka osoittaa leksikaaliseen ylätasoon. `var` nostetaan funktion näkyvyysalueeseen; `let` / `const` ovat lohkokohtaisia, ja niihin liittyy TDZ.
- **`CallStack`** (`src/Runtime/CallStack.php`) — eksplisiittinen pino, jolla on syvyysraja. Funktio- ja metodikutsut lisäävät kehyksiä; poikkeukset purkavat ne.
- **`Completion`** (`src/Runtime/Completion.php`) — eroteltu unioni tyypeistä `normal`, `return`, `throw`, `break` ja `continue` sekä valinnainen arvo ja tunniste.
- **`Reference`** (`src/Runtime/Reference.php`) — määrityksen Reference-tyyppi vasemman puolen evaluointiin sijoituksissa ja `delete`-lausekkeissa.

## Arvot

JavaScript-arvot ovat PHP-olioita kohteessa `src/Value/`. Kukin toteuttaa `JsValue`-rajapinnan ja sisältää omat tyyppimuunnosmetodinsa (`toNumber()`, `toString()`, `toBoolean()`).

| Luokka | JS-tyyppi |
|---|---|
| `JsUndefined` | `undefined` (singleton) |
| `JsNull` | `null` (singleton) |
| `JsBoolean` | `true` / `false` |
| `JsNumber` | IEEE 754 double; säilyttää arvot NaN, ±Infinity ja ±0 |
| `JsString` | UTF-16-merkkijonon semantiikka |
| `JsObject` | perusolio, jossa on ominaisuuskartta ja prototyyppiketju |
| `JsArray` | laajentaa `JsObject`-luokkaa ja seuraa pituutta |
| `JsFunction` | sulkeuma, johon on siepattu `Environment` |
| `JsSymbol` | Symbol-primitiivi |
| `JsBigInt` | mielivaltaisen tarkkuuden aritmetiikka bcmath-kirjastolla |
| `JsProxy` | kaikki 13 ansaa, peruutettavissa |
| `JsPromise` | synkroninen suorittaja + mikrotehtäväjono |
| `JsGenerator` | kaksitoiminen: kehysvedos (tavukoodiksi käännetyt rungot; katso [Tavukoodikäyttöympäristö § Kehysvedosgeneraattorit](https://phasis.inline0.dev/fi/docs/advanced/bytecode-vm)) ja PHP Fiber -vararatkaisu kohteille `yield*`, try-with-yield ja asynkroniset generaattorit |
| `JsMap`, `JsSet`, `JsWeakMap`, `JsWeakSet`, `JsWeakRef`, `JsFinalizationRegistry` | kokoelmat |
| `JsArrayBuffer`, `JsSharedArrayBuffer`, `JsDataView`, `JsTypedArray` | binääritiedot |

Lukujen käsittely on historiallisesti ollut suurin virhelähde: `NaN !== NaN` täytyy olla tosi, `-0 === 0` täytyy olla tosi, ja ToNumber-reunatapaukset vaativat täsmällistä bittitason käsittelyä. `Spec/TypeConversion`-luokka toteuttaa määrityksen algoritmit sanatarkasti, ja se on testattu kattavasti.

## Sisäänrakennettu kirjasto

`src/BuiltIn/` tarjoaa kaikki vakiokirjaston oliot: `Array`, `String`, `Object`, `Math`, `JSON`, `Date`, `RegExp`, `Map`, `Set`, `Promise`, `Proxy`, `Reflect`, `Symbol`, `BigInt`, `TypedArray`, `Temporal`, `Intl`, `console` sekä Web Platform- ja Fetch-paketit (`URL`, `Headers`, `Request`, `Response`, `Blob`, `FormData`, täysi Streams-tuki, `EventTarget`, `AbortController`/`AbortSignal`, `DOMException`, `structuredClone`, `performance`, `TextEncoder`/`TextDecoder`, `atob`/`btoa`), `crypto` + `SubtleCrypto`, `WebSocket`, `XMLHttpRequest` sekä tapahtumasilmukan (`setTimeout`, `setInterval`, `queueMicrotask`, `AsyncContext`).

Jokainen sisäänrakennettu toiminto on luokan staattinen `install()`-metodi. `Engine` kutsuu niitä riippuvuusjärjestyksessä alustuksen yhteydessä. Kukin määrittää prototyyppiketjunsa, asentaa metodit `JsFunction`-instansseina, jotka on kääritty PHP-sulkeumiin, ja tuo konstruktorin saataville globaalina.

## PHP↔JS-silta

Arvojen muuntaminen rajapinnan yli tapahtuu kahdessa kohdassa:

- **`Phasis\Interop\PhpToJs::convert()`** — PHP-arvo `JsValue`-arvoksi; käytössä kohteille `setGlobal()`, `call()`-argumentit ja isäntäfunktion palautusarvot. PHP-kutsukelpoisesta tulee `JsFunction`, jonka runko kutsuu sitä.
- **`PhpToJs::toPhp()`** — `JsValue` arvoksi PHP isäntäfunktion argumentteja varten.
- **`Engine::toPhp()`** (yksityinen) — `JsValue` arvoksi PHP kohteiden `eval()` ja `call()` tuloksia varten.

Oliosiltaa ei ole: PHP-oliot, jotka eivät ole kutsuttavia, muunnetaan muotoon `undefined` ([ongelma #18](https://github.com/inline0/phasis/issues/18)). Kohteen `src/Bytecode/` luokka `JsToPhp` ei liity yhteentoimivuuteen, vaan se on alla kuvattu transpilaattori. Muuntosäännöt on dokumentoitu kohdassa [Arvojen muuntaminen](https://phasis.inline0.dev/fi/docs/interop/values).

## Virheenkäsittely

Kiinni ottamaton JS `throw` muuttuu muotoon PHP `Phasis\Exceptions\JsThrowable`; moottorin itsensä nostamat virheet näkyvät muodossa `Phasis\Exceptions\TypeError`, `RangeError` tai `ReferenceError`, jotka kaikki ovat `Phasis\Exceptions\RuntimeError`-luokan aliluokkia. Moottorin sisällä JS `try` / `catch` ottaa kiinni kohteista `JsThrowable`, kaikista `RuntimeError`- ja `Phasis\Exceptions\SyntaxError`-arvoista. Muita PHP-poikkeuksia, kuten isäntäfunktioiden nostamia poikkeuksia, ei muunnettu, vaan ne välittyvät suoraan PHP-kutsujalle ([ongelma #20](https://github.com/inline0/phasis/issues/20)).

Rajoituksia on kaksi: kutsupinon yläraja (`CallStack::maxDepth`, 4,096 kehystä sekä 8,192-kehyksen suoja transpiloituja sulkeumia varten) ja `maxLoopIterations`, joka asetetaan kohteessa `Engine::setLimit()` ja jota valvoo vain puukävelijä ([ongelma #19](https://github.com/inline0/phasis/issues/19)). Molemmat nostavat `Phasis\Exceptions\InternalError`-poikkeuksen. Merkkijonon pituutta, tulosteen kokoa tai suoritusaikaa ei rajoiteta.

## Tavukoodikäyttöympäristö

Usein kutsutut funktiot muunnetaan kahdella tavalla. `JsToPhp` (kohteessa `src/Bytecode/`) tuottaa PHP-sulkeuman numeerisille ja paikallismuuttujapainotteisille rungoille, jotka kohteen PHP jäljityspohjainen JIT-kääntäjä voi kääntää natiivikoodiksi; putki muuntaa kohteiden `new Ctor(args)`, `obj.method(args)` ja `this.member` lukemiset. Näin tavallinen luokkametodikutsujen silmukka voi ohittaa VM:n kokonaan. Kaikki muu käännetään litteäksi tavukoodiksi, jota `VM::execute` suorittaa. Käytössä on mukautettu kutsupino, joka pitää jokaisen JS-kutsun yhden PHP-kehyksen sisällä, sekä PC-kohtainen välimuisti prototyyppimetodien lukemista varten. Myös yksinkertaiset generaattorirungot käännetään tavukoodiksi; `Op::YIELD` sieppaa kehyksen tilannevedoksen kekoon Fiberin PHP-rajapinnan ylittämisen sijaan.

Kaikki muodot, joita kääntäjä ei muunna (e.g, `with`, `try` uudelleen sidottavalla catch-käsittelijällä, `yield*` ja asynkroniset generaattorit), palautuvat puukävelijälle. Koko toimintamekanismi on kuvattu kohdassa [Tavukoodikäyttöympäristö](https://phasis.inline0.dev/fi/docs/advanced/bytecode-vm).
