اتبعْ قاعدةً من مصدرِ الحقيقةِ إلى الاختبارِ الذي يحرسُها
في هذا الدرسِ تتبعُ قاعدةً واحدةً بعينِها — gr.stmt.break، أي الكلمةَ توقف — من سطرِها في مصدرِ الحقيقةِ حتّى الحارسِ الذي يحمرُّ لو انفصلت عن شاهدِها. تنتهي وقد ترجمتَ وشغَّلتَ ورأيتَ التنزيل.
ما تحتاجُه
مستودعُ اللغةِ مبنيًّا، والثنائيُّ في build/bin/Release/sad-build.exe. والاستعمالُ خطوتان دائمًا: sad-build.exe file.ص -o file.exe ثمّ تشغيلُ الثنائيّ. لا مفسّرَ ولا REPL ولا آلةَ بايتكود — محرّكٌ واحد.
الخطوة ١ · اقرأِ المدخلَ في مصدرِ الحقيقة
القاعدةُ في language-truth/grammar/10_statements.yaml:
- id: gr.stmt.break
lhs: { nonterminal: BreakStatement, name_ar: "جملة توقف", name_en: break_statement }
ebnf: "BreakStatement = 'توقف' ;"
alternatives:
- symbols: [{ kind: terminal, ref: KEYWORD_BREAK, value: "توقف" }]
references: { keywords: [KW-RES-017] }
maps_to:
- file: shared/parser/src/statements/parser_statements.cpp
function: ParserCore::parseBreakStmt
ast_node: BreakStmt
since: "1.0.0"
status: stable
conformance:
compiler_optional: false
test_budget: { basic: 3, edge: 3, negative: 1, interaction: 2 }خمسةُ أشياءَ ههنا معرّفاتٌ تُحَلُّ، لا نثر: الرمزُ الطرفيُّ KEYWORD_BREAK، ومعرّفُ الكلمةِ KW-RES-017، وموضعُ التحليلِ maps_to، وعقدةُ الشجرة، وميزانيّةُ الاختبارات.
الخطوة ٢ · حُلَّ الإحالةَ إلى كتالوجِ الكلمات
grep -n "KW-RES-017" language-truth/keywords.yaml- { id: "KW-RES-017", subcategory: "control_flow", since: "1.0.0",
word: "توقف", tokenType: KEYWORD_BREAK, english: break }الكلمةُ العربيّةُ ورمزُها الطرفيُّ في مكانٍ واحدٍ في المستودعِ كلِّه، ومنه يُولَّدُ جدولُ الكلماتِ في المُعجَم. وcheck_grammar_terminal_refs.py يمنعُ قاعدةً تُحيلُ إلى رمزٍ لا وجودَ له في هذا الكتالوج.
الخطوة ٣ · اذهبْ إلى مجلَّدِ المرساة
اسمُ المجلَّدِ هو معرّفُ القاعدةِ حرفًا:
ls tests/behavior/rules_matrix/10_statements/gr.stmt.break/*/basic/ 001_in_while.ص 002_in_for.ص 003_first_iter.ص
edge/ 001_inner_only.ص … 050_break_nested_loop.ص
negative/ 001_top_level.ص … 004_break_in_switch_outside_loop.صستَّ عشرةَ بذرة. والفئاتُ الثلاثُ تقابلُ test_budget أعلاه.
الخطوة ٤ · ترجِمْ بذرةً وشغِّلْها
basic/001_in_while.ص كما هي في الشجرة:
# @rule: gr.stmt.break
# @description: توقف في بينما
# @priority: P1
# @requires: اطبع_سطر
# @expected: 0\n1
متغير ع = 0
بينما (ع < 10)
إذا (ع == 2) توقف نهاية
اطبع_سطر(ع)
ع = ع + 1
نهايةsad-build.exe 001_in_while.ص -o brk.exe
./brk.exe0
1المخرَجُ يطابقُ @expected حرفًا. لاحظْ أنّ الوسمَ عقدٌ يُحاكَمُ، لا تعليق: check_seed_contract.py يعدُّ البذورَ التي تُشغَّلُ بلا عقدٍ ويضربُ عليها سقفًا نازلًا.
الخطوة ٥ · انظرْ إلى التنزيل
sad-build.exe 001_in_while.ص --أظهر-sir -o brk2.exedefine void @__sad_main() {
entry:
%%ع = alloc
store 0, %%ع
br label %while_cond_0
while_cond_0:
%%2 = lt %%ع, 10
br.cond %%2, label %while_body_1, label %while_exit_2
while_body_1:
%%4 = eq %%ع, 2
br.cond %%4, label %while_exit_2, label %merge_4
while_exit_2:
ret.void
merge_4:
builtin.print %%ع
...
}🔑 لا أوپكودَ اسمُه BREAK. توقف صارت br.cond تقفزُ إلى while_exit_2 مباشرةً. وهذا بالضبطِ ما يجعلُ جدولَ التنزيلِ مشتقًّا بالتنفيذِ لا مكتوبًا باليد: كاتبٌ بشريٌّ كان سيخترعُ خانةً باسمِ القاعدة.
الخطوة ٦ · اقرأِ الصفَّ المشتقَّ وشواهدَه
في language-truth/grammar/lowers_to.yaml:
- id: gr.stmt.break
status: derived
lowers_to: [ADD_I64, ALLOC, ARRAY_GET, ARRAY_LEN, ARRAY_NEW, ARRAY_SET,
BR, BR_COND, BUILTIN_PRINT, EQ, LOAD, LT, MOVE, RET_VOID, STORE]
native_ok: [arm64, x86_64]
evidence: [001_in_while.ص, 002_in_for.ص, 003_first_iter.ص]evidence يُسمّي البذورَ الثلاثَ التي اشتُقَّ منها الصفّ — أُولاها هي التي ترجمتَها للتوّ. والدعوى قابلةٌ للتكذيبِ بخطوةٍ واحدة.
الخطوة ٧ · شغِّلْ مجموعةَ القاعدةِ كلَّها
python tests/runner.py --compiler build/bin/Release/sad-build.exe \
--dir rules_matrix/10_statements/gr.stmt.break إجمالي: 16
نجح: 15
فشل: 0
تخطي: 1والمتخطّاةُ ليست مسكوتًا عنها؛ سببُها مكتوبٌ في رأسِها:
# @rule: gr.stmt.break
# @description: توقف من داخل «حاول» تُنفّذ «أخيراً» قبل الخروج
# (المفسّر صحيح؛ المصرّف يتخطّاها — ISSUE-104)
# @skip_compilerهذا دَينٌ مُعلَنٌ باسمِه ورقمِه، لا خضرةٌ زائفة. وعدُّ هذه البذورِ محروسٌ بسقفٍ نازلٍ في check_seed_contract.py (المقيسُ اليوم: ٧٧ متخطّاةً في الشجرةِ كلِّها).
الخطوة ٨ · شاهِدِ الحارسَ الذي يحرسُ هذا الرباط
python scripts/codegen/check_anchor_integrity.py① مرساةٌ يتيمة (ترسو على غيرِ مُعلَن): 0 (المسموح 0 — لا سقفَ له)
② مُعلَنٌ بلا مرساة — دَينُ شهادةٍ نازلٌ لا يُرفَع:
النحو 105 / 107 · بلا شهادة: 2 (السقف 2)الاتّجاهانِ محروسانِ معًا:
- لو غيَّرتَ وسمَ بذرةٍ إلى
# @rule: gr.stmt.zz_invented— قاعدةٍ لا وجودَ لها — لحمِرَ البندُ ①، لأنّ سقفَه صفرٌ بلا هامش. - ولو نزعتَ آخرَ مرساةٍ لقاعدةٍ ما لصارَ «بلا شهادة: 3 > 2» فحمِرَ البندُ ②.
وهاتانِ الحالتانِ بالذاتِ ليستا افتراضًا: هما المجسّانِ ① و② في scripts/codegen/calibration/check_anchor_integrity.yaml، حُقِنَا فعلًا وأُثبِتَ احمرارُ الحارسِ بهما وسُجِّلَ ذلك ببصمةِ SHA. انظرْ لماذا يُعايَرُ الحارسُ أصلًا.
ماذا صارَ عندَك الآن
تتبَّعتَ سلسلةً كاملةً لا حلقةَ فيها مكتوبةٌ باليد مرّتين:
| الحلقة | الموضع | ما يحرسُها |
|---|---|---|
| القاعدة | grammar/10_statements.yaml | check_grammar_terminal_refs.py |
| الكلمة | keywords.yaml | مولِّدُ المُعجَمِ + x.py gen --check |
| المرساة | rules_matrix/…/gr.stmt.break/ | check_anchor_integrity.py |
| العقد | @expected في البذرة | check_seed_contract.py |
| التنزيل | lowers_to.yaml | check_lowers_to.py |
وللتوسُّع: مصدرُ الحقيقة · المطابقة · التنزيل · كيف تُضافُ قاعدةٌ جديدة.