Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

مقدّمة الدليل

دليل مطوّري لغة ص

الأنظمة الداخلية — من المصدر إلى التنفيذ

كيف تعمل لغة ص من الداخل، وكيف تُسهم في تطويرها بثقة: معجمي → نحوي → AST → مفسّر/مترجم، فوق مصدر حقيقة موحّد.

لمن هذا الدليل؟ لمن يطوّر لغة ص نفسها (المفسّر، المترجم sad-build، الأنظمة الداخلية) — لا لمن يكتب برامج بها. إن كنت تكتب .ص فهذا الدليل ليس لك.

ابدأ من هنا

فلسفة لغة ص الداخلية في سطور

  • معماريّة طبقيّة صارمة: Lexer → Parser → AST → (Interpreter | SIR → LLVM). كل طبقة تعتمد فقط على ما تحتها.
  • مدفوعة بالبيانات (data-driven): بيانات اللغة وقواعدها النحويّة تعيش في language-truth/ كمصدر موحّد (YAML)، ويُولَّد منها كود C++ والتوثيق. هذا جوهر تميّزنا.
  • عربيّة أصيلة: الكلمات المفتاحيّة والمعرّفات بالعربية، UTF-8، والكتل تُغلَق بـ«نهاية».
  • تنفيذ مزدوج: كل ميزة تعمل في المفسّر والمترجم (أو تُعفى صراحةً).

كيف يختلف عن rustc-dev-guide؟

استلهمنا أفضل ما فيه (mdBook، التتبّع للكود، فصل المساهمة) وأضفنا طبقةً لا يملكها:

flowchart LR
  subgraph sad["دليل لغة ص"]
    S1["language-truth/*.yaml<br/>مصدر موحّد"] --> S2["codegen<br/>gen_*.py"]
    S2 --> S3["كود C++ مُولَّد"]
    S1 --> S4["توثيق + مخطّطات مُولَّدة"]
  end

مصدر حقيقة موحّد للقواعد والبيانات · توثيق مُولَّد لا يتقادم · مخطّطات Mermaid منهجيّة · سير مساهمة حديث (worktrees + فرع dev محميّ + PR موقّع GPG).


اقرأ بعده: إعداد البيئة والبناء · أو تصفّح حالة الدليل.

حالة الدليل وخارطة الطريق

صفحة حيّة تتبّع نضج كل فصل وما يحتاج توسعة. ساهم! → دليل المساهمة.

نضج الفصول

الفصلالحالةملاحظة
مقدّمة · البدء · خريطة المستودع✅ مكتمل
المعمارية (طبقات · خطّ أنابيب · تشابك)✅ مكتملمخطّطات Mermaid
مصدر الحقيقة (فلسفة · language-truth · codegen · grammar SoT)✅ مكتملالميزة المميِّزة
الأماميّة (معجمي · نحوي · AST)✅ مكتمل
الخلفيّة: مفسّر · SIR · LLVM✅ مكتمل
الخلفيّة الأصليّة (بلا LLVM)✅ مكتملخمس معماريّات · درجتا القياس · حرّاس المُنادي
الخلفيّة: VM🗑️ مُزال فصلهvm/ حُذف من الشجرة (bcf0a746) «ستُعاد كتابتها من الصفر» — لا يُكتب الفصل قبل الكود
الأنظمة (أنواع · أخطاء · مضمنة)✅ مكتمل
المساهمة (سير العمل · DoD · الحوكمة)✅ مكتمل
مزامنة الدليل (Freshness + كاشف الانجراف)✅ مكتملفحص آليّ أسبوعيّ

خارطة الطريق (مقترَحة)

  • الخلفيّة الأصليّة (بلا LLVM): → الفصل.
  • إعادة توثيق VM: يُكتب الفصل من الكود عند إعادة كتابة الطبقة، لا قبلها.
  • مزامنة الدليل مع اللغة: بيان ربط + كاشف انجراف + فحص أسبوعيّ → Freshness.
  • جسر آليّ لقواعد المحلل: تضمين/مزامنة docs/parser_rule/_generated/ داخل الدليل.
  • فصل الأدوات: LSP · المنسّق · مدير الحزم (pkg) · المحلّل (analyze) · المُشخِّص (profiler).
  • فصل stdlib: بنية المكتبة القياسية ووحداتها.
  • فصل runtime/FFI: ABI المستقلّ (runtime/) والوضع الحرّ (freestanding).
  • أمثلة «دراسة حالة»: تتبّع ميزة كاملة عبر كل الطبقات (نهاية-لنهاية).
  • ترجمة إنجليزية اختياريّة (i18n).

كيف تُسهم في فصل؟

أنشئ فرع agent/dg-<فصل> من main، طوّر الفصل (مع مخطّط Mermaid وروابط ملف:سطر)، ابنِ mdbook build بلا أخطاء، ثم افتح PR. أضف الفصل الجديد إلى src/SUMMARY.md.


العودة للمقدّمة

إعداد البيئة والبناء

ماذا ستتعلّم: كيف تجلب المستودع، تهيّئ الأدوات، وتبني المفسّر والمترجم.

📎 المصدر: CMakeLists.txt · tests/config.yaml

المتطلبات

  • C++17 ومُصرِّف حديث (MSVC على Windows، أو Clang/GCC).
  • CMake ≥ 3.15 — هذا ما يشترطه cmake_minimum_required فعلًا، لا 3.20.
  • LLVM 18 — للمترجم sad-build. الخيار ENABLE_LLVM_BACKEND افتراضيّه ON، فلا تحتاج تمريره. ودقّق: هدفُ sad-build يُعرَّف داخل if(ENABLE_LLVM_BACKEND AND LLVM_FOUND) في apps/CMakeLists.txt — فبلا LLVM لا يوجد الهدفُ أصلًا، ويسقط x.py build معه، ولا يُنتَج build/bin/Debug/sad-build.exe الذي يشترطه tests/config.yaml أبدًا. البديلُ الحقيقيّ هو الهدفُ المستقلّ sad-build-native (المترجم النحيل، بلا LLVM، في كلّ التهيئات) — راجع فصل LLVM والخلفيّة الأصليّة. فإن لم تنوِ استعمالَ الخلفيّة الأصليّة، اعتبِر LLVM شرطًا لا خيارًا.
  • Python 3 — لمولّدات الكود (scripts/codegen/gen_*.py) وtests/runner.py.
  • Git + GPG — للمساهمة (الفروع المحميّة تشترط توقيع GPG).

الجلب

git clone https://github.com/sadlang/s-programming-language.git
cd s-programming-language

البناء

الطريق المُوصى به هو البوّابة الموحّدة x.py — تبني المحرّكين معًا لكلّ تهيئة، فلا تقع في اختلاف التهيئتين الموصوف تحت:

python x.py build          # بناء المحرّكين
python x.py test           # بناء + تحقّق + تشغيل الـrunner
python x.py gen            # توليد المصدر من language-truth/

(الأوامر الأخرى: configure · verify · conformance · clean.)

وإن بنيتَ بـCMake مباشرةً (PowerShell على Windows):

cmake -S . -B build                                 # تهيئة أولى
cmake --build build --config Debug --target sad-run    # المفسّر
cmake --build build --config Debug --target sad-build  # المترجم
cmake --build build --config Debug                  # كل شيء

⚠️ فخاخُ البناء:

  • ابنِ الاثنين في تهيئةٍ واحدة، وأعِد بناءهما معًا. tests/config.yaml يقرأ المسارَين من build/bin/Debug/ كليهما. فإن بنيتَ sad-build في Release لم يجده الـrunner، وإن أعدتَ بناءَ أحدهما وحدَه قِستَ ثنائيًّا بائتًا. وهذه بعينها عثرةُ «مفسّر Debug + مترجم Release» التي يقول تعليقُ tests/config.yaml نفسِه إنّها أُزيلت جذريًّا في cmake/llvm.cmake؛ لا تُعِدها بيدك.
  • sadc.exe اسمٌ متقاعد — لا يُنتجه أيّ هدف. اسمُ الهدف مُوحَّدٌ مع اسم المُخرَج: sad-run ⇒ sad-run.exe، وsad-build ⇒ sad-build.exe. فلا تنسخ ثنائيًّا باسمٍ آخر لأجل الـrunner — فهو يقرأ المسارَين من ذلك الملفّ.
  • sad.exe ليس المفسّر بل موزِّع أوامرٍ (hub) يُشغّل الأدواتِ عمليّاتٍ فرعيّة؛ المفسّرُ الفعليّ sad-run.exe، فاستدعِه مباشرةً.

التشغيل

الأمثلة الفعليّة تحت examples/ بأسماء عربيّة مرقّمة (01_مرحبا.ص … 07_مكوّن_مركّب.ص):

.\build\bin\Debug\sad-run.exe examples\01_مرحبا.ص      # تفسير
.\build\bin\Debug\sad-build.exe examples\01_مرحبا.ص    # ترجمة لملف تنفيذيّ

الاختبارات

الاختبارات معطّلة افتراضيًّا (option(BUILD_TESTS ... OFF))؛ فعّلها بـ-DBUILD_TESTS=ON. للتنفيذ المزدوج (مفسّر + مترجم) استخدم tests/runner.py — لا يوجد runner.py في جذر المستودع، وهذا هو الاستدعاء الذي يستعمله CI نفسه:

python tests/runner.py --level P0      # الحزمة الأساسيّة
python tests/runner.py --level P1      # المطلوبة قبل أي PR (لا تراجع)

(وpython x.py test يبني المحرّكين ثمّ يستدعي هذا الـrunner نفسَه — استعمِله حين تريد البناءَ والقياسَ في خطوة، والاستدعاءَ المباشرَ حين يكون البناءُ حاضرًا.)

توليد الكود من مصدر الحقيقة

بعد تعديل أي YAML في language-truth/:

python x.py gen                                    # كلُّ المولّدات دفعةً واحدة
python scripts/codegen/gen_keywords.py             # أو مولِّدٌ بعينه
python scripts/codegen/gen_parser_grammar_docs.py  # توثيق القواعد

راجع توليد الكود.


اقرأ بعده: خريطة المستودع.

خريطة المستودع

ماذا ستتعلّم: أين يعيش كل شيء في sadlang/s-programming-language.

📎 مقيسٌ على dev — الأسماء والأعداد أدناه من شجرة المستودع نفسها لا من الذاكرة.

s-programming-language/
├── x.py                    ← البوّابة الموحّدة: سبعةُ أوامر (build · test · gen ·
│                             verify · configure · conformance · clean)
├── shared/                 ← النواة المشتركة
│   ├── lexer/              ← المحلل المعجمي + Token + Position
│   ├── parser/             ← المحلل النحوي (recursive descent)
│   ├── ast/                ← عقد شجرة AST + ASTVisitor
│   ├── types/              ← Value (نوع القيم الموحّد) + SadType
│   └── errors/             ← نظام الأخطاء + رموزها
├── interpreter/            ← المفسّر الشجريّ (InterpreterCore + visitors + builtins)
├── compiler/               ← المترجم: AST → SIR → LLVM IR → تنفيذيّ
│   ├── src/frontend/       ← SIRBuilder + sir_types.h (opcodes الملكية)
│   ├── src/backend/llvm/   ← LLVMCodeGen + builders
│   └── include/backend/native/
│                            ← الخلفيّة الأصليّة (بلا LLVM)
├── stdlib/                 ← المكتبة القياسية: وحدات `.ص` في الجذر + مجلّدات دعم C++
├── features/               ← أنظمةٌ كبرى مستقلّة (منها SadUI للرسومات)
├── runtime/                ← ABI/FFI المستقلّ + الوضع الحرّ (freestanding)
├── tools/                  ← ١٥ مجلّدَ أدوات (sad-build · sad · lsp · repl …)
├── language-truth/         ← ⭐ مصدر الحقيقة الموحّد (YAML)
│   ├── keywords.yaml · operators.yaml · types.yaml · directives.yaml
│   ├── builtins/ · errors/ · grammar/   ← قواعد الإنتاج (SoT)
│   └── _schemas/                        ← مخطّطات JSON للتحقّق
├── scripts/codegen/        ← gen_*.py (تقرأ YAML وتُنتج C++/توثيق)
├── docs/                   ← توثيق (incl. parser_rule/_generated المُولَّد)
├── examples/               ← أمثلة `.ص` مرقّمة (`01_مرحبا.ص` … `07_مكوّن_مركّب.ص`)
├── tests/                  ← `runner.py` · `config.yaml` · `framework/` · `metrics/`
│   └── behavior/           ← ⟵ هنا تعيش حِزَم `.ص` الستّ (انظر الجدول أدناه)
├── _bmad-output/           ← نظام الحوكمة (سياسات/ستوريات/قرارات)
└── .github/skills/         ← مهارات الوكلاء (sad-lang-dev …)

ماذا في stdlib/ وfeatures/ وtests/behavior/

هذه القوائمُ خارج كتلة الكود عمدًا: كتلُ الكود تُعرَض LTR (theme/rtl.css)، فينقلبُ فيها ترتيبُ أيّ سلسلةٍ عربيّةٍ أمامَ عين القارئ.

المجلّدما فيه
stdlib/ — وحدات .صرياضيات · نصوص · مصفوفات · خرائط · ملفات · شبكات · جيسون · وقت
stdlib/ — مجلّدات دعم C++ (١٧)io · math · string · json · xml · database · filesystem · image · include · low_level · platform · system · test · freestanding · إضافات · نص · ويب
features/graphics (SadUI: التخطيط ومفاتيح الخصائص) · network — الرسومات ليست في stdlib
tools/ (١٥)analyze · apk_builder · build · check · compiler (واجهة sad-build) · formatter · hub (موزِّع sad) · installers · lsp · pkg · profiler · repl · security-scanner · shared · wasm
tests/behavior/ (٦ حِزَم)P0_smoke · sections · rules_matrix · grammar_gaps · null_safety · _regression

⚠️ rules_matrix تحت tests/behavior/ لا تحت tests/ مباشرةً — ولهذا يكتب CI --dir rules_matrix بعد أن يقرأ tests_dir: tests/behavior من config.yaml. ولا وجود لمجلّد tests/comprehensive.

ملفّات تُقرأ أولًا

الملفلماذا
shared/lexer/include/token.hأنواع الرموز وPosition
language-truth/keywords.yamlمعجم اللغة كلُّه: المحجوزُ والعواملُ اللفظيّة والسياقيُّ والأنواعُ المضمنة — ومنه يُولَّد المعجم
shared/lexer/src/lexer_keywords.cppتسجيل الكلمات المحجوزة (المولَّد منها)
shared/types/include/value.hنوع القيم في وقت التشغيل
shared/parser/include/parser_core.hواجهة المحلل (كل دوال parse*)
interpreter/include/core/interpreter_core.hنقطة دخول المفسّر
compiler/include/frontend/sir_types.hتعليمات/أنواع SIR
language-truth/README.mdمصدر الحقيقة
tests/config.yamlمسارا الثنائيَّين اللذان يقيس بهما الـrunner

اقرأ بعده: أوّل مساهمة.

أوّل مساهمة (Walkthrough)

ماذا ستتعلّم: المسار الكامل لمساهمة صغيرة — من فرع معزول حتى PR إلى dev.

نأخذ مثالًا واقعيًّا: إضافة دالة مضمنة جديدة. الخطوات تعمّ على أي تغيير.

1) افهم الطبقة والتشابك

لا تغيير «معزول». دالة مضمنة تَمَسّ: language-truth/builtins/ (المصدر) + المُولَّد + تنفيذ المفسّر + codegen المترجم + اختبار. راجع الأنظمة المتشابكة.

2) أنشئ فرع عمل معزول (worktree من dev)

cd /c/s_lang/s-programming-language
git fetch origin
git worktree add /c/s_lang/temp-brunch/builtin-جذر -b agent/builtin-جذر origin/dev
cd /c/s_lang/temp-brunch/builtin-جذر

3) ابدأ من مصدر الحقيقة (لا من الكود المُولَّد)

أضف الدالة إلى language-truth/builtins/<domain>.yaml، ثم أعد التوليد:

python x.py gen   # أو مولِّدٌ بعينه: scripts/codegen/gen_all_builtins_yaml.py

4) نفّذ في الطبقة الصحيحة

  • المفسّر: interpreter/src/builtins/builtin_*.cpp.
  • المترجم: compiler/src/backend/llvm/builders/builtins/*.cpp.

5) اكتب اختبار .ص (إيجابيّ + سلبيّ)

ملف .ص تحت tests/behavior/ (لا تحت tests/ مباشرةً) بصيغة @expected الصحيحة — وtests_dir: tests/behavior في tests/config.yaml هو ما يجعل الـrunner يراه. راجع خريطة المستودع للحِزَم الستّ وأيُّها يناسب اختبارَك.

6) ابنِ وشغّل (تنفيذ مزدوج)

python x.py build                  # المحرّكان معًا في تهيئة واحدة
python tests/runner.py --level P1  # يجب أن يمرّ 100% بلا تراجع

7) أودِع (موقّع GPG) وافتح PR

git add -A
git commit -m "feat(builtins): دالة جذر(...)"     # موقّع تلقائيًّا
git push -u origin agent/builtin-جذر
gh pr create --base dev --title "إضافة جذر" --body "قائمة الملفات + نتائج runner"

8) نظّف بعد الدمج

cd /c/s_lang/s-programming-language
git worktree remove /c/s_lang/temp-brunch/builtin-جذر
git branch -D agent/builtin-جذر

✅ راجع معيار الإنجاز قبل إعلان الانتهاء.


اقرأ بعده: نظرة عامّة على الطبقات.

نظرة عامّة على الطبقات

ماذا ستتعلّم: الطبقات الكبرى للغة ص ومسؤوليّة كلٍّ منها والحدود بينها.

المكوّنات

المكوّنالمجلدالدور
النواة المشتركةshared/معجمي، نحوي، AST، نظام الأنواع Value، نظام الأخطاء
المفسّرinterpreter/مفسّر شجريّ؛ InterpreterCore يدير المتغيّرات والدوال والنطاقات والتقييم
المترجمcompiler/AST → SIR → LLVM IR → ملفّ تنفيذيّ (SIR يدعم تعليمات ملكية)
الخلفيّة الأصليّةcompiler/include/backend/native/SIR → شيفرة آلة → ELF64 ساكن بلا LLVM ولا رابطٍ أجنبيّ — الفصل
الآلة الافتراضية—vm/ أُزيل من الشجرة بالإيداع bcf0a746 («ستُعاد كتابتها من الصفر») — لا فصل له حتّى تُكتب
المكتبة القياسيةstdlib/ثماني وحدات .ص عربيّة في الجذر + سبعةَ عشرَ مجلّدَ دعمٍ C++ — التعدادُ في خريطة المستودع
الرسوماتfeatures/graphics/SadUI: محرّك التخطيط ومفاتيح الخصائص — ليست في stdlib/ — الفصل
الأدواتtools/١٥ مجلّدًا على dev؛ منها compiler (واجهة sad-build) وhub (موزِّع sad) — التعدادُ في خريطة المستودع
مصدر الحقيقةlanguage-truth/YAML SoT لكل بيانات اللغة + القواعد

القاعدة الطبقيّة (CW-02)

الترتيب صارم: Lexer → Parser → AST → SIR → LLVM. كل طبقة تعتمد فقط على الطبقة التي تحتها. يُمنع الاعتماد العكسيّ أو القفز بين الطبقات. هذا يضمن:

  • عزل الأخطاء: خطأ معجميّ يُصلَح في المعجمي، خطأ ترتيب حقول في SIRBuilder، إلخ (BF-10).
  • قابليّة الاستبدال: يمكن تغيير الواجهة الخلفيّة (مفسّر/مترجم) دون مسّ الأماميّة.

مخطّط الطبقات

flowchart TD
  SRC["مصدر .ص (UTF-8)"] --> LEX["LexerCore<br/>shared/lexer"]
  LEX --> PAR["ParserCore<br/>shared/parser"]
  PAR --> AST["AST<br/>shared/ast"]
  AST --> INT["InterpreterCore<br/>(تنفيذ فوريّ)"]
  AST --> SIR["SIRBuilder<br/>compiler/src/frontend"]
  SIR --> OPT["SIROptimizer"]
  OPT --> LLVM["LLVMCodeGen<br/>compiler/src/backend/llvm"]
  LLVM --> EXE["ملفّ تنفيذيّ أصليّ"]
  SOT["language-truth/ (SoT)"] -. "توليد" .-> LEX
  SOT -. "توليد" .-> PAR
  SOT -. "توليد" .-> ERR["نظام الأخطاء"]

مبدأان عابران للطبقات

  1. مصدر الحقيقة أولًا: أي بيان لغويّ (كلمة/عامل/نوع/خطأ/دالة مضمنة) يبدأ من language-truth/ ثم يُولَّد. راجع الجزء الثالث.
  2. التنفيذ المزدوج: كل ميزة تعمل في المفسّر والمترجم؛ إن عملت في أحدهما فقط فالمشكلة في SIR/LLVM (BF-08).

اقرأ بعده: خطّ الأنابيب.

خطّ الأنابيب: من المصدر إلى التنفيذ

ماذا ستتعلّم: رحلة برنامج .ص خطوةً بخطوة عبر الطبقات، بفرعَي التفسير والترجمة.

المسار الكامل

flowchart LR
  A["مصدر .ص"] --> B["LexerCore.nextToken()<br/>→ تيار Tokens"]
  B --> C["ParserCore.parseProgram()<br/>→ StmtList (AST)"]
  C --> D{المسار}
  D -->|تفسير| E["InterpreterCore<br/>زيارة AST وتقييمه"]
  D -->|ترجمة| F["SIRBuilder<br/>AST → SIR"]
  F --> G["SIROptimizer<br/>تمريرات تحسين"]
  G --> H["LLVMCodeGen<br/>SIR → LLVM IR"]
  H --> I["LLVM → كائن → ربط → تنفيذيّ"]

المرحلة 1 — التحليل المعجمي

LexerCore يحوّل النصّ (UTF-8) إلى Tokenات (نوع + قيمة + Position). يتخطّى المسافات والتعليقات، ويجمّع تعليقات التوثيق ##. الكلمات المحجوزة الأربعون تُعرَّف في lexer_keywords.cpp. → المعجمي.

المرحلة 2 — التحليل النحوي

ParserCore (نزوليّ تعاوديّ) يبني AST. نقطة الدخول parseProgram() تكرّر parseDeclaration()، الذي يوزّع حسب الرمز إلى تصريحات/جمل/تعابير. سلسلة أسبقيّة التعابير تُعرّف العوامل. → النحوي وقواعد المحلل SoT.

المرحلة 3 — AST

شجرة من عقد (StmtPtr/ExprPtr) يزورها المستهلِكون عبر ASTVisitor (نمط Visitor). → AST.

المرحلة 4أ — التفسير

InterpreterCore يزور AST مباشرةً: يدير النطاقات والمتغيّرات والدوال، ويقيّم التعابير، وينفّذ الجمل. سريع للتطوير والاختبار.

المرحلة 4ب — الترجمة (sad-build)

  1. SIRBuilder: AST → SIR (تمثيل وسيط بتعليمات ملكية، sir_types.h).
  2. SIROptimizer: تمريرات على SIR.
  3. LLVMCodeGen: SIR → LLVM IR، ثم LLVM يُنتج كائنًا يُربَط لملفّ تنفيذيّ.

لماذا SIR وسيط؟ يفصل دلالة الملكية/الأنواع عن تفاصيل LLVM، ويسهّل التحسين والتشخيص (--أظهر-llvm، وتفريغات SIR).

نقاط تشخيص مفيدة

  • اختلاف سلوك المفسّر عن المترجم ⇒ المشكلة في SIRBuilder أو LLVMCodeGen (BF-08).
  • ولّد LLVM IR بـ--أظهر-llvm وافحص: الكتلة الأولى، تطابق أنواع الحقول، ترتيب التعليمات، getelementptr.

اقرأ بعده: الأنظمة المتشابكة.

الأنظمة المتشابكة

ماذا ستتعلّم: لماذا لا يوجد «تغيير معزول» في لغة ص، وأيّ ملفّات يَمَسّها كل نوع تغيير.

📎 المجلّدات المذكورة أدناه مبصومةٌ بأسماء مدخلاتها على dev — فظهورُ ملفٍّ جديدٍ فيها أو اختفاؤه يُنذر هذا الفصل، ولا يُنذره تعديلُ محتوًى داخلها.

كل ميزة تعبر عدّة أنظمة: مصدر الحقيقة + المُولَّد + المعجمي/النحوي + المفسّر + المترجم + الأخطاء + التوثيق + الاختبارات. تجاهل أحدها يكسر CI أو تجربة المستخدم.

جدول الأثر (File List) حسب التغيير

التغييرالملفّات المتأثّرة عادةً
كلمة مفتاحيّةlanguage-truth/keywords.yaml → shared/lexer/generated/keywords_generated.{h,cpp} (مُولَّد) + shared/parser/src/<dir>/ + shared/ast/include/ + interpreter/src/visitors/ (٣٠ ملفَّ .cpp في المجلّد — لا كلُّها يمسُّها كلُّ تغيير) + compiler/src/frontend/ (+opcode في sir_types.h) + اختبار .ص
دالة مضمنةlanguage-truth/builtins/<domain>.yaml (+_index.yaml) → مُولَّد shared/builtins/generated/builtin_registry_generated.h + interpreter/src/builtins/ + compiler/src/backend/llvm/builders/builtins/ (٩ مدخلات: ٨ .cpp + README.md) + اختبار
رمز خطأlanguage-truth/errors/<cat>.yaml (مصدر) + shared/errors/include/error_codes.h + مُولَّد + اختبار
توجيه @language-truth/directives.yaml + مُولَّد + parser + AST + visitors + codegen + اختبار
قاعدة نحويّةlanguage-truth/grammar/*.yaml (SoT) + shared/parser/src/ + توثيق مُولَّد docs/parser_rule/_generated/ (٨ أقسام + INDEX.md)
opcode SIRcompiler/include/frontend/sir_types.h + SIRBuilder + compiler/src/backend/llvm/ + اختبار

مخطّط التشابك (مثال: كلمة مفتاحيّة)

flowchart TD
  Y["keywords.yaml (SoT)"] --> G["gen_keywords.py"]
  G --> KGEN["keywords_generated.{h,cpp}"]
  KGEN --> LEX["Lexer يتعرّف الرمز"]
  LEX --> PAR["Parser يضيف قاعدة"]
  PAR --> AST["عقدة AST"]
  AST --> INT["زائر المفسّر"]
  AST --> CG["codegen المترجم"]
  INT & CG --> T["اختبار .ص (تنفيذ مزدوج)"]

القاعدة الذهبيّة

ابدأ من مصدر الحقيقة، أعد التوليد، اعبر كل الطبقات، وأثبت بـاختبار مزدوج. الكود «المتكامل» يعبرها كلها — وإلا فشل CI أو انكسرت التجربة.


اقرأ بعده: فلسفة مصدر الحقيقة.

الفلسفة: لماذا «مصدر الحقيقة» أولًا

ماذا ستتعلّم: المبدأ المعماريّ الأهمّ في لغة ص — البيانات تقود الكود، لا العكس.

المشكلة التي يحلّها

في المُصرِّفات التقليديّة، تتوزّع «حقائق اللغة» (الكلمات المفتاحيّة، العوامل وأسبقيّتها، الأنواع، رسائل الأخطاء، الدوال المضمنة) عبر عشرات الملفّات المكتوبة يدويًّا. النتيجة: تباعد — يُضاف عاملٌ في المعجمي ويُنسى في المنسّق أو LSP أو التوثيق.

الحلّ: مصدر موحّد مدفوع بالبيانات

لغة ص تعتمد language-truth/ كمصدر واحد (YAML) لكل بيانات اللغة، ويُولَّد منه كود C++ والتوثيق آليًّا:

flowchart LR
  Y["language-truth/*.yaml<br/>(المصدر الوحيد)"] --> GEN["scripts/codegen/gen_*.py<br/>(المولّد)"]
  GEN --> CPP["shared/*/generated/*.{h,cpp}<br/>(مُولَّد — لا يُحرَّر)"]
  GEN --> DOC["توثيق + مخطّطات مُولَّدة"]
  Y --> TOOLS["LSP · formatter · pkg · analyze"]
  CPP --> BUILD["بناء C++"]

القاعدة الذهبية (SoT-First)

أي تغيير في بيانات اللغة يبدأ من YAML — لا من كود C++ المُولَّد.

  • الملفّات تحت */generated/ مُولَّدة آليًّا — تحريرها يدويًّا يُمحى عند البناء التالي.
  • لكنها متتبَّعة في git (ليست build-only) — ضمّنها في نفس الـcommit مع YAML (يجب أن يتطابقا).

لماذا هذا «أكثر تطوّرًا» من rustc؟

  • rustc: قواعده وبياناته موصوفة نثرًا في الدليل + موزّعة في الكود.
  • لغة ص: مصدر منظَّم وقابل للتحقّق آليًّا (مخطّطات JSON في _schemas/)، يولّد الكود والتوثيق، ويُفحَص تماسكه في CI. يمتدّ حتى قواعد النحو نفسها (راجع قواعد المحلل SoT) — طبقة لا يملكها معظم المُصرِّفات.

ماذا يغطّي language-truth/؟

كلمات مفتاحيّة · عوامل (وأسبقيّاتها) · أنواع · توجيهات @ · رموز أخطاء ورسائلها · دوال مضمنة ووحدات · قواعد إنتاج النحو · تراكيب اللغة. → التفصيل.


اقرأ بعده: ‏language-truth/.

‏language-truth/ — كتالوج اللغة

ماذا ستتعلّم: بنية مجلد مصدر الحقيقة، وملفّاته، ومخطّطاته، وكيف تعدّله بأمان.

البنية

language-truth/
├── keywords.yaml          ← الكلمات المفتاحيّة (محجوزة + سياقيّة) + tokenType + KW-RES-NNN
├── operators.yaml         ← العوامل: الرمز، الأسبقية، الترابط، op.<name>
├── types.yaml             ← الأنواع المدمجة
├── directives.yaml        ← توجيهات @ (حجم/ذري/غير_آمن/…)
├── *_constructs.yaml      ← تراكيب اللغة (oop/expr/grammar)
├── builtins/              ← الدوال المضمنة لكل نطاق (+ _index.yaml)
├── errors/                ← رموز ورسائل الأخطاء (مصدر V5)
├── grammar/               ← ⭐ قواعد الإنتاج النحويّة (SoT) — انظر فصلها
├── _schemas/              ← مخطّطات JSON للتحقّق من كل ملف
└── _meta/ · learning/ · stdlib/ · tests/ · backend/ · dialects/ · tools/

📏 المقيس على dev: جذرُ language-truth/ فيه 18 ملفَّ YAML و11 مجلّدًا وملفَّي توثيق (README.md · VERSIONING.md) — 31 مدخلًا في الجملة. والشجرةُ أعلاه عيّنةُ توجيهٍ لا جردًا: backend/ (جداولُ الخلفيّة الأصليّة — انظر فصلها) وdialects/ وtools/ أُضيفت بعد كتابة الفصل، وثمانيةُ ملفّات ui_*.yaml تعيش في الجذر أيضًا. ولا وجودَ لمجلّد _notation/ في الجذر: الترميزُ ملفٌّ واحدٌ داخل القواعد، grammar/_notation.yaml.

أمثلة على الصيغة

كلمة مفتاحيّة (keywords.yaml):

- { id: "KW-RES-012", subcategory: "control_flow", since: "1.0.0",
    word: "إذا", tokenType: KEYWORD_IF, english: if,
    aliases: ["اذا"], roles: [block_opener] }

عامل (operators.yaml) — يحمل الأسبقية والترابط (مصدر سلسلة المحلل):

- { id: op.assign, symbol: "=", name_ar: "إسناد", category: assignment,
    arity: binary, precedence: 15, associativity: right, since: "1.0.0", status: stable }

التحقّق بالمخطّطات

كل ملف يُتحقَّق ضدّ مخطّط في _schemas/ (مثل keywords.schema.json، grammar_production.schema.json). هذا يمنع الانجراف ويضمن صحّة البنية آليًّا.

تعديله بأمان (الإجراء)

  1. عدّل YAML المصدر فقط (لا generated/).
  2. أعد التوليد بالمولّد المعنيّ (→ توليد الكود).
  3. تأكّد من تطابق YAML + المُولَّد، وضمّنهما في نفس الـcommit.
  4. حدّث الطبقات المستهلِكة (parser/visitors/codegen) واكتب اختبارًا.

ثوابت مُولَّدة لا سلاسل حرفيّة: سجّل الدوال بـBn::<Group>::<CPP_ID>، وطرق الأنواع بـTM::<Group>::<NAME>، وأطلِق الأخطاء بـErrorCode::<NAME>. يُمنع نصّ خطأ حرّ.


اقرأ بعده: توليد الكود.

توليد الكود (codegen)

ماذا ستتعلّم: كيف تتحوّل ملفّات YAML إلى كود C++ وتوثيق، وأين المولّدات.

الخطّ

flowchart LR
  Y["language-truth/*.yaml"] --> P["scripts/codegen/gen_*.py"]
  P --> H["shared/*/generated/*.{h,cpp}"]
  P --> D["docs/ (توثيق مُولَّد)"]
  H --> B["بناء C++ (CMake)"]

المولّدات (scripts/codegen/)

📏 المقيس على dev: 79 ملفَّ بايثون في scripts/codegen/، منها 31 بالبادئة gen_ (مولِّد فعليّ) و22 بالبادئة check_ (حرّاسٌ وفاحصون)، و5 في _lib/ (مكتباتٌ مساعدة)، والباقي (21) اختباراتُ test_* وسكربتاتُ ترحيلٍ لمرّةٍ واحدة. الجدولُ أدناه عيّنةٌ تشرح النمط، لا جردًا كاملًا — الجردُ في المجلّد نفسِه.

المولّدالمصدر → الناتج
gen_keywords.pykeywords.yaml → keywords_generated.{h,cpp}
gen_types.pytypes.yaml → كود الأنواع المُولَّد
gen_builtins_registry.py / gen_all_builtins_yaml.pybuiltins/ → builtin_registry_generated.h
gen_error_messages.pyerrors/ → رسائل/تشخيص مُولَّد (أداةُ sadinfo ومولِّدُها gen_sadinfo_errors.py تقاعدا في تمّوز ٢٠٢٦ — #144)
gen_parser_grammar_docs.pygrammar/*.yaml → docs/parser_rule/_generated/
check_grammar_conformance.pyيفحص تغطية القواعد وتماسك وسوم الاختبارات

اكتشف الواجهة بـpython scripts/codegen/<gen>.py --help. بعضها يعمل عبر CMake عند البناء.

قواعد ذهبيّة

  • لا تحرّر generated/ يدويًّا — عدّل YAML ثم أعد التوليد.
  • ضمّن المُولَّد في الـcommit مع YAML (متطابقين) — هو جزء من «قائمة الملفّات».
  • فحص CI: مولّدات --check (مثل gen_parser_grammar_docs.py --check) تفشل إن تباعد المُولَّد عن المصدر.

نمط مولّد التوثيق (مثال متقدّم)

gen_parser_grammar_docs.py يقرأ قواعد الإنتاج (grammar/*.yaml) ويُنتج لكل قاعدة: BNF + المسار إلى دالة المحلل (maps_to) ⇒ عقدة AST + مخطّط Mermaid آليّ + روابط «يستدعي/مُستدعى». ثم --check يضمن بقاء التوثيق محدَّثًا في CI. → قواعد المحلل SoT.


اقرأ بعده: قواعد المحلل كمصدر موحّد.

قواعد المحلل كمصدر موحّد (Grammar SoT) ⭐

ماذا ستتعلّم: كيف وُثِّقت قواعد نحو لغة ص كمصدر موحّد قابل للتحقّق، وكيف يُولَّد منها توثيق غنيّ — وهي الطبقة التي تميّز لغة ص عن معظم المُصرِّفات.

الفكرة

المحلل في لغة ص مكتوب يدويًّا (recursive descent). بدل ترك القواعد ضمنيّةً في الكود، نُدوِّنها كـقواعد إنتاج صوريّة في language-truth/grammar/، مع خريطة تتبُّع (maps_to) لكل دالة تحليل فعليّة. النتيجة: مواصفة معياريّة + جسر يمنع تباعد المواصفة عن التنفيذ (يفحصه CI).

flowchart LR
  PARSER["shared/parser/ (الكود = الحقيقة)"] -->|استخراج| Y["language-truth/grammar/*.yaml"]
  Y -->|gen_parser_grammar_docs.py| DOC["docs/parser_rule/_generated/<br/>(BNF + مخطّطات + مسار AST)"]
  Y -->|check_grammar_conformance.py| CI["فحص التغطية + التماسك"]

الطبقات (تطابق shared/parser/src/)

ملفمعرّفاتيغطّي
00_program.yamlgr.program.*البرنامج/التصريح/الجملة/الكتلة
10_statements.yamlgr.stmt.*إذا/بينما/لكل/طابق/حالة/حاول/…
20_declarations.yamlgr.decl.*متغيّر/دالة/معاملات/استيراد/تصدير/خارجي
30_oop.yamlgr.oop.*صنف/بنية/تعداد/سمة/تنفيذ/امتداد/أعضاء
40_expressions.yamlgr.expr.*سلسلة الأسبقية الكاملة + لامدا/f-string
50_patterns.yamlgr.pattern.*أنماط المطابقة
60_advanced.yamlgr.adv.*أنواع/قوالب/عمر/تزامن/استيعاب/ماكرو/FFI/واجهة
70_lexical.yamlgr.lex.*الطرفيات (جسر للمعجمي)

ومعها في المجلّد نفسِه ستّةُ ملفّاتٍ ليست قواعدَ إنتاج:

ملفّدوره
_notation.yamlالميتا-قواعد: كيف تُقرأ ملفّات القواعد (علاقة ebnf بـalternatives)
lowers_to.yamlربطُ كلّ قاعدةٍ بأوپكودات SIR — مُشتقٌّ بالتشغيل (انظر أدناه)
README.mdدليلُ المجلّد
CONFORMANCE_REPORT.md · CONFORMANCE_REPORT_detail.md · DISCOVERED_ISSUES.mdمخرجاتُ فحص المطابقة وما كشفه

lowers_to.yaml — الجسر إلى الخلفيّات

لا يكفي أن تُوثَّق القاعدةُ نحويًّا؛ السؤالُ العمليّ: إلى أيّ أوپكودات تنزل، وأيّ معماريّةٍ تخفضها كلَّها؟ يجيب lowers_to.yaml، وهو مُولَّدٌ آليًّا بـgen_grammar_lowers_to.py — والاشتقاقُ تجريبيّ لا تحليليّ: تُترجَم اختباراتُ القاعدة بـ--أظهر-sir وتُجمَع أوپكوداتُها الفعليّة.

الحقلمعناه
lowers_toكلّ أوپكودات القاعدة (عليها يقوم الحكم)
beyond_baselineما تضيفه القاعدةُ فوق سقالةِ أيّ دالّة
native_okالمعماريّات التي تخفض أوپكوداتِها كلَّها في الخلفيّة الأصليّة
native_missing_<قوس>الأوپكودات المانعة لكلّ معماريّة
evidenceملفّات الاختبار التي اشتُقّ منها
status: not_derivedلم يُترجَم — لا يُفترَض نجاحه

الإحصاء المسجَّل في الملفّ: ١٠٧ قواعد، منها ١٠٥ مشتقّةٌ و٢ not_derived؛ والتغطية الأصليّة ١٠٢ لـx86-64 و١٠٢ لـarm64 و٢٦ لـriscv64 (٣ عيّناتٍ لكلّ قاعدة). فهذا الملفّ هو المكان الذي تُقرأ فيه فجوةُ RISC-V كمًّا، لا انطباعًا.

🔑 not_derived إعلانٌ لا صمت. القاعدةُ التي لم تُترجَم تُوسَم صراحةً بدل أن تسقط من الإحصاء — فغيابُ البرهان مذكورٌ، لا مسكوتٌ عنه.

شكل قاعدة الإنتاج

كل قاعدة (مخطّط _schemas/grammar_production.schema.json):

  • id بصيغة gr.<area>.<name> (فريد، مرجِع).
  • ebnf (مقروء) + alternatives (تمثيل منظَّم آليًّا، المرجِع الدلاليّ: رموز terminal/nonterminal/optional/repeat/group/alt).
  • references (روابط keywords.yaml/operators.yaml).
  • maps_to (ملف:دالة المحلل) — جسر التتبُّع.
  • ast_node (العقدة المُنتَجة) + conformance.test_budget.
- id: gr.stmt.if
  lhs: { nonterminal: IfStatement, name_ar: "جملة إذا", name_en: if_statement }
  ebnf: "IfStatement = 'إذا' '(' Expression ')' Block { 'وإلا' ... } [ 'وإلا' Block ] ;"
  maps_to: [{ file: shared/parser/src/statements/parser_statements.cpp, function: "ParserCore::parseIfStmt" }]
  ast_node: "IfStmt"

التوليد والتحقّق

python scripts/codegen/gen_parser_grammar_docs.py          # ينتج docs/parser_rule/_generated/
python scripts/codegen/gen_parser_grammar_docs.py --check  # CI: هل التوثيق محدَّث؟
python scripts/codegen/check_grammar_conformance.py        # تغطية الاختبارات + تماسك الوسوم

التوثيق المُولَّد يحوي لكل قاعدة: BNF + تفصيل البدائل + مخطّط مسار الدوال حتى AST (مُشتقّ من maps_to ومراجع nonterminal) + مخطّط البنية النحويّة + روابط «يستدعي/مُستدعى».

التحقّق من الانجراف (الكود هو الحقيقة)

  • كل maps_to.function يجب أن توجد فعلًا في المحلل (فحص CI).
  • كل عقدة ast_node يجب أن توجد في shared/ast/ (أو نوع إرجاع معروف).
  • كل references/nonterminal ref صالح.

📎 المرجع الحيّ: language-truth/grammar/README.md وdocs/parser_rule/_generated/INDEX.md في المستودع الرئيسيّ.


اقرأ بعده: المحلل المعجمي.

المحلل المعجمي (Lexer)

ماذا ستتعلّم: كيف يحوّل LexerCore نصّ .ص (UTF-8) إلى تيار Token، بتفصيل دقيق من الكود الفعليّ — بما فيه المعالجة الذكيّة للمحارف العربية متعدّدة البايتات.

📎 المصدر: shared/lexer/src/lexer_core.cpp · token.h · lexer_keywords.cpp

البناء والنظر المسبق

LexerCore(source) يحتفظ بالمصدر ومؤشّر current_ وموقع Position. الأدوات الأساسيّة:

Position يبدأ من 1 (سطر/عمود) وoffset من 0.

موزِّع nextToken() — ترتيب الإرسال

الدالة المحوريّة nextToken() تتبع ترتيبًا حسّاسًا (الترتيب يمنع لبس المحارف العربية):

flowchart TD
  N["nextToken()"] --> WS["حلقة: skipWhitespace + التعليقات (# #* ## #**)"]
  WS --> EOF{"نهاية الملف؟"}
  EOF -- نعم --> E["END_OF_FILE"]
  EOF -- لا --> D{"تصنيف المحرف"}
  D -->|رقم عربيّ/إنجليزيّ| NUM["scanNumber()"]
  D -->|'r\"' أو 'ح\"'| RAW["scanRawString()"]
  D -->|'f\"' أو 'م\"' أو 'ص\"'| FS["scanFString()"]
  D -->|'\"'| STR["scanString()"]
  D -->|'×' (0xC3 0x97)| MUL["OP_MULTIPLY"]
  D -->|'،' '؛' '؟' (0xD8 ..)| ARB["ARABIC_COMMA / SEMICOLON / QUESTION"]
  D -->|حرف/‎_‎/عربيّ| ID["scanIdentifier()"]
  D -->|عامل| OP["scanOperator()"]

⭐ المعالجة الدقيقة لـUTF-8 (نقطة متقدّمة)

المحارف العربية متعدّدة البايتات تُفحَص قبل scanIdentifier وإلّا ابتلعها كجزء من مُعرّف:

المحرفUTF-8الرمز الناتجالموقع
رقم عربيّ ٠–٩0xD9 + 0xA0..0xA9عدد → scanNumberL1646
ح" (نصّ خام)0xD8 0xAD + "STRING_RAWL1675
م" / ص" (نصّ منسَّق)0xD9 0x85 / 0xD8 0xB5 + "STRING_FSTRINGL1694
× ضرب0xC3 0x97OP_MULTIPLYL1726
، فاصلة0xD8 0x8CARABIC_COMMAL1746
؛ منقوطة0xD8 0x9BARABIC_SEMICOLONL1753
؟ استفهام0xD8 0x9FQUESTION (و؟.→QUESTION_DOT)L1760

💡 لهذا تعمل س، ص وأ ؟ ب : ج و٣ × ٤ كما لو كُتبت باللاتينيّة — المعجمي يطبّع المحارف العربية إلى رموز قياسيّة.

الماسحات (Scanners)

الدالةالموقعيمسح
scanNumberL323صحيح/عشريّ + 0x/0b/0o؛ يترك .. لماسح العوامل (المدى)
scanStringL754"..." مع هروب \n \t \\ \" \r \b \f \v \0 \u \U \x
scanRawStringL998r"..."/ح"..." بلا معالجة هروب
scanFStringL1054f"...{تعبير}..."
scanDocCommentL1167## / #** **# (يُرفَق بأوّل تصريح)
scanIdentifierL1250مُعرّف UTF-8 (يتوقّف قبل ،/؛)
scanOperatorL1347العوامل (switch على `+ - * / % = < > ! . ^

التعليقات

حلقة التخطّي في بداية nextToken تعالج: # سطر · #* … *# كتلة · ## توثيق سطر · #** … **# توثيق كتلة (التعليق غير المغلق يرمي خطأً). تعليقات ## تُلتقَط وتُرفَق لاحقًا بأوّل تصريح (انظر تهيئة المحلل النحوي).

الكلمات والرموز

  • محجوزة (40): مسجّلة في lexer_keywords.cpp (KeywordTable::initialize())، مصدرها language-truth/keywords.yaml.
  • سياقيّة: لها KEYWORD_* في token.h لكنها لا تُسجَّل كمحجوزة؛ يميّزها المحلل النحوي بالسياق.
  • أنواع مدمجة كمُعرّفات: رقم/نص/… ليست محجوزة.

إضافة كلمة مفتاحيّة (مختصر)

  1. أضف KEYWORD_FOO إلى token.h. 2. محجوزة: سجّلها في keywords.yaml ثم أعد توليد keywords_generated. سياقيّة: لا تسجّلها، واستخدم التحقّق المزدوج في المحلل النحوي.
  2. أضف القاعدة + عقدة AST + الزوّار + codegen + اختبار. → التشابك.

اقرأ بعده: المحلل النحوي.

المحلل النحوي (Parser)

ماذا ستتعلّم: بنية المحلل النزوليّ التعاوديّ، نقطة الدخول، الموزِّعات، سلسلة أسبقيّة التعابير، وآليّة الكلمات السياقيّة — بتفصيل دقيق من الكود.

📎 المصدر: shared/parser/src/core/parser_main.cpp · parser_expressions.cpp · parser_core.h

البنية

Sad::Parser::ParserCore محلل نزوليّ تعاوديّ يحوّل تيار الرموز إلى AST. كل قاعدة ≈ دالة parseXxx(). الكود موزّع على core/، statements/، declarations/، specs/، ui/. التهيئة (ParserCore::ParserCore) تجلب رمزين مسبقًا (current_, nextToken_) وتتخطّى الفراغات/التعليقات وتجمّع تعليقات ## المعلّقة.

نقطة الدخول والموزِّعات

flowchart TD
  P["parseProgram() — L137"] -->|while !isAtEnd| D["parseDeclaration() — L343"]
  D -->|كلمة تصريح| DECL["دالة/صنف/متغيّر/تعداد/استيراد/…"]
  D -->|@| DIR["tryParseDirective() — L1588"]
  D -->|غير ذلك| S["parseStatement() — L1241"]
  S -->|كلمة جملة| CF["إذا/بينما/لكل/طابق/حاول/…"]
  S -->|افتراضي| ES["parseExpressionStmt() → parseExpression()"]
  • parseProgram() — يكرّر parseDeclaration حتى نهاية الملف، مع حماية من الحلقة اللانهائيّة (MAX_STUCK_ITERATIONS=3) وعدّ الدوال الرئيسيّة.
  • parseDeclaration() — موزّع التصريحات (~33 فرعًا): توجيهات @، مُزخرِفات، سمات [[..]]، استورد/من/صدّر/خارجي، دالة/مولد/async، قالب/فضاء، صنف/سمة/نفّذ/امتداد/ماكرو/نوع/عقد، متغير/ثابت، تصريح ببدء النوع/الصنف، اختبر/حالة/اعرض، تعداد/بنية، وأخيرًا parseStatement.
  • parseStatement() — موزّع الجمل: إذا/بينما/لكل/حالة/طابق/ارجع/أنتج/باستخدام/أجّل/أطلق/اختر/توقف/استمر/{/حاول/ارمي، وافتراضيًّا جملة تعبير.

سلسلة أسبقيّة التعابير

من الأدنى ربطًا (أعلى المستوى) إلى الأعلى — كل دالة تستدعي الأعلى أسبقيّةً ثم تحلّق على عاملها:

المستوىالدالةالعامل
1parsePipeline|>
2parseAssignment= := += -= *= /= //= %=
3parseTernary? :
4parseNullCoalesce??
5–10parseLogicalOr/And · parseBitwiseOr/Xor/And|| && | ^ &
11–12parseEquality · parseComparison== != < <= > >= في
13parseRange..
14–15parseTerm · parseFactor+ - << >> · * / // %
16parseUnary! - ~ ++ -- &(استعارة)
17parsePower** (يمينيّ)
18parsePostfix() . ?. [] ++ -- !()
19parsePrimaryالقيم الذريّة

هذا الترتيب يُعرّف أسبقيّة العوامل — ومصدره language-truth/operators.yaml.

⭐ الكلمات السياقيّة (التحقّق المزدوج)

كثير من «الكلمات» (سمة/نفّذ/امتداد/ماكرو/حالة/أجّل/أطلق/اختر) ليست محجوزة؛ يتعرّف عليها المحلل بنمطٍ مزدوج + نظر مسبق:

// قاعدة سياقيّة: تُعامَل تصريحًا فقط إن تلاها مُعرّف (وإلا مُعرّف عاديّ)
if (match(TT::KEYWORD_TRAIT) ||
    (check(TT::IDENTIFIER) && peekNext().getType() == TT::IDENTIFIER
     && current_.getValue() == "سمة" && (advance(), true))) {
    return parseTraitDecl();
}

مثال على فضّ الغموض: أجّل تُعامَل جملةً إلّا إن تلاها =/+=/. (فتصير مُعرّفًا). هذا يسمح باستعمال هذه الكلمات أسماءَ متغيّرات خارج سياقها.

السكر النحوي (Desugaring)

يُحوِّل المحلل بعض الصياغة وقت التحليل:

  • الأنبوب: أ |> د ← د(أ) · أ |> د(ب) ← د(أ، ب) (إزالةُ السكّر في parsePipeline L67).
  • الإسناد المركّب: س += ص ← س = س + ص (يعيد بناء طرف القراءة للحقول/الفهارس المتداخلة).
  • القيمة المطلقة: |تعبير| ← abs(تعبير).

التعافي من الأخطاء

عند الخطأ، يُستدعى synchronize() للقفز إلى نقطة مزامنة (بينما/حاول/امسك/صنف/…) المُسجَّلة في recoverySystem_، وتُجمَّع التشخيصات في ErrorManager. → نظام الأخطاء.

التوثيق الكامل للقواعد

قواعد المحلل موثّقة كمصدر موحّد (BNF + مخطّطات + مسار حتى AST) — راجع قواعد المحلل SoT وdocs/parser_rule/_generated/ في المستودع الرئيسيّ.


اقرأ بعده: شجرة AST.

شجرة AST

ماذا ستتعلّم: كيف تُمثَّل البرامج كشجرة، ونمط الزائر (Visitor) الذي يستهلكها — ومَن يطبّقه فعلًا ومَن لا يطبّقه رغم أنّه يستهلك الشجرة.

📎 المصدر: shared/ast/include/ast_visitor.h

الدور

shared/ast/ يُعرّف عقد الشجرة المجرّدة التي يبنيها المحلل النحوي ويستهلكها المفسّر والمترجم. القاعدة ASTNode، ومنها Statement (جمل) وExpression (تعابير).

أنواع العقد

الواجهة ASTVisitor تصرّح ٩٥ دالّة زيارة — واحدةً لكلّ نوع عقدة. فهذا العدد هو إحصاء أنواع العقد نفسه، لا تقديرًا: العقدة التي لا دالّةَ لها لا تُزار.

⚠️ ٩٠ صرفة و٥ لها جسمٌ فارغ. ليست الخمسُ والتسعون كلُّها = 0: خمسٌ منها (visitEnumVariantExpr · visitAsmBlockStmt · visitUIStateDecl · visitUIConditional · visitUILoop) صُرِّحت بجسمٍ فارغ {} داخل ASTVisitor نفسِها. فالزائرُ الذي يُهملها يُصرَّف بلا شكوى — وهذا بالضبط بابُ السلوك الصامت الذي يحرسُه = 0 في الباقي.

العائلةأمثلة
تعابيرBinaryExpr · UnaryExpr · TernaryExpr · LiteralExpr · VariableExpr · AssignExpr · CallExpr · IndexExpr · MemberExpr · ArrayExpr · MapExpr · LambdaExpr · RangeExpr · SliceExpr · TupleExpr
ملكيّة وتزامنBorrowExpr (استعارة) · AwaitExpr · WalrusExpr
أمان العدمOptionalChainExpr (?.) · NullCoalesceExpr (??) · ErrorPropagateExpr (انشر)
استيعاباتListComprehensionExpr · DictComprehensionExpr · SetComprehensionExpr · GeneratorExpr
توجيهات @UnsafeBlockStmt (@غير_آمن) · ComptimeBlockStmt (@وقت_الترجمة) · SizeofExpr (@حجم) · AtomicExpr (@ذري) · VolatileVarDeclStmt (@متطاير) · AsmBlockStmt (كتلة «تجميع … نهاية») · InlineAsmExpr
كائنيّةNewExpr · MemberAccessExpr · MemberAssignExpr · IndexAssignExpr · MethodCallExpr · ThisExpr · SuperExpr · ClassDeclStmt
جملExprStmt · VarDeclStmt · IfStmt · WhileStmt · ForStmt · ForRangeStmt · SwitchStmt · MatchStmt · ReturnStmt · YieldStmt · BreakStmt · ContinueStmt · BlockStmt · TryStmt · RaiseStmt · WithStmt · DeferStmt (تنظيفٌ مضمون) · GoStmt · SelectStmt/SelectCase
تصريحاتFunctionDecl · ClassDecl · FieldDecl · MethodDecl · PropertyDecl · ConstructorDecl · DestructorDecl · EnumDecl · StructDecl · TestDecl · ImportStmt/FromImportStmt · ExportStmt/ExportDecl/ReExportStmt
قوالب وعموميّاتTemplateFunctionDecl · TemplateClassDecl · TemplateInstantiation · NamespaceDecl · OperatorDecl · TraitDecl · ImplDecl · ExtensionDecl · MacroDecl · TypeAliasDecl · TupleDestructureStmt
تعدادٌ بحمولةEnumVariantExpr — بناء عضو تعداد بحمولة (ADT)
واجهة SadUIUIDeclaration · UIWidgetExpr · UIModifier · UIEventHandler · UIStateDecl · UIConditional · UILoop
مزخرِفاتDecoratorExpr

العقدة المُنتَجة لكل قاعدة نحويّة مُوثَّقة في حقل ast_node بمصدر القواعد. → grammar SoT.

نمط الزائر (Visitor)

العقد تُستهلَك عبر ASTVisitor (الواجهة الصرفة) وBaseASTVisitor (يرثها ويعطي تطبيقًا فارغًا لكلّ دالّة، فيَشتقّ منه الزائرُ ويعيد تعريف ما يحتاجه فقط). هذا يحقّق مبدأ المفتوح/المغلق: أضف مستهلِكًا جديدًا دون تعديل العقد.

flowchart LR
  AST["عقد AST"] --> V{"ASTVisitor / BaseASTVisitor"}
  V --> EE["ExpressionEvaluator<br/>StatementExecutor<br/>(المفسّر)"]
  V --> TC["TypeChecker<br/>(shared/semantic)"]
  V --> AN["AstAnalysisVisitor<br/>(tools/analyze)"]
  V --> PR["ASTPrinter"]
  AST -.->|"لا يمرّ بالزائر"| SB["SIRBuilder<br/>(dynamic_cast + بُناةٌ فرعيّة)"]

مَن يطبّق الزائر فعلًا (مقيسٌ على dev): interpreter/include/visitors/expression_evaluator.h · .../statement_executor.h · shared/semantic/include/semantic/type_checker.h · tools/analyze/include/ast_analysis_visitor.h · ast_printer.h.

⚠️ SIRBuilder ليس زائرًا. الواجهةُ الأماميّة للمترجم تستهلك الشجرة، لكنّها لا ترثُ ASTVisitor: class SIRBuilder : public SIRBuilderContext، مدخلُها buildModule(ProgramNode*)، وتوزيعُها على أنواع العقد بـdynamic_cast (٦٨٠ سطرًا تحوي dynamic_cast في ٣٧ ملفًّا تحت compiler/src/frontend/) عبر بُناةٍ فرعيّةٍ friend (StatementBuilder · ExpressionBuilder · ClassBuilder · CallBuilder · …). ولا يطبّقه منسّقٌ ولا LSP اليوم. أثرُ ذلك عمليٌّ: إضافةُ دالّة visit لا تصل المترجمَ من تلقائها — لا مترجمَ يشكو، وإنّما تسقط العقدةُ في dynamic_cast غيرِ مطابقٍ ⇒ سلوكٌ صامت.

إضافة عقدة AST

  1. عرّف الصنف في shared/ast/include/ (ورث من Statement/Expression).
  2. أضف تصريحها المسبق ودالّة visit<Node> الصرفة في ASTVisitor، وتطبيقًا فارغًا في BaseASTVisitor — وإلّا كسرتَ كلّ الزوّار دفعةً واحدة. ولا تُقلِّدها بالخمسِ ذواتِ الجسمِ الفارغ: هنّ استثناءٌ قائم، لا قدوة.
  3. نفّذ الزيارة في زائرَي المفسّر (interpreter/include/visitors/) وفي TypeChecker.
  4. أضف فرعَ dynamic_cast في بُناة compiler/src/frontend/ — يدويًّا، فالمترجمُ لا يذكّرك بها (انظر التحذير أعلاه).
  5. التوافق الخلفيّ: إضافة عقدة مسموحة — تغيير معنى عقدة موجودة ممنوع (CW-24).

ملاحظات

  • مرّر العقد الكبيرة بمرجع/مؤشّر ذكيّ؛ لا نسخ عميق إلا عبر clone() صريح (CW-29).
  • Value (وقت التشغيل) منفصل عن عقد AST — راجع نظام الأنواع.

اقرأ بعده: المفسّر الشجري.

دراسة حالة: الاستيعابات (من أنتج إلى SIR)

مثالٌ متكامل على تمرير ميزة لغويّة عبر خطّ الأنابيب: كيف تُحلَّل الاستيعابات (قوائم/ مجموعات/قواميس) بترتيب أنتج العربيّ، وتُبنى في AST، وتُنفَّذ في المحرّكين. المرجع اللغويّ للمستخدم في sadlang-docs؛ هذه الصفحة للمساهم في التنفيذ.

الصيغة: [لكل <متغيّر> في <مصدر> [إذا <شرط>] أنتج <ناتج>] (والمعقوفة {} للمجموعة/ القاموس). أُقرّت في RFC 25 (م1ب).

المبدأ الأهمّ: التغيير في المحلّل فقط. عقد AST لم تتغيّر (نفس الحقول)، فلم يُمَسّ المفسّر ولا المترجم في بناء العقدة — فقط ترتيب القراءة انقلب. هذا يقلّل سطح التغيير جذريًّا ويحافظ على تكافؤ المحرّكين.

خطّ الأنابيب: عقدة AST هي المحور

عقدة الاستيعاب هي نقطة الالتقاء بين المحرّكين: المحلّل يبنيها مرّة، ثمّ يستهلكها المفسّر (تقييم مباشر) والمترجم (توليد SIR) كلٌّ على حدة. ولأنّ التغيير لم يمسّ العقدة، بقي الطرفان متكافئين تلقائيًّا — وهذا ما تفرضه اختبارات التكافؤ المزدوج.

flowchart LR
  SRC["شيفرة ص<br/>لكل س في مصدر أنتج ناتج"] --> LEX["المحلّل المعجميّ<br/>رموز: KEYWORD_FOR، KEYWORD_YIELD…"]
  LEX --> PAR["المحلّل النحويّ<br/>parseArrayLiteral / parseMapLiteral"]
  PAR --> AST["عقدة AST المحوريّة<br/>List / Set / DictComprehensionExpr"]
  AST --> INT["المفسّر الشجريّ<br/>تقييم مباشر"]
  AST --> SIR["المترجم → SIR<br/>أوكواد ARRAY_* + مسح إزالة تكرار"]
  SIR --> LLVM["LLVM → ملفّ تنفيذيّ"]
  INT -.->|"تكافؤ مزدوج مضمون"| LLVM

1) المحلّل: كشف مبكّر + تمييز

الاستيعاب يُكتشَف مبكّرًا عبر لكل (KEYWORD_FOR) في أوّل المحتوى، قبل تحليل أيّ تعبير — فلا لبس مع مصفوفة/خريطة عاديّة:

  • القائمة: ParserCore::parseArrayLiteral — إن كان أوّل رمز لكل: في → مصدر → [إذا شرط] → أنتج → ناتج → ].
  • المجموعة/القاموس: ParserCore::parseMapLiteral — بعد أنتج يُحلَّل الناتج الأوّل بـparseTernary (لتجنّب التهام :)، ثمّ: وجود : (أو =) ⇒ قاموس (نُحلّل القيمة)، غيابها ⇒ مجموعة.

أنتج = KEYWORD_YIELD السياقيّة الموجودة أصلًا (تُستعمَل أيضًا للتوليد)، فلا تعديل على keywords.yaml. شرط القبول: match(KEYWORD_YIELD) || matchContextual(KEYWORD_YIELD) (في الكود حارسٌ منفيّ: if (!match(...) && !matchContextual(...)) error).

الفائدة من الكشف المبكّر: نظرة أمام برمز واحد (لكل أوّلًا) تحسم أنّ ما بين الأقواس استيعابٌ لا مصفوفة/خريطة — بلا تراجُع (backtracking) ولا تحليل تخمينيّ. والتمييز بين قائمة ومجموعة وقاموس يتأخّر إلى نقطتين حاسمتين فقط: نوع القوس، ووجود : بعد الناتج.

flowchart TD
  START["بعد فتح قوس مربّع أو معقوف"] --> Q1{"أوّل رمز = لكل؟"}
  Q1 -.->|"لا"| PLAIN["مصفوفة / خريطة عاديّة<br/>(المسار الافتراضيّ)"]
  Q1 -->|"نعم"| HEAD["رأس الحلقة:<br/>متغيّر → في → مصدر<br/>→ (إذا شرط: اختياريّ)<br/>→ أنتج → ناتج"]
  HEAD --> Q2{"نوع القوس؟"}
  Q2 -->|"قوس مربّع (قائمة)"| LIST["ListComprehensionExpr"]
  Q2 -->|"قوس معقوف"| Q3{"بعد الناتج ':' أو '='؟"}
  Q3 -->|"نعم"| DICT["DictComprehensionExpr<br/>مفتاح : قيمة"]
  Q3 -.->|"لا"| SET["SetComprehensionExpr<br/>ناتج مفرد"]

فخّ: الترتيب البايثونيّ القديم ([تعبير لكل …]) لم يعد يُبنى كاستيعاب — يُحلَّل كمصفوفة عاديّة ثمّ يتعثّر عند لكل. حُذفت دالّتا parseListComprehension/ parseDictComprehension القديمتان من مسار المحلّل الحيّ (parser_helpers.cpp). (وكانت تبقى نسخةٌ بالترتيب القديم في shared/parser/src/specs/flow/parser_comprehension.cpp نموذجًا ميتًا غيرَ مُترجَم — حُذفت من الشجرة؛ لم يعد للمسار وجودٌ في dev.)


2) عقد AST (لم تتغيّر)

في shared/ast/include/expressions.h (لا comprehension_nodes.h الميت):

العقدةالحقول
ListComprehensionExprelement، variable، valueVariable، iterable، condition
SetComprehensionExprexpression، variable، valueVariable، iterable، condition
DictComprehensionExprkey، value، variable، valueVariable، iterable، condition

الحقل valueVariable (افتراضيّ فارغ) أُضيف لدعم فكّ الزوج على الخرائط (لكل مفتاح، قيمة في خريطة) — انظر قسم «فكّ الزوج والتكرار على الخرائط» أدناه. يبقى فارغًا في الصيغة المفردة، فلا يتأثّر أيّ مستهلك قائم.


3) الواجهة الخلفيّة: التوليد في SIR

بانيات الاستيعاب في compiler/src/frontend/builders/:

  • القائمة والقاموس — expression_comprehensions.cpp: buildExprListComp يستعمل أوكواد المصفوفة المُلوَّنة في الخلفيّة (ARRAY_NEW/ARRAY_LEN/ARRAY_GET/ARRAY_APPEND)، وbuildExprDictComp يستعمل __sad_map_create + __sad_map_set_typed (نفس مسار الخريطة الحرفيّة). هذا هو الإصلاح التاريخيّ لـISSUE-016/017 (بدل نداءات رموز وقت تشغيل غير معرَّفة).
  • المجموعة — expression_comp2.cpp: buildExprSetComp. المجموعة = مصفوفة بعناصر فريدة (كالمفسّر). يستعمل نفس أوكواد المصفوفة زائد حلقة مسح داخليّة لإزالة التكرار: يبني قيمة الناتج، يمسح مصفوفة النتيجة، ويضيف عبر ARRAY_APPEND فقط إن غابت القيمة. إزالة التكرار على قيمة الناتج (لا متغيّر الحلقة) — مطابقةً للمفسّر.

لماذا بانٍ منفصل للمجموعة؟ إزالة التكرار تُدخِل حلقة مسح داخليّة متداخلة (كتل scan_cond/scan_body/scan_found/scan_next/scan_done/append) تضاعف حجم البانِي، فلا تتقاسم بنية List/Dict المستقيمة. (ترويسةُ الملفّ ما زالت تذكر «SetComp and Generator» تاريخيًّا، لكنّ buildExprGenerator حُذف في تمّوز ٢٠٢٦ (#205) — فالملفّ اليومَ يحمل buildExprSetComp وحدَها.)

نقاط تنفيذ دقيقة في بانِي المجموعة:

  • علَم «موجود» وعدّاد المسح الداخليّ يُخصَّصان بـALLOC مرّة في كتلة الدخول (قبل الحلقة الخارجيّة) — لا داخل الجسم — تفاديًا لتسريب مكدس (alloca متكرّر لكلّ عنصر).
  • هيمنة SSA محفوظة: elemExprResult يُعرَّف في كتلة القيمة التي تُهيمِن على عنقود المسح والإضافة؛ curIdxReg يُعرَّف في كتلة الشرط التي تُهيمِن على الجسم والزيادة.

الرسم البيانيّ للتحكّم (CFG) لبانِي المجموعة

حلقتان متداخلتان: خارجيّة تمرّ على المصدر (sc_cond → sc_body → … → sc_inc)، وداخليّة تمسح مصفوفة النتيجة لكلّ عنصر (sc_scan_*) وتضيف عبر ARRAY_APPEND فقط إن غابت القيمة. التعقيد O(ن²) في أسوأ حالة (مقبول لأحجام الاستيعاب المعتادة، ومطابق لدلالة «أضِف-إن-غاب» في المفسّر). العلَم found والعدّاد jdx يُخصَّصان في entry (لا في الحلقة) فلا تسريب مكدس:

flowchart TD
  ENTRY["entry<br/>ARRAY_NEW النتيجة<br/>ALLOC idx، found، jdx"] --> COND{"sc_cond<br/>idx أصغر من طول المصدر؟"}
  COND -.->|"لا"| EXIT["sc_exit<br/>return النتيجة (Array)"]
  COND -->|"نعم"| BODY["sc_body<br/>elem = ARRAY_GET المصدر عند idx<br/>ربط متغيّر الحلقة"]
  BODY --> CIF{"شرط إذا؟"}
  CIF -.->|"خطأ"| INC
  CIF -->|"صحيح / لا شرط"| VAL["sc_val<br/>elemExpr = بناء الناتج<br/>found = 0، jdx = 0"]
  VAL --> SCOND{"sc_scan_cond<br/>jdx أصغر من طول النتيجة؟"}
  SCOND -->|"نعم"| SBODY{"sc_scan_body<br/>عنصر النتيجة عند jdx == elemExpr؟"}
  SCOND -.->|"لا"| SDONE{"sc_scan_done<br/>found == 0؟"}
  SBODY -->|"تساوٍ"| FOUND["sc_scan_found<br/>found = 1"]
  SBODY -.->|"اختلاف"| SNEXT["sc_scan_next<br/>jdx = jdx + 1"]
  FOUND --> SDONE
  SNEXT --> SCOND
  SDONE -->|"غاب (found=0)"| APP["sc_append<br/>ARRAY_APPEND النتيجة، elemExpr"]
  SDONE -.->|"موجود"| INC["sc_inc<br/>idx = idx + 1"]
  APP --> INC
  INC --> COND

حدّ معروف: مقارنة إزالة التكرار (EQ) عدديّة (كبقيّة بنية الاستيعابات عدديّة النوع)، فالمجموعات الصحيحة الإزالة للأعداد؛ مجموعات النصوص/العشريّ تتباعد صامتًا حتى يُعمَّم النوع في الاستيعابات الثلاثة.


فكّ الزوج والتكرار على الخرائط (RFC 25 التعارض 1أ)

توسعة تجعل مصدر الاستيعاب خريطةً لا مصفوفةً، وتضيف صيغة فكّ الزوج [لكل مفتاح، قيمة في خريطة أنتج ناتج]. تمرّ عبر الطبقات الخمس، وتبني على إصلاحٍ تأسيسيّ لتكرار الخرائط في المترجم.

أ) الأساس: تكرار الخريطة في حلقة لكل بالمترجم

قبل هذه التوسعة كان المترجم يعامل الخريطة في حلقة لكل كأنّها مصفوفة: يطبّق ARRAY_GET على بنية الخريطة {count, cap, keys*, values*, types*} مباشرةً ⇒ قمامة (مؤشّرات خام تُطبع أعدادًا)، بينما المفسّر صحيح. الإصلاح في statement_for_range.cpp: عند iterableResult.type == SadTypeKind::Map نستبدل المصدر بمصفوفة مفاتيح الخريطة عبر __sad_map_keys (تُرجع SadArray {len, cap, data} عبر getOrCreateMapCollect في map_ops.cpp)، ونجلب القيم عبر __sad_map_values لمتغيّر القيمة إن وُجد. اسما الدالّتين ثابتان موحَّدان في sir_constants.h (kRuntimeMapKeys/kRuntimeMapValues) يتقاسمهما مسار الحلقة وبانِي الاستيعاب.

ب) الطبقات الخمس

الطبقةالتغيير
ASTحقل valueVariable (افتراضيّ فارغ) في العقد الثلاث
المحلّلبعد المتغيّر الأوّل، matchComma() اختياريّ ⇒ متغيّر قيمة ثانٍ (نفس نمط حلقة لكل في parseForStmt) في parseArrayLiteral وparseMapLiteral
المفسّرالزائرات الثلاث تقبل isMap()؛ تمرّ على toMapRef() وتربط variable=المفتاح وvalueVariable=القيمة
المترجممساعِد مشترك lowerMapComprehensionIterable يستدعيه البانون الثلاثة
SoTالقواعد الثلاث في 60_advanced.yaml تضيف [ '،' Identifier ] (نفس نمط حلقة لكل، قاعدة gr.stmt.for)

ج) المساعِد المشترك في المترجم

بدل تكرار منطق الخريطة في البانين الثلاثة، يجمعه ExpressionBuilder::lowerMapComprehensionIterable (expression_comprehensions.cpp): إن كان المصدر خريطةً استبدله بمصفوفة مفاتيحها ويُصدِر مصفوفة قيمها، ويحسم نوعَي المفتاح والقيمة. ثمّ يربط كلّ بانٍ متغيّر القيمة بتسجيل SSA (registerName = valElemReg) مُهيمَن عليه من كتلة الجسم.

تصنيف النوع دقيق (يطابق تمثيل التخزين الفعليّ في buildExprMap):

  • المفتاح دائمًا String: الخريطة تُخزّن المفاتيح بـstrdup وتحوّل المفاتيح العدديّة إلى نصّ (ISSUE-044).
  • القيمة تُشتقّ من elementType الذي يعقّبه بانِي الخريطة الحرفيّة (يُلتقَط قبل دهسه): Integer/Boolean ⇒ نفسها؛ String/Float ⇒ String (العشريّ يُخزَّن نصًّا داخليًّا)؛ مختلط (Void) ⇒ Integer.
flowchart TD
  ITER["buildExpression(المصدر)<br/>iterResult"] --> Q{"iterResult.type == Map؟"}
  Q -.->|"لا (مصفوفة)"| ARR["نوع العنصر = iterResult.elementType<br/>لا مصفوفة قيم"]
  Q -->|"نعم"| CAP["التقاط mapValueType = elementType<br/>(قبل الدهس)"]
  CAP --> KEYS["CALL __sad_map_keys ⇒ keysReg"]
  KEYS --> VQ{"valueVar غير فارغ؟"}
  VQ -.->|"لا"| DONE["استبدال المصدر بمصفوفة المفاتيح<br/>elementType := String"]
  VQ -->|"نعم"| VALS["CALL __sad_map_values ⇒ valuesReg<br/>حسم valueVarType"]
  VALS --> DONE
  DONE --> BODY["في الجسم: ARRAY_GET المفتاح + (القيمة إن وُجدت)<br/>تسجيل SSA لكلٍّ"]

د) إغلاق قيد الإخراج النصّيّ

المفاتيح نصّيّة، فطبعها كان يكشف قيدًا كونيًّا (يمسّ حتّى اطبع(["أ","ب"])): نتيجة الاستيعاب كانت elementType = Void فالوصول المفهرَس يطبع مؤشّرًا؛ ومساعِد الطبع __sad_array_to_string غير موسوم فيطبع كلّ عنصر بـ%lld. الإصلاح شقّان:

  1. تمرير نوع العنصر: البانون يمرّرون elemExprResult.type إلى elementType لنتيجة القائمة/المجموعة ⇒ الوصول المفهرَس النصّيّ يعمل (كالمصفوفة الحرفيّة).
  2. طبع كامل موسوم: أُضيف elementType إلى SIROperand، يمرّره بانِي الطبع (builtins_core.cpp)؛ وفي الخلفيّة (io_builtins_ops.cpp) يوزَّع على مساعِد نصّيّ __sad_array_to_string_str (تمريرتان: strlen للحجم ثمّ sprintf("%s")، يخصّص مخزنه) عند elementType == String. غير النصّيّة تبقى على المسار العدديّ الأصليّ — توافق تامّ.

ترتيب المدخلات غير محدَّد. المفسّر يستعمل std::unordered_map والمترجم ترتيب الخانات؛ فترتيب المرور غير مضمون (كحلقة لكل على الخرائط). اختبارات التكافؤ تستعمل خرائط بمفتاح واحد أو تجميعًا لا-ترتيبيًّا لتفادي هشاشة المقارنة الحرفيّة.


4) الاختبار (طبقتان)

  • سلوك (تكافؤ مزدوج) — tests/behavior/rules_matrix/60_advanced/gr.adv.{list,set, dict}_comprehension/، مولَّدة بـ_generators/gen_comprehension_tests.py (يحاكي الدلالة في بايثون ⇒ @expected حتميّ). حسّاس: اختبارات المجموعة تستعمل خرائط غير حقنيّة (س % 3) + probe قيمة بالفهرسة — وإلّا فخريطة حقنيّة تجعل الطول مستقلًّا عن التحويل، فيمرّ محرّك يتجاهل أنتج أو يزيل التكرار على المصدر زائفًا.
  • وحدة (C++) — tests/unit/parser/test_comprehensions_antaj.cpp (إطار sad_test.h): يتحقّق من عقد AST + تمييز قاموس/مجموعة + رفض الترتيب القديم + بنية BinaryExpr للناتج. مُسجَّل في CTest كـComprehensionAntajTests (وسم Unit) عبر cmake/tests.cmake.

تغطية فكّ الزوج: يضيف ملفّ الوحدة قسم PairUnpack (يتحقّق أنّ valueVariable يُملأ بالفاصلة ويبقى فارغًا بدونها، والحالة السلبيّة «فاصلة بلا اسم»)؛ وتضيف مجلّدات السلوك حالات فكّ زوج بإخراج صحيح أو فهرسة (لتفادي عدم تحديد الترتيب) — قائمة ومجموعة وقاموس. القِيَم النصّيّة تُختبَر عبر الفهرسة والطبع الكامل بعد إصلاح الإخراج النصّيّ (القسم د أعلاه).


انظر أيضًا

المفسّر الشجري (Interpreter)

ماذا ستتعلّم: كيف يزور Interpreter شجرة AST ويقيّمها فورًا (tree‑walking) — نمط الزائر بحامل النتيجة، تنسيق المدراء (نطاقات · متغيّرات · دوال · كائنات · ملكيّة)، الطور الساكن الذي يسبق أوّل جملة، دورة التقييم من البرنامج إلى القيمة، ونموذج التزامن (goroutines) وحدودُ عزله.

📎 المصدر: interpreter/include/core/interpreter_core.h · visitors/expression_evaluator.h · shared/ast/include/ast_visitor.h

الدور: المرجع الدلاليّ

المفسّر يزور AST ويقيّمه مباشرةً دون توليد كودٍ وسيط — الأسرع للتطوير والـREPL، وهو المرجع الدلاليّ الذي يجب أن يطابقه المترجم: إن طابق المفسّر وخالف المترجم، فالعيب في SIR/LLVM لا في الدلالة (BF‑08).

flowchart LR
  SRC[".ص"] --> LEX["المعجمي"] --> PAR["النحوي"] --> AST["AST"]
  AST --> INT["Interpreter<br/>(tree-walking)"]
  INT --> VAL["Value (نتيجة حيّة)"]
  AST -. "مسار الترجمة" .-> SIR["SIR → LLVM"]
  INT -. "يجب أن يطابق" .-> SIR

① التنسيق: Interpreter والمدراء

الصنف Interpreter منسِّقٌ نحيف: واجهته execute(program) / executeStatement / evaluateExpression، ويفوّض العمل إلى مدراء متخصّصين وزائرَين:

flowchart TB
  INT["Interpreter<br/>execute · executeStatement · evaluateExpression"]
  INT --> SE["StatementExecutor<br/>(تنفيذ الجمل)"]
  INT --> EE["ExpressionEvaluator<br/>(تقييم التعابير)"]
  subgraph MGR["مدراء المفسّر (interpreter/include/managers/)"]
    SM["ScopeManager — النطاقات"]
    VM["VariableManager — المتغيّرات"]
    FM["FunctionManager — الدوال"]
    OM["ObjectManager — الكائنات"]
    OW["OwnershipManager — الملكيّة"]
  end
  SE --> MGR
  EE --> MGR
  SE -.->|"getInstance()"| CM["ClassManager<br/>(مُفرَدٌ في shared/types — خارج المفسّر)"]
المديريديرأين
ScopeManagerسلسلة النطاقات (دخول/خروج، البحث الهرميّ)managers/scope_manager.h
VariableManagerربط الأسماء بالقيم داخل النطاقmanagers/variable_manager.h
FunctionManagerتعريفات الدوال (مشترَكٌ للقراءة فقط بين الخيوط)managers/function_manager.h
ObjectManagerمثيلات الكائنات (OOP)managers/object_manager.h
OwnershipManagerتتبّع الملكيّة/الاستعارة — يربط نظام الذاكرةmanagers/ownership_manager.h
GoroutineManagerدورة حياة الخيوط المتزامنةinterpreter/include/channel.h
UIStateManagerحالة عناصر الواجهة أثناء التفسيرinterpreter/include/ui/ui_state_manager.h

⚠️ ClassManager ليس من مدراء المفسّر. هو مُفرَدٌ (singleton) يعيش في shared/types/include/class_manager.h ويُنادى بـClassManager::getInstance() من المحلّل والمفسّر والمترجم جميعًا — فسجلُّ الأصناف عابرٌ للمحرّكين وعمليّةٌ واحدة، لا حالةٌ مملوكةٌ لمثيل Interpreter. راجع أثرَ ذلك على التزامن في §⑤.

② نمط الزائر بحامل النتيجة

كلّ عقدة AST تَقبل زائرًا (node.accept(visitor))، والزائر ExpressionEvaluator يرث BaseASTVisitor ويُنفّذ visitXxxExpr لكلّ نوع. لأن visit* تُرجع void، تُخزَّن النتيجة في lastResult_ وتُسحَب بـgetResult():

sequenceDiagram
  participant C as evaluateExpression(expr)
  participant N as عقدة AST
  participant V as ExpressionEvaluator
  C->>N: expr.accept(V)
  N->>V: visitBinaryExpr(node)  (إرسالٌ مزدوج)
  V->>V: قيّم الطرفين + طبّق العامل
  V->>V: lastResult_ = القيمة
  C->>V: getResult()
  V-->>C: Value

💡 الإرسال المزدوج (double dispatch): العقدة تعرف نوعها، فتستدعي visitBinaryExpr الصحيحة دون switch على نوعٍ مُعدَّد — إضافة عقدةٍ جديدة = دالة visit جديدة في الزائر.

الزائران مقسَّمان على ٣٠ ملفًّا في interpreter/src/visitors/ (تخفيفًا لزمن الترجمة): expression_evaluator_core · ..._binary_ops/..._binary_logic · ..._calls/..._calls_dispatch/..._calls_invoke/..._calls_user_func/..._calls_macro · ..._members/..._members_advanced/..._members_assign · ..._oop/..._oop_new/..._oop_array_methods/..._oop_string_map_methods/..._oop_concurrency · ..._overloads · ..._ui. ومثلها للجمل: statement_executor · ..._control · ..._control_exceptions · ..._functions/..._functions_templates · ..._modules · ..._oop/..._oop_types/..._oop_struct_test. ومعها sem045_report (تقرير SEM045 — عقد الغياب) وجسرا الواجهة ui_eval_bridge_core وui_widget_expr_dispatch.

③ الطور الساكن: execute لا يبدأ بالتنفيذ

🔑 المفسّر ليس «مشيًا على الشجرة» فقط. قبل تنفيذ أوّل جملة يمسح execute() البرنامجَ كلَّه — مرّتين افتراضيًّا، وثلاثًا إن فُعِّل فحصُ الأنواع (فهو مطفأٌ افتراضيًّا، انظر أدناه) — ويشمل المسحُ الدوالَّ غيرَ المُنادَاة والفروعَ الميّتة. مَن ظنّه محضَ مُقيِّمٍ كسولٍ فوجئ بتشخيصٍ يخرج من كودٍ لا يُنفَّذ أبدًا.

flowchart TD
  S0["execute(program)"] --> S1["مسحٌ عن الدالّة «رئيسية»"]
  S1 --> S2{"قاعدة الدالّة الرئيسيّة<br/>checkMainFunctionRule"}
  S2 -->|"كودٌ تنفيذيٌّ خارج الدوال مع وجود رئيسية"| ERR1["SEM_MAIN_FUNCTION_RULE ⇒ توقّف"]
  S2 --> S3{"options_.enableTypeCheck؟"}
  S3 -->|"false (الافتراضيّ)"| S4
  S3 -->|"true"| TC["TypeChecker على كلّ جملة ⇒ فشلٌ عند أيّ خطأ"]
  TC --> S4["NullSafetyAnalyzer — دائمًا"]
  S4 --> CLR["ErrorManager::clear()"]
  CLR --> P1["الطور الأوّل: تنفيذ كلّ الجمل العلويّة<br/>(تسجيل الدوال والأصناف والعوامّ)"]
  P1 --> P2["الطور الثاني: تنفيذ «رئيسية» إن وُجدت"]
الطورمتى يعملإن فشل
قاعدة الدالّة الرئيسيّةحين توجد رئيسيةSEM_MAIN_FUNCTION_RULE وتوقّفٌ قبل التنفيذ
فحص الأنواع (TypeChecker)مطفأٌ افتراضيًّا: enableTypeCheck = falseتوقّفٌ بعدّ الأخطاء
تحليل أمان العدمدائمًا، وصرامتُه مشتقّةٌ من سياسة الذاكرة عبر strictnessFromOwnershipModeتحذيراتٌ دائمًا؛ وتوقّفٌ عند الصرامة القاتلة

⚠️ العَلَمُ الافتراضيّ يكذبُ على القارئ. enableTypeCheck = false — فالمفسّر يعمل بفاحصِ الأنواع مُطفأً ما لم يُطلَب صراحةً. فإن جرى برنامجٌ على sad-run وسقط على sad-build، فليس بالضرورة تباعُدَ محرّكين: قد يكون طورًا ساكنًا شغّله المترجمُ ولم يشغّله المفسّر.

⚠️ الأطوارُ الساكنة كلُّها محجوبةٌ بـ#if !defined(__EMSCRIPTEN__) && !defined(SAD_PLATFORM_ANDROID) — فبناءُ wasm أو أندرويد لا يفحص شيئًا قبل التنفيذ. لا تَقِس دلالةَ اللغة على أيٍّ منهما.

④ دورة التقييم الكاملة

flowchart TD
  P["الطور الأوّل/الثاني: تنفيذ الجمل"] --> LOOP{"لكلّ جملة"}
  LOOP --> ES["executeStatement → StatementExecutor"]
  ES --> KIND{"نوع الجملة"}
  KIND -->|تعبيريّة| EVAL["evaluateExpression → accept → getResult"]
  KIND -->|تحكّم (إذا/طالما/لكل)| CTRL["statement_executor_control"]
  KIND -->|دالة/صنف| DEF["تسجيل في FunctionManager/ClassManager"]
  KIND -->|استثناء (حاول/أمسك)| EXC["statement_executor_control_exceptions"]
  EVAL --> R["ExecutionResult{success, Value, error}"]
  CTRL --> R
  DEF --> R
  EXC --> R

العمليّات الثنائيّة تُفرَّق داخل الزائر حسب الصنف: evaluateArithmeticOp · evaluateComparisonOp · evaluateLogicalOp (قِصَر دائرة) · evaluateBitwiseOp — كلّها تأخذ (left, TokenType op, right, Position) وتتشاور مع نظام الأنواع للتحميل الزائد والإكراه.

⑤ التزامن (Goroutines)

نموذج التزامن يقوم على العزل: كلّ goroutine يعمل بـStatementExecutor مستقلّ مع ScopeManager / VariableManager / OwnershipManager خاصّةٍ به، ويُشارَك FunctionManager للقراءة فقط:

flowchart LR
  MAIN["الخيط الرئيسيّ"] -->|اذهب func() | SNAP["captureVisibleVariables()<br/>(لقطة من المتغيّرات المرئيّة)"]
  SNAP --> G1["goroutine #1<br/>Executor + مدراء خاصّون"]
  SNAP --> G2["goroutine #2<br/>Executor + مدراء خاصّون"]
  FM["FunctionManager<br/>(مشترَك — قراءة فقط)"] --- G1
  FM --- G2
  G1 <-->|قناة| CH["SadChannel<br/>(mutex داخليّ)"]
  G2 <-->|قناة| CH

⚠️ المتغيّرات تُلتقَط لقطةً عبر captureVisibleVariables() لا بالمرجع — فلا سباق على نطاق المنشئ. FunctionManager مشترَكٌ لكنّه للقراءة فقط، والقنوات (SadChannel) آمنةٌ بـmutex داخليّ.

🔑 حدُّ العزل: ClassManager خارجه. العزلُ أعلاه يغطّي مدراءَ المفسّر، لا المُفرَدَ العابرَ للمحرّكين. وقياسُ class_manager.cpp يقول: getInstance وresetInstance وحدهما يقفلان instanceMutex_ — أمّا registerClass وgetClass وhasClass وregisterTrait وسائرُ الخريطة فبلا قفل. فالتعليقُ الذي يصف الصنفَ بأنّه «آمنٌ للخيوط المتعدّدة» يصفُ إنشاءَ المُفرَد لا محتواه. عمليًّا: أطلق على شيفرةٍ تُصرِّح صنفًا (أو تحمِّل وحدةً تُصرِّحه) يكتبُ في خريطةٍ عامّةٍ غيرِ متزامنة. هذا عقدٌ معلَنٌ لا يقيسه أحد، لا حكمٌ بأنّه عطبٌ مرصود — قِسه قبل أن تبنيَ عليه.

ملاحظات للمطوّر

  • القيم كلّها Value (std::variant على ValueType)؛ OBJECT يحمل shared_ptr<ObjectInstance> ⇒ تمرير الكائنات بالمرجع → نظام الأنواع.
  • شغّل .ص مباشرةً بـsad-run لاختبارٍ سريع — لا حاجة لخطوة ترجمة.
  • إن طابق المفسّر وخالف المترجم ⇒ المشكلة في SIR/LLVM لا في الدلالة (BF‑08).
  • لإضافة عقدة AST جديدة: أضف visit<Node> الصرفة إلى ASTVisitor وتطبيقًا فارغًا في BaseASTVisitor، ثمّ نفّذها في الزائرَين → شجرة AST.
  • الأصنافُ تُسجَّل في ClassManager::getInstance() (مُفرَدُ shared/types)، لا في مديرٍ يملكه المفسّر.

اقرأ بعده: التمثيل الوسيط SIR.

التمثيل الوسيط SIR

ماذا ستتعلّم: ما هو SIR، ولماذا طبقة وسيطة بين AST وLLVM، وأين تعليماته.

ما هو SIR؟

SIR (Sad Intermediate Representation) تمثيل وسيط يبنيه SIRBuilder من AST قبل توليد LLVM IR. يفصل دلالة لغة ص (الملكية، الأنواع، تدفّق التحكّم) عن تفاصيل LLVM.

لماذا طبقة وسيطة؟

  • تحسين مستقلّ: المُحسِّن Sad::Compiler::Optimizer::Optimizer يطبّق تمريراتٍ على SIR.
  • تشخيص أسهل: SIR dumps أوضح من LLVM IR الخام.
  • عزل: تغيير الواجهة الخلفيّة (LLVM) لا يَمَسّ منطق بناء SIR.
  • دلالة الملكية: SIR يدعم تعليمات ملكية (ownership) خاصّة بلغة ص.

الملفّات

الملفالمحتوى
compiler/include/frontend/sir_types.hتعداد SIROpcode + أنواع SIR (الملكية)
compiler/src/frontend/SIRBuilder (AST → SIR) + بانياتُه في builders/
compiler/include/sir_optimizer/ · compiler/src/sir_optimizer/Optimizer + تمريراتُه (cse_pass · licm_pass · sroa_pass · dead_code_elimination_pass · …)

الموضع في الخطّ

flowchart LR
  AST --> SB["SIRBuilder"] --> SIR["وحدة SIR"]
  SIR --> SO["Optimizer<br/>(sir_optimizer/)"] --> SIR2["SIR محسَّن"]
  SIR2 --> CG["LLVMCodeGen"] --> IR["LLVM IR"]

إضافة opcode (الإجراء)

  1. أضف SIROpcode::NEW_OP إلى sir_types.h (إضافة فقط — لا تغيّر معنى موجود، CW-24).
  2. أنتجه في SIRBuilder من العقدة المعنيّة.
  3. ترجمه في compiler/src/backend/llvm/.
  4. اختبار مزدوج (مفسّر + مترجم بنفس المخرَج).

قواعد دقيقة

  • ترتيب الحقول: يُحدَّد في SIRBuilder (حيث تُبنى البنية) — أصلِح أخطاء الترتيب هنا، لا في codegen (BF-10).
  • لا اقتطاع صامت: كل تحويل نوع يمرّ عبر دالة مُسمّاة (CW-14).

تخفيض مطابقة الأنماط (طابق)

يُخفَّض طابق في الواجهة الأماميّة (compiler/src/frontend/sir_builder_match_patterns.cpp + compiler/src/frontend/builders/statement_match.cpp) — لا في الواجهة الخلفيّة. الملفّ الخلفيّ pattern_codegen*.cpp (generateMatchCode) ميّتٌ تمامًا (صفر مستدعٍ) رغم بقائه في البناء؛ لا تُضِف إليه.

مساران للنمط حسب تعقيده:

  • النمط البسيط (حرفيّ/متغيّر/شامل/قائمة أحاديّة المستوى): يُحسَب شرطٌ منطقيٌّ مسطّح لكلّ ذراع (كتلة اختبار تُنتج condReg)، ثمّ br condReg → الجسم / الذراع التالي. الاستخراجات المؤجَّلة (MatchDeferredField) تُنفَّذ في كتلة الجسم بعد التفرّع (آمنة الطول).
  • النمط المركّب المتداخل (قائمة داخل قائمة، حقل بنية بنمطٍ مركّب): يُوجَّه إلى المُطابِق قاصر الدائرة بالتفريع emitPatternMatchShortCircuit — يفحص الطول/النوع ويتفرّع إلى failLabel عند الفشل، ثمّ (بعد التحقّق ⇒ آمن) يستخرج الأبناء متعاوِدًا. يُفعَّل فقط عند وجود ابنٍ مركّب (patternHasCompositeChild) فيبقى كلّ نمطٍ بسيطٍ على المسار المسطّح ⇒ صفر انحدار.

⚠️ المترجم بلا وسوم نوعٍ تشغيليّة. BUILTIN_IS_ARRAY ساكنٌ (يفحص نوع LLVM وقت الترجمة)، وقراءة خارج الحدود تُجهِض. فمطابقة نمطٍ مركّب على قيمةٍ لا يُثبِت نوعها ساكنًا كانت تُصدر ARRAY_LEN على عددٍ مُعامَلٍ كمؤشّر ⇒ Segfault. الحلّ: بوّابات نوعٍ ساكنة صارمة — لا نزول في ابنٍ مركّب إلّا بنوعٍ مُثبَت (مصفوفة للقائمة، كائن للبنية)؛ المجهول (Void) ⇒ فشلٌ ساكن ⇒ سقوطٌ آمن إلى افتراضي. النطاق العامل المؤكَّد: القوائم المتداخلة المتجانسة النوع؛ المتباين/العميق/حقل البنية حدٌّ معماريّ (ISSUE-070) يحتاج وسوم نوعٍ تشغيليّة كـISSUE-045/052.

💡 فخّ استنتاج نوع العنصر: المصفوفة الحرفيّة غير المتجانسة يجب أن تستنتج elementType=Void (لا نوع العنصر-الأوّل) وإلّا مرّ عنصرٌ عدديّ من بوّابة القائمة فعاود ARRAY_LEN عليه (expression_collections.cpp). ونمط الباقي [أ، *بقية] يُخفَّض على أوبكود BUILTIN_ARRAY_SLICE المُضمَّن (كـم[أ..]) لا على CALL لرمزٍ خارجيّ غير معرَّف (ISSUE-068).


اقرأ بعده: توليد LLVM.

توليد LLVM (المترجم sad-build)

ماذا ستتعلّم: كيف يحوّل LLVMCodeGen تمثيل SIR إلى LLVM IR ثم ملفّ تنفيذيّ أصليّ.

الدور

compiler/src/backend/llvm/ يأخذ SIR المحسَّن ويُنتج LLVM IR، ثم يستخدم LLVM لتوليد كائن ثم ربطه في ملفّ تنفيذيّ. هذا قلب المترجم sad-build.

الاعتماد على LLVM

  • LLVM 18 — مفعّل بشرط ENABLE_LLVM_BACKEND=ON؛ راجع cmake/llvm.cmake و#ifdef HAS_LLVM.
  • واجهة المترجم تعيش في tools/compiler/ (compiler_driver_*.cpp)، وهدفُها sad-build (sad-build.exe). أمّا sadc.exe فاسمٌ متقاعد لا يُنتجه أيّ هدف.

⚠️ علَمٌ عربيٌّ قانونيٌّ وحيد — لا مرادف لاتينيّ. أعلامُ المترجم الطويلة كلُّها في language-truth/cli_flags.yaml، ونصُّه صريح: «اسمٌ عربيٌّ قانونيٌّ وحيد، بلا مرادفات ولا توافقٍ خلفيّ». فمخرجاتُ LLVM الوسيطة هي --أظهر-llvm و--أظهر-bc — و--emit-llvm لا تُقبَل. تبقى الأعلامُ القصيرة الموروثة من سلسلة الأدوات (-o -c -S -g -O* -L -l -Werror -T -h) أعرافًا في المحلِّل، خارجَ هذا المصدر — بنصّ المصدر نفسِه: «النطاق = الأعلام الطويلة فقط».

📎 لا سقوطَ صامتًا حين تغيب LLVM. إلى جانب sad-build يُبنى دائمًا هدفٌ ثانٍ نحيلٌ بلا LLVM البتّة: sad-build-native. وهو لا يفترق عن أخيه إلّا في ملفٍّ واحد — compiler_driver_backend_llvm_absent.cpp بدل compiler_driver_backend.cpp — الذي يرفض صراحةً كلّ طلبٍ لأثرٍ من صنع LLVM برمز الكتالوج INT_LLVM_PATH_ABSENT، بدل تسليم أثرٍ من الخلفيّة الأصليّة وكأنّه هو. مَن طلب أثرًا بعينه يُعطاه أو يُرَدّ.

ولاحظ لماذا وحدةُ ترجمةٍ كاملةٌ لا فرعُ #ifdef: الفرعُ الذي لا يُصرَّف في أكثر الخانات ينجرف صامتًا عن أخيه — وهو عطبٌ مقيسٌ في هذا المستودع من قبل. فكلُّ سطرٍ هنا يُصرَّف على المنصّات الثلاث وفي التكوينَين.

البناة (builders)

compiler/src/backend/llvm/builders/ — ثمانية مجلّداتٍ مقيسةٍ على dev، مولِّدٌ لكلّ عائلة: core · arithmetic · collections · oop · memory · directives (توجيهات @) · platform · builtins (الدوالّ المضمنة).

مخطّط

flowchart LR
  SIR["SIR محسَّن"] --> CG["LLVMCodeGen + builders"]
  CG --> IR["LLVM IR"]
  IR --> OBJ["كائن (LLVM)"]
  OBJ --> LINK["ربط"]
  LINK --> EXE["تنفيذيّ أصليّ"]

التشخيص (BF-07)

عند خطأ في المترجم، ولّد IR بـ--أظهر-llvm وافحص:

  • هل الكتلة الأولى (entry block) صحيحة؟
  • هل أنواع الحقول والمعاملات متّسقة؟
  • هل ترتيب التعليمات يحترم تبعيّات البيانات؟
  • هل getelementptr يستخدم الفهارس الصحيحة؟

قواعد codegen مهمّة

  • أسماء كتل واصفة: precond_fail, loop_body, then_block — لا bb1/label2 (CW-11).
  • الترتيب يكسر الدلالة: ترتيب أبجديّ لأسماء الكتل يكسر ترتيب التنفيذ — استخدم std::vector محافظًا على ترتيب الإدراج (CW-27).
  • أصلِح في الطبقة الصحيحة: خطأ تحويل أنواع يُصلَح في codegen؛ خطأ ترتيب حقول في SIRBuilder (BF-10).

اقرأ بعده: الخلفيّة الأصليّة بلا LLVM.

الخلفيّة الأصليّة بلا LLVM (SIR → ELF64)

ماذا ستتعلّم: كيف تُترجَم لغة ص إلى شيفرة آلة بلا LLVM ولا رابطٍ أجنبيّ؛ طبقات الخلفيّة الأربع (جداول SoT · المرمِّزات · المخفِّضات · كاتب ELF)؛ كيف تُقاس صحّتها بدرجتَين متمايزتَين (التصريف والتنفيذ)؛ وأين تبدأ إن أردت إضافة أوپكود أو معماريّة.

📎 المصدر: compiler/include/backend/native/ · language-truth/backend/ · tools/compiler/compiler_driver_native.cpp · scripts/native_backend/

📌 الخلاصة في ثلاثة أسطر: ثلاثُ معماريّاتٍ لها مخفِّضٌ موصولٌ اليوم — x86-64 وARM64 بـ١٠٤ أوپكودات، وRV64 بـ٧ — واثنتان مخطَّطتان؛ والخانةُ المضمونة عبرها جميعًا هي ELF على لينكس/الوضع الحرّ لا غير، فماك وويندوز يبقيان على LLVM. والصحّةُ تُقاس بدرجتَين لا بواحدة (بصمةُ التصريف · التشغيل الحيّ)، وجداولُ اختيار التعليمات ما تزال بذرةً والتخفيضُ الفعليّ يعيش في C++.

جئتَ لتُسهم؟ اقفز إلى «أين تبدأ». جئتَ لسؤالٍ بعينه؟ كيف يُبلَّغ عن فشل التخفيض · كيف تُقاس الخلفيّة · ما الحدودُ والدَّينُ المُعلَن.

لماذا خلفيّةٌ ثالثة؟

للغة ص ثلاثة مسارات تنفيذ: المفسّر الشجريّ، وSIR → LLVM، وهذا. المسار الثالث ليس تحسينَ أداء بل سيادةً: أن تُنتَج شيفرةُ الآلة من مصدر ص دون أن يمرّ البرنامج بأيّ أداةٍ لا نملكها — لا clang ولا ld ولا lld ولا as.

عقدُ السيادة مكتوبٌ في مصدر الحقيقة لا في نيّة أحد (targets.yaml): خمس معماريّات فأكثر بلا LLVM إطلاقًا، والخانة الإلزاميّة عبرها جميعًا هي ELF + لينكس/الوضع الحرّ (freestanding). أمّا Mach-O/darwin وPE/Win64 فخارج نواة السيادة صراحةً — يبقيان على LLVM حتّى قرارٍ لاحق.

الموضع في خطّ الأنابيب

flowchart TD
  SRC["مصدر .ص"] --> AST["AST"]
  AST --> SIR["SIRBuilder → SIR"]
  SIR --> OPT["SIROptimizer"]
  OPT -->|المسار الافتراضيّ| LLVM["LLVMCodeGen → ملفّ تنفيذيّ"]
  OPT -->|"--خلفية-أصلية"| NAT["مخفِّض SIR الأصليّ<br/>(x86-64 · ARM64 · RV64)"]
  NAT --> ENC["المرمِّز (بايتات)"]
  ENC --> ELF["Elf64Writer → ELF64 ساكن"]

الفارق الجوهريّ: مسار LLVM يسلّم IR إلى مكتبةٍ أجنبيّة تتولّى الترميز والربط، والمسار الأصليّ يكتب البايتات بنفسه ثمّ يلفّها في حاويةِ ELF بنفسه.

الأهداف الخمسة — والفجوة التي لا تُكتَم

📎 المصدر: targets.yaml

عمود «المعلم» أدناه يستعمل الترقيم م٠ … م٨ — و«م-» اختصارُ معلَمٍ في خارطة الخلفيّة.

الهدفالمعلمالحالةأوپكودات مخفَّضة
x86-64م٠–م٣lowered104
AArch64/ARM64م٥lowered104
RISC-V RV64GCم٦lowered7
ARMv7-A/Thumb-2م٧planned—
x86 i686م٨planned—
استخراج الطبقة الجدوليّةم٤in_progress(طبقةٌ مشترَكة لا هدف)

⚠️ «مدعوم» ≠ «يترجم كلّ برنامج». lowered تعني «له مخفِّضٌ موصولٌ ومُبرهَنٌ بالتشغيل» فحسب. الفارق مقيسٌ لا مخفيّ: حقل native_lowered في 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) ويقود إلى 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؛ ويندوز وماك حاويتان مختلفتان لا يكتبهما هذا المسار.

الطبقات الأربع

flowchart TB
  subgraph SoT["① مصدر الحقيقة — language-truth/backend/"]
    I["instructions.yaml<br/>(جدول الترميز)"]
    R["registers.yaml"]
    S["isel.yaml<br/>(أنماط الاختيار)"]
    A["abi/*.yaml<br/>(e_machine · اتّفاقيّة النداء)"]
  end
  subgraph ENC["② المرمِّزات (header-only)"]
    V["x86_variable_encoder<br/>REX + ModRM"]
    F["arm64/riscv64_fixed32_encoder<br/>كلمة 32-بت ثابتة"]
  end
  subgraph LOW["③ المخفِّضات"]
    C["sir_lowering_common.h<br/>LoweringDriver&lt;Target&gt; · LoweringDiagnostics"]
    X["sir_native_lowering.h"]
    M["arm64_sir_lowering.h"]
    W["riscv64_sir_lowering.h"]
  end
  E["④ elf64_writer.h<br/>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:

  • مسندات تحليل SIR عديمة الحالة: isComparison · isConstInt · findFusedComparison · hasResultAndArity · عقد الشكل (نتيجةٌ موجودة + عدد معامِلات متوقَّع).
  • LoweringDiagnostics — قاعدةُ تركيبٍ لا تعدّدِ أشكال: مُدمِّرٌ محميٌّ غير افتراضيّ عمدًا، فلا حذفَ عبر مؤشّر قاعدة. (وما الذي يُبلَّغ به فعلًا حين يفشل تخفيضٌ؟ انظر §التشخيص.)
  • LoweringDriver<Target> بنمط CRTP — تتابعُ التخفيض يعيش مرّةً واحدة، والهدفُ يقدّم خطّافاته الثلاثة: الثنائيّ · الأحاديّ · المقارنة.

✅ عقدُ الاستخراج مقيسٌ لا مُدَّعًى: بصمةُ sha256 لمخرَج ELF — وهي بصمةُ درجةِ التصريف، تُشرَح في §«كيف تُقاس الخلفيّة» — عبر مصفوفة القواعد × {x86_64, arm64} ⇒ صفرُ بايتةٍ مختلفة. ومع ذلك نطاقُ البرهان محدودٌ ومُعلَن: المصفوفة لا تمثّل معامِل Any في عمليّةٍ عشريّة، فبرهانُ تلك الحالة منفصلٌ (prove_any_float.sh). البصمةُ حارسٌ ضدّ تغييرٍ غير مقصود، لا بديلٌ عن مراجعة.

تدفّق التحكّم بمرورين: إزاحةُ اللصيقة لا تكون معروفةً ساعةَ بثّ القفزة التي تقصدها، فيُقسَم العمل مرورَين ينتهيان بترقيع كلّ rel32:

flowchart TB
  subgraph P1["المرور ① — البثّ"]
    B1["ابثّ بايتات الكتلة بالترتيب"]
    B2["سجّل إزاحة لصيقة الكتلة"]
    B3["ابثّ القفزة بإزاحةٍ نائبة<br/>+ سجّل طلبَ ترقيع"]
    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 يبني تنفيذيًّا ساكنًا دنيا: رأس ELF (64 بايت) + program header واحد PT_LOAD (R+X) + .text، ونقطةُ الدخول عند vbase + 0x78. وهو محايدُ المعماريّة: بنيةُ التنفيذيّ الساكن واحدةٌ عبر الأهداف، والفارقُ حقلٌ واحد (e_machine: ٦٢ لـx86-64، ١٨٣ لـAArch64، ٢٤٣ لـRV64) يُمرَّر من جدول الـABI — بيانًا لا كودًا.

التشخيص: لا نصَّ رسالةٍ في الخلفيّة

كلّ فشلِ تخفيضٍ يحمل ErrorCode من كتالوج SoT + حمولةَ {detail} = وسمُ سياق + قيمةٌ تُحسَب زمنَ التشغيل. الوسوم مُوحَّدةٌ ثوابتَ مسمّاة في native_diagnostics.yaml يولّدها gen_native_diagnostics.py إلى هيدر C++ يستهلكه المخفِّضان — بدل حرفيّاتٍ خام. ووسمُ السياق ليس نصًّا يراه المستخدم؛ الرسالةُ من الكتالوج، والوسمُ سياقٌ لمطوّر الخلفيّة. أمثلة: kMoveKind · kArrayGetBoxed · kEnumPayloadDyn · kObjectUnknownClass.

وبالمنطق نفسه يوحّد value_repr.yaml وسومَ SadDyn ونصوصَ عرض القيم بين المحرّكات الثلاثة — وهذه وسومُ نوعٍ زمنَ التشغيل، لا وسومُ السياق التشخيصيّ أعلاه. جاء التوحيد بعد عيبٍ حقيقيّ: كان العدم يُعرَض «عدم» في الخلفيّة الأصليّة و«لاشيء» في المفسّر وLLVM.

كيف تُقاس الخلفيّة: درجتان لا واحدة

صحّةُ الخلفيّة تُقاس بدرجتَين متمايزتَين: أن تخرج البايتاتُ كما يجب (التصريف)، وأن يعمل الثنائيُّ الخارج (التنفيذ).

الدرجةما تقيسهالأداةأين تعمل
① التصريف (emit)أنّ صورة ELF لمصدرٍ وهدفٍ بعينهما واحدةٌ بايتًا بايتًا عبر المنصّات الثلاثprove_elf_emit_fingerprint.py + elf_emit_fingerprints.jsonالمنصّات الثلاث، التكوينان
② التنفيذ (run)أنّ الثنائيّ المُخرَج يعمل ويطابق المفسّر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 عبر findArchConstraint() وتردّ SEM_TARGET_ARCH_UNSUPPORTED_BUILTIN. ودَينان مُعلَنان باقيان: البوّابةُ في مسار LLVM (وهذه الأوپكودات native_lowered: [] فلا يخفّضها المسارُ الأصليّ أصلًا)، وكتلةُ «تجميع … نهاية» لا تمرّ بها.
  • البرهانُ الذي لا يُعيد أحدٌ إنتاجَه دعوى مهما صدق قائلُه: كان أحدُ حقول targets.yaml يسوق تشغيلًا نصًّا بلا سكربتٍ يُعيده، فاستُبدل بسكربتٍ مُصرَّحٍ به في المُنادي.

أين تبدأ

هذا الجدول هو بابُ المُسهِم: صفٌّ لكلّ نيّة، وكلُّ صفٍّ ينتهي بما يجعل الإسهامَ مقيسًا لا مُدَّعًى — سكربتَ برهانٍ مُصرَّحًا به، أو اختبارَ تطابقٍ، أو حقلًا في مصدر الحقيقة.

تريد أن…ابدأ من
تضيف أوپكودًا مخفَّضًاsir_native_lowering.h (أو نظيره) + حدّث native_lowered في sir_opcodes.yaml + سكربت برهانٍ مُصرَّحٍ به في المُنادي
تضيف تعليمةً أو صيغةَ ترميزlanguage-truth/backend/<arch>/instructions.yaml ثمّ اختبارُ التطابق ضدّ llvm-mc
تضيف معماريّةًtargets.yaml أوّلًا — وانتظر م٤ (استخراج الطبقة الجدوليّة)، فإضافتُها قبلها تعني مخفِّضًا يدويًّا ثالثًا
تفهم لماذا فشل تخفيضٌ عندكوسمُ السياق في native_diagnostics.yaml + رسالةُ الكتالوج

اقرأ بعده: دراسة حالة: توحيد هاش/شفر/فك_تشفير — الفصلُ التالي في الفهرس، وهو أخفُّ ويُري التوحيدَ نفسَه من زاويةِ مكتبةٍ لا خلفيّة · أو اقفز إلى نظام الأنواع وفاحص الأنواع.

دراسة حالة: توحيد هاش/شفر/فك_تشفير بين المحرّكين

مثال متكامل على إزالة تباعُد بين المفسّر والمترجم (لا إضافة ميزة جديدة): دالّتا هاش/شفر/فك_تشفير في وحدة تأكيدات كانتا مُنفَّذتين مرّتين بخوارزميّتين مختلفتين تمامًا — والمترجم كان يملك نسخة ثالثة منفصلة لهدف Android لم يمسّها أحد. المرجع اللغويّ للمستخدم في sadlang-docs؛ هذه الصفحة للمساهم في التنفيذ.

المبدأ الأهمّ: التوحيد بين المحرّكين لا يعني «اختيار نسخة والحذف» فقط — يعني أيضًا البحث عن كل نسخة. النسخة الثالثة في مُصدِّر Android كانت لتبقى متباعدة لو لم تكشفها مراجعة مستقلّة قبل الدفع (انظر «الفخّ» أدناه).

الوضع قبل التوحيد

flowchart TD
  MOD["وحدة تأكيدات: هاش / شفر / فك_تشفير"]
  MOD --> INT["المفسّر<br/>builtin_module_assertions.cpp<br/>SHA-256 + SHA-256-CTR حقيقيّ"]
  MOD --> CMP["المترجم (سطح مكتب/Windows/Linux)<br/>sad_embedded_runtime.c<br/>FNV-1a + XOR بسيط"]
  MOD --> AND["المترجم (هدف Android)<br/>compiler_driver_android_linker.cpp<br/>نسخة ثالثة منفصلة: FNV-1a"]
  DEAD["stdlib/crypto/*<br/>Hash/HMAC/AES/Base64 عبر OpenSSL"] -.->|"لا مستهلك — ميتة"| MOD

ثلاث حقائق متزامنة سبّبت الالتباس:

  1. هاش("نفس النص") يُعطي قيمتين مختلفتين حسب المحرّك المُشغِّل — FNV-1a (عدد صحيح) في المترجم مقابل SHA-256 حقيقيّ (نصّ ست عشريّ) في المفسّر.
  2. شفر/فك_تشفير غير متبادلين عبر المحرّكين — XOR بسيط في المترجم لا يفكّه SHA-256-CTR الذي شفر به المفسّر، والعكس.
  3. stdlib/crypto/ كانت تبدو الحلّ الصحيح (OpenSSL كامل: Hash/HMAC/AES/ Base64) لكن بلا أيّ مستهلك في المفسّر أو المترجم — بُنِيت واختُبِرت (هدف CMake crypto_tests) دون أن تُربَط بأيّ مسار تنفيذ فعليّ.

القرار: SHA-256 هو المصدر الحقيقيّ، لا OpenSSL

الخيار الأول (ربط stdlib/crypto بالمترجم) يعمّق الانقسام بدل إغلاقه: لا يزال يترك FNV-1a قائمًا في مسارات لا تستورد stdlib/crypto صراحةً، ويكسر هدف الوضع الحرّ (freestanding) الذي لا يمكنه الربط بـOpenSSL أو أيّ مكتبة نظام تشغيل مضيف — قيد قائم أصلًا على tools/compiler/runtime/sad_embedded_runtime.c (راجع تعليقات SEM019 فيه). القرار: حذف stdlib/crypto كليًّا، ونقل خوارزميّة SHA-256/SHA-256-CTR الذاتيّة التنفيذ (self-rolled، بلا اعتماديّات) من المفسّر إلى وقت تشغيل المترجم — لا العكس.

التنفيذ عبر الطبقات

الطبقةقبلبعد
المفسّر (builtin_module_assertions.cpp)SHA-256 حقيقيّ (بلا تغيير — هو المرجع)—
وقت تشغيل المترجم (sad_embedded_runtime.c)sad_security_hash يُرجع long long (FNV-1a)؛ XOR بسيطsad_sha256_raw/sad_sha256_rotr + sad_security_hash يُرجع const char * (سلسلة ست عشريّة 64 حرفًا)؛ CTR بنفس بنية nonce+عدّاد للمفسّر
واجهة SIR الأماميّة (builtins_security.cpp)نوع الإرجاع SadTypeKind::Integer لـHASHSadTypeKind::String
مولِّد LLVM (security_builtins_ops.cpp)توقيع sad_security_hash: (i8*) -> i64(i8*) -> i8*
مُصدِّر Android (compiler_driver_android_linker.cpp)نسخة ثالثة FNV-1a منفصلة تمامًانفس SHA-256 (sad_sha256_rotr/sad_sha256_raw/sad_security_hash) — هذا الهدف لا يملك sad_security_encrypt/decrypt أصلًا

📎 الاسم القانونيّ شفر بلا شدّة. مصدرُ الحقيقة assertions.yaml يسجّل canonical: شفر، وكذلك builtin_registry_generated.h. وتُكتَب في اختبارات السلوك شفّر بشدّةٍ فتعمل، لأنّ المعجميّ يتخطّى علامات التشكيل العربيّة (U+064B–U+065F) داخل المعرِّفات (lexer_core.cpp). فالشدّةُ زينةُ كتابةٍ لا فرقُ اسم — والقانونيُّ للتوثيق والأدوات هو المجرَّد.

sad_security_encrypt/decrypt نُقِلا بنفس بنية المفسّر: مقطع nonce عشوائيّ 8 بايت في بداية الناتج، وكل كتلة 32 بايت تُخفى بـSHA-256(مفتاح ‖ nonce ‖ عدّاد) كتيّار مفاتيح XOR.

flowchart LR
  SRC["هاش(نص) / شفر(نص، مفتاح)"] --> INTP["المفسّر: تقييم مباشر<br/>sha256 lambda"]
  SRC --> SIR["المترجم: CALL sad_security_*<br/>(SadTypeKind::String الآن)"]
  SIR --> RT["sad_embedded_runtime.c<br/>sad_sha256_raw مطابق للمفسّر"]
  INTP -.->|"تكافؤ حرفيّ + تبادليّة عابرة للمحركين"| RT

الفخّ: نسخة ثالثة كادت تفوت المراجعة

التغيير الأوّلي مسّ sad_embedded_runtime.c فقط (المسار المشترك لسطح المكتب/Windows/Linux). مراجعة مستقلّة قبل الدفع (وفق سياسة الفريق: مراجع مستقلّ إلزاميّ قبل أيّ git push) كشفت أنّ compiler_driver_android_linker.cpp يحمل نسخة كاملة منفصلة من دوال وقت التشغيل لهدف Android — بما فيها FNV-1a القديمة — لم يكن grep الأوّلي على sad_security_hash عبر شجرة الكود قد شملها لأنّها بحث لم يُعَد على كامل الشجرة. الدرس: البحث عن كل نسخة يسبق حذف/توحيد أيّ منطق مكرَّر — لا يكفي تتبّع نقطة تسجيل الدالّة المضمنة الواحدة (compiler_strategy: RUNTIME_CALL في SoT) لأنّ نقطة الربط الفعليّ (linker) قد تتفرّع حسب الهدف.

كما كُشِف تسريب أمنيّ ثانويّ في نفس المراجعة: تشفير/فكّ التشفير كانا يستعملان مخزنًا ثابت الحجم على المكدس (unsigned char input[8+8+256]) يقتطع المفاتيح الأطول من 256 بايت صامتًا بدل رفضها أو دعمها — أُصلح بتخصيص ديناميكيّ (malloc(klen + 16)).

نقطة الحسم: هل يُذكَر خطأ فكّ_تشفير على مدخل غير صالح؟

بقي تباعُد واحد موثَّق عمدًا لا مُصلَح: عند مدخل ست عشريّ غير صالح، المفسّر يرمي استثناء لغويًّا قابلًا للالتقاط بـحاول/امسك، بينما وقت تشغيل المترجم (دالّة C خالصة) يطبع رسالة على stderr ويُعيد النصّ الأصليّ دون رمي استثناء — لأنّ ربط دالّة C خارجيّة بآليّة الاستثناءات المبنيّة على LLVM (exception_ops.cpp) تغيير معماريّ أعمق من نطاق التوحيد الحاليّ. مُوثَّق في وصف DECRYPT بـ language-truth/builtins/assertions.yaml وفي RISK.md الخاصّ بقسم اختبارات المكتبة القياسيّة، بدل إخفائه خلف اختبار لا يغطّي مسار الخطأ.

الاختبار (تكافؤ مزدوج)

tests/behavior/sections/09_المكتبة_القياسية/04_تشفير/ — ٢٠ ملفًّا اليوم، وُلد من هذا التوحيد أربعةٌ منها (150–153): شعاعات FIPS 180-4 الرسميّة لـهاش("")/هاش("abc")، تبادليّة شفر/فك_تشفير عبر نصوص عربيّة/مختلطة/متعدّدة الكتل، شعاع ثابت مُسجَّل يدويًّا كمرساة انحدار، واختبار مفتاح أطول من 256 بايت (يرصد رجوع باغ الاقتطاع الصامت). أمّا البقيّة (154–168) فمن حملة توسيع مكتبة التشفير اللاحقة: BLAKE3 · PBKDF2 · HKDF · AEAD · Argon2id · X25519 · Ed25519 وحدودُها القصوى. كلّ ملفّ يُشغَّل عبر sad-run.exe وsad-build.exe ويُقارَن الناتج حرفيًّا (ADR-03) — هذا ما يضمن ألّا يعود التباعُد.


اقرأ بعده: دوال مضمنة ووحدات · نظام معالجة الأخطاء.

دراسة حالة: توسيع مكتبة التشفير (٥ مراحل + Argon2id)

بعد توحيد هاش/شفر (هاش/شفر/فك_تشفير في وحدة تأكيدات)، وُسِّعت اللغة بمكتبة تشفير حديثة كاملة في وحدة منفصلة تشفير — عبر RFC كامل (sadlang-rfcs/text/0000-توسيع-مكتبة-التشفير.md) نُفِّذ على ٥ مراحل + مرحلة إضافيّة (Argon2id) بطلب صريح من المالك رغم تأجيلها في نصّ RFC الأصليّ. المرجع اللغويّ للمستخدم في sadlang-docs؛ هذه الصفحة للمساهم في التنفيذ والمنهجيّة. (نصّ RFC نفسه موجود على فرع rfc/expand-crypto-library في sadlang-rfcs — لم يُدمَج إلى main بعد.)

المراحل

المرحلةالدوالالمعيارPR
٠عشوائي_آمنCSPRNG النظاممدموج
١بلايك3 / هاش_مفتاحBLAKE3 (keyed mode)مدموج
٢اشتق_مفتاح_مرور / اشتق_مفتاحPBKDF2-HMAC-SHA256 (RFC 2898/8018) / HKDF-SHA256 (RFC 5869)#217
—أرجون2Argon2id (RFC 9106) — مؤجَّلة أصلًا، أُدرِجت بطلب صريح#219
٣شفر_موثق / فك_تشفير_موثقChaCha20-Poly1305 AEAD (RFC 8439)#220
٤ولّد_مفتاح_خاص_x25519 وعائلتها (7 دوال)X25519 (RFC 7748) + Ed25519 (RFC 8032) + SHA-512 ذاتيّ#221

كل الدوال ذاتيّة التنفيذ (self-rolled، بلا OpenSSL/libsodium) حفاظًا على قابليّة العمل على هدف الوضع الحرّ (نفس قيد [[crypto-unification|توحيد هاش/شفر]])، وسطح اللغة لكل دالّة سلسلة تدخل ⇒ سلسلة تخرج حصرًا على كلا المحرِّكين — لا كائنات مركَّبة عابرة للحدود.

المنهجيّة: كل مرحلة عبر worktree معزول

flowchart LR
  RFC["sadlang-rfcs: نصّ المرحلة"] --> WT["worktree معزول<br/>wt-crypto-phaseN"]
  WT --> REF["مرجع C مستقلّ (scratchpad)<br/>يُختبَر مقابل شعاعات RFC الرسميّة"]
  REF -->|"تطابق تامّ"| IMPL["نقل الكود لثلاث نسخ إنتاج:<br/>مفسّر C++ / وقت تشغيل C / رابط أندرويد C"]
  IMPL --> AMELIA["مراجعة أميليا مستقلّة<br/>(متزامنة لا خلفيّة)"]
  AMELIA -->|"إجازة"| PR["PR ضدّ dev<br/>«لا دمج بلا إذن صريح»"]
  AMELIA -->|"عِلَّة"| WT

كل مرحلة استُنسِخت بنفس القالب: (1) مرجع C مستقلّ في scratchpad يُختبَر بايتًا بايت مقابل شعاعات الاختبار الرسميّة للمعيار (لا بيانات اختبار مؤلَّفة يدويًّا)؛ (2) بعد التطابق التامّ فقط، نقل نفس المنطق إلى ثلاث نسخ إنتاج متطابقة — المفسّر (interpreter/src/builtins/builtin_module_crypto.cpp)، وقت تشغيل المترجم (tools/compiler/runtime/sad_embedded_runtime.c)، ورابط أندرويد (tools/compiler/compiler_driver_android_linker.cpp — نسخة ثالثة منفصلة لأنّ هذا الهدف لا يشارك رابط سطح المكتب؛ يصطدم عادةً بحدّ سلاسل MSVC الخام، انظر أدناه)؛ (3) مراجعة أميليا مستقلّة متزامنة إلزاميّة قبل أيّ git push (لا خلفيّة — راجع فخّ التوقّف الموثَّق في الذاكرة الداخليّة)؛ (4) PR ضدّ dev بلا دمج تلقائيّ، وسطر «لا دمج بلا إذن صريح» حرفيًّا في كل PR.

الدرس الأهمّ: تحيّز اختيار شعاعات الاختبار (Argon2id)

أثناء Argon2id، طابق المرجع المستقلّ libargon2 الرسميّة (عبر argon2-cffi) تطابقًا تامًّا عبر 12 حالة اختبار اخترتُها بنفسي. رغم ذلك كشفت مراجعة أميليا المستقلّة عِلَّة حقيقيّة: التجزئة الأوّليّة H0 (RFC 9106 §3.2) كانت تُطعَم بقيمة تكلفة الذاكرة المقرَّبة داخليًّا لمضاعِف 4 (m') بدل القيمة الخام — والسبب أنّ كل الحالات الاثنتَي عشرة صدفةً استعملت تكلفة ذاكرة مضاعِفة لـ4 (8/1024/2048/4096)، فكان m'==m دومًا وأخفى العلّة رغم “التطابق التامّ” المُعلَن.

الدرس: اختيار حالات الاختبار بنفسك — حتى مع تنويع البارامترات — قد يحمل تحيّزًا غير واعٍ ينبع من نفس سوء الفهم الذي قد يكون وقع فيه التنفيذ نفسه. “طابق تنفيذًا مرجعيًّا مستقلًّا” ليس كافيًا وحده لكود تشفيريّ حسّاس — لازم أيضًا مراجعة تقرأ الخوارزميّة سطرًا سطرًا مقابل نصّ المعيار مباشرة، لا تكتفِ بالثقة بنتائج الاختبار. لخوارزميّة فيها تقريب/تقليم داخليّ، اختبر عمدًا حالة لا تقع على حدود التقريب.

فخّ الدمج المتسلسل: دالّة مشتركة الجسم مختلفة الاسم

تفرّعت PR #219 (Argon2id) وPR #220 (AEAD) وPR #221 (X25519/Ed25519) من نفس القاعدة (قبل #219) وتمسّ نفس الملفّات المشتركة (SIR opcode enum، مولّدات LLVM، المفسّر، وقت تشغيل C، رابط أندرويد، YAML SoT). دمج كلّ فرع لاحق كشف جولة تعارض جديدة، والقاعدة المتّبعة دومًا: أبقِ إضافات الجانبين معًا، لا تُسقِط أيًّا منهما. الفخّ: كل فرع أضاف دالّة توليد عشوائيّة مساعدة باسم مختلف (sadx_random_bytes مقابل sad_crypto_random_bytes) لكن جسمها شبه متطابق — والنصّ “المشترك” الظاهر بعد علامة >>>>>>> في تعارض git ثلاثيّ ينتمي في الحقيقة لدالّة واحدة فقط. محاولة حلّ هذا بتحرير واحد ضخم يفترض إعادة استخدام هذا النصّ لكلتا الدالّتين تنتج فسادًا صامتًا: دالّة مكرَّرة، تعليق مبتور، جسم دالّة ناقص — دون أيّ خطأ بناء فوريّ يكشفه.

الاكتشاف: فحص توازن الأقواس (python -c "text.count('{')==text.count('}')") ثمّ إعادة الملفّ لحالته المتعارضة (git checkout --conflict=merge -- <path>) والبدء من جديد بتحريرات صغيرة متسلسلة على حدود كل علامة تعارض على حدة، مع كتابة جسم كل دالّة كاملًا صراحة (تكرار المنطق بدل محاولة مشاركته نصّيًّا). طُبِّق هذا الدرس بنجاح من المحاولة الأولى على الملفّ التالي المصاب بنفس النمط.

فخّ ما بعد الدمج: حدّ MSVC C2026 لسلاسل C الخام

رابط أندرويد يُصدِر runtime التشفير كسلسلة C++ خام (R"( ... )") مقسَّمة استباقيًّا لتفادي حدّ MSVC (٦٥٥٣٥ بايت لكل حرفيّ سلسلة). كل فرع (PR #220 وPR #221) قسَّم محتواه الخاصّ تحت الحدّ بمعزل عن الآخر — لكن دمج X25519/Ed25519 مع AEAD في نفس القطعة بعد حلّ التعارض أعاد تجاوز الحدّ، وهذا لا يكشفه أيّ حارس ساكن أو مولِّد — يظهر فقط كخطأ بناء C2026: string too big وقت الترجمة الفعليّة على MSVC.

القاعدة العامّة: أيّ دمج لسلاسل C/C++ خام مقسَّمة مسبقًا بسبب حدّ حجم يحتاج تحقّقًا من الحدّ من جديد بعد الدمج — لا تفترض بقاء التقسيم الأصليّ كافيًا لمجرّد أنّ كل فرع وحده كان تحته. الحلّ: إغلاق )"; بعد حدّ دالّة نظيف وفتح R"( جديدة، فيتحوّل تقسيم كل فرع (قطعتان) إلى ثلاث/أربع قطع بعد الدمج.

قرار تصميميّ: X25519/Ed25519 بدوال مقسَّمة لا كائن

نصّ RFC اقترح دالّة توليد زوج مفاتيح تُرجع كائنًا بحقلين (عامّ وخاصّ). التصميم الفعليّ استعمل دالّتين منفصلتين بدلًا من ذلك (ولّد_مفتاح_خاص_x25519 + اشتق_مفتاح_عام_x25519) — تجنّبًا لبناء آليّة تمرير كائنات مركَّبة جديدة عابرة للمحرِّكين حين يكفي عقد “سلسلة ⇒ سلسلة” الموجود أصلًا لكل دالّة أخرى في المكتبة. لا خسارة أمنيّة: المفتاح العامّ دومًا دالّة حتميّة للخاصّ (تقييد سلميّ + ضرب بالنقطة الأساس)، فالفصل بلا أثر جانبيّ.

قرار تصميميّ: تباعُد فشل مقصود بين AEAD ونظيراتها

فك_تشفير_موثق (AEAD) يفشل مُغلَقًا (fail-closed) دومًا على فشل مصادقة — بخلاف فك_تشفير (وحدة تأكيدات، تشفير بلا مصادقة) الذي يُرجع مدخله كما هو صامتًا على خطأ تنسيق. لكن آليّة الإبلاغ عن هذا الفشل المُغلَق تتباعد بين المحرِّكين، بنفس السبب الجذريّ الموثَّق سلفًا: المفسّر يرمي استثناءً قابلًا للالتقاط بـحاول/امسك؛ المترجم يطبع رسالة عامّة على stderr (لا تُسرّب الوسم/المفتاح) ثمّ exit(1)، لأنّه لا يوجد اليوم مسار استثناء C-callable من وقت تشغيل المترجم إلى LLVM. بناء هذا المسار العامّ عمل لاحق موثَّق، خارج نطاق هذه الحملة — لكن الفشل نفسه مُغلَق على الحالتين، فلا يُرجع أيّ محرّك نصًّا مُحرَّفًا صامتًا أبدًا (أأمن من تباعُد فك_تشفير غير الموثَّق فيه فشل مفتوح).

الاختبار

كل مرحلة أضافت ملفّات إلى tests/behavior/sections/09_المكتبة_القياسية/04_تشفير/ (20 ملفًّا في dev اليوم؛ كانت 14 عند كتابة الفصل)، كلّ واحد يُشغَّل عبر sad-run.exe وsad-build.exe ويُقارَن الناتج حرفيًّا — شعاعات RFC/مسابقة رسميّة لكل معيار (BLAKE3-team، RFC 2898/5869/7748/8032/8439/ 9106)، إضافة إلى حالات رفض صريحة (سرّ مشترك كلّه أصفار في X25519، توقيع/مفتاح مُعبَث بهما في Ed25519، بارامترات خارج الحدود الآمنة في KDFs).


اقرأ بعده: توحيد هاش/شفر/فك_تشفير · دوال مضمنة ووحدات · نظام معالجة الأخطاء.

نظام الذاكرة (ذاكرة ص الذكية)

ماذا ستتعلّم: كيف تُدير لغة ص الذاكرة عبر نظامٍ مزدوج — جامع قمامة (GC) سهلٌ للتطوير، وملكيّةٌ صارمة بصفر تكلفة للإنتاج — وكيف يُختار الوضع، وما الإعدادات المسبقة، وكيف يصل العلَم من سطر الأوامر إلى سلوك الذاكرة.

📎 المصدر: shared/memory_policy/include/memory/policy/gc_mode.h · memory_mode_flag.h · memory_mode_flag.cpp

الفكرة الجوهريّة: ذاكرةٌ مزدوجة

أغلب اللغات تختار مرّةً واحدة: إمّا GC (سهلٌ، لكن بتكلفة وقت تشغيل) أو ملكيّة يدويّة (سريعٌ، لكن منحنى تعلّم حادّ). لغة ص تَجمع الطريقين في وضعين قابلين للتبديل، فالكود نفسه يُجرَّب بـGC ثم يُشحَن بملكيّة صارمة:

flowchart TB
  CODE["كود .ص نفسه"]
  CODE --> DEV["وضع التطوير (--جامع)<br/>GC تلقائيّ · بلا تفكير في الملكيّة<br/>مثاليّ لـREPL والتجريب"]
  CODE --> PROD["وضع الإنتاج (--إنتاج)<br/>ملكيّة صارمة كـRust · صفر overhead<br/>فحص الاستعارة وقت الترجمة"]
  DEV -. "المترجم يقترح تحويلات للملكيّة" .-> PROD

💡 الميزة الفريدة: المترجم يُحلِّل كود وضع التطوير ويقترح تحويلات تلقائيّة للملكيّة (enableOwnershipSuggestions)، فينقلك من التجريب السريع إلى الإنتاج الصارم تدريجيًّا.

ثلاثة تعدادات تَحكم كل شيء

السلوك كلّه ينضبط بثلاثة محاور مستقلّة (في gc_mode.h):

flowchart LR
  subgraph MODE["MemoryMode — الوضع الرئيسيّ"]
    M1["Development"]:::a
    M2["Production"]:::a
    M3["Hybrid ⚠️ مهجور → GCOnly"]:::dep
    M4["Auto — اكتشاف بالسياق"]:::a
  end
  subgraph GC["GCStrategy — استراتيجيّة الجمع"]
    G1["None — بلا GC (إنتاج)"]:::b
    G2["ReferenceCounting — عدّ مراجع"]:::b
    G3["AtomicReferenceCounting — ذرّيّ (خيوط)"]:::b
    G4["Tracing — Mark & Sweep"]:::b
    G5["Incremental — تدريجيّ"]:::b
  end
  subgraph OWN["OwnershipMode — صرامة الملكيّة"]
    O1["Disabled — GC يدير كلّ شيء"]:::c
    O2["Warnings — تحذيرات للتعلّم"]:::c
    O3["Strict — أخطاء ترجمة"]:::c
    O4["UltraStrict — كـRust"]:::c
  end
  classDef a fill:#0b728522,stroke:#0b7285;
  classDef b fill:#2b8a3e22,stroke:#2b8a3e;
  classDef c fill:#e8590c22,stroke:#e8590c;
  classDef dep fill:#86868622,stroke:#868686,stroke-dasharray:4;
المحورالتعدادالقيم
الوضعMemoryModeDevelopment · Production · Hybrid (مهجور) · Auto
الجمعGCStrategyNone · ReferenceCounting · AtomicReferenceCounting · Tracing · Incremental
الملكيّةOwnershipModeDisabled · Warnings · Strict · UltraStrict

⚠️ Hybrid مهجور: يُعامَل كـGCOnly عند مصادفته. لا تُصمِّم على أساسه.

الإعدادات المجمَّعة: MemoryModeSettings

البنية MemoryModeSettings تربط المحاور الثلاثة مع خياراتٍ إضافيّة (enableCycleDetection، gcMemoryLimitMB = 256، teacherMode، …)، وتُقدَّم عبر إعداداتٍ مسبقة جاهزة:

الإعداد المسبقmodegcStrategyownershipModeاقتراحاتكشف دوراتمُعلِّم
gcDefaults() (--جامع)DevelopmentTracingDisabled✗✓✗
developmentDefaults()DevelopmentReferenceCountingWarnings✓✓✓
productionDefaults() (--إنتاج)ProductionNoneUltraStrict✗✗✗
learningDefaults() (--تعلم)DevelopmentReferenceCountingWarnings✓✓✓
kernelDefaults() (--حرّ)ProductionNoneUltraStrict✗✗حدّ=0

📌 النواة (no_std): عند #![بلا_مكتبة_قياسية] يُفرَض kernelDefaults — لا GC إطلاقًا (gcMemoryLimitMB = 0)، ملكيّة UltraStrict، بلا اقتراحات ولا كشف دورات. ملكيّةٌ صرفة كما يليق ببرمجة الأنظمة.

من العلَم إلى السلوك: MemoryModeFlag

MemoryModeFlag::parse() يقرأ argv ويبني MemoryModeSettings. الأعلام مسجَّلة في flagHandlers_ (initializeFlags())، وهي مبنيّةٌ كلُّها من مصدر الحقيقة (cli_flags.yaml، عائلة memory) لا مكتوبةً يدويًّا: التسمية تأتي من الجدول، ولا يبقى في C++ إلّا السلوك (switch على FlagAction).

⚠️ اسمٌ عربيٌّ قانونيٌّ وحيدٌ لكلّ مفهوم — لا مرادفات ولا اختصارات ولا توافقَ خلفيّ. أُلغي 18 مرادفًا إنجليزيًّا/مختصرًا (--gc · --no-std · --freestanding · --kernel · --production/-p · --learn/-l · --auto/-a · …)؛ يضبط المُهيِّئ shortName = "" وlongNameEnglish = longNameArabic. فإن قرأت في وثيقةٍ أقدم علَمًا لاتينيًّا لسياسة الذاكرة، فهو لم يعد يُقبَل.

flowchart TD
  ARGV["argv[] / متغيّرات البيئة / ملف التهيئة"] --> PARSE["MemoryModeFlag::parse()"]
  PARSE --> H{"تصنيف العلَم"}
  H -->|"--إنتاج"| PROD["productionDefaults"]
  H -->|"--جامع"| GCD["gcDefaults"]
  H -->|"--تعلم"| LRN["learningDefaults"]
  H -->|"--حرّ"| KRN["kernelDefaults + noStdRequested"]
  H -->|"--تلقائي"| AUTO["اكتشاف بالسياق"]
  H -->|"--ملكية= · --جامع=استراتيجية · --حد-الذاكرة="| TUNE["ضبطٌ دقيقٌ فوق الإعداد المسبق"]
  H -->|"علَمٌ مُزال (--dev · --hybrid · --mixed …)"| REJ["فشلُ تحليلٍ صريح<br/>(لا تحويلَ صامت)"]
  PROD & GCD & LRN & KRN & AUTO & TUNE --> OUT["MemoryModeSettings ← FlagParseResult"]

عائلة memory كاملةً على dev (عشرةُ أعلام — لا غيرها):

العلَمالنوعالأثر (sets)
--إنتاجرايةproductionDefaults — ملكيّة صارمة، بلا جامع
--جامع[=استراتيجية]قيمة اختياريّةgcDefaults؛ ومع قيمةٍ يضبط gcStrategy
--تعلمرايةlearningDefaults — جامع + تحذيرات + رسائل تعليميّة
--حرّرايةkernelDefaults + noStdRequested — ملكيّة صرفة بلا مكتبة قياسيّة
--تلقائيرايةMemoryMode::Auto — اكتشافٌ بالسياق
--ملكية=قيمةownershipMode (off|warnings|strict|ultra)
--حد-الذاكرة=قيمةgcMemoryLimitMB — حدّ ذاكرة الجامع بالميغابايت
--اقتراحاترايةenableOwnershipSuggestions
--كشف-دوراترايةenableCycleDetection
--تصحيح-الذاكرةرايةDebugMemory

🛑 الأعلام المُزالة تُرفَض، لا تُترجَم. --dev · --development · -d · --تطوير · --hybrid · --mixed · --مختلط كلُّها في جدول deprecatedFlags (memory_mode_flag.cpp)، لكنّ الجدول لا يُحوِّلها: يضبط result.success = false ويُصدِر «أُزيل نهائيًّا في Phase E-3، استخدم --gc بديلًا». الإحلالُ المذكور في الرسالة إرشادٌ للقارئ، لا تحويلٌ يجريه المُحلِّل — والبديلُ القانونيُّ اليومَ --جامع.

ترتيب الأولويّة في حسم الإعداد

الإعداد النهائيّ يُحسَم بأولويّةٍ تصاعديّة (الأخصّ يَغلب):

flowchart LR
  D["الافتراضيّ (Auto)"] --> E["ملف التهيئة<br/>readConfigFile()"]
  E --> F["متغيّرات البيئة<br/>applyEnvironmentSettings()"]
  F --> G["سمة الملف #![…]"]
  G --> H["علَم سطر الأوامر<br/>(الأعلى أولويّة)"]

أين يَظهر هذا في خطّ الأنابيب؟

🧭 القاعدة الذهبيّة: الوضع سياسة، لا بنية. الكود لا يتغيّر بين التطوير والإنتاج؛ يتغيّر MemoryModeSettings فقط، فينتقل البرنامج من سهولة الـGC إلى صرامة الملكيّة دون إعادة كتابة.


اقرأ بعده: نظام الأنواع (فاحص الأنواع).

نظام الأنواع وفاحص الأنواع (SadType / Value)

ماذا ستتعلّم: كيف تُمثَّل الأنواع في لغة ص — من تعداد SadTypeKind المولَّد عن مصدر الحقيقة، إلى هرم أصناف SadType، إلى علاقات الفحص (تطابق · إسناد · نوع‑فرعيّ · إكراه)، وكيف تُمثَّل القيمة الحيّة عبر عالَمَي التنفيذ، وكيف يتشابك نظام الأنواع مع بقيّة الأنظمة — وعلى رأسها نظام الأخطاء.

📎 المصدر: sad_type_system.h · sad_type_kind_generated.h · value.h · sir_types.h

نظام واحد، لا جسر

✅ بعد التوحيد (RFC sadlang/rfcs#8): نظام النوع واحد هو SadTypeKind/SadType. أُزيلت السقالة الانتقالية بالكامل: type_bridge وSadValue المهجور وValueType التوافقيّ وDataType — لا تبحث عنها. القيمة الحيّة Value تعتمد SadType مباشرةً.

تمييزٌ واحد لا يلتبس:

يمثّلأينالمصدر
SadTypeKind / SadTypeالنوع الثابت (وقت الترجمة/الفحص)المحلّل + الدلالات + SIR + المفسّرsad_type_system.h
Valueالقيمة الحيّة (وقت التشغيل) — تحمل SadTypeKind+SadTypePtr بداخلهاالمفسّرvalue.h

النوع يُجيب «ما الذي يَصلُح؟»، والقيمة تُجيب «ما الموجود الآن؟» — وكلاهما يدور حول محورٍ واحد.

① مصدر الحقيقة: SadTypeKind (مولَّد)

التعداد SadTypeKind يُولَّد آليًّا من language-truth/types.yaml (49 قيمة، والعددُ نفسُه مولَّدٌ ثابتًا SAD_TYPE_KIND_COUNT — لا تنسخه نثرًا في موضعٍ ثانٍ) — لا يُحرَّر يدويًّا. أيّ نوعٍ جديد يُضاف إلى الكتالوج ثم يُعاد التوليد:

flowchart LR
  YAML["language-truth/types.yaml<br/>(مصدر الحقيقة)"] -->|codegen| GEN["sad_type_kind_generated.h<br/>enum SadTypeKind : int"]
  GEN --> SYS["sad_type_system.h<br/>الأصناف + الدوال"]
  SYS --> AST["AST + الدلالات"]
  SYS --> SIR["SIR / المترجم"]
  SYS --> INT["المفسّر (Value)"]

القيم موزَّعة على عائلات: أوّليّة (Void · Integer · Float · Boolean · String) · أوّليّة محدَّدة الحجم (Int8 · Int16 · Int32 · UInt8 · UInt16 · UInt32 · UInt64 · Float32 · Char) · مركّبة (Array · Map · Tuple · Slice) · معرَّفة مستخدِمًا (Class · Struct · Enum · Trait) · قابلة للاستدعاء (Function · Closure) · جبريّة (Union · Intersection · Optional · Result) · عامّة (Generic · TypeParameter · TypeAlias) · مراجع (Pointer · Reference · MutableRef) · خاصّة (Any · Never · Unknown · Error · Null) · تزامن (Future · Generator · Comprehension) · واجهة/رسوميّات (Color · Widget · Window · Event · Vector · Point · Rect).

💡 العربيّة/الإنجليزيّة ليست تعليقًا فحسب: sadTypeKindToArabic() وsadTypeKindToEnglish() تُرجعان الاسمين رسميًّا — فالنوع يُطبَع بلغة المستخدم، وهذا بذاته يغذّي نظام الأخطاء برسائلَ نوعٍ ثنائيّة اللغة.

② هرم الأصناف: SadType

الصنف المجرَّد SadType (يرث enable_shared_from_this) جذرٌ لكلّ نوعٍ غنيّ يحفظ معاملاته (عنصر المصفوفة، مفتاح/قيمة الخريطة…). يُتداول دومًا كـSadTypePtr (shared_ptr<SadType>):

classDiagram
  class SadType {
    <<abstract>>
    +SadTypeKind getKind()
    +string arabicName()*
    +string englishName()*
    +bool equals(other)
    +bool isAssignableTo(target)
    +bool isSubtypeOf(parent)
    +bool coercesTo(target)
    +bool isCopyable()
    +size_t sizeInBytes()
    +isMutable / lifetimeName
  }
  SadType <|-- SadPrimitiveType
  SadType <|-- SadSpecialType
  SadType <|-- SadArrayType
  SadType <|-- SadMapType
  SadType <|-- SadTupleType
  SadType <|-- SadFunctionType
  SadType <|-- SadClassType
  SadType <|-- SadOptionalType
  SadType <|-- SadResultType
  SadType <|-- SadUnionType
  SadType <|-- SadGenericType
  SadArrayType : +getElementType()
  SadMapType : +getKeyType() / getValueType()
  SadOptionalType : +innerType
  SadResultType : +ok / err

كلّ صنفٍ يَفحص التساوي البنيويّ صحيحًا: مصفوفة<عدد> تساوي مصفوفة<عدد> لا مصفوفة<نص> (يقارن getElementType()، لا الـkind وحده). وSadTypeRegistry (Singleton) يَختزن (interning) الأنواع المركّبة فتصير مقارنة المؤشّر == صحيحةً للمتماثلة بنيويًّا.

③ التصنيفات السريعة

دوال inline على SadTypeKind تُسرِّع الفحص دون إنشاء كائن — تستخدمها الدلالات بكثافة:

الدالةتصدُق على
isPrimitiveKind(k)Void/Integer/Float/Boolean/String/UInt8 + المدى Int8…Char
isNumericKind(k)يُفوَّض إلى sadTypeKindIsNumeric() المولَّد عن حقل numeric في types.yaml — لا مدًى على ترتيب التعداد (كان Int8…Float64؛ أُزيل لأنّه يربط الدلالة بترتيب رأسٍ مولَّد)
isCompositeKind(k)Array · Map · Tuple · Slice فقط (لا Struct ولا Class)
isCallableKind(k)القابلة للاستدعاء (Function · Closure)

وعلى مستوى الكائن: isNullable() · isCopyable() (تَفصِل القيميّ عن المرجعيّ) · isMutable() · lifetimeName() (للملكيّة — يربط نظام الأنواع بـنظام الذاكرة).

④ علاقات الفحص — قلب «فاحص الأنواع»

عند فحص إسنادٍ أو تمرير وسيطٍ أو إرجاع، يسأل الفاحص أربعة أسئلة متدرّجة الصرامة، وآخر المطاف عند الفشل هو نظام الأخطاء:

flowchart TD
  Q["هل النوع s يُقبَل حيث يُتوقَّع t؟"] --> EQ{"equals(s,t)؟<br/>تطابق بنيويّ تامّ"}
  EQ -- نعم --> OK["✅ يُقبَل مباشرةً"]
  EQ -- لا --> SUB{"isSubtypeOf(s,t)؟<br/>s فرعٌ من t"}
  SUB -- نعم --> OK
  SUB -- لا --> ASG{"isAssignableTo(s,t)؟<br/>إسناد مسموح (مثل T→اختياري&lt;T&gt;)"}
  ASG -- نعم --> OK
  ASG -- لا --> CO{"coercesTo(s,t)؟<br/>إكراه ضمنيّ (مثل عدد→عشريّ)"}
  CO -- نعم --> WARN["✅ بإكراه ضمنيّ"]
  CO -- لا --> ERR["❌ خطأ نوع دلاليّ (SEM)<br/>→ ErrorManager.reportError"]
  ERR -.-> ERRSYS["نظام الأخطاء"]
  click ERRSYS "errors.html" "اذهب إلى نظام الأخطاء"
  • equals — تطابقٌ تامّ (يقارن kind والمعاملات).
  • isSubtypeOf — العلاقة الفرعيّة (الافتراضيّ equals، تُوسَّع للأصناف/السمات).
  • isAssignableTo — هل يَصِحّ الإسناد؟ (مثلًا T إلى اختياري<T>، أو أيّ نوعٍ إلى Any).
  • coercesTo — التحويل الضمنيّ المسموح (مثل عدد → عشريّ). الفشل هنا ⇒ خطأ نوع.

⑤ القيمة الحيّة عبر عالَمَي التنفيذ

النوع واحد، لكن القيمة لها تمثيلان لأن للّغة عالَمَي تنفيذ منفصلين — وهذا فصلٌ مقصود لا ازدواج:

flowchart TD
  SRC["قيمة في برنامج .ص<br/>(مثلاً 42)"] --> Q{أداة التشغيل}
  Q -->|sad-run| V["Value (صنف C++)<br/>SadTypeKind + SadTypePtr + variant"]
  Q -->|sad-build| C["SadValue (struct C)<br/>type + union — في الثنائيّ الناتج"]
  V --> INT["تنفيذ المفسّر مباشرةً"]
  C --> BIN["تشغيل الثنائيّ native"]
ValueSadValue (وقت تشغيل الثنائيّ)
العالَمالمفسّر (sad-run)الثنائيّ المُترجَم
اللغةصنف C++ (shared/types/include/value.h)struct C (compiler/include/backend/llvm/llvm_runtime.h)
النوعSadTypeKind + SadTypePtrوسم type

⚠️ لا يلتقيان في الذاكرة: برنامجٌ بالمفسّر لا يلمس SadValue، وثنائيٌّ مُترجَم لا يلمس Value. الاسمان متشابهان والكيانان منفصلان (تصادُم اسمٍ لا أكثر).

⑥ علاقة نظام الأنواع ببقيّة الأنظمة

نظام الأنواع محورٌ تتشابك حوله أغلب الأنظمة. هذه خريطة العلاقات:

flowchart TB
  TS(("نظام الأنواع<br/>SadTypeKind / SadType")):::core

  SOT["مصدر الحقيقة<br/>types.yaml"] -->|يولّد| TS
  LEX["المحلّل المعجمي"] -->|"مُعرّفات نوع سياقيّة<br/>رقم · نص …"| TS
  PARSE["المحلّل النحوي + AST"] -->|عُقد تحمل SadTypeKind| TS
  TS -->|قرارات الفحص| SEM["الدلالات / فاحص الأنواع"]
  SEM -->|فشل فحص ⇒ SEM xxx| ERR["نظام الأخطاء"]:::err
  TS -->|isCopyable / lifetimeName| MEM["نظام الذاكرة (الملكيّة)"]
  MEM -->|انتهاكات ⇒ ownership xxx| ERR
  TS -->|نوع موحَّد| INT["المفسّر (Value)"]
  TS -->|نوع موحَّد| SIR["SIR → LLVM"]
  INT -->|أخطاء نوع وقت التشغيل ⇒ RUN xxx| ERR
  TS --> BUILTIN["الدوال المضمنة<br/>(تواقيع + إكراه الوسائط)"]
  BUILTIN -->|وسيط غير متوافق ⇒ خطأ| ERR

  classDef core fill:#1864ab22,stroke:#1864ab,stroke-width:2px;
  classDef err fill:#c92a2a22,stroke:#c92a2a,stroke-width:2px;
النظامكيف يتعلّق بنظام الأنواع
مصدر الحقيقةيولّد تعداد الأنواع كلّه — لا نوع خارج types.yaml
المحلّل المعجميأسماء الأنواع (رقم/نص…) مُعرّفات سياقيّة لا كلماتٌ محجوزة
المحلّل النحوي/ASTعُقد التصريحات تحمل SadTypeKind مباشرةً (لا تمثيل وسيط)
نظام الأخطاءالوجهة النهائيّة لكل فشل نوعٍ — انظر §⑦
نظام الذاكرةisCopyable/lifetimeName/isMutable تقود الملكيّة وحركة القيم
المفسّرValue تحمل النوع؛ التحميل الزائد والاستدعاءات تتشاور مع SadType
SIR/المترجميبني أنواعه على SadTypeKind نفسه ⇒ سلوك موحَّد بين التفسير والترجمة
الدوال المضمنةتواقيعها تُفحَص وتُكرَه وسائطها عبر SadType

⑦ نظام الأنواع ⟷ نظام الأخطاء (العلاقة المحوريّة)

كل فشل نوعٍ ينتهي رمزَ خطأٍ مكتلَجًا يُطلَق عبر ErrorManager — لا نصًّا حرًّا. والطورُ الذي يُكتشَف فيه الفشل يحدّد نطاق الرمز:

flowchart LR
  subgraph TYPE["قرار نوعٍ فاشل"]
    A["إسناد/تمرير غير متوافق<br/>(coercesTo=false)"]
    B["متغيّر/دالة بلا نوع معروف"]
    C["إسناد عدمٍ لغير اختياريّ<br/>(Optional/Null)"]
    D["عمليّة على نوعٍ خاطئ<br/>وقت التشغيل"]
  end
  A -->|طور دلاليّ| SEM["SEM xxx<br/>(Semantic)"]
  B -->|طور دلاليّ| SEM
  C -->|أمان null| SEM
  D -->|طور تشغيل| RUN["RUN xxx<br/>(Runtime)"]
  SEM --> EM["ErrorManager<br/>reportError(code, موقع, قوالب)"]
  RUN --> EM
  EM --> OUT["تشخيص ثنائيّ اللغة<br/>(عربيّ/إنجليزيّ) + موقع + تلميح إصلاح"]
  • النوع يصف، الخطأ يبلِّغ: يستعمل التشخيص أسماء الأنواع (arabicName()/englishName()) لبناء رسالةٍ مفهومةٍ بلغة المستخدم (مثل «متوقَّع نص لكن وُجد رقم»).
  • الطور يحدّد النطاق: فشلٌ يُكتشَف في الدلالات ⇒ SEM؛ يُكتشَف وقت التشغيل ⇒ RUN. (راجع نطاقات ErrorCode في نظام الأخطاء.)
  • أمان null جزءٌ من العلاقة: أنواع Optional/Null تُحوِّل «الوصول لعدمٍ محتمل» إلى تشخيصٍ مبكّر بدل انهيارٍ صامت.
  • لا نصّ حرّ: يُمنع throw std::runtime_error("…") لأخطاء النوع — أطلِق ErrorCode دومًا.

ملاحظات للمطوّر

  • الأنواع المدمجة (رقم/نص/…) مُعرّفات سياقيّة لا كلماتٌ محجوزة — انظر المحلل المعجمي.
  • القسمة / تُعطي عشريًّا دائمًا — استخدم رقم(ن/م) للقسمة الصحيحة.
  • لإضافة نوعٍ جديد: حرِّر types.yaml ثم أعد التوليد — لا تَلمس sad_type_kind_generated.h.
  • لا تَبحث عن type_bridge/ValueType/DataType/SadValue(C++): أُزيلت بالتوحيد (RFC sadlang/rfcs#8). المحور الوحيد SadTypeKind.

مرجع SoT

الأنواع مُكتلَجة في language-truth/types.yaml؛ تفاصيل أنواع المترجم في sir_types.h → SIR.


اقرأ بعده: نظام الأخطاء.

نظام الأخطاء والتشخيص

ماذا ستتعلّم: كيف تُكتلَج الأخطاء كمصدر حقيقة موحَّد (رمز · رسالة ثنائيّة اللغة · تلميح إصلاح)، وكيف تُطلَق من كل طبقة، وكيف يجمعها ErrorManager ويعرضها بالعربيّة والإنجليزيّة مع الموقع واقتباس المصدر — وكيف تضيف رمزًا جديدًا.

📎 المصدر: shared/errors/include/error_codes.h · error_manager.h · language-truth/errors/

المبدأ: الأخطاء بيانات لا نصوص

خطأٌ مكتوبٌ نصًّا حرًّا في الكود يَتعفّن: لا يُترجَم، لا يُختبَر، لا يُوحَّد. لذا أخطاء ص مكتلَجة كمصدر حقيقة (SoT): لكلّ خطأٍ رمزٌ ورسالةٌ ثنائيّة اللغة وتلميحُ إصلاح يعيش في language-truth/errors/، ويُولَّد منه كود C++ والتشخيص. الإطلاق يُشير إلى رمز، لا إلى جملة.

flowchart LR
  YAML["language-truth/errors/*.yaml<br/>(مصدر V5)"] -->|gen_error_messages.py| GEN["error_messages_generated.{h,cpp}"]
  YAML --> SCHEMA["_schemas/error.schema.json<br/>(تحقّق)"]
  EC["error_codes.h<br/>enum ErrorCode"] --> GEN
  GEN --> EM["ErrorManager<br/>(جمع + عرض)"]
  RAISE["طبقات اللغة<br/>المعجمي/النحوي/الدلالي/التشغيل"] -->|reportError(code, …)| EM
  EM -->|printAll| OUT["عرض ثنائيّ اللغة + موقع + اقتباس"]
  EM -->|toJSON| JSON["تشخيص آليّ (IDE/أدوات)"]

① الكتالوج: بنية إدخال الخطأ

كلّ ملفّ فئةٍ (runtime.yaml، semantic.yaml، …) قائمةُ أخطاء؛ الإدخال الواحد:

- code: RUN_DIVISION_BY_ZERO      # المعرّف الرمزيّ (مفتاح enum)
  id: RUN001                      # المعرّف المرقَّم (الفئة + الرقم)
  category: runtime
  title:   { ar: القسمة على صفر,            en: Division by zero }
  brief:   { ar: "محاولة قسمة {a} على صفر",  en: "Attempting to divide {a} by zero" }
  placeholders: [ a ]             # القوالب التي تُملأ وقت الإطلاق
  fix_hint:  { ar: "تحقّق من المقام…",       en: "Check denominator…" }
  detailed:  { ar: "القسمة على صفر…",        en: "Division by zero is…" }
الحقلالدور
code / idالمفتاح الرمزيّ + المرقَّم (يربطان YAML بـenum ErrorCode)
titleعنوانٌ قصير ثنائيّ اللغة
brief + placeholdersالرسالة التي تُملأ قوالبُها ({a}) وقت الإطلاق
fix_hintكيف تُصلِحه — يميّز ص: لا يكتفي بالشكوى
detailedشرحٌ تعليميّ موسَّع (يَظهر حسب مستوى التفصيل)

② التصنيف والنطاقات: ErrorCode

التعداد ErrorCode مقسَّمٌ بنطاقاتٍ حسب طور الكشف — كلّ طورٍ نطاقُ أرقامٍ خاصّ:

flowchart TB
  subgraph PIPE["طور الكشف ← فئة الخطأ"]
    LEX["LEX001–099<br/>معجميّ (Lexical)<br/>رمز غير صالح · نصّ غير مغلق · UTF-8"]:::lx
    SYN["SYN001–099<br/>نحويّ (Syntax)<br/>رمز متوقَّع · قوس غير مغلق"]:::sy
    SEM["SEM001–099<br/>دلاليّ (Semantic)<br/>نوع غير متطابق · متغيّر غير معرَّف"]:::se
    RUN["RUN001–099<br/>وقت التشغيل (Runtime)<br/>قسمة على صفر · فهرس خارج النطاق"]:::ru
    ICE["ICE<br/>أخطاء المترجم الداخليّة (عيوب)"]:::ic
  end
  LEX --> SYN --> SEM --> RUN
  classDef lx fill:#0b728522,stroke:#0b7285;
  classDef sy fill:#5f3dc422,stroke:#5f3dc4;
  classDef se fill:#e8590c22,stroke:#e8590c;
  classDef ru fill:#c92a2a22,stroke:#c92a2a;
  classDef ic fill:#86868622,stroke:#868686;

فئاتٌ إضافيّة لها ملفّاتُها: import · io · ownership (يربط نظام الذاكرة) · internal (ICE).

📏 المقيس على dev: كتالوجُ language-truth/errors/ ثمانيةُ ملفّات، لكنّ خمسةً منها فقط تحمل رموزًا: lexical (6) · syntactic (32) · semantic (48) · runtime (74) · internal (23) = 183 رمزًا. أمّا import وio وownership فملفّاتُ إحالةٍ بـerrors: [] — أخطاؤها مُصنَّفةٌ تحت runtime (مثل RUN008 للاستيراد)، أُبقيت لتوثّق الإحالة لا لتُكرِّر الرمز.

ويقابلُ الـ183 في shared/errors/include/error_codes.h 183 عضوًا في enum class ErrorCode، كلُّ عضوٍ موثَّقٌ بتعليقٍ يحمل رمزَه المرقَّم (///< SEM002: … · ///< RUN008: … — لا وسمَ حرفيًّا اسمُه CODE). فلا عضوَ بلا رمز، ولا رمزَ بلا عضو. النطاقاتُ أعلاه حدودٌ معماريّة لا أعدادٌ فعليّة — فئةٌ لم تبلغ 099 ليست ناقصة.

③ دورة حياة الخطأ: من الإطلاق إلى العرض

الطبقة تُطلِق رمزًا + قوالبَ + موقعًا؛ يجمعها ErrorManager في DiagnosticSink، ثم يُبنى العرض ثنائيّ اللغة عند الطباعة:

sequenceDiagram
  participant L as طبقة (معجمي/دلاليّ/مفسّر)
  participant EM as ErrorManager
  participant S as DiagnosticSink
  participant U as المستخدم
  L->>EM: reportError(ErrorCode, SourceLocation, {placeholders})
  EM->>EM: buildBilingualMessage(code, …) عن الكتالوج
  EM->>S: add(Diagnostic{code, موقع, رسالة})
  Note over S: تجميع · عدّ أخطاء/تحذيرات · حدّ أقصى
  U->>EM: printAll(lang, colorize)
  EM->>U: عنوان + رسالة (ar/en) + سطر/عمود + اقتباس المصدر + fix_hint

⚠️ يُمنع النصّ الحرّ: لا throw std::runtime_error("…") ولا runtime_throw.h — هذا نمطٌ مهجور. أطلِق دومًا ErrorCode::<NAME> عبر reportError / reportFromCatalog. نمطُ المعالجة موحَّدٌ لكل طبقة: reportError في codegen · استثناءات في المحلل النحوي · رموز في FFI (CW‑22).

④ ErrorManager — الجامع والعارض

ErrorManager واجهةٌ آمنة الخيوط (std::mutex) فوق DiagnosticSink:

الوظيفةالواجهة
الإطلاقreportError(code, loc, …) · reportWarning(…) · reportFromCatalog(code, …)
البناءbuildBilingualMessage(code, …) — يدمج الكتالوج بالقوالب
العرضprintAll(lang, colorize) · toJSON() · saveToFile()
الضبطsetLanguage · setColorize · setMaxErrors · setExplanationLevel
الذكاءsetSmartErrorsEnabled — تشخيصٌ أذكى (اقتراحات سياقيّة)
المصدرsetSourceCode(source, filename) — لاقتباس السطر المخطئ

DiagnosticSink يَعُدّ الأخطاء والتحذيرات (getErrorCount / hasErrors) ويدعم حدًّا أقصى (truncateTo) كي لا تَغرق الشاشة بآلاف الأخطاء المتتالية.

💡 ثنائيّة اللغة ليست ترجمةً لاحقة: الرسالتان (ar/en) تَسكنان الكتالوج جنبًا إلى جنب، فيختار المستخدم لغته (setLanguage) دون فقدان الدقّة.

⑤ إضافة رمز خطأ جديد

flowchart LR
  A["① أضف الإدخال إلى<br/>errors/&lt;cat&gt;.yaml"] --> B["② أضف ErrorCode::NAME<br/>إلى error_codes.h (إن لزم)"]
  B --> C["③ أعد التوليد<br/>gen_error_messages.py"]
  C --> D["④ أطلِقه من الطبقة الصحيحة<br/>reportError(NAME, loc, …)"]
  D --> E["⑤ اختبار سلبيّ<br/>@expected خطأ"]
  1. أضف الرمز/الرسالة/التلميح إلى language-truth/errors/<cat>.yaml (تحقَّق بـerror.schema.json).
  2. أضف ErrorCode::<NAME> إلى error_codes.h إن لزم.
  3. أعد التوليد (gen_error_messages.py أو عبر البناء).
  4. أطلِقه من الطبقة الصحيحة بـErrorCode::<NAME> + placeholders.
  5. اكتب اختبارًا سلبيًّا (@expected خطأ) — وإلّا فالرمز غير محميّ من الانحدار.

راجع بنى YAML للأخطاء والهجرة V4→V5 في مهارة sad-lang-dev (references/error-yaml-structures.md).


اقرأ بعده: الدوال المضمنة.

الدوال المضمنة والوحدات

ماذا ستتعلّم: كيف تُعرَّف الدوال المضمنة كمصدر موحّد، وأين تُنفَّذ في المفسّر والمترجم.

التصنيفان

📏 المقيس من الكتالوج (language-truth/builtins/، فرع dev): 1205 مضمنةً إجماليًّا، منها 865 بحقل require_import: false. والرقمُ الأخيرُ يشمل مضمناتِ العتاد والنواة (acpi_* · apic_* · uefi_*)، فلا تقرأه «ما يستعمله المبرمج يوميًّا». العدّادُ الوحيدُ الصادقُ هو الكتالوج — لا تنسخ عددًا نثرًا في موضعٍ ثانٍ.

  • تلقائيّة (بلا استيراد): المجموعةُ اليوميّةُ منها ~21: إخراج (اطبع/اطبع_سطر)، إدخال (اقرأ)، طول/نوع (طول/نوع)، تحويل (رقم/عشري/نص/منطقي)، تزامن (قناة/مجموعة_انتظار/قفل/مستقبل).
  • طرق على الأنواع: مصفوفات (.اضف/.رتب/.خريطة/…)، نصوص (.تقسيم/.استبدل/…)، خرائط، قنوات.
  • وحدات تحتاج استيراد: رياضيات، نصوص، أساسيات، خرائط، شبكة_عالية، تشفير، مقابس، …

مصدر الحقيقة

language-truth/builtins/<domain>.yaml   ← المصدر (cpp_id, canonical, namespace, params, …)
language-truth/builtins/_index.yaml     ← فهرس النطاقات
        │ gen_builtins_registry.py / gen_all_builtins_yaml.py
        ▼
shared/builtins/generated/builtin_registry_generated.h   ← مُولَّد

التنفيذ (طبقتان)

الطبقةالمكان
المفسّرinterpreter/src/builtins/builtin_*.cpp
المترجمcompiler/src/backend/llvm/builders/builtins/*.cpp

سجّل الدوال بثوابت مُولَّدة Bn::<Group>::<CPP_ID>، وطرق الأنواع بـTM::<Group>::<NAME> — لا سلاسل حرفيّة.

إضافة دالة مضمنة (الإجراء)

  1. أضفها إلى language-truth/builtins/<domain>.yaml (+_index.yaml إن ملف جديد).
  2. أعد التوليد ⇒ builtin_registry_generated.h.
  3. نفّذها في المفسّر والمترجم.
  4. اختبار .ص مزدوج (إيجابيّ + سلبيّ).

الاستيراد: استورد رياضيات · استورد رياضيات كـ ر · من رياضيات استورد جذر · من رياضيات استورد *. التفاصيل في مهارة sad-lang-dev (references/builtins-system.md).


اقرأ بعده: دراسة حالة: توحيد هاش/شفر/فك_تشفير · سير عمل الفروع.

محرّك تخطيط SadUI والمحاذاة المتقاطعة (RTL)

الغرض: شرح محرّك تخطيط واجهات SadUI (LayoutEngine) ومنظومة المحاذاة المتقاطعة مع دعم RTL الأصيل — كيف تُوضَع أبناء الحاويات، وخاصّيّة «محاذاة»، والهامش والأوزان، ومصدر حقيقة مفاتيح الخصائص.

إطار SadUI عربيّ RTL-أوّلًا: الافتراض LayoutDirection::RTL في كلّ الطبقات. لذا يبدأ محتوى الشاشة من اليمين لا اليسار.

المرحلتان المدموجتان

محرّك التخطيط (features/graphics/core/src/layout.cpp) يجمع منطقَي القياس والترتيب في تمريرة تنازليّة واحدة: layout() يستدعي arrange() من الجذر للأوراق، وarrange يستدعي measure() عند كلّ مستوًى حسب الحاجة.

  • المحور الرئيسيّ: للعمود عموديّ (تكديس رأسيّ)، وللصفّ أفقيّ.
  • المحور المتقاطع: المحور الآخر — للعمود أفقيّ (اتّجاهيّ RTL/LTR)، وللصفّ عموديّ (غير اتّجاهيّ).

خاصّيّة «محاذاة» المتقاطعة

الوضعالعمود (RTL)العمود (LTR)الصفّ
بداية (افتراضيّ)اليميناليسارالأعلى
وسطتوسيطتوسيطتوسيط عموديّ
نهايةاليساراليمينالأسفل
تمدّديملأ العرضيملأ العرضيملأ الارتفاع
  • تمدّد يجعل الابن يملأ المحور المتقاطع كاملًا فيتخطّط هو وأحفاده بالمقاس الكامل؛ الابن ذو المقاس الصريح في ذلك المحور يفوز على التمدّد (كـ align-items: stretch في CSS).
  • حدّ: «محاذاة» يُكرِّمها العمود والصفّ حصرًا؛ الشبكة/المكدّس/الالتفاف/ التمرير لها تموضع RTL مبيَّت خاصّ لكنّها تتجاهلها.

الهامش والحشو والأوزان

  • حشو: إزاحة داخليّة تُقلّص منطقة المحتوى من الجانبين.
  • هامش: إزاحة خارجيّة تُقحِم المحتوى [هامش+حشو، العرض−هامش−حشو]، ويُخصم من قيود الأبناء فلا يتجاوز الابن مالئ-المحور فجوة الهامش.
  • وزن (وزن/flex): حصّة الابن من المساحة المتبقّية على المحور الرئيسيّ؛ توزيعه يحترم المقاس الصريح للحاوية (إن وُجد فالتوزيع ضمنه، وإلّا يتمدّد ليملأ القيد).

مصدر حقيقة مفاتيح الخصائص (SoT)

كلّ مفتاح خاصّيّة (نحو «محاذاة»/«حشو»/«عرض») معرَّف في language-truth/ui_props.yaml — ١٠٤ مفاتيحَ على dev — يُولَّد منه sad_ui/prop_keys.h (ثوابت sad::ui::props::<ID>، وعددُها ١٠٤ أيضًا: تكافؤٌ عدديٌّ بين المصدر والمولَّد) عبر x.py gen. لا سلسلة مفتاح خام في كود الرسوميّات — يُقرأ المفتاح دائمًا عبر الثابت المولَّد:

node.findProperty(props::ALIGN)   // ✓  لا  findProperty("محاذاة")

يحرسه check_no_raw_props.py + check_ui_props_consistency.py ضمن x.py gen --check (محلّيًّا + CI)، وworkflow props-literals-lint.yml.

⚠️ latin_alias ليس اسمًا قانونيًّا. بعضُ المفاتيح يحمل حقلًا اختياريًّا latin_alias — نصُّ المصدر يحدّه: «بديلٌ احتياطيٌّ لاتينيّ يقرؤه المُرسِّم فقط، لا قانونيّ». فلا يُكتَب في IRNode ولا يُوثَّق للمستخدم؛ القانونيُّ هو canonical العربيُّ وحده.

التفصيل المعماريّ الكامل (مع الأمثلة والرسوم) في مستودع اللغة: docs/architecture/sadui-layout-alignment.md.

سير عمل الفروع (dev · worktrees · PR)

ماذا ستتعلّم: كيف تعمل على لغة ص بأمان عبر فروع معزولة، وتدمج في dev المحميّ.

النموذج

العمل يتكامل في فرع dev (لا graphic). كلا الفرعين محميّ على GitHub (Rulesets: dev=17779574، graphic=16775713) بقواعد متطابقة: منع الحذف · منع force-push · تاريخ خطّيّ · توقيع GPG إلزاميّ · PR إلزاميّ. المستودع: sadlang/s-programming-language.

🔑 القاعدة الذهبية: لا تُودِع/تدفع مباشرةً على dev أو graphic — كل تغيير عبر PR من فرع agent/* معزول في git worktree.

لماذا worktrees؟

كل وكيل/مهمّة يأخذ مجلدًا فرعيًّا = فرعًا يشارك نفس .git (لا استنساخ). تبديل فرع المستودع الرئيسيّ لا يمسّ عملك المعزول. الموطن: C:/s_lang/temp-brunch/.

flowchart LR
  DEV["origin/dev (محميّ)"] -->|worktree add -b| A["agent/مهمة-1"]
  DEV -->|worktree add -b| B["agent/مهمة-2"]
  A -->|PR موقّع GPG| DEV
  B -->|PR موقّع GPG| DEV

الخطوات

# 1) فرع معزول من dev
cd /c/s_lang/s-programming-language
git fetch origin
git worktree add /c/s_lang/temp-brunch/<مهمة> -b agent/<مهمة> origin/dev

# 2) العمل + الإيداع (موقّع GPG تلقائيًّا)
cd /c/s_lang/temp-brunch/<مهمة>
git add -A && git commit -m "وصف"

# 3) دفع + PR إلى dev (بعد اجتياز DoD)
git push -u origin agent/<مهمة>
gh pr create --base dev --head agent/<مهمة> --title "<عنوان>" --body "<وصف + قائمة الملفّات + نتائج runner>"
gh pr merge --merge

# 4) تنظيف بعد الدمج
cd /c/s_lang/s-programming-language
git worktree remove /c/s_lang/temp-brunch/<مهمة> && git branch -D agent/<مهمة>

التوقيع GPG (إلزاميّ على dev/graphic)

git config commit.gpgsign true
git config user.signingkey <KEY_ID>     # مفتاح GPG مُهيّأ

commit غير موقّع يُرفَض عند الدمج (required_signatures).

ممنوعات

❌ git push origin dev مباشرةً (محظور) · ❌ العمل في المجلد الأساسي على dev/graphic · ❌ commit غير موقّع · ❌ خلط مهمّتين في worktree واحد · ❌ نسيان تنظيف الworktree.

مرجع الحماية: .github/BRANCH_PROTECTION_POLICY.md وC:/s_lang/temp-brunch/README.md.


اقرأ بعده: معيار الإنجاز.

معيار الإنجاز (Definition of Done)

ماذا ستتعلّم: متى يكون تغييرك «منجَزًا» فعلًا — قائمة تحقّق إلزاميّة.

علّم التغيير منجَزًا فقط عند استيفاء كل بند:

الصحّة والطبقة

  • التغيير في الطبقة الصحيحة فقط — لا ترقيع في مكان الاستعمال (BF-09, BF-10).
  • بدأ من YAML إن كان مدفوعًا بالبيانات؛ لم تُحرَّر أي generated/ يدويًّا.
  • أُعيد التوليد، وYAML + المُولَّد متطابقان في نفس الـcommit.

التنفيذ المزدوج والاختبار

  • الدعم مضاف في المفسّر والمترجم (أو @skip_compiler موثّق بسبب صريح).
  • اختبار .ص جديد (إيجابيّ + سلبيّ) بصيغة @expected الصحيحة.
  • python tests/runner.py --level P0 (وقسم الميزة) يمرّ 100%.
  • python tests/runner.py --level P1 يمرّ قبل أي PR — لا تراجع (BF-29).
  • الثنائيّان مبنيّان في تهيئةٍ واحدة — tests/config.yaml يقرأ كليهما من build/bin/Debug/.
  • sad-build يبني بلا أخطاء، وsad-run يعمل بلا تراجع.

الجودة والتوافق

  • تعليقات مزدوجة اللغة على كل API عام (CW-08).
  • التوافق الخلفيّ محفوظ (لا تغيير معنى opcode/token/خطأ موجود — CW-24).
  • قائمة الملفّات محدَّثة بكل ما تغيّر (بما فيه المُولَّد).

تزامن الدليل (إن مسّ التغييرُ سلوكًا موثَّقًا)

  • رُوجِع الفصل المرتبط في دليل المطوّرين (المزامنة)، وثُبِّتت البصمات بـpython scripts/check_sync.py --update في dev-guide.

الفروع (عند العمل المحكوم)

  • العمل على فرع agent/* من dev (لا commit مباشر على dev/graphic).
  • كل الـcommits موقّعة GPG.
  • الدمج عبر PR إلى dev.

الحوكمة (إن مسّت _bmad-output/)

  • سطر إقرار السياسة مكتوب + تحديث status/ بدليل فعليّ (GR-01).

ممنوعات صريحة

❌ تحرير generated/ يدويًّا · ❌ تعطيل/تبسيط اختبار لتفادي فشله · ❌ ادّعاء نجاح اختبارات غير موجودة/غير ناجحة · ❌ دعم المفسّر فقط بلا @skip_compiler موثّق · ❌ اعتبار P0 كافيًا لـPR.


اقرأ بعده: نظام الحوكمة.

نظام الحوكمة (BMAD)

ماذا ستتعلّم: متى تنطبق الحوكمة، وما الواجب قبل لمس _bmad-output/.

متى تنطبق؟

عند أي تعديل/إضافة/قراءة في _bmad-output/ (سياسات، أنظمة، ستوريات، حالة، قرارات)، أو عند العمل ضمن ستوري محكوم. خارج ذلك، اتبع سير العمل وDoD.

الملفّات الإلزاميّة للقراءة (بالترتيب)

  1. السياسة الأمّ: _bmad-output/governance/1-policy/planning/PRD.md.
  2. إطار إدارة المشروع: …/PROJECT_MANAGEMENT_FRAMEWORK.md.
  3. آخر تقرير تحقّق (مصدر الحقيقة للحالة): …/1-policy/status/VERIFICATION_REPORT_<date>.md.
  4. السبرنت الحالي: …/sprints/SPRINT_CURRENT.md.
  5. عقد الكود: …/3-code-contract/planning/prd.md.

البنية الموحّدة (لكل نظام)

planning/ · epics/ · stories/ · sprints/ · status/ · decisions/ · README.md.

قواعد سلوكيّة صارمة

  • GR-01: لا ادّعاء نسب إنجاز بلا أدلة من الكود الفعليّ (grep/build/list).
  • GR-02: لا تَحذف ADRs — المُلغى يُعلَّم Superseded ويُربط بـsupersededBy.
  • GR-03: السبرنت لا ينتهي بلا RETRO.
  • GR-04: الملفّ الزائف يُعلَّم OUT-OF-DATE فورًا (لا حذف للمحتوى التاريخيّ).
  • GR-05: قبل نظام جديد، انسخ _TEMPLATE/ بنفس بنية الستة مجلدات.
  • GR-06: التواريخ من الجهاز فقط (Get-Date -Format "yyyy-MM-dd").

علامة الإقرار

في أوّل ردّ بعد بدء مهمّة تَمَسّ _bmad-output/، اكتب سطرًا صريحًا:

«قرأت السياسة في _bmad-output/governance/1-policy/؛ آخر تقرير تحقّق: VERIFICATION_REPORT_<YYYY-MM-DD>.md؛ السبرنت الحالي: <اسم>.»

التفاصيل الكاملة في مهارة sad-lang-dev (references/governance.md).


اقرأ بعده: مسرد المصطلحات.

مزامنة الدليل مع تطوّر اللغة (Freshness)

ماذا ستتعلّم: كيف يبقى هذا الدليل مطابقًا للكود مع تطوّر لغة ص — آليّة كشف الانجراف (drift)، بيان الربط، والعقد التعاقديّ على كل مساهمة.

الدليل مرجعٌ داخليّ، وأخطر ما يصيب مرجعًا أن «يكذب بثقة»: شرحٌ أنيق لكودٍ تغيّر. نُعالج هذا بثلاث طبقات: بيان ربط يَصِل كل فصل بمصادره، كاشف انجراف يقارن بصمات المصادر آليًّا، وعقد مساهمة يجعل تحديث الدليل جزءًا من تغيير اللغة لا أثرًا لاحقًا.

1) بيان الربط — sync/sources.yaml

لكلّ فصلٍ تقنيّ قائمةُ مصادره الحقيقيّة في مستودع اللغة:

chapters:
  - file: src/frontend/lexer.md
    sources:
      - { path: shared/lexer/src/lexer_core.cpp, lines: "100-1770" }  # نطاق فقط
      - shared/lexer/include/token.h                                  # ملف كامل
      - language-truth/keywords.yaml

للمصدر أربعةُ أنماطِ بصم، ولكلٍّ دعوًى يطابقها:

النمطالصيغةيرصدلا يرصد
مسارٌ كاملpath/to/fileأيّ تغيّرٍ في الملفّ/المجلّد—
نطاقُ أسطر{path, lines: "10-90"}تعفّنَ المنطقة الموثَّقةتعديلًا خارجها
أسماءُ المدخلات{path, names: true}ظهورَ مدخلٍ أو اختفاءَه أو تبدّلَ نوعهتعديلَ محتوًى داخل المجلّد
مصدرٌ داخليّself:pathتغيّرَ أدواتِ الدليل نفسِها (بلا شبكة، ويُقاس في الـPR)—

بصمةُ self: تُحسب على الأسطر مُطبَّعةً (\n)، فلا تنقلب بتبديل CRLF/LF بين ويندوز ولينكس — مقيسٌ لا مفترَض. وبصمةُ names الداخليّة تُشتقّ من git ls-tree لا من مسحِ القرص، فلا تدخلها مخرجاتُ البناء ولا .venv ولا ملفّاتُ خطوةِ الحارسِ نفسِها (قِيس: جذرُ مستودعِ اللغة ٤٣ مدخلًا في git و٧٠ على القرص).

وقاعدةٌ تسري على الأنماط كلِّها: العدمُ ليس قياسًا. مجلّدٌ فارغ · نطاقٌ خارجَ الملفّ · ردٌّ فارغٌ من gh · مجلّدٌ تجاوز سقفَ ١٠٠٠ مدخلٍ في contents API — كلُّها تُقرأ «متعذّرًا» يحمرّ، لا بصمةً تُثبَّت. لولا ذلك لأنتجت هذه الحالاتُ بصمةً واحدةً مشتركة (sha256:e3b0c442…)، فيحمرُّ الفحصُ مرّةً ثمّ يثبّتها --update، ويبقى المصدرُ أخضرَ إلى الأبد وهو غيرُ مقيسٍ أصلًا.

النطاق يقلّل الإنذارات الكاذبة في الملفّات الكبيرة (تعديلٌ خارج المنطقة الموثَّقة لا يُطلِق إنذارًا) ويرصد تعفّن المنطقة المقصودة تحديدًا.

نمط names هو الوحيد الذي يطابق دعوى «هذا المجلّد يحوي كذا وكذا» — وهي دعوى repo-map.md وoverview.md. المقيسُ يُبيّن لِمَ لا تصلح بصمةُ الشجرة (tree sha) لها: بين إيداعَين على dev تباعدا ٢٥٠ إيداعًا، انقلبت بصمةُ شجرة tools/ وبقيت أسماءُ مدخلاتها كما هي — أي إنذارٌ أسبوعيٌّ دائمٌ بلا واقعة. وفي المدّة نفسِها التقط النمطُ في stdlib/ ما يجب التقاطُه بالضبط: اختفاء async وaudio3d وcrypto وembedded وظهور جيسون.ص. ويُكتب path: "." لبصم جذر المستودع — فعودةُ vm/ أو ظهورُ جزءٍ جديدٍ تُنذر خريطةَ المستودع.

ونمط self: يبصم من هذا المستودع لا من مستودع اللغة، لأنّ فصلًا كهذا يوثّق check_sync.py وsync-check.yml أنفسَهما. وسببُه واقعة: أُصلح افتراضُ المرجع في الأداة، وبقي هذا الفصلُ يصف الافتراضَ المنقوض في اليوم نفسِه — بلا كاشف. الآن أيُّ تعديلٍ في الأداتين يُنذر الفصلَ الذي يصفهما.

الحقل covers_version يوثّق حالة اللغة التي رُوجِع الدليل تجاهها. ولمّا كان ref: dev — لا وسمَ إصدارٍ — فصيغتُه اليوم dev@<إيداع مختصر> (مثلًا dev@1138f5e1)، أدقُّ من رقم semver لأنّها تسمّي الإيداع الذي قِيست عنده البصمات لا إصدارًا مُتخيَّلًا. و--update يشتقّه من git ls-remote عند المرجع المقيس بدل نسخِه من البيان — فلا يصير عددًا منثورًا صادقًا بالمصادفة؛ و--set-version يتقدّم عليه حين تُثبِّت عند وسمِ إصدار. لا يقيّده مخطّطٌ ولا يقرؤه حارس؛ مستهلِكُه الوحيد نصُّ قضيّة sync-check.yml الذي يطبعه كما هو.

2) كاشف الانجراف — scripts/check_sync.py

يجلب بصمة (sha) كل مصدرٍ من المستودع الرئيسيّ عبر gh api (لا يلزم استنساخ)، ويقارنها بالبصمات المثبَّتة في sync/sources.lock.json:

الأمرالأثر
python scripts/check_sync.pyيفحص ويُبلّغ؛ يفشل (خروج 1) عند الانجراف
python scripts/check_sync.py --jsonتقرير JSON للأتمتة
python scripts/check_sync.py --updateيثبّت البصمات الحاليّة بعد مراجعة الدليل

عند تغيّر مصدر، يطبع الكاشف الفصول المتأثّرة (عكسيًّا من البيان)، فتعرف مباشرةً ما يحتاج مراجعة.

flowchart LR
  SRC["مصادر اللغة@dev"] -->|gh api sha| CHK["check_sync.py"]
  LOCK["sources.lock.json"] --> CHK
  CHK -->|تطابق| OK["✅ متزامن"]
  CHK -->|اختلاف| DRIFT["⚠️ انجراف → فصول متأثّرة"]

3) الأتمتة

.github/workflows/sync-check.yml يقيس الانجراف تجاه مرجع القفل نفسِه (sources.lock.json.ref — اليوم dev)، لأنّ البصمات أُخذت عنده، فقياسُها عند مرجعٍ سواه يقارن شيئًا بغيره. (كان الافتراضُ «آخر وسم إصدار»، فأعلن التقريرُ عند v1.0.0 حذفَ ٣٠ مسارًا وتعذّرَ ٤٧ — وكلّها قائمةٌ سليمةٌ على dev — فصار الإنذارُ ضجيجًا لا يُقرأ؛ القضيّة #1 شاهدُه.) ولقياس الدليل تجاه إصدارٍ منشور: أعِد البصم عند وسمِه (--ref vX --update --set-version X) فيصير هو مرجعَ القفل. يعمل:

  • عند إصدار لغة جديد (repository_dispatch: language-release يطلقه مستودع اللغة)،
  • أسبوعيًّا (شبكة أمان)، ويدويًّا (مع تحديد ref اختياريّ).

عند الانجراف يفتح/يحدّث قضيّةً واحدة «🔄 انجراف الدليل» تُلخّص المصادر المتغيّرة والفصول المتأثّرة وأمرَ التثبيت الصحيح (مع رفع النسخة إن كان إصدارًا).

4) الحُرّاس — ضدّ الكتم الصامت وتعفّن البيان

الكشف وحده لا يكفي؛ يلزم منعُ الالتفاف عليه. حارسان في CI (ci.yml) على كل PR:

  • حارس القفل (لا كتم صامت): يرفض تقدّم أيّ بصمةٍ في sources.lock.json ما لم يُعدَّل الفصل المرتبط بها في نفس الـPR. فلا يصير --update زرَّ إسكاتٍ بلا مراجعة. ويقيس الاتّجاهين: التقدّمَ والإسقاط. فإزالةُ مصدرٍ من البيان ثمّ --update كتمٌ صامتٌ من البابِ المقابل — يُقتَل الكاشفُ لفصولٍ كاملةٍ بلا مراجعةِ حرف. ولمّا كان المفتاحُ المُسقَط لا أثرَ له في البيان الحاليّ، تُقرأ فصولُه من بيان الأساس عبر --base-manifest؛ وإن لم يُمرَّر، احمرّ الحارسُ ولم يمرّ: الحارسُ الذي لا يجد ما يقيس به لا يقول «مرّ».
  • حارس البيان: يتحقّق (بلا شبكة) من وجود ملفّات الفصول، صحّة نطاقات الأسطر، وسلامةِ النمطين الجديدين (names وlines لا يجتمعان؛ ومصدرُ self: موجودٌ فعلًا)، وأنّ كلّ فصلٍ في SUMMARY تحت الأقسام السبعة frontend/ · backend/ · systems/ · sot/ · architecture/ · getting-started/ · contributing/ له مصادرُ. كانت المظلّةُ أربعةَ أقسامٍ فقط، فبقيت الثلاثةُ الأخيرة — وهي حاملةُ دعاوى الأوامرِ التي يُشغّلها القارئ — خارجها كلّيًّا حتّى تعفّنت. (المرجع: GUARDED_SECTIONS في scripts/check_sync.py.) صفحاتُ الجذر (introduction · glossary · status · SUMMARY) خارج المظلّة عمدًا: دعاواها عن الدليل لا عن اللغة. ومنها ما يُسجَّل طوعًا حين يحمل دعوًى مقيسة — كـglossary.md وأسماءِ الثنائيّات. ويقيس هذا الحارسُ كذلك تعفّنَ مصادر self: في نفس الـPR: هي بلا شبكة، فلا عذرَ لتأجيلها إلى الفحص الأسبوعيّ. فلو عُدِّل check_sync.py وحده دون تثبيتٍ ومراجعةِ هذا الفصل، احمرّ الـPR — لا مرّ أخضرَ وفصلُه يصفُ سلوكًا منقوضًا (وهي عينُ الواقعة التي وُلد منها النمط). وغيابُ القفل نفسِه خطأٌ صريح في --validate و--guard-lock كليهما: حذفُ ملفٍّ واحدٍ كان يُطفئ البوّابةَ صامتةً وخضراء. وحين يَنقص البيانُ فصلًا محروسًا، يُختَم السجلُّ بـ❌ لا بـ✅ — لأنّ مَن يمسح سجلَّ CI بعينه يقرأ آخرَ سطرٍ حكمًا.

5) العقد عبر المستودعين

العقد يجب أن يصل لمن يغيّر اللغة فعلًا (في مستودع اللغة، لا هنا):

  • workflow dev-guide-sync-reminder في مستودع اللغة يرصد — على كل PR — تقاطع الملفّات المعدَّلة مع المصادر المسجَّلة هنا، فيعلّق تذكيرًا بالفصول المتأثّرة.
  • عند نشر إصدار، يطلق المستودعُ repository_dispatch لتشغيل فحص المزامنة هنا فورًا.
  • وبندٌ في معيار الإنجاز: راجع الفصل المرتبط ثم ثبّت البصمات.

💡 القاعدة الذهبيّة: الكود هو الحقيقة؛ والدليل بصمةٌ متعقَّبة لها، لا ذاكرةٌ تتقادم. الكاشف يرصد الاختلاف، والحُرّاس يمنعان دفنه صامتًا، والعقد يوصِل المسؤوليّة لمصدرها.


اقرأ بعده: مسرد المصطلحات.

مسرد المصطلحات

المصطلحالمعنى
ASTالشجرة النحويّة المجرّدة — تمثيل البرنامج بعد التحليل النحوي (shared/ast/).
SIRSad Intermediate Representation — تمثيل وسيط بين AST وLLVM (compiler/src/frontend/).
SoTSingle Source of Truth — مصدر الحقيقة الموحّد (language-truth/).
language-truth/مجلد YAML يكتلج بيانات اللغة والقواعد؛ يُولَّد منه الكود والتوثيق.
codegenتوليد الكود — scripts/codegen/gen_*.py تقرأ YAML وتُنتج C++/توثيق.
مُولَّد (generated)ملفّات تحت */generated/ تُنتَج آليًّا — لا تُحرَّر يدويًّا، لكن متتبَّعة في git.
recursive descentالمحلل النزوليّ التعاوديّ — كل قاعدة نحويّة دالة parseXxx().
production ruleقاعدة إنتاج نحويّة في language-truth/grammar/ (gr.<area>.<name>).
maps_toحقل يربط قاعدة الإنتاج بدالة المحلل الفعليّة (جسر التتبُّع).
Visitorنمط الزائر لاستهلاك عقد AST (ASTVisitor).
Valueنوع القيم الموحّد وقت التشغيل (shared/types/include/value.h).
goroutineخيط خفيف للتزامن؛ يتواصل عبر قنوات (SadChannel).
التنفيذ المزدوجاشتراط أن تعمل الميزة في المفسّر والمترجم بنفس المخرَج (BF-08).
DoDDefinition of Done — معيار اعتبار التغيير منجَزًا.
worktreeفرع git في مجلد منفصل يشارك نفس المستودع (C:/s_lang/temp-brunch/).
CW-NN / BF-NN / GR-NNقواعد كتابة الكود / إصلاح الأخطاء / الحوكمة (مراجع معياريّة).
sad-buildمُترجِم لغة ص (AST → SIR → LLVM → تنفيذيّ). هدفُ CMake ومُخرَجُه sad-build.exe، ويقرأه الـrunner من build/bin/Debug/.
sad-runالمفسّر الشجريّ (sad-run.exe) — من build/bin/Debug/ كذلك: الثنائيّان في تهيئةٍ واحدة.
sadموزِّع الأوامر (hub) — يُشغّل الأدوات عمليّاتٍ فرعيّة، وليس المفسّر.
sadcاسمٌ متقاعد: لا هدفَ يُنتجه اليوم. المترجم هو sad-build.
tests/config.yamlمصدرُ مسارَي الثنائيَّين وtests_dir — لا تنسخ ثنائيًّا باسمٍ آخر لأجل الـrunner.

العودة للمقدّمة