مقدّمة الدليل
دليل مطوّري لغة ص
الأنظمة الداخلية — من المصدر إلى التنفيذ
كيف تعمل لغة ص من الداخل، وكيف تُسهم في تطويرها بثقة: معجمي → نحوي → AST → مفسّر/مترجم، فوق مصدر حقيقة موحّد.
لمن هذا الدليل؟ لمن يطوّر لغة ص نفسها (المفسّر، المترجم
sadc، الأنظمة الداخلية) — لا لمن يكتب برامج بها. إن كنت تكتب.صفهذا الدليل ليس لك.
ابدأ من هنا
فلسفة لغة ص الداخلية في سطور
- معماريّة طبقيّة صارمة:
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 | ✅ مكتمل | |
| الخلفيّة: VM | 🚧 قيد التوسعة | يحتاج تفصيل بنية البايت كود |
| الأنظمة (أنواع · أخطاء · مضمنة) | ✅ مكتمل | |
| المساهمة (سير العمل · DoD · الحوكمة) | ✅ مكتمل | |
| مزامنة الدليل (Freshness + كاشف الانجراف) | ✅ مكتمل | فحص آليّ أسبوعيّ |
خارطة الطريق (مقترَحة)
- توسعة VM: تنسيق البايت كود، حلقة التنفيذ، الربط بالمفسّر.
- مزامنة الدليل مع اللغة: بيان ربط + كاشف انجراف + فحص أسبوعيّ → Freshness.
- جسر آليّ لقواعد المحلل: تضمين/مزامنة
docs/parser_rule/_generated/داخل الدليل. - فصل الأدوات: LSP · المنسّق · مدير الحزم (pkg) · sadinfo.
- فصل stdlib: بنية المكتبة القياسية ووحداتها.
- فصل runtime/FFI: ABI المستقلّ وربط VM.
- أمثلة «دراسة حالة»: تتبّع ميزة كاملة عبر كل الطبقات (نهاية-لنهاية).
- ترجمة إنجليزية اختياريّة (i18n).
كيف تُسهم في فصل؟
أنشئ فرع agent/dg-<فصل> من main، طوّر الفصل (مع مخطّط Mermaid وروابط ملف:سطر)،
ابنِ mdbook build بلا أخطاء، ثم افتح PR. أضف الفصل الجديد إلى src/SUMMARY.md.
إعداد البيئة والبناء
ماذا ستتعلّم: كيف تجلب المستودع، تهيّئ الأدوات، وتبني المفسّر والمترجم.
المتطلبات
- C++17 ومُصرِّف حديث (MSVC على Windows، أو Clang/GCC).
- CMake ≥ 3.20.
- LLVM 18 — اختياريّ، للمترجم
sadcفقط (ENABLE_LLVM_BACKEND=ON). - Python 3 — لمولّدات الكود (
scripts/codegen/gen_*.py) وrunner.py. - Git + GPG — للمساهمة (الفروع المحميّة تشترط توقيع GPG).
الجلب
git clone https://github.com/sadlang/s-programming-language.git
cd s-programming-language
البناء (PowerShell على Windows)
cmake -S . -B build # تهيئة أولى
cmake --build build --config Debug --target sad # المفسّر (أسرع)
cmake --build build --config Release --target sadc # المترجم (LLVM)
cmake --build build --config Debug # كل شيء
⚠️ فخّان مهمّان (مذكوران في تعليمات المشروع):
- هدف
sadcقد يُنتجsad-build.exe— انسخه إلىsadc.exeقبلrunner.pyوإلا فنتائج المترجم بائتة.- الـrunner يقرأ مفسّر Debug ومترجم Release — أعد بناء التهيئتين معًا.
التشغيل
.\build\bin\Debug\sad.exe examples\test_simple.ص # تفسير
.\build\bin\Release\sadc.exe examples\test_simple.ص # ترجمة لملف تنفيذيّ
الاختبارات
الاختبارات معطّلة افتراضيًّا؛ فعّلها بـ-DBUILD_TESTS=ON. للتنفيذ المزدوج (مفسّر + مترجم) استخدم runner.py:
python runner.py --level P0 # الحزمة الأساسيّة
python runner.py --level P1 # المطلوبة قبل أي PR (لا تراجع)
توليد الكود من مصدر الحقيقة
بعد تعديل أي YAML في language-truth/:
python scripts/codegen/gen_keywords.py # مثال: الكلمات المفتاحيّة
python scripts/codegen/gen_parser_grammar_docs.py # توثيق القواعد
راجع توليد الكود.
اقرأ بعده: أوّل مساهمة.
أوّل مساهمة (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 scripts/codegen/gen_all_builtins_yaml.py # أو المولّد المعنيّ
4) نفّذ في الطبقة الصحيحة
- المفسّر:
interpreter/src/builtins/builtin_*.cpp. - المترجم:
compiler/src/backend/llvm/builders/builtins/*.cpp.
5) اكتب اختبار .ص (إيجابيّ + سلبيّ)
ملف تحت tests/ بصيغة @expected الصحيحة.
6) ابنِ وشغّل (تنفيذ مزدوج)
cmake --build build --config Debug --target sad
cmake --build build --config Release --target sadc
python 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-جذر
✅ راجع معيار الإنجاز قبل إعلان الانتهاء.
اقرأ بعده: خريطة المستودع.
خريطة المستودع
ماذا ستتعلّم: أين يعيش كل شيء في
sadlang/s-programming-language.
s-programming-language/
├── 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
├── vm/ ← الآلة الافتراضية (بايت كود مرتبط بالمفسّر)
├── stdlib/ ← المكتبة القياسية (core/io/math/string/network/graphics)
├── runtime/ ← ABI/FFI المستقلّ + ربط VM
├── tools/ ← sadinfo · lsp · formatter · pkg · repl · compiler(sadc CLI)
├── 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 المُولَّد)
├── tests/ ← اختبارات .ص + rules_matrix + comprehensive
├── _bmad-output/ ← نظام الحوكمة (سياسات/ستوريات/قرارات)
└── .github/skills/ ← مهارات الوكلاء (sad-lang-dev …)
ملفّات تُقرأ أولًا
| الملف | لماذا |
|---|---|
shared/lexer/include/token.h | أنواع الرموز وPosition |
shared/lexer/src/lexer_keywords.cpp | تسجيل الكلمات المحجوزة (40) |
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 | مصدر الحقيقة |
اقرأ بعده: نظرة عامّة على الطبقات.
نظرة عامّة على الطبقات
ماذا ستتعلّم: الطبقات الكبرى للغة ص ومسؤوليّة كلٍّ منها والحدود بينها.
المكوّنات
| المكوّن | المجلد | الدور |
|---|---|---|
| النواة المشتركة | shared/ | معجمي، نحوي، AST، نظام الأنواع Value، نظام الأخطاء |
| المفسّر | interpreter/ | مفسّر شجريّ؛ InterpreterCore يدير المتغيّرات والدوال والنطاقات والتقييم |
| المترجم | compiler/ | AST → SIR → LLVM IR → ملفّ تنفيذيّ (SIR يدعم تعليمات ملكية) |
| الآلة الافتراضية | vm/ | بايت كود مرتبط مباشرةً بالمفسّر |
| المكتبة القياسية | stdlib/ | وحدات عربية: core/io/math/string/network/graphics |
| الأدوات | tools/ | lsp · formatter · pkg · repl · sadc CLI · sadinfo |
| مصدر الحقيقة | 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 / VM<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["نظام الأخطاء"]
مبدأان عابران للطبقات
- مصدر الحقيقة أولًا: أي بيان لغويّ (كلمة/عامل/نوع/خطأ/دالة مضمنة) يبدأ من
language-truth/ثم يُولَّد. راجع الجزء الثالث. - التنفيذ المزدوج: كل ميزة تعمل في المفسّر والمترجم؛ إن عملت في أحدهما فقط فالمشكلة في 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ب — الترجمة (sadc)
SIRBuilder: AST → SIR (تمثيل وسيط بتعليمات ملكية،sir_types.h).SIROptimizer: تمريرات على SIR.LLVMCodeGen: SIR → LLVM IR، ثم LLVM يُنتج كائنًا يُربَط لملفّ تنفيذيّ.
لماذا SIR وسيط؟ يفصل دلالة الملكية/الأنواع عن تفاصيل LLVM، ويسهّل التحسين والتشخيص (
--emit-llvm, SIR dumps).
نقاط تشخيص مفيدة
- اختلاف سلوك المفسّر عن المترجم ⇒ المشكلة في
SIRBuilderأوLLVMCodeGen(BF-08). - ولّد LLVM IR بـ
--emit-llvmوافحص: الكتلة الأولى، تطابق أنواع الحقول، ترتيب التعليمات،getelementptr.
اقرأ بعده: الأنظمة المتشابكة.
الأنظمة المتشابكة
ماذا ستتعلّم: لماذا لا يوجد «تغيير معزول» في لغة ص، وأيّ ملفّات يَمَسّها كل نوع تغيير.
كل ميزة تعبر عدّة أنظمة: مصدر الحقيقة + المُولَّد + المعجمي/النحوي + المفسّر + المترجم + الأخطاء + التوثيق + الاختبارات. تجاهل أحدها يكسر CI أو تجربة المستخدم.
جدول الأثر (File List) حسب التغيير
| التغيير | الملفّات المتأثّرة عادةً |
|---|---|
| كلمة مفتاحيّة | language-truth/keywords.yaml → shared/lexer/generated/keywords_generated.{h,cpp} (مُولَّد) + shared/parser/src/<dir>/ + shared/ast/include/ + interpreter/src/visitors/ + compiler/src/frontend/ (+opcode في sir_types.h) + اختبار .ص |
| دالة مضمنة | language-truth/builtins/<domain>.yaml (+_index.yaml) → مُولَّد builtin_registry_generated.h + interpreter/src/builtins/ + compiler/src/backend/llvm/builders/builtins/ + اختبار |
| رمز خطأ | 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/ |
| opcode SIR | compiler/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 · sadinfo · pkg"]
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/ · _notation/ · learning/ · stdlib/ · tests/
أمثلة على الصيغة
كلمة مفتاحيّة (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). هذا يمنع الانجراف ويضمن صحّة البنية آليًّا.
تعديله بأمان (الإجراء)
- عدّل YAML المصدر فقط (لا
generated/). - أعد التوليد بالمولّد المعنيّ (→ توليد الكود).
- تأكّد من تطابق YAML + المُولَّد، وضمّنهما في نفس الـcommit.
- حدّث الطبقات المستهلِكة (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/)
| المولّد | المصدر → الناتج |
|---|---|
gen_keywords.py | keywords.yaml → keywords_generated.{h,cpp} |
gen_types.py | types.yaml → كود الأنواع المُولَّد |
gen_builtins_registry.py / gen_all_builtins_yaml.py | builtins/ → builtin_registry_generated.h |
gen_error_messages.py / gen_sadinfo_errors.py | errors/ → رسائل/تشخيص مُولَّد |
gen_parser_grammar_docs.py | grammar/*.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.yaml | gr.program.* | البرنامج/التصريح/الجملة/الكتلة |
10_statements.yaml | gr.stmt.* | إذا/بينما/لكل/طابق/حالة/حاول/… |
20_declarations.yaml | gr.decl.* | متغيّر/دالة/معاملات/استيراد/تصدير/خارجي |
30_oop.yaml | gr.oop.* | صنف/بنية/تعداد/سمة/تنفيذ/امتداد/أعضاء |
40_expressions.yaml | gr.expr.* | سلسلة الأسبقية الكاملة + لامدا/f-string |
50_patterns.yaml | gr.pattern.* | أنماط المطابقة |
60_advanced.yaml | gr.adv.* | أنواع/قوالب/عمر/تزامن/استيعاب/ماكرو/FFI/واجهة |
70_lexical.yaml | gr.lex.* | الطرفيات (جسر للمعجمي) |
شكل قاعدة الإنتاج
كل قاعدة (مخطّط _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. الأدوات الأساسيّة:
peek()— المحرف الحاليّ دون استهلاك.peekNext(offset)— نظر مسبق.advance()— يستهلك ويتقدّم (يحدّث السطر/العمود).skipWhitespace()— يتخطّى الفراغات.
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 | عدد → scanNumber | L1646 |
ح" (نصّ خام) | 0xD8 0xAD + " | STRING_RAW | L1675 |
م" / ص" (نصّ منسَّق) | 0xD9 0x85 / 0xD8 0xB5 + " | STRING_FSTRING | L1694 |
× ضرب | 0xC3 0x97 | OP_MULTIPLY | L1726 |
، فاصلة | 0xD8 0x8C | ARABIC_COMMA | L1743 |
؛ منقوطة | 0xD8 0x9B | ARABIC_SEMICOLON | L1753 |
؟ استفهام | 0xD8 0x9F | QUESTION (و؟.→QUESTION_DOT) | L1760 |
💡 لهذا تعمل
س، صوأ ؟ ب : جو٣ × ٤كما لو كُتبت باللاتينيّة — المعجمي يطبّع المحارف العربية إلى رموز قياسيّة.
الماسحات (Scanners)
| الدالة | الموقع | يمسح |
|---|---|---|
scanNumber | L323 | صحيح/عشريّ + 0x/0b/0o؛ يترك .. لماسح العوامل (المدى) |
scanString | L754 | "..." مع هروب \n \t \\ \" \r \b \f \v \0 \u \U \x |
scanRawString | L998 | r"..."/ح"..." بلا معالجة هروب |
scanFString | L1054 | f"...{تعبير}..." |
scanDocComment | L1167 | ## / #** **# (يُرفَق بأوّل تصريح) |
scanIdentifier | L1250 | مُعرّف UTF-8 (يتوقّف قبل ،/؛) |
scanOperator | L1347 | العوامل (switch على `+ - * / % = < > ! . ^ |
التعليقات
حلقة التخطّي في بداية nextToken تعالج: # سطر · #* … *# كتلة · ## توثيق سطر ·
#** … **# توثيق كتلة (التعليق غير المغلق يرمي خطأً). تعليقات ## تُلتقَط وتُرفَق لاحقًا
بأوّل تصريح (انظر تهيئة المحلل النحوي).
الكلمات والرموز
- محجوزة (40): مسجّلة في
lexer_keywords.cpp(KeywordTable::initialize())، مصدرهاlanguage-truth/keywords.yaml. - سياقيّة: لها
KEYWORD_*فيtoken.hلكنها لا تُسجَّل كمحجوزة؛ يميّزها المحلل النحوي بالسياق. - أنواع مدمجة كمُعرّفات:
رقم/نص/… ليست محجوزة.
إضافة كلمة مفتاحيّة (مختصر)
- أضف
KEYWORD_FOOإلىtoken.h. 2. محجوزة: سجّلها فيkeywords.yamlثم أعد توليدkeywords_generated. سياقيّة: لا تسجّلها، واستخدم التحقّق المزدوج في المحلل النحوي. - أضف القاعدة + عقدة 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() — L124"] -->|while !isAtEnd| D["parseDeclaration() — L364"] D -->|كلمة تصريح| DECL["دالة/صنف/متغيّر/تعداد/استيراد/…"] D -->|@| DIR["tryParseDirective() — L1479"] D -->|غير ذلك| S["parseStatement() — L1173"] S -->|كلمة جملة| CF["إذا/بينما/لكل/طابق/حاول/…"] S -->|افتراضي| ES["parseExpressionStmt() → parseExpression()"]
parseProgram()— يكرّرparseDeclarationحتى نهاية الملف، مع حماية من الحلقة اللانهائيّة (MAX_STUCK_ITERATIONS=3) وعدّ الدوال الرئيسيّة.parseDeclaration()— موزّع التصريحات (~33 فرعًا): توجيهات@، مُزخرِفات، سمات[[..]]، استورد/من/صدّر/خارجي، دالة/مولد/async، قالب/فضاء، صنف/سمة/نفّذ/امتداد/ماكرو/نوع/عقد، متغير/ثابت، تصريح ببدء النوع/الصنف، اختبر/حالة/اعرض، تعداد/بنية، وأخيرًاparseStatement.parseStatement()— موزّع الجمل: إذا/بينما/لكل/حالة/طابق/ارجع/أنتج/باستخدام/أجّل/أطلق/اختر/توقف/استمر/{/حاول/ارمي، وافتراضيًّا جملة تعبير.
سلسلة أسبقيّة التعابير
من الأدنى ربطًا (أعلى المستوى) إلى الأعلى — كل دالة تستدعي الأعلى أسبقيّةً ثم تحلّق على عاملها:
| المستوى | الدالة | العامل |
|---|---|---|
| 1 | parsePipeline | |> |
| 2 | parseAssignment | = := += -= *= /= //= %= |
| 3 | parseTernary | ? : |
| 4 | parseNullCoalesce | ?? |
| 5–10 | parseLogicalOr/And · parseBitwiseOr/Xor/And | || && | ^ & |
| 11–12 | parseEquality · parseComparison | == != < <= > >= في |
| 13 | parseRange | .. |
| 14–15 | parseTerm · parseFactor | + - << >> · * / // % |
| 16 | parseUnary | ! - ~ ++ -- &(استعارة) |
| 17 | parsePower | ** (يمينيّ) |
| 18 | parsePostfix | () . ?. [] ++ -- !() |
| 19 | parsePrimary | القيم الذريّة |
هذا الترتيب يُعرّف أسبقيّة العوامل — ومصدره language-truth/operators.yaml.
⭐ الكلمات السياقيّة (التحقّق المزدوج)
كثير من «الكلمات» (سمة/نفّذ/امتداد/ماكرو/حالة/أجّل/أطلق/اختر) ليست محجوزة؛ يتعرّف عليها المحلل بنمطٍ مزدوج + نظر مسبق:
// قاعدة سياقيّة: تُعامَل تصريحًا فقط إن تلاها مُعرّف (وإلا مُعرّف عاديّ)
if (match(TT::KEYWORD_TRAIT) ||
(check(TT::IDENTIFIER) && peekNext().getType() == TT::IDENTIFIER
&& current_.getValue() == "سمة" && (advance(), true))) {
return parseTraitDecl();
}
مثال على فضّ الغموض: أجّل تُعامَل جملةً إلّا إن تلاها =/+=/. (فتصير مُعرّفًا).
هذا يسمح باستعمال هذه الكلمات أسماءَ متغيّرات خارج سياقها.
السكر النحوي (Desugaring)
يُحوِّل المحلل بعض الصياغة وقت التحليل:
- الأنبوب:
أ |> د←د(أ)·أ |> د(ب)←د(أ، ب)(parsePipeline L70). - الإسناد المركّب:
س += ص←س = س + ص(يعيد بناء طرف القراءة للحقول/الفهارس المتداخلة). - القيمة المطلقة:
|تعبير|←abs(تعبير).
التعافي من الأخطاء
عند الخطأ، يُستدعى synchronize() للقفز إلى نقطة مزامنة (بينما/حاول/امسك/صنف/…)
المُسجَّلة في recoverySystem_، وتُجمَّع التشخيصات في ErrorManager. → نظام الأخطاء.
التوثيق الكامل للقواعد
قواعد المحلل موثّقة كمصدر موحّد (BNF + مخطّطات + مسار حتى AST) — راجع
قواعد المحلل SoT وdocs/parser_rule/_generated/ في المستودع الرئيسيّ.
اقرأ بعده: شجرة AST.
شجرة AST
ماذا ستتعلّم: كيف تُمثَّل البرامج كشجرة، ونمط الزائر (Visitor) الذي يستهلكها.
الدور
shared/ast/ يُعرّف عقد الشجرة المجرّدة التي يبنيها المحلل النحوي ويستهلكها المفسّر
والمترجم. القاعدة ASTNode، ومنها Statement (جمل) وExpression (تعابير).
أنواع العقد (أمثلة)
- جمل:
IfStmt,WhileStmt,ForRangeStmt,ReturnStmt,BlockStmt,TryStmt,SwitchStmt,MatchStmt,ExprStmt، تصريحات (FunctionDecl,ClassDecl,VarDeclStmt,EnumDecl,StructDecl, …). - تعابير:
BinaryExpr,UnaryExpr,CallExpr,MethodCallExpr,MemberExpr,IndexExpr,LiteralExpr,VariableExpr,LambdaExpr,TernaryExpr,RangeExpr,ArrayExpr,MapExpr,NewExpr,ThisExpr/SuperExpr,AwaitExpr، …
العقدة المُنتَجة لكل قاعدة نحويّة مُوثَّقة في حقل
ast_nodeبمصدر القواعد. → grammar SoT.
نمط الزائر (Visitor)
العقد تُستهلَك عبر ASTVisitor (في shared/ast/include/ast_visitor.h): كل مستهلِك
(مفسّر، SIRBuilder، منسّق، LSP) يطبّق الزائر. هذا يحقّق مبدأ المفتوح/المغلق:
أضف مستهلِكًا جديدًا دون تعديل العقد.
flowchart LR
AST["عقد AST"] --> V{"ASTVisitor"}
V --> INT["InterpreterVisitor (تقييم)"]
V --> SIR["SIRBuilder (→ SIR)"]
V --> FMT["Formatter / LSP"]
إضافة عقدة AST
- عرّف الصنف في
shared/ast/include/(ورث منStatement/Expression). - أضف تصريحها في
ast_visitor.hودالة الزيارة. - نفّذ الزيارة في المفسّر (
interpreter/include/visitors/) وcompiler/src/frontend/. - التوافق الخلفيّ: إضافة عقدة مسموحة — تغيير معنى عقدة موجودة ممنوع (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لكنّها نموذج ميت غير مُترجَم — لا إحالة إليه في CMake؛ يُنظَّف في طور لاحق.)
2) عقد AST (لم تتغيّر)
في shared/ast/include/expressions.h
(لا comprehension_nodes.h الميت):
| العقدة | الحقول |
|---|---|
ListComprehensionExpr | element، variable، valueVariable، iterable، condition |
SetComprehensionExpr | expression، variable، valueVariable، iterable، condition |
DictComprehensionExpr | key، 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 المستقيمة؛ ويشارك الملفّbuildExprGenerator.
نقاط تنفيذ دقيقة في بانِي المجموعة:
- علَم «موجود» وعدّاد المسح الداخليّ يُخصَّصان بـ
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) عدديّة (كبقيّة بنية الاستيعابات عدديّة النوع)، فالمجموعات الصحيحة الإزالة للأعداد؛ مجموعات النصوص/العشريّ تتباعد صامتًا حتى يُعمَّم النوع في الاستيعابات الثلاثة.
فخّ متبقٍّ:
buildExprGeneratorفي نفس الملفّ ما زال بالنهج المكسور (CALL __sad_len/__sad_array_pushغير المعرَّفة) — سينهار الربط متى استُخدم مولِّد؛ خارج نطاق م1ب، يُتابَع بنفس نمط الإصلاح.
فكّ الزوج والتكرار على الخرائط (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. الإصلاح شقّان:
- تمرير نوع العنصر: البانون يمرّرون
elemExprResult.typeإلىelementTypeلنتيجة القائمة/المجموعة ⇒ الوصول المفهرَس النصّيّ يعمل (كالمصفوفة الحرفيّة). - طبع كامل موسوم: أُضيف
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يُملأ بالفاصلة ويبقى فارغًا بدونها، والحالة السلبيّة «فاصلة بلا اسم»)؛ وتضيف مجلّدات السلوك حالات فكّ زوج بإخراج صحيح أو فهرسة (لتفادي عدم تحديد الترتيب) — قائمة ومجموعة وقاموس. القِيَم النصّيّة تُختبَر عبر الفهرسة والطبع الكامل بعد إصلاح الإخراج النصّيّ (القسم د أعلاه).
انظر أيضًا
- المحلل النحوي (Parser) · شجرة AST · التمثيل الوسيط SIR
- قواعد المحلل كمصدر موحّد — قواعد
gr.adv.*_comprehensionفيlanguage-truth/grammar/.
المفسّر الشجري (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["المدراء (Data::*)"]
SM["ScopeManager — النطاقات"]
VM["VariableManager — المتغيّرات"]
FM["FunctionManager — الدوال"]
CM["ClassManager — الأصناف"]
OM["ObjectManager — الكائنات"]
OW["OwnershipManager — الملكيّة"]
end
SE --> MGR
EE --> MGR
| المدير | يدير |
|---|---|
ScopeManager | سلسلة النطاقات (دخول/خروج، البحث الهرميّ) |
VariableManager | ربط الأسماء بالقيم داخل النطاق |
FunctionManager | تعريفات الدوال (مشترَكٌ للقراءة فقط بين الخيوط) |
ClassManager / ObjectManager | الأصناف ومثيلاتها (OOP) |
OwnershipManager | تتبّع الملكيّة/الاستعارة — يربط نظام الذاكرة |
② نمط الزائر بحامل النتيجة
كلّ عقدة 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جديدة في الزائر.
الزائر مقسَّمٌ على ملفّات حسب العائلة (تخفيفًا للترجمة): expression_evaluator_binary_ops،
..._calls_*، ..._members_*، ..._oop_*، ..._overloads، ..._ui. ومثلها للجمل:
statement_executor، ..._control، ..._control_exceptions، ..._functions*،
..._modules، ..._oop*.
③ دورة التقييم الكاملة
flowchart TD
P["execute(program: جمل)"] --> 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 داخليّ.
ملاحظات للمطوّر
- القيم كلّها
Value(std::variantعلىValueType)؛OBJECTيحملshared_ptr<ObjectInstance>⇒ تمرير الكائنات بالمرجع → نظام الأنواع. - شغّل
.صمباشرةً بـsad.exeلاختبارٍ سريع — لا حاجة لخطوة ترجمة. - إن طابق المفسّر وخالف المترجم ⇒ المشكلة في SIR/LLVM لا في الدلالة (BF‑08).
- لإضافة عقدة AST جديدة: أضف
visit<Node>إلىBaseASTVisitorونفّذها في الزائرَين.
اقرأ بعده: التمثيل الوسيط SIR.
التمثيل الوسيط SIR
ماذا ستتعلّم: ما هو SIR، ولماذا طبقة وسيطة بين AST وLLVM، وأين تعليماته.
ما هو SIR؟
SIR (Sad Intermediate Representation) تمثيل وسيط يبنيه SIRBuilder من AST قبل
توليد LLVM IR. يفصل دلالة لغة ص (الملكية، الأنواع، تدفّق التحكّم) عن تفاصيل LLVM.
لماذا طبقة وسيطة؟
- تحسين مستقلّ:
SIROptimizerيطبّق تمريرات على SIR. - تشخيص أسهل: SIR dumps أوضح من LLVM IR الخام.
- عزل: تغيير الواجهة الخلفيّة (LLVM) لا يَمَسّ منطق بناء SIR.
- دلالة الملكية: SIR يدعم تعليمات ملكية (ownership) خاصّة بلغة ص.
الملفّات
| الملف | المحتوى |
|---|---|
compiler/include/frontend/sir_types.h | تعداد SIROpcode + أنواع SIR (الملكية) |
compiler/src/frontend/ | SIRBuilder (AST → SIR) + SIROptimizer |
الموضع في الخطّ
flowchart LR AST --> SB["SIRBuilder"] --> SIR["وحدة SIR"] SIR --> SO["SIROptimizer"] --> SIR2["SIR محسَّن"] SIR2 --> CG["LLVMCodeGen"] --> IR["LLVM IR"]
إضافة opcode (الإجراء)
- أضف
SIROpcode::NEW_OPإلىsir_types.h(إضافة فقط — لا تغيّر معنى موجود، CW-24). - أنتجه في
SIRBuilderمن العقدة المعنيّة. - ترجمه في
compiler/src/backend/llvm/. - اختبار مزدوج (مفسّر + مترجم بنفس المخرَج).
قواعد دقيقة
- ترتيب الحقول: يُحدَّد في
SIRBuilder(حيث تُبنى البنية) — أصلِح أخطاء الترتيب هنا، لا في codegen (BF-10). - لا اقتطاع صامت: كل تحويل نوع يمرّ عبر دالة مُسمّاة (CW-14).
اقرأ بعده: توليد LLVM.
توليد LLVM (المترجم sadc)
ماذا ستتعلّم: كيف يحوّل
LLVMCodeGenتمثيل SIR إلى LLVM IR ثم ملفّ تنفيذيّ أصليّ.
الدور
compiler/src/backend/llvm/ يأخذ SIR المحسَّن ويُنتج LLVM IR، ثم يستخدم LLVM لتوليد
كائن ثم ربطه في ملفّ تنفيذيّ. هذا قلب المترجم sadc.
الاعتماد على LLVM
- LLVM 18 — مفعّل بشرط
ENABLE_LLVM_BACKEND=ON؛ راجعcmake/llvm.cmakeو#ifdef HAS_LLVM. - واجهة
sadcتعيش فيtools/compiler/(compiler_driver_*.cpp).
البناة (builders)
compiler/src/backend/llvm/builders/ — مولّدات لكل بنية (تعابير، تحكّم، دوال،
كائنات، الدوال المضمنة في builders/builtins/).
مخطّط
flowchart LR SIR["SIR محسَّن"] --> CG["LLVMCodeGen + builders"] CG --> IR["LLVM IR"] IR --> OBJ["كائن (LLVM)"] OBJ --> LINK["ربط"] LINK --> EXE["تنفيذيّ أصليّ"]
التشخيص (BF-07)
عند خطأ في المترجم، ولّد IR بـ--emit-llvm وافحص:
- هل الكتلة الأولى (entry block) صحيحة؟
- هل أنواع الحقول والمعاملات متّسقة؟
- هل ترتيب التعليمات يحترم تبعيّات البيانات؟
- هل
getelementptrيستخدم الفهارس الصحيحة؟
قواعد codegen مهمّة
- أسماء كتل واصفة:
precond_fail,loop_body,then_block— لاbb1/label2(CW-11). - الترتيب يكسر الدلالة: ترتيب أبجديّ لأسماء الكتل يكسر ترتيب التنفيذ — استخدم
std::vectorمحافظًا على ترتيب الإدراج (CW-27). - أصلِح في الطبقة الصحيحة: خطأ تحويل أنواع يُصلَح في codegen؛ خطأ ترتيب حقول في
SIRBuilder(BF-10).
اقرأ بعده: الآلة الافتراضية.
الآلة الافتراضية (VM)
ماذا ستتعلّم: دور الـVM في لغة ص وعلاقتها بالمفسّر.
الدور
vm/ آلة بايت كود مرتبطة مباشرةً بالمفسّر — مسار تنفيذ بديل يجمع بين سرعة أعلى من
المشي الشجريّ الصرف ومرونة التفسير، دون المرور بـLLVM/الترجمة الكاملة.
الموضع
flowchart LR AST --> INT["InterpreterCore"] INT <-->|ربط مباشر| VM["VM (بايت كود)"] AST -.->|مسار منفصل| SIR["SIR → LLVM (sadc)"]
وقت التشغيل والربط
runtime/يوفّر ABI/FFI مستقلّ (freestanding) + ربط VM.- القنوات/الخيوط الخفيفة (goroutines) آمنة للتزامن عبر mutex داخليّ في
SadChannel.
هذه الطبقة أقلّ سطحًا للمساهمات الجديدة من المفسّر/المترجم؛ ابدأ منهما عادةً. هذا الفصل قيد التوسعة — ساهم بتفاصيل بنية البايت كود إن عملت عليها.
اقرأ بعده: نظام الأنواع.
نظام الذاكرة (ذاكرة ص الذكية)
ماذا ستتعلّم: كيف تُدير لغة ص الذاكرة عبر نظامٍ مزدوج — جامع قمامة (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["وضع التطوير (--gc)<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;
| المحور | التعداد | القيم |
|---|---|---|
| الوضع | MemoryMode | Development · Production · Hybrid (مهجور) · Auto |
| الجمع | GCStrategy | None · ReferenceCounting · AtomicReferenceCounting · Tracing · Incremental |
| الملكيّة | OwnershipMode | Disabled · Warnings · Strict · UltraStrict |
⚠️
Hybridمهجور: يُعامَل كـGCOnlyعند مصادفته. لا تُصمِّم على أساسه.
الإعدادات المجمَّعة: MemoryModeSettings
البنية MemoryModeSettings تربط المحاور الثلاثة مع خياراتٍ إضافيّة (enableCycleDetection،
gcMemoryLimitMB = 256، teacherMode، …)، وتُقدَّم عبر إعداداتٍ مسبقة جاهزة:
| الإعداد المسبق | mode | gcStrategy | ownershipMode | اقتراحات | كشف دورات | مُعلِّم |
|---|---|---|---|---|---|---|
gcDefaults() (--gc) | Development | Tracing | Disabled | ✗ | ✓ | ✗ |
developmentDefaults() | Development | ReferenceCounting | Warnings | ✓ | ✓ | ✓ |
productionDefaults() (--إنتاج) | Production | None | UltraStrict | ✗ | ✗ | ✗ |
learningDefaults() (--تعلم) | Development | ReferenceCounting | Warnings | ✓ | ✓ | ✓ |
kernelDefaults() (--نواة) | Production | None | UltraStrict | ✗ | ✗ | حدّ=0 |
📌 النواة (
no_std): عند#![بلا_مكتبة_قياسية]يُفرَضkernelDefaults— لا GC إطلاقًا (gcMemoryLimitMB = 0)، ملكيّةUltraStrict، بلا اقتراحات ولا كشف دورات. ملكيّةٌ صرفة كما يليق ببرمجة الأنظمة.
من العلَم إلى السلوك: MemoryModeFlag
MemoryModeFlag::parse() يقرأ argv ويبني MemoryModeSettings.
الأعلام مسجَّلة في flagHandlers_ (initializeFlags())، ولكلّ علَمٍ عربيٍّ مرادفاتٌ إنجليزيّة ومختصرة:
flowchart TD
ARGV["argv[] / متغيّرات البيئة / ملف التهيئة"] --> PARSE["MemoryModeFlag::parse()"]
PARSE --> H{"تصنيف العلَم"}
H -->|"--إنتاج / --production / -p"| PROD["productionDefaults"]
H -->|"--gc / --dev*(مهجور)"| GCD["gcDefaults"]
H -->|"--تعلم / --learn / -l"| LRN["learningDefaults"]
H -->|"--نواة / --no-std / --kernel"| KRN["kernelDefaults"]
H -->|"--تلقائي / --auto / -a"| AUTO["اكتشاف بالسياق"]
H -->|"--ملكية= / --gc-strategy= / --حد_ذاكرة="| TUNE["ضبطٌ دقيقٌ فوق الإعداد المسبق"]
PROD & GCD & LRN & KRN & AUTO & TUNE --> OUT["MemoryModeSettings ← FlagParseResult"]
جدول الأعلام الرئيسيّة (عربيّ ← مرادفات):
| العربيّ | المرادفات | الأثر |
|---|---|---|
--إنتاج | --production · --prod · --release · -p | ملكيّة صارمة، بلا GC |
--gc | --dev، --development، --تطوير، -d | GC (يحلّ محلّ --dev المهجور) |
--تعلم | --learn · --learning · -l | GC + تحذيرات + رسائل تعليميّة |
--نواة | --no-std · --kernel · --freestanding · --بلا-مكتبة-قياسية | ملكيّة صرفة no_std |
--ملكية= | --ownership= | يضبط OwnershipMode يدويًّا |
--gc-strategy= | — | يختار GCStrategy |
--حد_ذاكرة= | --gc-memory-limit= | حدّ ذاكرة الـGC بالميغابايت |
🔁
--dev/--تطوير/--mixedتُحوَّل جميعها إلى--gc(تنبيهُ إهجار) — انظر جدول الإهجار فيmemory_mode_flag.cpp.
ترتيب الأولويّة في حسم الإعداد
الإعداد النهائيّ يُحسَم بأولويّةٍ تصاعديّة (الأخصّ يَغلب):
flowchart LR D["الافتراضيّ (Auto)"] --> E["ملف التهيئة<br/>readConfigFile()"] E --> F["متغيّرات البيئة<br/>applyEnvironmentSettings()"] F --> G["سمة الملف #![…]"] G --> H["علَم سطر الأوامر<br/>(الأعلى أولويّة)"]
أين يَظهر هذا في خطّ الأنابيب؟
- المفسّر (وضع GC الافتراضيّ): القيم الكائنيّة تُحمَل عبر
shared_ptr<ObjectInstance>(عدّ مراجع ضمنيّ) — انظر نظام الأنواع. - المترجم (وضع الإنتاج): يولّد تخصيص/تحرير الذاكرة مباشرةً وفق الملكيّة عبر
memory_codegen— انظر توليد LLVM. no_std: المخصِّصات الحرّةsad_allocator.hوsad_bump_allocator.h.
🧭 القاعدة الذهبيّة: الوضع سياسة، لا بنية. الكود لا يتغيّر بين التطوير والإنتاج؛ يتغيّر
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 (52 قيمة) —
لا يُحرَّر يدويًّا. أيّ نوعٍ جديد يُضاف إلى الكتالوج ثم يُعاد التوليد:
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 · Byte · 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) | الأوّليّات (عدد، عشريّ، منطقيّ، نصّ، بايت، حرف، فراغ) + الأوّليّات محدَّدة الحجم (Int8…Char) |
isNumericKind(k) | العدديّة (Integer · Float · Byte) + العدديّة محدَّدة الحجم (Int8…Float64) |
isCompositeKind(k) | المركّبة (Array · Map · Tuple · 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→اختياري<T>)"}
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"]
Value | SadValue (وقت تشغيل الثنائيّ) | |
|---|---|---|
| العالَم | المفسّر (sad-run) | الثنائيّ المُترجَم |
| اللغة | صنف C++ (shared/types/value.h) | struct C (compiler/.../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).
③ دورة حياة الخطأ: من الإطلاق إلى العرض
الطبقة تُطلِق رمزًا + قوالبَ + موقعًا؛ يجمعها 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/<cat>.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 خطأ"]
- أضف الرمز/الرسالة/التلميح إلى
language-truth/errors/<cat>.yaml(تحقَّق بـerror.schema.json). - أضف
ErrorCode::<NAME>إلىerror_codes.hإن لزم. - أعد التوليد (
gen_error_messages.pyأو عبر البناء). - أطلِقه من الطبقة الصحيحة بـ
ErrorCode::<NAME>+ placeholders. - اكتب اختبارًا سلبيًّا (
@expectedخطأ) — وإلّا فالرمز غير محميّ من الانحدار.
راجع بنى YAML للأخطاء والهجرة V4→V5 في مهارة
sad-lang-dev(references/error-yaml-structures.md).
اقرأ بعده: الدوال المضمنة.
الدوال المضمنة والوحدات
ماذا ستتعلّم: كيف تُعرَّف الدوال المضمنة كمصدر موحّد، وأين تُنفَّذ في المفسّر والمترجم.
التصنيفان
- تلقائيّة (بلا استيراد، ~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>— لا سلاسل حرفيّة.
إضافة دالة مضمنة (الإجراء)
- أضفها إلى
language-truth/builtins/<domain>.yaml(+_index.yamlإن ملف جديد). - أعد التوليد ⇒
builtin_registry_generated.h. - نفّذها في المفسّر والمترجم.
- اختبار
.صمزدوج (إيجابيّ + سلبيّ).
الاستيراد:
استورد رياضيات·استورد رياضيات كـ ر·من رياضيات استورد جذر·من رياضيات استورد *. التفاصيل في مهارةsad-lang-dev(references/builtins-system.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الصحيحة. -
runner.py --level P0(وقسم الميزة) يمرّ 100%. -
runner.py --level P1يمرّ قبل أي PR — لا تراجع (BF-29). -
sadcيبني بلا أخطاء، و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.
الملفّات الإلزاميّة للقراءة (بالترتيب)
- السياسة الأمّ:
_bmad-output/governance/1-policy/planning/PRD.md. - إطار إدارة المشروع:
…/PROJECT_MANAGEMENT_FRAMEWORK.md. - آخر تقرير تحقّق (مصدر الحقيقة للحالة):
…/1-policy/status/VERIFICATION_REPORT_<date>.md. - السبرنت الحالي:
…/sprints/SPRINT_CURRENT.md. - عقد الكود:
…/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, lines}
يبصم نطاقًا بعينه فقط. النطاق يقلّل الإنذارات الكاذبة في الملفّات الكبيرة
(تعديلٌ خارج المنطقة الموثَّقة لا يُطلِق إنذارًا) ويرصد تعفّن المنطقة المقصودة تحديدًا.
الحقل covers_version يوثّق نسخة اللغة التي رُوجِع الدليل تجاهها (يُرفَع آليًّا عند
مراجعة إصدار: --update --set-version).
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 يقيس الانجراف تجاه آخر وسم إصدار للغة (لا تجاه
dev المتقلّب)، فتعني النتيجة «هل يطابق الدليلُ الإصدارَ المنشور؟». يعمل:
- عند إصدار لغة جديد (
repository_dispatch: language-releaseيطلقه مستودع اللغة)، - أسبوعيًّا (شبكة أمان)، ويدويًّا (مع تحديد ref اختياريّ).
عند الانجراف يفتح/يحدّث قضيّةً واحدة «🔄 انجراف الدليل» تُلخّص المصادر المتغيّرة والفصول المتأثّرة وأمرَ التثبيت الصحيح (مع رفع النسخة إن كان إصدارًا).
4) الحُرّاس — ضدّ الكتم الصامت وتعفّن البيان
الكشف وحده لا يكفي؛ يلزم منعُ الالتفاف عليه. حارسان في CI (ci.yml) على كل PR:
- حارس القفل (لا كتم صامت): يرفض تقدّم أيّ بصمةٍ في
sources.lock.jsonما لم يُعدَّل الفصل المرتبط بها في نفس الـPR. فلا يصير--updateزرَّ إسكاتٍ بلا مراجعة. - حارس البيان: يتحقّق (بلا شبكة) من وجود ملفّات الفصول، صحّة نطاقات الأسطر، وأنّ
كل فصل تقنيّ في
SUMMARYمسجَّلٌ مصادرُه — فلا يَفلت فصلٌ جديد من المظلّة.
5) العقد عبر المستودعين
العقد يجب أن يصل لمن يغيّر اللغة فعلًا (في مستودع اللغة، لا هنا):
- workflow
dev-guide-sync-reminderفي مستودع اللغة يرصد — على كل PR — تقاطع الملفّات المعدَّلة مع المصادر المسجَّلة هنا، فيعلّق تذكيرًا بالفصول المتأثّرة. - عند نشر إصدار، يطلق المستودعُ
repository_dispatchلتشغيل فحص المزامنة هنا فورًا. - وبندٌ في معيار الإنجاز: راجع الفصل المرتبط ثم ثبّت البصمات.
💡 القاعدة الذهبيّة: الكود هو الحقيقة؛ والدليل بصمةٌ متعقَّبة لها، لا ذاكرةٌ تتقادم. الكاشف يرصد الاختلاف، والحُرّاس يمنعان دفنه صامتًا، والعقد يوصِل المسؤوليّة لمصدرها.
اقرأ بعده: مسرد المصطلحات.
مسرد المصطلحات
| المصطلح | المعنى |
|---|---|
| AST | الشجرة النحويّة المجرّدة — تمثيل البرنامج بعد التحليل النحوي (shared/ast/). |
| SIR | Sad Intermediate Representation — تمثيل وسيط بين AST وLLVM (compiler/src/frontend/). |
| SoT | Single 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). |
| DoD | Definition of Done — معيار اعتبار التغيير منجَزًا. |
| worktree | فرع git في مجلد منفصل يشارك نفس المستودع (C:/s_lang/temp-brunch/). |
| CW-NN / BF-NN / GR-NN | قواعد كتابة الكود / إصلاح الأخطاء / الحوكمة (مراجع معياريّة). |
| sadc | مُترجِم لغة ص (AST → SIR → LLVM → تنفيذيّ). |
| sad / sad-run | المفسّر الشجريّ. |