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(); + } +})();