انتقل إلى المحتوى

البنية

Phasis هو متجوّل شجرة من أربع مراحل مع آلة افتراضية للبايت كود تعمل عند الحاجة. ومسار البيانات هو:

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

كل مرحلة هي صنف PHP بواجهة صغيرة تستهلكها المرحلة التالية. لا توجد خطوة ربط؛ فالتحليل ينتج شجرة بنية مجردة، ويمر عليها المفسّر مباشرةً.

المحلّل المعجمي

يحوّل src/Lexer/Lexer.php النص المصدر إلى تدفق من مثيلات Token. وكل رمز بنية للقراءة فقط تضم النوع (تعداد TokenType بنحو 80 حالة) والقيمة والسطر والعمود.

يتعامل المحلّل المعجمي مع:

  • القيم العددية الحرفية — عشرية وسداسية عشرية (0x) وثمانية (0o) وثنائية (0b) وأسية، مع فواصل الأرقام.
  • السلاسل الحرفية — علامات اقتباس مفردة ومزدوجة، وكل تسلسلات الهروب، والقوالب الحرفية ذات التعبيرات المتداخلة.
  • التعبيرات النمطية الحرفية — تحليل كامل للرموز وفق /pattern/flags، مع مراعاة الحالات التي ينبغي فيها تفسير / كتعبير نمطي أو كعملية قسمة.
  • المعرّفات والكلمات المفتاحية وعلامات الترقيم.
  • التعليقات السطرية والكتلية، مع الاحتفاظ بها كمعلومات إضافية.
  • مصدر Unicode — تُفسَّر بايتات UTF-8 على أنها مصدر JS بترميز UTF-16.

المخرجات مولّد يُصدر الرموز عند الحاجة. ويستهلكها المحلّل مع استباق رمز واحد.

المحلّل

src/Parser/Parser.php محلّل Pratt مكتوب يدويًا. وتوجد أسبقية التعبيرات في src/Parser/Precedence.php. أما التعليمات فتستخدم أسلوب التحليل التنازلي العودي الخاص بها.

ينتج عقد شجرة بنية مجردة غير قابلة للتغيير من src/Ast/. وتحتفظ كل عقدة بموضعها في المصدر لتتبّع مكدس الاستدعاءات والإبلاغ عن الأخطاء.

أشكال بارزة:

  • التعبير: Literal، Identifier، BinaryExpression، UnaryExpression، AssignmentExpression، CallExpression، MemberExpression، ArrowFunction، ObjectExpression، ArrayExpression، ConditionalExpression، TemplateLiteral، SpreadElement، NewExpression، ThisExpression، SequenceExpression، TaggedTemplate، ClassExpression، YieldExpression، AwaitExpression.
  • التعليمة: BlockStatement، IfStatement، ForStatement، ForInStatement، ForOfStatement، WhileStatement، DoWhileStatement، SwitchStatement، ReturnStatement، ThrowStatement، TryStatement، BreakStatement، ContinueStatement، ExpressionStatement.
  • التصريح: VariableDeclaration، FunctionDeclaration، ClassDeclaration، ImportDeclaration.
  • النمط (التفكيك): ArrayPattern، ObjectPattern، RestElement، AssignmentPattern.

يُنفَّذ الإدراج التلقائي للفاصلة المنقوطة في المحلّل (وليس في الماسح اللغوي) وفقًا للمواصفات.

المفسّر

src/Runtime/Interpreter.php هو متجوّل AST. يزور كل عقدة ويعيد JsValue (أو Completion للعبارات التي تغيّر تدفّق التحكم).

الفئات المساندة الأساسية:

  • Environment (src/Runtime/Environment.php) — سلسلة نطاق معجمية. تنشئ كل مكالمة دالة بيئة جديدة تشير إلى والدها المعجمي. يُرفع var إلى نطاق الدالة؛ أما let وconst فنطاقهما كتلي ولهما TDZ.
  • CallStack (src/Runtime/CallStack.php) — مكدس صريح بحد أقصى للعمق. تدفع مكالمات الدوال والطرائق إطارات إليه، وتفكّ الاستثناءات هذه الإطارات.
  • Completion (src/Runtime/Completion.php) — اتحاد مميّز من normal وreturn وthrow وbreak وcontinue، إضافة إلى قيمة وتسمية اختياريتين.
  • Reference (src/Runtime/Reference.php) — نوع Reference في المواصفات لتقييم الطرف الأيسر في عمليات الإسناد وdelete.

القيم

قيم JavaScript هي كائنات PHP ضمن src/Value/. يطبّق كل منها الواجهة JsValue ويحمل طرائق تحويل النوع الخاصة به (toNumber() وtoString() وtoBoolean()).

الفئة نوع JS
JsUndefined undefined (مفردة)
JsNull null (مفردة)
JsBoolean true / false
JsNumber عدد عشري مزدوج وفق IEEE 754؛ يحافظ على NaN و±Infinity و±0
JsString دلالات السلاسل وفق UTF-16
JsObject كائن أساسي بخريطة خصائص وسلسلة نماذج أولية
JsArray يوسّع JsObject مع تتبّع الطول
JsFunction إغلاق يلتقط Environment
JsSymbol قيمة أولية Symbol
JsBigInt دقة اعتباطية عبر bcmath
JsProxy الفخاخ الـ13 كلها، قابلة للإلغاء
JsPromise منفّذ متزامن + طابور مهام دقيقة
JsGenerator وضع مزدوج: لقطة إطار (أجسام مترجمة إلى بايت كود؛ راجع مولّدات لقطة الإطار في آلة البايت كود) مع خيار احتياطي باستخدام Fiber من PHP لـyield* وtry-with-yield والمولّدات غير المتزامنة
JsMap وJsSet وJsWeakMap وJsWeakSet وJsWeakRef وJsFinalizationRegistry مجموعات
JsArrayBuffer وJsSharedArrayBuffer وJsDataView وJsTypedArray بيانات ثنائية

يمثّل التعامل مع الأعداد أكبر مصدر منفرد للأخطاء التاريخية: يجب أن يكون NaN !== NaN صحيحًا، ويجب أن يكون -0 === 0 صحيحًا، وتتطلب الحالات الطرفية لـToNumber عناية دقيقة على مستوى البتات. تطبّق الفئة Spec/TypeConversion خوارزميات المواصفات حرفيًا، وقد خضعت لاختبارات شاملة.

المكتبة المضمّنة

يوفّر src/BuiltIn/ كل كائنات المكتبة القياسية: Array وString وObject وMath وJSON وDate وRegExp وMap وSet وPromise وProxy وReflect وSymbol وBigInt وTypedArray وTemporal وIntl وconsole، إلى جانب حزم Web Platform وFetch (URL وHeaders وRequest وResponse وBlob وFormData وStreams كاملة وEventTarget وAbortController/AbortSignal وDOMException وstructuredClone وperformance وTextEncoder/TextDecoder وatob/btoa)، وcrypto + SubtleCrypto وWebSocket وXMLHttpRequest وحلقة الأحداث (setTimeout وsetInterval وqueueMicrotask وAsyncContext).

كل مكوّن مضمّن هو طريقة static من install() في فئة. يستدعيها Engine بترتيب الاعتماد عند الإنشاء. ويعرّف كل منها سلسلة النماذج الأولية الخاصة به، ويثبّت الطرائق ككائنات JsFunction مغلّفة حول إغلاقات PHP، ويتيح المُنشئ كعنصر عام.

جسر PHP↔JS

يتم تحويل القيم عبر الحدود في موضعين:

  • Phasis\Interop\PhpToJs::convert() — قيمة PHP إلى JsValue، وتُستخدم من قبل setGlobal() ومعاملات call() والقيم المعادة من دوال المضيف. يتحول callable من PHP إلى JsFunction يستدعي جسمه ذلك العنصر.
  • PhpToJs::toPhp() — من JsValue إلى PHP لمعاملات دالة المضيف.
  • Engine::toPhp() (خاص) — من JsValue إلى PHP لنتائج eval() وcall().

لا يوجد جسر للكائنات: تتحول كائنات PHP غير القابلة للاستدعاء إلى undefined (المشكلة #18). لا علاقة لفئة JsToPhp في src/Bytecode/ بالتشغيل البيني؛ فهي أداة التحويل الموضحة أدناه. قواعد التحويل موثقة في تحويل القيم.

معالجة الأخطاء

يتحوّل throw غير المعالج في JS إلى PHP Phasis\Exceptions\JsThrowable؛ أما الأخطاء التي يرفعها المحرك نفسه فتظهر كـPhasis\Exceptions\TypeError أو RangeError أو ReferenceError، وكلها فئات فرعية من Phasis\Exceptions\RuntimeError. داخل المحرك، يلتقط try / catch في JS كلاً من JsThrowable وأي RuntimeError وPhasis\Exceptions\SyntaxError. أما استثناءات PHP الأخرى، بما فيها تلك التي تطلقها دوال المضيف، فلا يجري تحويلها وتنتقل مباشرة إلى مستدعي PHP (المشكلة #20).

يوجد حدّان: سقف مكدس الاستدعاءات (CallStack::maxDepth، 4,096 إطارًا، إضافة إلى حارس من 8,192 إطارًا للإغلاقات المحوّلة) وmaxLoopIterations، الذي يُضبط عبر Engine::setLimit() ولا يطبّقه سوى متجوّل الشجرة (المشكلة #19). كلاهما يطلق Phasis\Exceptions\InternalError. لا توجد حدود لطول السلسلة أو حجم المخرجات أو زمن التنفيذ.

آلة البايت كود

تُحوّل الدوال كثيرة الاستخدام بطريقتين. يصدر JsToPhp (في src/Bytecode/) إغلاق PHP للأجسام التي تكثر فيها الأعداد أو المتغيرات المحلية، ويمكن لمترجم JIT التتبّعي في PHP تجميعه إلى شيفرة أصلية؛ إذ تحوّل سلسلة المعالجة قراءات new Ctor(args) وobj.method(args) وthis.member، وهذا يكفي لجعل النمط الشائع لطريقة فئة داخل حلقة يتجاوز الآلة الافتراضية بالكامل. أما كل ما عدا ذلك فيُترجم إلى بايت كود مسطّح يستهلكه VM::execute، مع مكدس استدعاءات مخصص يُبقي كل استدعاء JS داخل إطار PHP واحد، وذاكرة تخزين مؤقت مضمنة لكل PC لقراءات طرائق النماذج الأولية. كما تُترجم أجسام المولّدات البسيطة إلى بايت كود، إذ تلتقط Op::YIELD لقطة إطار على الكومة بدلًا من العبور عبر Fiber من PHP.

أي بنية لا يحوّلها المترجم (e.g وwith وtry مع catch يعيد الربط وyield* والمولّدات غير المتزامنة) تعود إلى متجوّل الشجرة. راجع آلة البايت كود للاطلاع على الآلية كاملة.