
# Väärtuste teisendamine

Iga kõne, mis ületab piiri PHP↔JS — `setGlobal`, `call`, host-funktsiooni kutsumine JS-st — läbib teisendaja:

- **PHP → JS**, kui hosti väärtused liiguvad mootorisse (`setGlobal()`, `call()` argumendid, host-funktsioonide tagastusväärtused).
- **JS → PHP**, kui JS väärtused liiguvad tagasi välja. Kasutusel on kaks veidi erinevate reeglitega rada: `eval()` ja `call()` tulemused ning argumendid, mille JS host-funktsioonile edastab.

Teisendamisel tehakse koopia. Primitiivid teisenduvad primitiivideks, massiivid ja objektid kopeeritakse uutesse struktuuridesse ning PHP-i kutsutavad objektid pakendatakse JS funktsioonideks. Viite kaudu jagatakse ainult kutsutavaid objekte.

## PHP → JS

| PHP väärtus | JS tulemus |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (IEEE 754 double; täisarvud üle 2^53 kaotavad täpsuse) |
| `float` | `number` (NaN, ±Infinity, ±0 säilivad) |
| `string` | `string` (UTF-8 PHP baite tõlgendatakse UTF-16 koodiühikutena), ka siis, kui see nimetab PHP funktsiooni |
| loendi `array` (võtmed 0, 1, 2, … järjekorras) | uus `Array`, elemendid teisendatakse rekursiivselt |
| mis tahes muu `array` | uus lihtne `Object`, väärtused teisendatakse rekursiivselt |
| `Closure`, väljakutsutav objekt, väljakutsutav massiiv, näiteks `[$object, 'method']` | `function`, mis kutsub PHP-i väljakutsutavat objekti |
| mis tahes muu objekt, `Generator`, ressurss | `undefined` |

Segavõtmetega massiivid (PHP-i `[0 => 'a', 'name' => 'b']`) muutuvad lihtobjektideks, sest JS massiividel peab olema katkematu täisarvuline indeksiruum.

```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
```

Kuna massiivid kopeeritakse, jäävad JS muudatused JS-i:

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

PHP objektid, mida ei saa välja kutsuda, ei teisendu ja saabuvad kujul `undefined` ([probleem #18](https://github.com/inline0/phasis/issues/18)). Teisenda need esmalt massiivideks, näiteks käsuga `get_object_vars()`, või avalikusta nende meetodid sulunditena. Vt [Shared objects](https://phasis.inline0.dev/et/docs/interop/shared-objects).

## JS → PHP: tulemused `eval()` ja `call()` korral

| JS väärtus | PHP tulemus |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (täisarv, int-vahemikus) | `int` (`-0` muutub väärtuseks `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (muu) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | loend `array`, elemendid teisendatakse rekursiivselt |
| `TypedArray` | arvude loend `array` |
| mis tahes muu objekt (lihtobjektid, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | assotsiatiivne `array`, mis sisaldab objekti enda andmeomadusi; gettereid ja funktsiooniväärtusega omadusi ei arvestata |

Viimane rida tähendab, et objektid, mille olek asub sisemistes pesades, teisenduvad tühjaks massiiviks: `new Date(0)`, `new Map([["a", 1]])` ja `Promise.resolve(5)` tagastavad kõik `[]`. `Error` tagastab enda `message` ja `stack`. Iseendale viitava objekti sisemised viited teisenduvad väärtuseks `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)');              // []
```

Kui vajad mõnda neist väärtustest PHP-s, teisenda see JS sees:

```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: host-funktsiooni argumendid

Argumendid, mille JS edastab host-funktsioonile PHP, järgivad primitiivide, massiivide ja lihtobjektide puhul samu reegleid, kuid erinevad järgmiselt:

| JS väärtus | PHP argument |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | mootori enda `JsValue` objekt, teisendamata |
| objekt | assotsiatiivne `array` kõigi objekti enda omadustega, sealhulgas getteritega (mis käivitatakse) ja funktsiooniväärtusega omadustega |
| noolefunktsioon | `array` (tavaliselt tühi) |
| `function`, mis on deklareeritud märksõnaga `function`, või mis tahes iseendale viitav objekt | ei teisendata: kopeerimine ei lõpe ja PHP-l saab mälu otsa ([probleem #21](https://github.com/inline0/phasis/issues/21)) |

Host-funktsioonis ei saa praegu vastu võtta JS tagasihelistusfunktsiooni. Edasta üle selle piiri andmeid, mitte funktsioone.

## Arvud ja täpsus

JavaScript arvud on IEEE 754 topelttäpsusega arvud. PHP täisarvud kasutavad platvormi algset vormingut (kõigis tänapäevastes järkudes 64-bitine). Teisendamisel säilivad täisarvud täpselt kuni ±2^53-ni; sellest suuremad PHP täisarvud muutuvad väärtuseks `number` ja kaotavad täpsuse üle 16 kümnendkoha. Kui vajad suuremate täisarvudega täpset arvutamist, kasuta selleks JS-s `BigInt` ja edasta tulemus stringina.

## Stringid

PHP stringid on baidimassiivid; JS stringid on UTF-16 koodiühikute jadad. Mootor teisendab need, tõlgendades PHP baite UTF-8-na ja kodeerides need sisemiselt uuesti UTF-16-na. Kehtiv UTF-8-sisend teisendub kadudeta. Kehtetu UTF-8-ga baidid edastatakse muutmata, selle asemel et asendada need märgiga U+FFFD.

`String.length`, indekseerimine (`s[0]`) ja lõikamine loendavad ECMAScripti spetsifikatsiooni järgi UTF-16 koodiühikuid, mitte koodipunkte ega baite. Seetõttu on astralsümboleid, näiteks `'🍕'`, sisaldavatel stringidel `.length === 2` (U+1F355 kaks asenduspaari poolt).

## Edasi-tagasi teisendamise stabiilsus

Lihtandmed säilivad edasi-tagasi teisendamisel: skalaare, loendeid ja assotsiatiivseid massiive sisaldav väärtus PHP, mis siseneb `setGlobal()` kaudu ja tuleb tagasi `eval()` või `call()` kaudu, annab sama struktuuri, välja arvatud see, et täisarvulised ujukomaarvud tulevad tagasi kujul `int` (`1.0` tagastab `1`).

Kõik muu ülaltoodud tabelites, mis teisendub kujule `null`, `undefined` või `[]`, ei säili edasi-tagasi teisendamisel.
