Skip to content

مصدرُ الحقيقةِ الواحدُ وكيف يولّدُ الشفرة

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

١ · النطاقاتُ — ما يُولَّدُ وما يُفحَص

x.py يحملُ سجلَّ النطاقات: نطاقٌ مولِّدٌ له script وout_dir وoutputs، ونطاقٌ حارسٌ خالصٌ لا مُخرَجَ له.

bash
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.md70_lexical.md) وcli_flags.ar.md. أي أنّ الشفرةَ والوثيقةَ يخرجانِ من الملفِّ نفسِه، فلا تُوصَفُ قاعدةٌ في وثيقةٍ وصفًا يخالفُ ما يُصرَّف: ليس ثمّةَ نسختان لتتباعدا.

٢ · مسارٌ كاملٌ محسوس: من YAML إلى نصِّ الخطأ

مدخلُ SEM001 في language-truth/errors/semantic.yaml:

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، ويظهرُ حرفًا في مخرَجِ الثنائيّ:

bash
sad-build.exe undef.ص -o undef.exe
undef.ص: error: [SEM001] (AR) المتغير 'مجهول' غير معرَّف
  → عرّف المتغير قبل استخدامه: متغير مجهول = <قيمة>
1 error(s) generated.

النصُّ ليس في .cpp كتبَه أحد؛ هو حقلٌ في YAML مرَّ بمولِّد. ولذلك لا يمكنُ أن يتباعدَ نصُّ الكتالوجِ عن نصِّ المترجّم: ليس ثمّةَ نسختان.

٣ · الحارسُ يقارِنُ ولا يكتب

_gen_check() في x.py يُعيدُ التوليدَ إلى مجلَّدٍ مؤقّت ثمّ يقارِنُ بايتاتِه بالملفِّ المُودَع، ولا يمسُّ الشجرة. وهذا القيدُ هو كلُّ الفائدة: مولِّدٌ يكتبُ عندَ البناءِ يُصلِحُ الانجرافَ صامتًا، فلا يعلمُ أحدٌ أنّ المُودَعَ كان كاذبًا.

يُعادُ إنتاجُ المطابقةِ يدويًّا هكذا — المولِّدُ نفسُه، ووِجهةٌ مؤقّتة:

bash
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. لا تُدرَّسُ قواعدَ حيّة، والحارسُ يمنعُ عودةَ باعثٍ لها.

مُرخَّص بموجب رخصة MIT