diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 75ffb2a..0cf91e6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,14 +21,18 @@ jobs:
- name: جلب المستودع
uses: actions/checkout@v4
+ # (AR) نسخةٌ مثبَّتةٌ لا «latest»: قياساتُ الثيم (حاويةُ الجدول `.table-wrapper`،
+ # بنيةُ `print.html`، مُعرِّفاتُ العناوين العربيّة الخام) أُجريت على 0.5.4،
+ # وحرّاسُ `theme/page-toc.js` مبنيّةٌ عليها. ترقيةٌ صامتةٌ في «latest» تُبطلها
+ # بلا إنذار. الترقيةُ قرارٌ يُراجَع في PR لا حدثٌ يقع من تلقائه.
- name: تثبيت mdBook
uses: peaceiris/actions-mdbook@v2
with:
- mdbook-version: latest
+ mdbook-version: "0.5.4"
- name: تثبيت mdbook-mermaid + أصوله
run: |
- cargo install mdbook-mermaid --locked
+ cargo install mdbook-mermaid --version 0.17.0 --locked
mdbook-mermaid install .
- name: البناء
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index a3adeef..847a101 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -26,14 +26,18 @@ jobs:
- name: جلب المستودع
uses: actions/checkout@v4
+ # (AR) نسخةٌ مثبَّتةٌ لا «latest»: قياساتُ الثيم (حاويةُ الجدول `.table-wrapper`،
+ # بنيةُ `print.html`، مُعرِّفاتُ العناوين العربيّة الخام) أُجريت على 0.5.4،
+ # وحرّاسُ `theme/page-toc.js` مبنيّةٌ عليها. ترقيةٌ صامتةٌ في «latest» تُبطلها
+ # بلا إنذار. الترقيةُ قرارٌ يُراجَع في PR لا حدثٌ يقع من تلقائه.
- name: تثبيت mdBook
uses: peaceiris/actions-mdbook@v2
with:
- mdbook-version: latest
+ mdbook-version: "0.5.4"
- name: تثبيت mdbook-mermaid + أصوله
run: |
- cargo install mdbook-mermaid --locked
+ cargo install mdbook-mermaid --version 0.17.0 --locked
mdbook-mermaid install .
- name: البناء
diff --git a/book.toml b/book.toml
index bdfa3c2..8236372 100644
--- a/book.toml
+++ b/book.toml
@@ -16,7 +16,7 @@ preferred-dark-theme = "navy"
git-repository-url = "https://github.com/sadlang/dev-guide"
edit-url-template = "https://github.com/sadlang/dev-guide/edit/main/{path}"
additional-css = ["theme/rtl.css", "theme/custom.css"]
-additional-js = ["theme/font-zoom.js"]
+additional-js = ["theme/font-zoom.js", "theme/page-toc.js"]
mathjax-support = false
[output.html.fold]
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index cf54982..c40c89b 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -36,7 +36,7 @@
- [المفسّر الشجري (Interpreter)](backend/interpreter.md)
- [التمثيل الوسيط SIR](backend/sir.md)
- [توليد LLVM (المترجم sadc)](backend/llvm.md)
-- [الآلة الافتراضية (VM)](backend/vm.md)
+- [الخلفيّة الأصليّة بلا LLVM (SIR → ELF64)](backend/native.md)
- [دراسة حالة: توحيد هاش/شفّر/فك_تشفير](backend/crypto-unification.md)
- [دراسة حالة: توسيع مكتبة التشفير (٥ مراحل + Argon2id)](backend/crypto-library-expansion.md)
diff --git a/src/architecture/overview.md b/src/architecture/overview.md
index 96e1493..7ec943f 100644
--- a/src/architecture/overview.md
+++ b/src/architecture/overview.md
@@ -9,7 +9,8 @@
| النواة المشتركة | `shared/` | معجمي، نحوي، AST، نظام الأنواع `Value`، نظام الأخطاء |
| المفسّر | `interpreter/` | مفسّر شجريّ؛ `InterpreterCore` يدير المتغيّرات والدوال والنطاقات والتقييم |
| المترجم | `compiler/` | AST → SIR → LLVM IR → ملفّ تنفيذيّ (SIR يدعم تعليمات ملكية) |
-| الآلة الافتراضية | `vm/` | بايت كود مرتبط مباشرةً بالمفسّر |
+| الخلفيّة الأصليّة | `compiler/include/backend/native/` | SIR → شيفرة آلة → ELF64 ساكن بلا LLVM ولا رابطٍ أجنبيّ — [الفصل](../backend/native.md) |
+| ~~الآلة الافتراضية~~ | — | `vm/` أُزيل من الشجرة بالإيداع `bcf0a746` («ستُعاد كتابتها من الصفر») — لا فصل له حتّى تُكتب |
| المكتبة القياسية | `stdlib/` | وحدات عربية: core/io/math/string/network/graphics |
| الأدوات | `tools/` | lsp · formatter · pkg · repl · sadc CLI · sadinfo |
| مصدر الحقيقة | `language-truth/` | YAML SoT لكل بيانات اللغة + القواعد |
@@ -26,7 +27,7 @@ flowchart TD
SRC["مصدر .ص (UTF-8)"] --> LEX["LexerCore
shared/lexer"]
LEX --> PAR["ParserCore
shared/parser"]
PAR --> AST["AST
shared/ast"]
- AST --> INT["InterpreterCore / VM
(تنفيذ فوريّ)"]
+ AST --> INT["InterpreterCore
(تنفيذ فوريّ)"]
AST --> SIR["SIRBuilder
compiler/src/frontend"]
SIR --> OPT["SIROptimizer"]
OPT --> LLVM["LLVMCodeGen
compiler/src/backend/llvm"]
diff --git a/src/backend/llvm.md b/src/backend/llvm.md
index 2d544b5..1658c9f 100644
--- a/src/backend/llvm.md
+++ b/src/backend/llvm.md
@@ -37,4 +37,4 @@ flowchart LR
- **أصلِح في الطبقة الصحيحة:** خطأ تحويل أنواع يُصلَح في codegen؛ خطأ ترتيب حقول في `SIRBuilder` (BF-10).
---
-**اقرأ بعده:** [الآلة الافتراضية](vm.md).
+**اقرأ بعده:** [الخلفيّة الأصليّة بلا LLVM](native.md).
diff --git a/src/backend/native.md b/src/backend/native.md
new file mode 100644
index 0000000..377219c
--- /dev/null
+++ b/src/backend/native.md
@@ -0,0 +1,278 @@
+# الخلفيّة الأصليّة بلا LLVM (SIR → ELF64)
+
+> **ماذا ستتعلّم:** كيف تُترجَم لغة ص إلى شيفرة آلة **بلا LLVM ولا رابطٍ أجنبيّ**؛ طبقات
+> الخلفيّة الأربع (جداول SoT · المرمِّزات · المخفِّضات · كاتب ELF)؛ كيف تُقاس صحّتها
+> بدرجتَين متمايزتَين (التصريف والتنفيذ)؛ وأين تبدأ إن أردت إضافة أوپكود أو معماريّة.
+
+> 📎 **المصدر:** [`compiler/include/backend/native/`](https://github.com/sadlang/s-programming-language/tree/dev/compiler/include/backend/native) ·
+> [`language-truth/backend/`](https://github.com/sadlang/s-programming-language/tree/dev/language-truth/backend) ·
+> [`tools/compiler/compiler_driver_native.cpp`](https://github.com/sadlang/s-programming-language/blob/dev/tools/compiler/compiler_driver_native.cpp) ·
+> [`scripts/native_backend/`](https://github.com/sadlang/s-programming-language/tree/dev/scripts/native_backend)
+
+> 📌 **الخلاصة في ثلاثة أسطر:** ثلاثُ معماريّاتٍ لها مخفِّضٌ موصولٌ اليوم — x86-64 وARM64
+> بـ**١٠٤** أوپكودات، وRV64 بـ**٧** — واثنتان مخطَّطتان؛ والخانةُ المضمونة عبرها جميعًا هي
+> **ELF على لينكس/الوضع الحرّ** لا غير، فماك وويندوز يبقيان على LLVM. والصحّةُ تُقاس
+> **بدرجتَين** لا بواحدة (بصمةُ التصريف · التشغيل الحيّ)، وجداولُ اختيار التعليمات ما تزال
+> بذرةً والتخفيضُ الفعليّ يعيش في C++.
+>
+> **جئتَ لتُسهم؟** اقفز إلى [«أين تبدأ»](#أين-تبدأ). **جئتَ لسؤالٍ بعينه؟**
+> [كيف يُبلَّغ عن فشل التخفيض](#التشخيص-لا-نصَّ-رسالةٍ-في-الخلفيّة) ·
+> [كيف تُقاس الخلفيّة](#كيف-تُقاس-الخلفيّة-درجتان-لا-واحدة) ·
+> [ما الحدودُ والدَّينُ المُعلَن](#مزالق-مقيسة-لا-تكرّرها).
+
+## لماذا خلفيّةٌ ثالثة؟
+للغة ص ثلاثة مسارات تنفيذ: [المفسّر الشجريّ](interpreter.md)، و[SIR → LLVM](llvm.md)،
+وهذا. المسار الثالث ليس تحسينَ أداء بل **سيادةً**: أن تُنتَج شيفرةُ الآلة من مصدر ص
+دون أن يمرّ البرنامج بأيّ أداةٍ لا نملكها — لا `clang` ولا `ld` ولا `lld` ولا `as`.
+
+عقدُ السيادة مكتوبٌ في مصدر الحقيقة لا في نيّة أحد
+([`targets.yaml`](https://github.com/sadlang/s-programming-language/blob/dev/language-truth/backend/targets.yaml)):
+**خمس معماريّات فأكثر بلا LLVM إطلاقًا**، والخانة الإلزاميّة عبرها جميعًا هي
+**ELF + لينكس/الوضع الحرّ** (freestanding). أمّا Mach-O/darwin وPE/Win64 فخارج نواة
+السيادة صراحةً — يبقيان على LLVM حتّى قرارٍ لاحق.
+
+## الموضع في خطّ الأنابيب
+```mermaid
+flowchart TD
+ SRC["مصدر .ص"] --> AST["AST"]
+ AST --> SIR["SIRBuilder → SIR"]
+ SIR --> OPT["SIROptimizer"]
+ OPT -->|المسار الافتراضيّ| LLVM["LLVMCodeGen → ملفّ تنفيذيّ"]
+ OPT -->|"--خلفية-أصلية"| NAT["مخفِّض SIR الأصليّ
(x86-64 · ARM64 · RV64)"]
+ NAT --> ENC["المرمِّز (بايتات)"]
+ ENC --> ELF["Elf64Writer → ELF64 ساكن"]
+```
+الفارق الجوهريّ: مسار LLVM يسلّم IR إلى مكتبةٍ أجنبيّة تتولّى الترميز والربط، والمسار
+الأصليّ **يكتب البايتات بنفسه** ثمّ يلفّها في حاويةِ ELF بنفسه.
+
+## الأهداف الخمسة — والفجوة التي لا تُكتَم
+> 📎 **المصدر:** [`targets.yaml`](https://github.com/sadlang/s-programming-language/blob/dev/language-truth/backend/targets.yaml)
+
+عمود «المعلم» أدناه يستعمل الترقيم `م٠ … م٨` — و«م-» اختصارُ **معلَمٍ** في خارطة الخلفيّة.
+
+| الهدف | المعلم | الحالة | أوپكودات مخفَّضة |
+|---|---|---|---|
+| x86-64 | م٠–م٣ | `lowered` | **104** |
+| AArch64/ARM64 | م٥ | `lowered` | **104** |
+| RISC-V RV64GC | م٦ | `lowered` | **7** |
+| ARMv7-A/Thumb-2 | م٧ | `planned` | — |
+| x86 i686 | م٨ | `planned` | — |
+| استخراج الطبقة الجدوليّة | م٤ | `in_progress` | (طبقةٌ مشترَكة لا هدف) |
+
+> ⚠️ **«مدعوم» ≠ «يترجم كلّ برنامج».** `lowered` تعني «له مخفِّضٌ موصولٌ ومُبرهَنٌ
+> بالتشغيل» فحسب. الفارق مقيسٌ لا مخفيّ: حقل `native_lowered` في
+> [`sir_opcodes.yaml`](https://github.com/sadlang/s-programming-language/blob/dev/language-truth/backend/sir_opcodes.yaml)
+> يسجّل لكلّ أوپكود مَن يخفّضه فعلًا، وكتلة `stats` تجمعه:
+> `native_lowered_x86_64: 104` · `native_lowered_arm64: 104` · `native_lowered_riscv64: 7`.
+> وRV64 **يرفض ما عدا مجموعتَه صراحةً** لا يُنتِج ثنائيًّا مبتورًا.
+
+وثمّة موضعٌ واحدٌ في الشجرة ما يزال يقول غيرَ هذا الرقم، فلا تأخذه عنه:
+
+> 📌 **ترويسةُ `sir_native_lowering.h` أقدمُ من هذا الرقم:** ما تزال تصف «مجموعةً دنيا
+> من الأوپكودات (MOVE/ADD_I64/SUB_I64/المقارنات/BR/BR_COND/RET)» و«بلا انسكابٍ ولا
+> PHI/ذاكرة» — وهو وصفُ م٠. المقيسُ اليومَ ١٠٤، وفي المستودع `prove_sir_spill.sh`
+> و`prove_sir_memory.sh`. الحقيقةُ هنا كتلةُ `stats` المُولَّدة في `sir_opcodes.yaml`،
+> لا نصُّ الترويسة.
+
+وحقلٌ ثانٍ متمايزٌ عمدًا: `isel_declared` — مَن يُعلن نمطًا في `backend/*/isel.yaml`
+(٣ لكلٍّ من x86_64 وarm64، ٠ لـriscv64). **الفجوة بين الحقلَين هي الرسالة:** جداولُ
+اختيار التعليمات ما تزال بذرةً، والتخفيضُ الفعليُّ يعيش في C++.
+
+## المدخل من سطر الأوامر
+العَلَم `--خلفية-أصلية` مُعرَّف في مصدر الحقيقة
+([`cli_flags.yaml:211-218`](https://github.com/sadlang/s-programming-language/blob/dev/language-truth/cli_flags.yaml#L211-L218))
+ويقود إلى [`compiler_driver_native.cpp`](https://github.com/sadlang/s-programming-language/blob/dev/tools/compiler/compiler_driver_native.cpp).
+والمعماريّة **تُشتقّ من ثالوث `--هدف` لا من عَلَمٍ ثانٍ** — الهدف مصدرٌ واحد:
+
+| ثالوث الهدف | المخفِّض | ملاحظة |
+|---|---|---|
+| `aarch64-*` / `arm64-*` | `arm64_sir_lowering.h` | مطابقةٌ تامّة على حقل architecture بعد تفكيك الثالوث، لا مطابقةُ بادئة على نصّ خام |
+| `riscv64-*` | `riscv64_sir_lowering.h` | م٦ |
+| `x86_64-*` | `sir_native_lowering.h` | الافتراض؛ وبلا `--هدف` يكون الثالوث ثالوثَ المضيف |
+
+وما عدا هذه الثلاث **يُرفَض** لا يُخفَّض افتراضًا: `targetIsSupported` تشترط معماريّةً
+مخفَّضةً *و*نظامًا حاويتُه ELF معًا، كي لا يخرج ELF x86-64 لهدفِ wasm أو ويندوز صامتًا.
+
+نظام التشغيل يُفحَص أيضًا: `linux` و`none` (معدنٌ عارٍ/الوضع الحرّ) وحدهما يصلحان
+لكاتب ELF64 — وثالوثٌ بلا نظامٍ مذكور (`aarch64` مجرّدًا) يُعامَل معاملةَ `none`؛
+ويندوز وماك حاويتان مختلفتان لا يكتبهما هذا المسار.
+
+## الطبقات الأربع
+```mermaid
+flowchart TB
+ subgraph SoT["① مصدر الحقيقة — language-truth/backend/"]
+ I["instructions.yaml
(جدول الترميز)"]
+ R["registers.yaml"]
+ S["isel.yaml
(أنماط الاختيار)"]
+ A["abi/*.yaml
(e_machine · اتّفاقيّة النداء)"]
+ end
+ subgraph ENC["② المرمِّزات (header-only)"]
+ V["x86_variable_encoder
REX + ModRM"]
+ F["arm64/riscv64_fixed32_encoder
كلمة 32-بت ثابتة"]
+ end
+ subgraph LOW["③ المخفِّضات"]
+ C["sir_lowering_common.h
LoweringDriver<Target> · LoweringDiagnostics"]
+ X["sir_native_lowering.h"]
+ M["arm64_sir_lowering.h"]
+ W["riscv64_sir_lowering.h"]
+ end
+ E["④ elf64_writer.h
ELF64 ساكن"]
+ SoT --> ENC --> LOW --> E
+```
+
+### ① الجداول (SoT)
+`instructions.yaml` يصف الترميز بيانًا (`form` · `operands` · `encode`)، و`isel.yaml`
+يصف النمط (`sir → match → emit + cost`). **بنية النمط مشتركة عبر ISAs والمحتوى مختلف**:
+x86 يدمّر الوجهة (`add dst, src`) بينما ARM64/RISC-V ثلاثيّةُ المعاملات.
+
+### ② المرمِّزات
+محرّكٌ عامٌّ واحد لكلّ عائلة: **المنطق الضيّق يُكتَب مرّةً، والاختلاف بين التعليمات
+بياناتٌ (`EncSpec`) لا كود**. `x86_variable_encoder.h` يكتب بادئة REX وModRM؛ ونظيراه
+`arm64_fixed32_encoder.h` و`riscv64_fixed32_encoder.h` يبنيان كلمةً ثابتة الطول.
+والصحّة **مقيسةٌ بايتًا ببايت ضدّ `llvm-mc`** في `test_native_backend_m1.cpp` — أي أنّ
+LLVM حاضرٌ في *القياس* وغائبٌ عن *المنتَج*.
+
+### ③ المخفِّضات والطبقة المشتركة
+م٤ تستخرج ما لا يخصّ معماريّةً بعينها إلى
+[`sir_lowering_common.h`](https://github.com/sadlang/s-programming-language/blob/dev/compiler/include/backend/native/sir_lowering_common.h):
+
+- **مسندات تحليل SIR عديمة الحالة:** `isComparison` · `isConstInt` · `findFusedComparison`
+ · `hasResultAndArity` · عقد الشكل (نتيجةٌ موجودة + عدد معامِلات متوقَّع).
+- **`LoweringDiagnostics`** — قاعدةُ **تركيبٍ لا تعدّدِ أشكال**: مُدمِّرٌ محميٌّ غير
+ افتراضيّ عمدًا، فلا حذفَ عبر مؤشّر قاعدة. (وما الذي يُبلَّغ به فعلًا حين يفشل تخفيضٌ؟
+ انظر [§التشخيص](#التشخيص-لا-نصَّ-رسالةٍ-في-الخلفيّة).)
+- **`LoweringDriver`** بنمط CRTP — تتابعُ التخفيض يعيش **مرّةً واحدة**، والهدفُ
+ يقدّم خطّافاته الثلاثة: الثنائيّ · الأحاديّ · المقارنة.
+
+> ✅ **عقدُ الاستخراج مقيسٌ لا مُدَّعًى:** بصمةُ `sha256` لمخرَج ELF — وهي بصمةُ درجةِ
+> التصريف، تُشرَح في §«كيف تُقاس الخلفيّة» — عبر مصفوفة القواعد × `{x86_64, arm64}` ⇒
+> **صفرُ بايتةٍ مختلفة**. ومع ذلك نطاقُ البرهان محدودٌ ومُعلَن: المصفوفة لا تمثّل معامِل
+> `Any` في عمليّةٍ عشريّة، فبرهانُ تلك الحالة منفصلٌ (`prove_any_float.sh`). البصمةُ
+> حارسٌ ضدّ تغييرٍ غير مقصود، لا بديلٌ عن مراجعة.
+
+**تدفّق التحكّم بمرورين:** إزاحةُ اللصيقة لا تكون معروفةً ساعةَ بثّ القفزة التي تقصدها،
+فيُقسَم العمل مرورَين ينتهيان بترقيع كلّ `rel32`:
+
+```mermaid
+flowchart TB
+ subgraph P1["المرور ① — البثّ"]
+ B1["ابثّ بايتات الكتلة بالترتيب"]
+ B2["سجّل إزاحة لصيقة الكتلة"]
+ B3["ابثّ القفزة بإزاحةٍ نائبة
+ سجّل طلبَ ترقيع"]
+ B1 --> B2 --> B3
+ end
+ subgraph P2["المرور ② — الترقيع"]
+ F1["لكلّ ترقيعٍ مسجَّل"]
+ F2["rel32 = (هدف − نهاية القفز)"]
+ F1 --> F2
+ end
+ P1 --> P2 --> OUT[".text مكتمل"]
+```
+
+والمقارنةُ المُغذِّية لـ`BR_COND` تُدمَج أو لا تُدمَج بحسب نوعها:
+
+| المقارنة المُغذِّية | تُدمَج؟ | التفصيل |
+|---|---|---|
+| صحيحة | ✅ | `cmp` ثمّ `jCC` ثمّ `jmp` — فلا حاجة إلى `setcc`/`movzx` |
+| عوائم | ❌ | لأجل NaN |
+| معامِلٌ **معلَّب** (ملفوفٌ في تمثيلٍ عامٍّ يحمل وسمَ نوعه) | ❌ | لأنّ نوعها لا يُعرَف إلّا زمنَ التشغيل |
+
+### ④ كاتب ELF64
+[`elf64_writer.h`](https://github.com/sadlang/s-programming-language/blob/dev/compiler/include/backend/native/elf64_writer.h)
+يبني تنفيذيًّا ساكنًا دنيا: رأس ELF (64 بايت) + `program header` واحد `PT_LOAD` (R+X) +
+`.text`، ونقطةُ الدخول عند `vbase + 0x78`. وهو **محايدُ المعماريّة**: بنيةُ التنفيذيّ
+الساكن واحدةٌ عبر الأهداف، والفارقُ حقلٌ واحد (`e_machine`: ٦٢ لـx86-64، ١٨٣ لـAArch64، ٢٤٣ لـRV64)
+**يُمرَّر من جدول الـABI — بيانًا لا كودًا**.
+
+## التشخيص: لا نصَّ رسالةٍ في الخلفيّة
+كلّ فشلِ تخفيضٍ يحمل `ErrorCode` من كتالوج SoT + حمولةَ `{detail}` = **وسمُ سياق** +
+قيمةٌ تُحسَب زمنَ التشغيل. الوسوم مُوحَّدةٌ ثوابتَ مسمّاة في
+[`native_diagnostics.yaml`](https://github.com/sadlang/s-programming-language/blob/dev/language-truth/backend/native_diagnostics.yaml)
+يولّدها `gen_native_diagnostics.py` إلى هيدر C++ يستهلكه المخفِّضان — بدل حرفيّاتٍ خام.
+ووسمُ السياق **ليس نصًّا يراه المستخدم**؛ الرسالةُ من الكتالوج، والوسمُ سياقٌ لمطوّر
+الخلفيّة. أمثلة: `kMoveKind` · `kArrayGetBoxed` · `kEnumPayloadDyn` · `kObjectUnknownClass`.
+
+وبالمنطق نفسه يوحّد
+[`value_repr.yaml`](https://github.com/sadlang/s-programming-language/blob/dev/language-truth/backend/value_repr.yaml)
+وسومَ `SadDyn` ونصوصَ عرض القيم بين المحرّكات الثلاثة — وهذه **وسومُ نوعٍ زمنَ التشغيل،
+لا وسومُ السياق التشخيصيّ أعلاه**. جاء التوحيد بعد عيبٍ حقيقيّ: كان العدم يُعرَض «عدم»
+في الخلفيّة الأصليّة و«لاشيء» في المفسّر وLLVM.
+
+## كيف تُقاس الخلفيّة: درجتان لا واحدة
+صحّةُ الخلفيّة تُقاس بدرجتَين متمايزتَين: **أن تخرج البايتاتُ كما يجب** (التصريف)،
+و**أن يعمل الثنائيُّ الخارج** (التنفيذ).
+
+| الدرجة | ما تقيسه | الأداة | أين تعمل |
+|---|---|---|---|
+| ① التصريف (emit) | أنّ صورة ELF لمصدرٍ وهدفٍ بعينهما **واحدةٌ بايتًا بايتًا عبر المنصّات الثلاث** | [`prove_elf_emit_fingerprint.py`](https://github.com/sadlang/s-programming-language/blob/dev/scripts/native_backend/prove_elf_emit_fingerprint.py) + `elf_emit_fingerprints.json` | المنصّات الثلاث، التكوينان |
+| ② التنفيذ (run) | أنّ الثنائيّ المُخرَج **يعمل** ويطابق المفسّر | [`run_native_proofs.sh`](https://github.com/sadlang/s-programming-language/blob/dev/scripts/native_backend/run_native_proofs.sh) + 21 سكربت `prove_*.sh` | لينكس (+ `qemu-user-static` لـARM64 وRV64) |
+
+> ⚠️ **خلطُ الدرجتَين هو مكمنُ الأخضر الكاذب:** خطوةٌ تُسمّى «براهين الخلفيّة الأصليّة»
+> على ماك ولا تقيس إلّا وجودَ الملفّ **أسوأُ من غيابها**.
+
+البصمةُ **سِقّاطةٌ ثنائيّة الاتّجاه**: بصمةٌ تخالف المسجَّل ⇒ أحمر، وبصمةٌ مسجَّلةٌ لمدخلٍ
+لم يعد يُقاس ⇒ أحمر أيضًا. وهي تكشف ما لا يكشفه اختبار «هل خرج بـ٤٢»: اعتمادٌ على ترتيب
+جدول تجزئة، أو على حجم `size_t` المضيف، أو مسارٌ يتسرّب إلى الصورة.
+
+### حرّاسُ المُنادي الأربعة
+**المُنادي** هو السكربت الجامع `run_native_proofs.sh` الذي يشغّل سكربتاتِ البرهان كلَّها.
+كُتب لسدّ فجوةٍ بنيويّة: كانت البراهين موجودةً **بلا مُنادٍ**، فعاشت في الخلفيّة عيوبٌ
+حيّةٌ شهورًا. وهو يفعل أربعةً لا يفعلها تشغيلٌ يدويّ:
+
+1. **الإنتاجُ ثمّ البرهان** في مجلّدٍ **يُمحى أوّلًا** — مُنتِجٌ ينهار قبل الكتابة يترك
+ ثنائيَّ التشغيلة الماضية فيُبرهَن عليه ويخضرّ. **البقيّةُ أخطرُ من الغياب، لأنّ
+ الغيابَ يُرى.** ويُحكَم برمز خروج المُنتِج أيضًا.
+2. **حارسُ التغطية** — سكربتُ برهانٍ جديدٌ غيرُ مُصرَّحٍ به في المُنادي يُخفِق البوّابة،
+ فلا تعود فجوةُ «سكربتٌ بلا مُنادٍ» بالتسلّل.
+3. **التخطّي إخفاقٌ افتراضيًّا** — مجموعُ `SKIP` أصفارًا يُقرأ «نجح الكلّ» وهو أخضرُ بلا
+ قياس (يُرفَع بـ`SAD_PROOFS_ALLOW_SKIP=1` صراحةً).
+4. **غيابُ `qemu-aarch64` إخفاق** — وإلّا حُذف نصفُ البراهين (AArch64) من الحساب بلا
+ أثرٍ في المخرَج. (ما عداه يُلتقَط نصًّا: `SKIP` يُعدّ ولو خرج السكربتُ بصفر.)
+
+وفي CI تظهر الدرجتان كذلك: خطوة «🔬 براهين الخلفيّة الأصليّة (تنفيذٌ حيّ)» على لينكس،
+وبصمةُ التصريف **غيرُ مشروطةٍ بالتكوين** في الخانات الستّ كلّها. وسببُ تعميمها مقيس:
+كانت الخلفيّة محبوسةً داخل هدفٍ لا يُعرَّف إلّا مع LLVM، فعاش عيبٌ واحد **ستَّ جولات CI**
+لأنّ الخانة الكاشفة واحدة.
+
+## مزالق مقيسة (لا تكرّرها)
+كلُّ بندٍ أدناه أثرُ عطبٍ **قِيس** لا عطبٍ يُخشى — وهذه القائمةُ محضرُ ما التقطته الدرجتان
+أعلاه. وهي تخلط نوعَين: ما **سُدَّ** وبقي مكتوبًا كي لا يُعاد، وما هو **قيدٌ مُعلَنٌ باقٍ**
+يُرفَض صراحةً ولا يُبتَر صامتًا. والتمييزُ منصوصٌ في كلّ بند:
+
+- **حارسٌ مبنيٌّ على الأوپكود والفرقُ في النوع لا يراه:** طُبع `طبيعي` على RV64 **`-1`**
+ بينما المفسّر وx86-64 يطبعان القيمة الصحيحة — لأنّ الطابع موقَّعٌ حصرًا ولم يوزّع أحدٌ
+ على النوع.
+- **حالةٌ مشروطةٌ بالمُحسِّن دون أن يقول ذلك أحد:** `MOVE` لم يكن مخفَّضًا على RV64، فمرّ
+ الهدفُ في `-O2` (حيث يحذفه DCE) و**أخفق في `-O0`**. كشفه اختبارُ الجسر الوحدويّ (يبني
+ SIR بلا مُحسِّن) لا البرهانُ الحيّ الذي كان يقيس `-O2` وحده.
+- **قيدٌ باقٍ مُعلَن:** إزاحات الإطار على RV64 فوريٌّ ١٢-بت موقَّع ⇒ سقف ٢٠٤٧ بايتًا؛
+ ما فوقه **يُرفَض صراحةً** (`kFrameTooLarge`) لا يُبتَر صامتًا.
+- **دَينٌ موثَّق قبل تشغيل isel:** صيغةُ المركم القصيرة (`add=05` · `sub=2D` · `cmp=3D`
+ بلا ModRM) تخالف الشكلَ العامّ `81 /r id` بايتًا، فتفشل المطابقةُ التفاضليّة ضدّ
+ `llvm-mc` **صامتةً** إن اختارت isel المركمَ وجهةً للفوريّ.
+- **أوپكوداتٌ مقيَّدةٌ بمعماريّة** (`rdtsc` · `cli` · `outb` · `mov %crN`): كانت تُبَثّ
+ **لأيّ هدفٍ يُطلَب** بخروجٍ صفريّ — `عداد_الدورات()` بـ`--هدف=aarch64-unknown-elf` كان
+ يخرج بصفرٍ ويبثّ `rdtsc` — فيقع الإخفاق عند المُجمِّع برسالةٍ لا تدلّ على السبب، أو لا
+ يقع فيخرج ثنائيٌّ لا يعمل. **سُدَّ ذلك**: بوّابةٌ في `emitInstruction` تقرأ الجردَ من
+ [`arch_specific_opcodes.yaml`](https://github.com/sadlang/s-programming-language/blob/dev/language-truth/backend/arch_specific_opcodes.yaml)
+ عبر `findArchConstraint()` وتردّ `SEM_TARGET_ARCH_UNSUPPORTED_BUILTIN`. ودَينان
+ مُعلَنان باقيان: البوّابةُ في مسار LLVM (وهذه الأوپكودات `native_lowered: []` فلا
+ يخفّضها المسارُ الأصليّ أصلًا)، وكتلةُ «تجميع … نهاية» لا تمرّ بها.
+- **البرهانُ الذي لا يُعيد أحدٌ إنتاجَه دعوى** مهما صدق قائلُه: كان أحدُ حقول `targets.yaml`
+ يسوق تشغيلًا نصًّا بلا سكربتٍ يُعيده، فاستُبدل بسكربتٍ مُصرَّحٍ به في المُنادي.
+
+## أين تبدأ
+هذا الجدول هو بابُ المُسهِم: صفٌّ لكلّ نيّة، وكلُّ صفٍّ ينتهي بما يجعل الإسهامَ **مقيسًا**
+لا مُدَّعًى — سكربتَ برهانٍ مُصرَّحًا به، أو اختبارَ تطابقٍ، أو حقلًا في مصدر الحقيقة.
+
+| تريد أن… | ابدأ من |
+|---|---|
+| تضيف أوپكودًا مخفَّضًا | `sir_native_lowering.h` (أو نظيره) + حدّث `native_lowered` في `sir_opcodes.yaml` + سكربت برهانٍ مُصرَّحٍ به في المُنادي |
+| تضيف تعليمةً أو صيغةَ ترميز | `language-truth/backend//instructions.yaml` ثمّ اختبارُ التطابق ضدّ `llvm-mc` |
+| تضيف معماريّةً | `targets.yaml` أوّلًا — وانتظر م٤ (استخراج الطبقة الجدوليّة)، فإضافتُها قبلها تعني **مخفِّضًا يدويًّا ثالثًا** |
+| تفهم لماذا فشل تخفيضٌ عندك | وسمُ السياق في `native_diagnostics.yaml` + رسالةُ الكتالوج |
+
+---
+**اقرأ بعده:** [دراسة حالة: توحيد هاش/شفّر/فك_تشفير](crypto-unification.md) —
+الفصلُ التالي في الفهرس، وهو أخفُّ ويُري التوحيدَ نفسَه من زاويةِ مكتبةٍ لا خلفيّة ·
+أو اقفز إلى [نظام الأنواع وفاحص الأنواع](../systems/types.md).
diff --git a/src/backend/vm.md b/src/backend/vm.md
deleted file mode 100644
index 074e6e2..0000000
--- a/src/backend/vm.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# الآلة الافتراضية (VM)
-
-> **ماذا ستتعلّم:** دور الـVM في لغة ص وعلاقتها بالمفسّر.
-
-## الدور
-`vm/` آلة **بايت كود** مرتبطة مباشرةً بالمفسّر — مسار تنفيذ بديل يجمع بين سرعة أعلى من
-المشي الشجريّ الصرف ومرونة التفسير، دون المرور بـLLVM/الترجمة الكاملة.
-
-## الموضع
-```mermaid
-flowchart LR
- AST --> INT["InterpreterCore"]
- INT <-->|ربط مباشر| VM["VM (بايت كود)"]
- AST -.->|مسار منفصل| SIR["SIR → LLVM (sadc)"]
-```
-
-## وقت التشغيل والربط
-- `runtime/` يوفّر ABI/FFI مستقلّ (freestanding) + ربط VM.
-- القنوات/الخيوط الخفيفة (goroutines) آمنة للتزامن عبر mutex داخليّ في `SadChannel`.
-
-> هذه الطبقة أقلّ سطحًا للمساهمات الجديدة من المفسّر/المترجم؛ ابدأ منهما عادةً.
-> هذا الفصل **قيد التوسعة** — ساهم بتفاصيل بنية البايت كود إن عملت عليها.
-
----
-**اقرأ بعده:** [نظام الأنواع](../systems/types.md).
diff --git a/src/getting-started/repo-map.md b/src/getting-started/repo-map.md
index 272fb27..d28a392 100644
--- a/src/getting-started/repo-map.md
+++ b/src/getting-started/repo-map.md
@@ -14,9 +14,8 @@ s-programming-language/
├── compiler/ ← المترجم: AST → SIR → LLVM IR → تنفيذيّ
│ ├── src/frontend/ ← SIRBuilder + sir_types.h (opcodes الملكية)
│ └── src/backend/llvm/ ← LLVMCodeGen + builders
-├── vm/ ← الآلة الافتراضية (بايت كود مرتبط بالمفسّر)
├── stdlib/ ← المكتبة القياسية (core/io/math/string/network/graphics)
-├── runtime/ ← ABI/FFI المستقلّ + ربط VM
+├── runtime/ ← ABI/FFI المستقلّ + الوضع الحرّ (freestanding)
├── tools/ ← sadinfo · lsp · formatter · pkg · repl · compiler(sadc CLI)
├── language-truth/ ← ⭐ مصدر الحقيقة الموحّد (YAML)
│ ├── keywords.yaml · operators.yaml · types.yaml · directives.yaml
diff --git a/src/introduction.md b/src/introduction.md
index 2337216..beaaf0b 100644
--- a/src/introduction.md
+++ b/src/introduction.md
@@ -28,7 +28,7 @@
⚡الواجهة الخلفيّة
- المفسّر · SIR · LLVM · VM.
+ المفسّر · SIR · LLVM.
🌿المساهمة
diff --git a/src/status.md b/src/status.md
index 4666640..a5301b3 100644
--- a/src/status.md
+++ b/src/status.md
@@ -10,18 +10,20 @@
| مصدر الحقيقة (فلسفة · language-truth · codegen · grammar SoT) | ✅ مكتمل | الميزة المميِّزة |
| الأماميّة (معجمي · نحوي · AST) | ✅ مكتمل | |
| الخلفيّة: مفسّر · SIR · LLVM | ✅ مكتمل | |
-| الخلفيّة: VM | 🚧 قيد التوسعة | يحتاج تفصيل بنية البايت كود |
+| الخلفيّة الأصليّة (بلا LLVM) | ✅ مكتمل | خمس معماريّات · درجتا القياس · حرّاس المُنادي |
+| الخلفيّة: VM | 🗑️ مُزال فصله | `vm/` حُذف من الشجرة (`bcf0a746`) «ستُعاد كتابتها من الصفر» — لا يُكتب الفصل قبل الكود |
| الأنظمة (أنواع · أخطاء · مضمنة) | ✅ مكتمل | |
| المساهمة (سير العمل · DoD · الحوكمة) | ✅ مكتمل | |
| مزامنة الدليل (Freshness + كاشف الانجراف) | ✅ مكتمل | فحص آليّ أسبوعيّ |
## خارطة الطريق (مقترَحة)
-- [ ] **توسعة VM:** تنسيق البايت كود، حلقة التنفيذ، الربط بالمفسّر.
+- [x] **الخلفيّة الأصليّة (بلا LLVM):** → [الفصل](backend/native.md).
+- [ ] **إعادة توثيق VM:** يُكتب الفصل من الكود عند إعادة كتابة الطبقة، لا قبلها.
- [x] **مزامنة الدليل مع اللغة:** بيان ربط + كاشف انجراف + فحص أسبوعيّ → [Freshness](contributing/freshness.md).
- [ ] **جسر آليّ لقواعد المحلل:** تضمين/مزامنة `docs/parser_rule/_generated/` داخل الدليل.
- [ ] **فصل الأدوات:** LSP · المنسّق · مدير الحزم (pkg) · sadinfo.
- [ ] **فصل stdlib:** بنية المكتبة القياسية ووحداتها.
-- [ ] **فصل runtime/FFI:** ABI المستقلّ وربط VM.
+- [ ] **فصل runtime/FFI:** ABI المستقلّ (`runtime/`) والوضع الحرّ (freestanding).
- [ ] **أمثلة «دراسة حالة»:** تتبّع ميزة كاملة عبر كل الطبقات (نهاية-لنهاية).
- [ ] **ترجمة إنجليزية** اختياريّة (i18n).
diff --git a/sync/sources.lock.json b/sync/sources.lock.json
index d9e8944..6c6aaaa 100644
--- a/sync/sources.lock.json
+++ b/sync/sources.lock.json
@@ -37,12 +37,8 @@
"shared/parser/src/core/parser_main.cpp#L40-1500": "sha256:3ae6310f5ebc9df6",
"shared/types/generated/sad_type_kind_generated.h": "fd336d51f192b64fe7e63ca25fbb2938479003a5",
"shared/types/include/sad_type_system.h": "4e792e5b8ddb605fbb1c33200c019e9ded39f656",
- "shared/types/include/type_bridge.h": "0bf37baafc415409082a284c9b2de84d3837e4e1",
"shared/types/include/value.h": "9b40909fd452fac4e27e41aed85852020f2af1e6",
"tools/compiler/compiler_driver_android_linker.cpp": "e0c69cb8859b4d6b65bde3e4a4d37980c108dbd9",
- "tools/compiler/runtime/sad_embedded_runtime.c": "6fa885fb7277c79674aaab298ee5cadd6ff162b3",
- "vm/include/sad_vm_executor.h": "2a9c74f182c5d76643b58dfd036877ad03ad97c1",
- "vm/include/sad_vm_opcodes.h": "8b92d998a5a26d3b9716d25bd6dee8a61a5fca4d",
- "vm/src/sad_vm_executor.cpp": "76a1d059b635ebc6a3d55f0fb9b75c95fa2984a7"
+ "tools/compiler/runtime/sad_embedded_runtime.c": "6fa885fb7277c79674aaab298ee5cadd6ff162b3"
}
}
diff --git a/sync/sources.yaml b/sync/sources.yaml
index c11b419..27685d7 100644
--- a/sync/sources.yaml
+++ b/sync/sources.yaml
@@ -59,11 +59,16 @@ chapters:
sources:
- compiler/src/backend/llvm
- - file: src/backend/vm.md
+ - file: src/backend/native.md
sources:
- - vm/include/sad_vm_opcodes.h
- - vm/include/sad_vm_executor.h
- - vm/src/sad_vm_executor.cpp
+ - { path: compiler/include/backend/native/sir_native_lowering.h, lines: "1-30" } # عقد الجسر (الترويسة)
+ - compiler/include/backend/native/sir_lowering_common.h # LoweringDriver + LoweringDiagnostics
+ - compiler/include/backend/native/elf64_writer.h
+ - compiler/include/backend/native/x86_variable_encoder.h
+ - tools/compiler/compiler_driver_native.cpp # المدخل من --خلفية-أصلية
+ - language-truth/backend # targets · sir_opcodes · isel · abi · diagnostics · value_repr
+ - scripts/codegen/gen_native_diagnostics.py
+ - scripts/native_backend/run_native_proofs.sh # المُنادي وحرّاسه الأربعة
- file: src/systems/memory.md
sources:
@@ -75,7 +80,6 @@ chapters:
sources:
- shared/types/include/sad_type_system.h
- shared/types/generated/sad_type_kind_generated.h
- - shared/types/include/type_bridge.h
- shared/types/include/value.h
- language-truth/types.yaml
diff --git a/theme/custom.css b/theme/custom.css
index 7cf6556..d345574 100644
--- a/theme/custom.css
+++ b/theme/custom.css
@@ -62,12 +62,36 @@
}
/* ── الجداول ───────────────────────────────────────────────────────────────── */
+/* (AR) الجدولُ يُمرَّر أفقيًّا ولا يُسحَق.
+ الواقعُ المقيس: mdBook يلفّ كلَّ جدولٍ بـ`div.table-wrapper` (٦٦/٦٦ جدولًا في
+ الإخراج المبنيّ)، و`css/general-*.css:35` يعطيها `overflow-x:auto` أصلًا.
+ فالعلّةُ لم تكن غيابَ الحاوية بل `width:100%` على الجدول: يُساوي عرضَ الحاوية
+ دائمًا فلا يقعُ تجاوزٌ ولا يعملُ التمرير — فتُسحَق الأعمدة. الإصلاحُ الفعليّ هو
+ `width:auto; min-width:100%` أدناه. القاعدةُ التالية لا تُغيّر سلوكَ التمرير
+ (مكرِّرةٌ عمدًا كتوثيقٍ وحارسٍ لو تغيّر ثيمُ mdBook)، وتُضيف تدويرَ الحوافّ فقط. */
+.content .table-wrapper {
+ overflow-x: auto;
+ border-radius: var(--sad-radius);
+}
.content table {
border-collapse: collapse;
border-radius: var(--sad-radius);
overflow: hidden;
box-shadow: 0 1px 8px rgba(0,0,0,.06);
- width: 100%;
+ width: auto;
+ min-width: 100%;
+}
+/* (AR) على الشاشات الضيّقة: أرضيّةُ عرضٍ للخليّة كي يقعَ التجاوزُ فالتمريرُ بدل
+ السحق. الأرضيّةُ للخلايا النثريّة؛ الخليّةُ الأولى (المفتاح) أعرض.
+ قياسٌ: `--content-max-width: 750px` في mdBook، فعمودُ المحتوى لا يتجاوز ٧٥٠px
+ مهما اتّسعت الشاشة؛ لذا الأرضيّةُ تخصّ الهاتفَ حصرًا (أعرضُ جدولٍ في `src`
+ سبعةُ أعمدة: `src/systems/memory.md:71` — يتجاوز ٧٥٠px بمحتواه وحدَه فيُمرَّر
+ على كلّ عرض). */
+@media (max-width: 760px) {
+ .content table th,
+ .content table td { min-width: 8rem; }
+ .content table th:first-child,
+ .content table td:first-child { min-width: 10rem; }
}
.content table thead {
background: linear-gradient(90deg, var(--sad-accent), var(--sad-accent-2));
@@ -161,3 +185,51 @@
.navy .content .card,
.coal .content .card,
.ayu .content .card { background: rgba(255,255,255,.04); color: #c8d3de !important; }
+
+/* ── فهرس الصفحة (يبنيه theme/page-toc.js) ───────────────────────────────────── */
+.page-toc {
+ background: var(--sad-accent-soft);
+ border: 1px solid rgba(11,114,133,.18);
+ border-radius: var(--sad-radius);
+ padding: .6rem .9rem;
+ margin: 0 0 1.6rem;
+ font-size: .92em;
+}
+.page-toc > summary {
+ cursor: pointer;
+ font-weight: 700;
+ color: var(--sad-accent);
+ list-style: none;
+}
+.page-toc > summary::-webkit-details-marker { display: none; }
+.page-toc > summary::before { content: "☰ "; }
+.page-toc ul {
+ margin: .6rem 0 .2rem;
+ padding-right: 1.2rem;
+ padding-left: 0;
+ list-style: none;
+ columns: 2;
+ column-gap: 1.6rem;
+}
+.page-toc li { margin: .18rem 0; break-inside: avoid; }
+.page-toc .page-toc-l3 { padding-right: 1rem; opacity: .85; font-size: .95em; }
+/* (AR) الرفعُ إلى `.content .page-toc a` ضروريّ: `.content a:not(.header)` أعلاه
+ خصوصيّتُه (٠,٢,١) وتهزم (٠,١,١) لو كُتبت `.page-toc a` وحدَها. */
+.content .page-toc a { color: var(--sad-ink); text-decoration: none; }
+.content .page-toc a:hover { color: var(--sad-accent); text-decoration: underline; }
+@media (max-width: 760px) { .page-toc ul { columns: 1; } }
+@media print { .page-toc { display: none; } }
+/* (AR) الوضعُ الداكن: `--sad-accent-soft` (#e3fafc) لوحٌ فاتحٌ يصرخ في navy/coal/ayu
+ — نفسُ معالجةِ الاقتباسات والبطاقات أعلاه. */
+.navy .content .page-toc,
+.coal .content .page-toc,
+.ayu .content .page-toc { background: rgba(16,152,173,.10); border-color: rgba(16,152,173,.28); }
+.navy .content .page-toc a,
+.coal .content .page-toc a,
+.ayu .content .page-toc a { color: #c8d3de; }
+.navy .content .page-toc > summary,
+.coal .content .page-toc > summary,
+.ayu .content .page-toc > summary { color: #66d9e8; }
+.navy .content .page-toc a:hover,
+.coal .content .page-toc a:hover,
+.ayu .content .page-toc a:hover { color: #66d9e8; }
diff --git a/theme/page-toc.js b/theme/page-toc.js
new file mode 100644
index 0000000..57ff23c
--- /dev/null
+++ b/theme/page-toc.js
@@ -0,0 +1,86 @@
+/* ============================================================================
+ فهرسُ الصفحة (في هذه الصفحة) — يبني قائمةَ عناوينِ h2/h3 للفصل المفتوح
+ ويضعها بعد عنوانِ الصفحة مباشرةً. شريطُ mdBook الجانبيّ يعرض عناوينَ الفصول
+ لا عناوينَ الأقسام، فالفصولُ الطويلة تُقرأ جدارًا بلا مسح.
+ بلا تبعيّةٍ خارجيّة ولا معالجٍ مسبق (mdbook-toc غير مستعمَل عمدًا: يُضيف
+ تبعيّةَ cargo إلى CI ويكتب الفهرسَ في HTML المبنيّ فيقيسه lychee).
+ يُحمَّل عبر additional-js في book.toml.
+ ============================================================================ */
+(function () {
+ "use strict";
+
+ var MIN_HEADINGS = 4; // (AR) أقلّ من ذلك: الفصلُ يُمسَح بلا فهرس
+ var COLLAPSE_WIDTH = 900; // (AR) تحت هذا العرض يبدأ الفهرسُ مطويًّا
+
+ // (AR) عنوانٌ صالحٌ للفهرسة: له مُعرِّفٌ يولّده mdBook، ولا يُدرَج h3 قبل أوّل h2.
+ function collect(main) {
+ var nodes = main.querySelectorAll("h2[id], h3[id]");
+ var out = [], sawH2 = false, i;
+ for (i = 0; i < nodes.length; i++) {
+ var h = nodes[i];
+ if (h.tagName === "H2") sawH2 = true;
+ else if (!sawH2) continue;
+ var text = (h.textContent || "").replace(/\s+/g, " ").trim();
+ if (!text) continue;
+ out.push({ id: h.id, text: text, level: h.tagName === "H2" ? 2 : 3 });
+ }
+ return out;
+ }
+
+ function build(items) {
+ var box = document.createElement("details");
+ box.className = "page-toc";
+ box.open = window.innerWidth >= COLLAPSE_WIDTH;
+
+ var head = document.createElement("summary");
+ head.textContent = "في هذه الصفحة";
+ box.appendChild(head);
+
+ var list = document.createElement("ul");
+ for (var i = 0; i < items.length; i++) {
+ var it = items[i];
+ var li = document.createElement("li");
+ li.className = "page-toc-l" + it.level;
+ var a = document.createElement("a");
+ // (AR) مرساةٌ خام لا مُرمَّزة: مُعرِّفاتُ mdBook عربيّةٌ حرفيّةٌ في HTML، وروابطُ
+ // العناوين التي يولّدها mdBook نفسُها خامّة (`href="#لماذا-خلفيّةٌ-ثالثة"`)
+ // — فالمطابقةُ اتّساقٌ مع الصفحة لا اجتهادُ ترميز.
+ a.setAttribute("href", "#" + it.id);
+ a.textContent = it.text;
+ li.appendChild(a);
+ list.appendChild(li);
+ }
+ box.appendChild(list);
+ return box;
+ }
+
+ function init() {
+ var main = document.querySelector(".content main");
+ if (!main || main.querySelector(".page-toc")) return;
+
+ // (AR) صفحةُ الطباعة (`print.html`) تلصق كلَّ الفصول في `main` واحد: ٣٣ عنوانَ h1
+ // و١٨٣ عنوانَ h2/h3 بمُعرِّفاتٍ قد تتكرّر — فهرسٌ واحدٌ عملاقٌ بمراسٍ ملتبسة.
+ // علامةُ الصفحة: أكثرُ من h1 واحدٍ في `main`.
+ var h1s = main.querySelectorAll("h1");
+ if (h1s.length !== 1) return;
+
+ var items = collect(main);
+ if (items.length < MIN_HEADINGS) return;
+
+ // (AR) الموضعُ: بعد كتلةِ العنوان. h1 ليس دائمًا ابنًا مباشرًا لـ`main`:
+ // في `introduction.md` هو داخل `div.hero`، فالإدراجُ عند `main.firstChild`
+ // كان يضعُ الفهرسَ *فوق* لافتةِ الهبوط. نصعدُ إلى أعلى سلفٍ تحت `main`.
+ var anchor = h1s[0];
+ while (anchor && anchor.parentNode !== main) anchor = anchor.parentNode;
+ var box = build(items);
+ if (anchor && anchor.nextSibling) main.insertBefore(box, anchor.nextSibling);
+ else if (anchor) main.appendChild(box);
+ else main.insertBefore(box, main.firstChild);
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", init);
+ } else {
+ init();
+ }
+})();