كيف تُضافُ قاعدةُ نحوٍ جديدة
وصفةٌ لمن يريدُ إضافةَ إنتاجٍ نحويٍّ جديدٍ إلى اللغة. والترتيبُ ههنا سببيٌّ لا ذوقيّ: كلُّ خطوةٍ يحرسُها ما بعدَها، وتقديمُ الشفرةِ على القاعدةِ يُنتِجُ سلوكًا بلا سند.
القاعدةُ الحاكمة
@expected بلا قاعدةٍ في language-truth/ رأيٌ لا عقد. تُكتَبُ القاعدةُ أوّلًا، ثمّ يُطلَبُ التنفيذ. ولا يُشتَقُّ توقُّعٌ من مُخرَجِ مُنفِّذٍ أبدًا.
١ · إن لزمتْك كلمةٌ جديدة، فالكتالوجُ أوّلًا
language-truth/keywords.yaml هو المكانُ الوحيدُ الذي تُعلَنُ فيه كلمةٌ محجوزةٌ ورمزُها الطرفيّ:
- { id: "KW-RES-0NN", subcategory: "control_flow", since: "1.x.0",
word: "كلمتك", tokenType: KEYWORD_YOURS, english: yours,
roles: [block_opener] }roles يُقرَأ، فلا يُترَكُ اعتباطًا. ومن هذا الملفِّ يُولَّدُ shared/lexer/generated/keywords_generated.{h,cpp} — لا تُحرَّرْ يدًا.
٢ · أعلِنِ القاعدةَ في ملفِّ طبقتِها
الطبقاتُ ثمانٍ: 00_program · 10_statements · 20_declarations · 30_oop · 40_expressions · 50_patterns · 60_advanced · 70_lexical. والمدخلُ الأدنى:
- id: gr.stmt.yours # المعرّفُ = اسمُ مجلَّدِ المرساةِ لاحقًا
lhs: { nonterminal: YoursStatement, name_ar: "جملة كذا", name_en: yours_statement }
ebnf: "YoursStatement = 'كلمتك' Expression ;"
alternatives:
- symbols:
- { kind: terminal, ref: KEYWORD_YOURS, value: "كلمتك" }
- { kind: nonterminal, ref: gr.expr.expression }
references: { keywords: ["KW-RES-0NN"] }
maps_to:
- { file: shared/parser/src/statements/parser_statements.cpp,
function: "ParserCore::parseYoursStmt" }
ast_node: YoursStmt
description_ar: "…"
since: "1.x.0"
status: experimental # stable حين يُبرهَنَ لا حين يُرجى
conformance:
compiler_optional: false
test_budget: { basic: 3, edge: 3, negative: 1, interaction: 2 }ثلاثةُ تنبيهاتٍ مقيسة:
references.keywordsيُحَلُّ فعلًا. رمزٌ طرفيٌّ لا وجودَ له في الكتالوجِ يُوقِفُcheck_grammar_terminal_refs.py.alternatives[*].symbols[*].refإلى قاعدةٍ أخرى يبني رسمَ قابليّةِ التركيب الذي تُشتقُّ منه أزواجُ التداخلِ المطلوبة (§٥). فالإحالةُ الناقصةُ تُنقِصُ التغطيةَ المطلوبةَ منك بصمت.- لا تكتبْ
#غيرَ مقتبسٍ في حقلِ وصف.check_yaml_desc_hash.pyيمنعُه لأنّه بترٌ صامتٌ لبقيّةِ السطر.
٣ · نفِّذْ في الشفرةِ ثمّ أعِدِ التوليد
نفِّذْ parseYoursStmt في الملفِّ الذي سمّاه maps_to (المدخلُ عقدٌ مقروءٌ لا تعليق)، ثمّ:
python x.py gen # يكتبُ المولَّدَ من language-truth/
python x.py build --config Release
python x.py gen --check # يجبُ أن يخضرّ قبلَ أيِّ إيداعgen --check يُعيدُ التوليدَ إلى مجلَّدٍ مؤقّتٍ ويقارِنُ بايتاتِه بالمُودَع — فأيُّ فرقٍ بينَ YAML والمُودَعِ يُوقِفُ الدمج. أودِعِ المولَّدَ مع مصدرِه في الإيداعِ نفسِه.
٤ · أنشئ مجلَّدَ المرساة — واسمُه المعرّفُ حرفًا
tests/behavior/rules_matrix/10_statements/gr.stmt.yours/
├── basic/ 001_….ص
├── edge/ 001_….ص
└── negative/ 001_….صهذه الخطوةُ ليست اختياريّة
سقفُ «مُعلَنٌ بلا مرساة» في check_anchor_integrity.py هو ٢ ولا يُرفَع. فقاعدةٌ جديدةٌ بلا بذرةٍ واحدةٍ تحملُ وسمَها ترفعُ العددَ إلى ٣ فيُوقَفُ الدمج. وهذا هو المجسُّ ⑥ في سجلِّ عيارِ ذلك الحارس، مُثبَتًا بالحقنِ لا مفترَضًا.
وكلُّ بذرةٍ تحملُ في رأسِها عقدًا يُحاكَمُ:
# @rule: gr.stmt.yours
# @description: أقصرُ صورةٍ صحيحة
# @priority: P1
# @requires: اطبع_سطر
# @expected: نللبذرةِ السالبةِ استعملْ @expect_error برمزٍ من الكتالوج:
# @rule: gr.stmt.yours
# @expect_error: SEM013رمزٌ لا وجودَ له في language-truth/errors/ يُحمِّرُ حارسَ المرساة — مُثبَتٌ بالمجسِّ ④ الذي يحقنُ SEM999 ويتحقّقُ من ظهورِ err.SEM999.
لا تخترعْ رمزَ خطأ
إعلانُ رمزٍ جديدٍ قرارُ مصدرِ حقيقةٍ لا ترقيعُ باني. وخمسةُ رموزٍ في الكتالوجِ وسمُها retired لأنّ تعريفَها كان المفسّرَ المحذوف (INT008 · INT009 · INT010 · RUN059 · SEM027) — لا تُوجَّهُ إليها بذرة.
٥ · غطِّ أزواجَ التداخل
الأزواجُ المطلوبةُ مشتقّةٌ من إحالاتِ alternatives، فبذرةُ تداخلٍ تُوضَعُ في _interactions/<موضوع>/ وتَسرُدُ كلَّ القواعدِ التي تمسُّها:
# @rule: gr.stmt.yours, gr.expr.expressionpython scripts/codegen/check_grammar_conformance.py # يطبعُ العجزَ صفًّا صفًّاما يُوقِفُ الدمجَ فعلًا
ميزانيّةُ الفئاتِ وتغطيةُ الأزواجِ تُقاسانِ وتُنشَرانِ ولا تُوقفانِ الدمجَ اليوم: check_grammar_conformance.py يُخفِقُ برمزِ ١ وليس في x.py gen --check. المُشغَّلُ في البوّابةِ هو gen_rules_matrix.py --check — سلامةٌ مرجعيّةٌ فحسب — مع حارسِ المرساةِ وحارسِ عقدِ البذور. لا تبنِ افتراضًا على أنّ نقصَ الميزانيّةِ سيُوقِفُك.
٦ · اشتقَّ التنزيل
python scripts/codegen/gen_grammar_lowers_to.py \
--compiler build/bin/Release/sad-build.exe --recompute
python scripts/codegen/check_lowers_to.pyالاشتقاقُ يُترجِمُ بذورَك بـ--أظهر-sir ويجمعُ أوپكوداتِها. فإن لم تُترجَمْ بذرةٌ واحدةٌ منها بنجاحٍ وُسِمَت القاعدةُ not_derived صراحةً — لا تُترَكُ خانتُها فارغةً موحيةً بالنجاح. ولا تُحرِّرْ lowers_to.yaml بيد: هو مُخرَجُ مولِّد.
٧ · شغِّلْ ما بنيتَ
python tests/runner.py --compiler build/bin/Release/sad-build.exe \
--dir rules_matrix/10_statements/gr.stmt.yoursخضرةٌ هنا لا تكفي وحدَها: بذرةٌ بلا @expected ولا وسمٍ سالبٍ تُشغَّلُ ولا تُحاكَمُ، وهي مقيسةٌ ومحروسةٌ بسقفٍ نازلٍ في check_seed_contract.py. تحقّقْ أنّ عقدَك مكتوب.
٨ · قائمةُ الإيداع
python x.py gen --check # المولَّدُ متزامنٌ + حرّاسُ الاتّساقِ كلُّهم
python x.py build --config Release
python x.py test --config Release- [ ] القاعدةُ في ملفِّ طبقتِها، ومعرّفُها فريدٌ ومعناه واضح.
- [ ] كلُّ رمزٍ طرفيٍّ مُحال إليه موجودٌ في
keywords.yaml. - [ ] المولَّدُ مُودَعٌ مع مصدرِه.
- [ ] مجلَّدُ المرساةِ باسمِ المعرّفِ حرفًا، وفيه بذرةٌ واحدةٌ على الأقلّ.
- [ ] كلُّ بذرةٍ لها
@expectedأو@expect_errorبرمزٍ من الكتالوج. - [ ]
lowers_to.yamlأُعيدَ اشتقاقُه وأُودِع. - [ ]
statusصادقٌ:experimentalحتّى تكتملَ الميزانيّةُ ويُبرهَنَ التنزيل.
ما لا تفعلْه
لا تضِفْ بذرةً يتيمةً إلى
sections/. نسبةُ@skip_compilerفيrules_matrix— حيثُ المرساةُ قاعدة — ٥ من ٣٬٠٥٥ = ٠٫١٦٪؛ وفيsections— حيثُ المرساةُ موضوعٌ — ٦٥ من ١٬٢٧٧ = ٥٫٠٩٪. نحوَ ٣٢ ضعفًا، والمجموعةُ الأنظفُ أكبرُ بمرّتين ونصف. الدَّينُ يتراكمُ حيث لا قاعدةَ تحكم.bashpython - <<'EOF' import glob, re for d in ("rules_matrix", "sections"): fs = glob.glob(f"tests/behavior/{d}/**/*.ص", recursive=True) sk = sum(bool(re.search(r"^#\s*@skip_compiler", open(f, encoding="utf-8-sig").read(3000), re.M)) for f in fs) print(d, len(fs), sk, round(100 * sk / len(fs), 2)) EOFلا تكتبْ قاعدةً تصفُ ما يجبُ رفضُه. النحوُ يعلنُ ما يُقبَل. أمّا الرفضُ فيُعلَنُ رمزًا في كتالوجِ الأخطاء، وذلك قرارٌ مستقلٌّ عن إضافةِ الإنتاج.
لا تُحرِّرْ ملفًّا صدرُه «مُولَّد آليًّا». ستُعادُ كتابتُه، وسيحمرُّ
gen --checkقبلَ ذلك.