مصدرُ الحقيقةِ الواحدُ وكيف يولّدُ الشفرة
القاعدةُ الحاكمةُ جملةٌ واحدة: جدولٌ يملكُه مصدرُ الحقيقةِ لا يُكتَبُ باليدِ في شفرةِ المترجّم. والفرقُ بين هذا وبين «توليدِ الشفرة» المعتادِ أنّ المُخرَجَ ههنا مُودَعٌ في المستودعِ ومحروسٌ بالمقارنة، لا مبنيٌّ عندَ البناء.
١ · النطاقاتُ — ما يُولَّدُ وما يُفحَص
x.py يحملُ سجلَّ النطاقات: نطاقٌ مولِّدٌ له script وout_dir وoutputs، ونطاقٌ حارسٌ خالصٌ لا مُخرَجَ له.
python - <<'EOF'
import re
src = open("x.py", encoding="utf-8").read()
i = src.index("# (AR) حرّاس فحص خالصون")
print("نطاقاتٌ مولِّدة:", len(re.findall(r'"name":\s*"', src[:i])))
print("حرّاسٌ خالصون:", len(re.findall(r'"name":\s*"', src[i:])))
EOFالمقيسُ اليوم: ٢٦ نطاقًا مولِّدًا تُنتِجُ ٣٧ ملفًّا، و٢٢ حارسًا خالصًا — ثمانيةٌ وأربعونَ نطاقًا يمرُّ عليها x.py gen --check كلُّها في تشغيلةٍ واحدة.
والمُخرَجُ ليس شفرةً فحسب: من الملفّاتِ السبعةِ والثلاثينَ عشرةُ ملفّاتِ Markdown — تسعُ صفحاتِ مرجعِ نحوٍ (00_program.md … 70_lexical.md) وcli_flags.ar.md. أي أنّ الشفرةَ والوثيقةَ يخرجانِ من الملفِّ نفسِه، فلا تُوصَفُ قاعدةٌ في وثيقةٍ وصفًا يخالفُ ما يُصرَّف: ليس ثمّةَ نسختان لتتباعدا.
٢ · مسارٌ كاملٌ محسوس: من YAML إلى نصِّ الخطأ
مدخلُ SEM001 في language-truth/errors/semantic.yaml:
- code: SEM_UNDEFINED_VARIABLE
id: SEM001
category: semantic
title: { ar: "متغير غير معرَّف", en: "Undefined variable" }
brief: { ar: "المتغير '{name}' غير معرَّف{suggestion}", ... }
fix_hint: { ar: "عرّف المتغير قبل استخدامه: متغير {name} = <قيمة>", ... }
placeholders: [name, suggestion]يُترجَمُ إلى shared/errors/generated/error_messages_generated.cpp بـgen_error_messages.py، ويظهرُ حرفًا في مخرَجِ الثنائيّ:
sad-build.exe undef.ص -o undef.exeundef.ص: error: [SEM001] (AR) المتغير 'مجهول' غير معرَّف
→ عرّف المتغير قبل استخدامه: متغير مجهول = <قيمة>
1 error(s) generated.النصُّ ليس في .cpp كتبَه أحد؛ هو حقلٌ في YAML مرَّ بمولِّد. ولذلك لا يمكنُ أن يتباعدَ نصُّ الكتالوجِ عن نصِّ المترجّم: ليس ثمّةَ نسختان.
٣ · الحارسُ يقارِنُ ولا يكتب
_gen_check() في x.py يُعيدُ التوليدَ إلى مجلَّدٍ مؤقّت ثمّ يقارِنُ بايتاتِه بالملفِّ المُودَع، ولا يمسُّ الشجرة. وهذا القيدُ هو كلُّ الفائدة: مولِّدٌ يكتبُ عندَ البناءِ يُصلِحُ الانجرافَ صامتًا، فلا يعلمُ أحدٌ أنّ المُودَعَ كان كاذبًا.
يُعادُ إنتاجُ المطابقةِ يدويًّا هكذا — المولِّدُ نفسُه، ووِجهةٌ مؤقّتة:
python scripts/codegen/gen_error_messages.py --yaml-dir language-truth/errors --schema language-truth/_schemas/error.schema.json --enum-header shared/errors/include/error_codes.h --header /tmp/gen/error_messages_generated.h --source /tmp/gen/error_messages_generated.cpp --quiet
cmp shared/errors/generated/error_messages_generated.cpp /tmp/gen/error_messages_generated.cpp && echo IDENTICALالمقيس: IDENTICAL. وحين تُغيَّرُ كلمةٌ واحدةٌ في fix_hint في نسخةٍ من مجلَّدِ الأخطاءِ ثمّ يُولَّدُ منها:
... differ: char 45616, line 300
cmp EXIT=1هذا بالضبطِ ما يفعلُه python x.py gen --check، مضروبًا في ٣٧ ملفًّا. والعلاجُ المُعلَنُ في نصِّ الإخفاقِ واحد: python x.py gen ثمّ إيداعُ الناتج.
البوّابةُ الحيّة
.github/workflows/gate-codegen-sot.yml يُشغِّلُ هذا الحارسَ على كلِّ دمج. وانجرافُ المولَّدِ عن مصدرِ الحقيقةِ يُوقِفُ الدمج — لا يُنبِّه.
٤ · لماذا لا يكفي أن يُعلَنَ الحقلُ في YAML
مصدرُ حقيقةٍ يُكتَبُ فيه حقلٌ لا يقرؤه مستهلك ليس عقدًا؛ هو نثرٌ في ثوبِ بيانات. ولذلك تُوصَلُ الحقولُ بحرّاسٍ يقرؤونها:
| الحارس | ما يمنعُه |
|---|---|
check_builtin_engine_coverage.py | مدمَجةٌ stable بلا ذراعِ إرسالٍ في المترجّم — «وعدٌ كاذب» |
check_retired_error_codes.py | رمزٌ مُتقاعَدٌ ما زالَ له باعثٌ في الشفرة |
check_grammar_terminal_refs.py | قاعدةٌ تُحيلُ إلى رمزٍ طرفيٍّ لا وجودَ له في كتالوجِ الكلمات |
check_yaml_desc_hash.py | # غيرُ مقتبسٍ في حقلِ وصفٍ — بترٌ صامتٌ لبقيّةِ السطر |
مقيسٌ الآن بـpython scripts/codegen/check_builtin_engine_coverage.py:
معلَنٌ في مصدرِ الحقيقة: 1205
له مرجعُ إرسالٍ مؤهَّل: 606
① وعدٌ كاذب (stable بلا إرسال): 0 (المسموح 0 — لا سقفَ له)
② دَينُ تأجيل (intent: مؤجَّل): 599 (السقف 599 — نازلٌ لا يُرفَع)الرقمُ الثاني هو الشاهد: ٥٩٩ مدخلًا مُعلَنٌ أنّه دَين، لا مُدَّعًى أنّه قدرة. والسقفُ نازلٌ فلا ينمو الدَّينُ صامتًا.
٥ · حدودٌ مقيسة
- مصدرُ الحقيقةِ ليس معصومًا. قِيسَ فيه مُصيِّراتُ منصّاتٍ مُعلَنةٌ
enabled: trueوهي داخلَ شرطِ ترجمةٍ لا تُبنى — ولذلك كُتِبَ لها حارسٌ مُعايَرٌ خاصٌّ بها (check_ui_render_completeness.py). الإعلانُ وحدَه ليس دليلًا؛ الدليلُ حارسٌ يقرأُ الإعلانَ ويقابلُه بالبناء. - ثلاثةُ ملفّاتِ أخطاءٍ قائمةٌ وفارغة:
import.yaml·io.yaml·ownership.yaml— صفرُ مدخلاتٍ في كلٍّ منها (مقيسٌ بعدِّerrors). - خمسةُ رموزٍ مُتقاعَدة لأنّ تعريفَها نفسَه كان المفسّرَ المحذوف:
INT008·INT009·INT010·RUN059·SEM027. لا تُدرَّسُ قواعدَ حيّة، والحارسُ يمنعُ عودةَ باعثٍ لها.