
# Arvojen muunnos

Jokainen kutsu, joka ylittää rajan PHP↔JS — `setGlobal`, `call` tai kohteesta JS kutsuttu isäntäfunktio — kulkee muuntimen kautta:

- **PHP → JS**, kun isäntäarvot siirtyvät moottoriin (`setGlobal()`, `call()`-argumentit ja isäntäfunktioiden paluuarvot).
- **JS → PHP**, kun JS-arvot siirtyvät takaisin ulos. Käytössä on kaksi hieman eri säännöillä toimivaa polkua: `eval()`- ja `call()`-tulokset sekä argumentit, jotka JS välittää isäntäfunktiolle.

Muunnos kopioi arvot. Primitiivit muunnetaan primitiiveiksi, taulukot ja oliot kopioidaan uusiin rakenteisiin, ja PHP-kutsuttavat kääritään JS-funktioiksi. Mitään ei jaeta viitteenä kutsuttavia itseään lukuun ottamatta.

## PHP → JS

| PHP-arvo | JS-tulos |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (IEEE 754 -kaksoistarkkuusluku; kokonaisluvut yli 2^53 menettävät tarkkuutta) |
| `float` | `number` (NaN, ±Infinity ja ±0 säilyvät) |
| `string` | `string` (UTF-8 PHP-tavut tulkitaan UTF-16-koodiyksiköiksi), myös silloin, kun nimi viittaa PHP-funktioon |
| list `array` (avaimet 0, 1, 2, … järjestyksessä) | uusi `Array`, alkiot muunnetaan rekursiivisesti |
| mikä tahansa muu `array` | uusi tavallinen `Object`, arvot muunnetaan rekursiivisesti |
| `Closure`, kutsuttava olio tai kutsuttava taulukko, kuten `[$object, 'method']` | `function`, joka kutsuu PHP-kutsuttavaa |
| mikä tahansa muu olio, `Generator` tai resurssi | `undefined` |

Sekalaisten avainten taulukoista (kohteen PHP `[0 => 'a', 'name' => 'b']`) tulee tavallisia olioita, koska kohteen JS taulukoiden indeksien on muodostettava yhtenäinen kokonaislukujono.

```php
$engine->setGlobal('list', [1, 2, 3]);                 // → JS Array
$engine->setGlobal('map',  ['a' => 1, 'b' => 2]);      // → JS Object
$engine->setGlobal('mixed', [0 => 'x', 'k' => 'v']);   // → JS Object
```

Koska taulukot kopioidaan, kohteessa JS tehdyt muutokset jäävät kohteeseen JS:

```php
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list);                 // 3
$engine->eval('list.length'); // 4
```

PHP-oliot, jotka eivät ole kutsuttavia, eivät muunnu; ne saapuvat muodossa `undefined` ([issue #18](https://github.com/inline0/phasis/issues/18)). Muunna ne ensin taulukoiksi esimerkiksi `get_object_vars()`-määrityksellä tai tarjoa niiden metodit sulkeumina. Katso [Jaetut oliot](https://phasis.inline0.dev/fi/docs/interop/shared-objects).

## JS → PHP: `eval()`- ja `call()`-tulokset

| JS-arvo | PHP-tulos |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (kokonaisluku, int-alueella) | `int` (`-0` muuttuu arvoksi `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (muu) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | list `array`, alkiot muunnetaan rekursiivisesti |
| `TypedArray` | list `array` numeroita |
| mikä tahansa muu olio (tavalliset oliot, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | assosiatiivinen `array`, joka sisältää olion omat dataominaisuudet; getterit ja funktioarvoiset ominaisuudet ohitetaan |

Viimeinen rivi tarkoittaa, että oliot, joiden tila on sisäisissä paikoissa, muuntuvat tyhjiksi taulukoiksi: `new Date(0)`, `new Map([["a", 1]])` ja `Promise.resolve(5)` palauttavat kaikki arvon `[]`. `Error` palauttaa omat `message`- ja `stack`-arvonsa. Itseensä viittaavan olion sisäiset viittaukset muunnetaan arvoon `null`.

```php
$engine->eval('[1, 2, 3]');                // [1, 2, 3]
$engine->eval('({a: 1, b: 2})');           // ['a' => 1, 'b' => 2]
$engine->eval('new Uint8Array([1, 2, 3])'); // [1, 2, 3]
$engine->eval('1234567890123456789n');     // null
$engine->eval('new Date(0)');              // []
```

Muunna arvo kohteessa JS, kun tarvitset jonkin näistä arvoista kohteessa PHP:

```php
$engine->eval('(1234567890123456789n).toString()');  // "1234567890123456789"
$engine->eval('new Date(0).toISOString()');           // "1970-01-01T00:00:00.000Z"
$engine->eval('Object.fromEntries(new Map([["a", 1]]))'); // ['a' => 1]
```

## JS → PHP: isäntäfunktion argumentit

Argumentit, jotka JS välittää PHP-isäntäfunktiolle, noudattavat samoja primitiivien, taulukoiden ja tavallisten olioiden sääntöjä, seuraavin poikkeuksin:

| JS-arvo | PHP-argumentti |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | moottorin oma `JsValue`-olio, muuntamattomana |
| olio | assosiatiivinen `array`, joka sisältää kaikki olion omat ominaisuudet, myös getterit (joita kutsutaan) ja funktioarvoiset ominaisuudet |
| nuolifunktio | `array` (yleensä tyhjä) |
| `function`, joka on määritetty avainsanalla `function`, tai mikä tahansa itseensä viittaava olio | ei muunnosta: kopiointi ei pääty, ja PHP-muistista loppuu ([issue #21](https://github.com/inline0/phasis/issues/21)) |

Isäntäfunktiossa ei vielä voi vastaanottaa JS-takaisinkutsua. Välitä rajan yli dataa, älä funktioita.

## Numerot ja tarkkuus

JavaScript-numerot ovat IEEE 754-kaksoistarkkuuslukuja. PHP-kokonaisluvut ovat alustakohtaisia (64-bittisiä kaikissa nykyaikaisissa koontiversioissa). Muunnos säilyttää kokonaisluvut täsmälleen arvoon ±2^53 asti; suuremmat PHP-kokonaisluvut muuttuvat arvoon `number` ja menettävät tarkkuutta yli 16 desimaalin jälkeen. Käytä kohteessa JS toimintoa `BigInt`, jos tarvitset täsmällistä laskentaa sitä suuremmilla kokonaisluvuilla, ja välitä tulos ulos merkkijonona.

## Merkkijonot

PHP-merkkijonot ovat tavutaulukoita; JS-merkkijonot ovat UTF-16-koodiyksikköjonoja. Moottori muuntaa ne tulkitsemalla PHP-tavut UTF-8-muodossa ja koodaamalla ne uudelleen sisäisesti UTF-16-muotoon. Tämä on häviötöntä kelvolliselle UTF-8-syötteelle. Virheelliset UTF-8-tavut välitetään sellaisinaan sen sijaan, että ne korvattaisiin merkillä U+FFFD.

`String.length`, indeksoitu käyttö (`s[0]`) ja viipalointi laskevat kaikki UTF-16-koodiyksiköt ECMAScript-määrityksen mukaisesti — eivät koodipisteitä tai tavuja. Siksi merkkijonoissa, joissa on astral-merkkejä, kuten `'🍕'`, on `.length === 2` (U+1F355:n kaksi surrogaattipuolikasta).

## Edestakaisen muunnoksen vakaus

Pelkkä data säilyy edestakaisessa muunnoksessa: PHP-arvo, joka koostuu skalaareista, listoista ja assosiatiivisista taulukoista ja joka välitetään sisään `setGlobal()`-kautta ja palautetaan `eval()`- tai `call()`-kautta, tuottaa saman rakenteen, paitsi että kokonaislukuarvoiset liukuluvut palautuvat muodossa `int` (`1.0` palauttaa arvon `1`).

Kaikki muu yllä olevissa taulukoissa, joka muunnetaan muotoon `null`, `undefined` tai `[]`, ei säily edestakaisessa muunnoksessa.
