Lewati ke konten

Arsitektur

Phasis adalah tree walker empat tahap dengan VM bytecode oportunistis. Alur datanya:

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

Setiap tahap adalah kelas PHP dengan antarmuka kecil yang digunakan tahap berikutnya. Tidak ada tahap penautan — parsing menghasilkan AST yang langsung ditelusuri interpreter.

Lexer

src/Lexer/Lexer.php mengubah teks sumber menjadi aliran instans Token. Setiap token adalah struct readonly: tipe (enum TokenType, sekitar 80 kasus), nilai, baris, kolom.

Lexer menangani:

  • Literal angka — desimal, heksadesimal (0x), oktal (0o), biner (0b), eksponensial, pemisah numerik.
  • Literal string — tanda kutip tunggal dan ganda, semua urutan escape, literal template dengan ekspresi bersarang.
  • Literal ekspresi reguler — tokenisasi /pattern/flags penuh dengan mengetahui kapan / harus dibaca sebagai regex atau pembagian.
  • Identifier, kata kunci, tanda baca.
  • Komentar (baris dan blok), dipertahankan sebagai trivia.
  • Sumber Unicode — byte UTF-8 ditafsirkan sebagai sumber UTF-16 JS.

Output-nya berupa generator yang menghasilkan token secara malas. Parser mengonsumsinya dengan lookahead satu token.

Parser

src/Parser/Parser.php adalah parser Pratt yang ditulis manual. Prioritas ekspresi berada di src/Parser/Precedence.php. Pernyataan menggunakan recursive descent tersendiri.

Parser menghasilkan node AST immutable dari src/Ast/. Setiap node menyimpan lokasi sumbernya untuk stack trace dan pelaporan error.

Bentuk penting:

  • Ekspresi: Literal, Identifier, BinaryExpression, UnaryExpression, AssignmentExpression, CallExpression, MemberExpression, ArrowFunction, ObjectExpression, ArrayExpression, ConditionalExpression, TemplateLiteral, SpreadElement, NewExpression, ThisExpression, SequenceExpression, TaggedTemplate, ClassExpression, YieldExpression, AwaitExpression.
  • Pernyataan: BlockStatement, IfStatement, ForStatement, ForInStatement, ForOfStatement, WhileStatement, DoWhileStatement, SwitchStatement, ReturnStatement, ThrowStatement, TryStatement, BreakStatement, ContinueStatement, ExpressionStatement.
  • Deklarasi: VariableDeclaration, FunctionDeclaration, ClassDeclaration, ImportDeclaration.
  • Pola (destructuring): ArrayPattern, ObjectPattern, RestElement, AssignmentPattern.

Penyisipan titik koma otomatis diterapkan di parser (bukan lexer) sesuai spesifikasi.

Interpreter

src/Runtime/Interpreter.php adalah penelusur AST. Ia mengunjungi setiap node dan mengembalikan JsValue (atau Completion untuk pernyataan yang mengubah alur kontrol).

Kelas pendukung utama:

  • Environment (src/Runtime/Environment.php) — rantai cakupan leksikal. Setiap pemanggilan fungsi membuat lingkungan baru yang menunjuk ke induk leksikalnya. var diangkat ke cakupan fungsi; let / const memiliki cakupan blok dengan TDZ.
  • CallStack (src/Runtime/CallStack.php) — stack eksplisit dengan batas kedalaman. Pemanggilan fungsi dan metode menambahkan frame; exception mengurainya.
  • Completion (src/Runtime/Completion.php) — union bertag dari normal, return, throw, break, continue beserta nilai dan label opsional.
  • Reference (src/Runtime/Reference.php) — tipe Reference spesifikasi untuk evaluasi sisi kiri dalam assignment dan delete.

Nilai

Nilai JavaScript adalah objek PHP di bawah src/Value/. Masing-masing menerapkan antarmuka JsValue dan memiliki metode konversi tipenya sendiri (toNumber(), toString(), toBoolean()).

Kelas Tipe JS
JsUndefined undefined (singleton)
JsNull null (singleton)
JsBoolean true / false
JsNumber IEEE 754 double; mempertahankan NaN, ±Infinity, ±0
JsString semantik string UTF-16
JsObject objek dasar dengan peta properti dan rantai prototipe
JsArray memperluas JsObject dengan pelacakan panjang
JsFunction closure dengan Environment yang tertangkap
JsSymbol primitif Symbol
JsBigInt presisi arbitrer melalui bcmath
JsProxy semua 13 trap, dapat dicabut
JsPromise executor sinkron + antrean microtask
JsGenerator dua mode: frame-snapshot (badan terkompilasi bytecode; lihat VM Bytecode § Generator frame-snapshot) dengan fallback Fiber PHP untuk yield*, try-with-yield, dan generator asinkron
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry koleksi
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray data biner

Penanganan angka adalah sumber terbesar bug historis: NaN !== NaN harus bernilai true, -0 === 0 harus bernilai true, dan kasus khusus ToNumber memerlukan ketelitian hingga tingkat bit. Kelas Spec/TypeConversion menerapkan algoritme spesifikasi secara verbatim dan telah diuji secara menyeluruh.

Pustaka bawaan

src/BuiltIn/ menyediakan semua objek pustaka standar: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, serta paket Web Platform / Fetch (URL, Headers, Request, Response, Blob, FormData, Streams lengkap, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest, dan event loop (setTimeout, setInterval, queueMicrotask, AsyncContext).

Setiap bawaan adalah metode statis install() pada sebuah kelas. Engine memanggil semuanya sesuai urutan dependensi saat konstruksi. Masing-masing mendefinisikan rantai prototipe, memasang metode sebagai instans JsFunction yang dibungkus dalam closure PHP, dan mengekspos konstruktor sebagai global.

Jembatan PHP↔JS

Konversi nilai lintas batas berlangsung di dua tempat:

  • Phasis\Interop\PhpToJs::convert() — nilai PHP menjadi JsValue, digunakan untuk argumen setGlobal(), call(), dan nilai kembalian fungsi host. Callable PHP menjadi JsFunction yang isi fungsinya memanggil callable tersebut.
  • PhpToJs::toPhp() — JsValue menjadi PHP untuk argumen fungsi host.
  • Engine::toPhp() (privat) — JsValue menjadi PHP untuk hasil eval() dan call().

Tidak ada jembatan objek: objek PHP yang bukan callable dikonversi menjadi undefined (masalah #18). Kelas JsToPhp di src/Bytecode/ tidak terkait dengan interoperabilitas; kelas itu adalah transpiler yang dijelaskan di bawah. Aturan konversi didokumentasikan di Konversi nilai.

Penanganan error

JS throw yang tidak tertangkap menjadi PHP Phasis\Exceptions\JsThrowable; error yang dimunculkan engine sendiri muncul sebagai Phasis\Exceptions\TypeError, RangeError, atau ReferenceError, yang semuanya merupakan subclass dari Phasis\Exceptions\RuntimeError. Di dalam engine, JS try / catch menangkap JsThrowable, setiap RuntimeError, dan Phasis\Exceptions\SyntaxError. Exception PHP lainnya, termasuk yang dilempar oleh fungsi host, tidak dikonversi dan langsung diteruskan ke pemanggil PHP (masalah #20).

Ada dua batas: batas maksimum call stack (CallStack::maxDepth, 4,096 frame, ditambah penjaga 8,192 frame untuk closure yang ditranspilasi) dan maxLoopIterations, yang ditetapkan melalui Engine::setLimit() dan hanya diberlakukan oleh tree-walker (masalah #19). Keduanya memunculkan Phasis\Exceptions\InternalError. Tidak ada batas panjang string, ukuran output, atau waktu eksekusi.

VM Bytecode

Fungsi yang sering dipanggil diturunkan dengan dua cara. JsToPhp (di src/Bytecode/) menghasilkan closure PHP untuk isi fungsi yang berat pada numerik / variabel lokal dan dapat dikompilasi menjadi native oleh tracing JIT milik PHP; pipeline ini menurunkan pembacaan new Ctor(args), obj.method(args), dan this.member, sehingga pola kanonis metode kelas di dalam loop dapat sepenuhnya melewati VM. Sisanya dikompilasi menjadi bytecode datar yang dijalankan oleh VM::execute, dengan call stack khusus yang menjaga setiap pemanggilan JS di dalam satu frame PHP serta cache inline per-PC untuk pembacaan metode prototipe. Isi generator sederhana juga dikompilasi menjadi bytecode, dengan Op::YIELD yang menangkap snapshot frame di heap alih-alih melintasi Fiber PHP.

Bentuk apa pun yang tidak diturunkan oleh kompiler (e.g. with, try dengan catch yang mengikat ulang, yield*, generator asinkron) kembali menggunakan tree-walker. Lihat VM Bytecode untuk mekanisme selengkapnya.