أنت تعرف بالفعل كيف تبني اقتباسًا من أجزاء وتشغّله. والاقتباس ليس إلا تسلسلًا من الكود، لذا تنطبق عليه كلمات التسلسل التي تعرفها:
USING: kernel math prettyprint quotations sequences ;
{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call . ! => 9
[ 1 + ] dup append . ! => [ 1 + 1 + ]
تربط append بين اقتباسين، وتُسطّح concat مصفوفة منهما، وتحوّل map البيانات إلى اقتباسات، وتجعل >quotation النتيجة قابلة للاستدعاء. وMACRO: تأخذ هذه المهارة نفسها وتنقلها إلى وقت الترجمة.
يشبه تعريف MACRO: كلمة عادية، لكن جسمه يُنفَّذ أثناء ترجمة برنامجك، لا أثناء تشغيله. ويجب أن يترك الجسم اقتباسًا واحدًا على المكدس. ثم يُدرج المترجم ذلك الاقتباس في موضع الاستدعاء، أي أنه يستبدل استدعاء الماكرو بالكود الذي أنتجه الجسم. وفي وقت التشغيل لم يبقَ أي ماكرو، بل الكود المولَّد فقط.
USING: kernel macros sequences ;
MACRO: twice ( quot -- spliced )
dup append ;
فأينما كتبت [ 1 + ] twice، فإن المترجم:
[ 1 + ] على المكدس،dup append الاقتباسَ [ 1 + 1 + ]،1 + 1 +، في ذلك الموضع.لذا فإن كلمة تستدعي twice تُترجم كما لو أنك كتبت 1 + 1 + بيدك:
: inc-twice ( x -- y ) [ 1 + ] twice ;
5 inc-twice . ! => 7
الفرق الجوهري: الكلمة العادية التي تأخذ اقتباسًا تستدعيه في وقت التشغيل عبر call. أما الماكرو فلا يستدعي شيئًا أبدًا، بل يسلّم المترجم كودًا جديدًا ليترجمه في مكانه.
لأن الجسم يُنفَّذ في وقت الترجمة، يجب أن تكون مدخلات الماكرو معروفة حينذاك. فيجب أن تكون قيمًا حرفية في موضع الاستدعاء: اقتباس [ … ]، أو عدد، أو مصفوفة، أو سلسلة نصية. أما القيمة التي لا وجود لها إلا في وقت التشغيل فلا يمكن تسليمها إلى ماكرو.
! DOCTEST: SKIP — illustrative call sites, not standalone code
[ 1 + ] twice ! ok: the quotation is a literal
[ 1 + ] 3 repeat-quot ! ok: the quotation and the number are literals
جسم الماكرو كود عادي يُرجع اقتباسًا. والكلمات التي التقيتها من قبل تقوم بمعظم العمل:
| لبناء… | استخدم |
|---|---|
[ A B ] من [ A ] و[ B ]
|
append |
| اقتباس واحد من مصفوفة منها | concat >quotation |
| اقتباس لكل عنصر من عناصر تسلسل |
map، ثم concat >quotation
|
وتكتمل العُدّة بكلمتين أخريين من sequences وkernel.
<repetition> (في sequences) تُنشئ تسلسلًا من n نسخة من عنصر واحد. وتأثير المكدس لها هو ( length elt -- repetition )، حيث يأتي العدد أولًا. والنتيجة تسلسل افتراضي، لذا سطّحها كما اعتدت:
3 [ 1 + ] <repetition> concat >quotation . ! => [ 1 + 1 + 1 + ]
curry (في kernel) تُضيف قيمة إلى مقدمة اقتباس، فتنتج اقتباسًا جديدًا يدفع تلك القيمة أولًا:
3 [ + ] curry . ! => [ 3 + ]
{ 1 2 3 } [ . ] curry . ! => [ { 1 2 3 } . ]
وبـcurry تُدمج عنصرًا محددًا في اقتباس، وهي بالضبط ما يحوّل { 10 20 30 } و[ + ] إلى [ 10 + ] [ 20 + ] [ 30 + ].
في بعض اللغات، تحتاج الماكرو إلى صياغة خاصة للاقتباس وإلغاء الاقتباس، وإلى آليات إضافية لتجنّب تعارض الأسماء. أما Factor فلا يحتاج إلى أي من ذلك: فالاقتباس هو أصلًا تسلسل من كلمات محلولة، إذ بحث المحلل عن كل من dup و+ وappend عندما قرأ [ … ]. لذا فجسم الماكرو ليس إلا التعامل مع التسلسلات الذي تجيده بالفعل، والذي يصادف أنه يُرجع اقتباسًا.
$[ … ]
يُرجع الماكرو اقتباسًا ليُدرج في استدعاء. وعندما تريد بدلًا من ذلك قيمة عادية تُحسب في وقت التحليل، فإن $[ code ] تشغّل code أثناء قراءة الملف وتُضمِّن النتيجة في المصدر كقيمة حرفية:
! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;
CONSTANT: build-time $[ now ]
وتأتي $[ … ] من مفردات literals، التي تجمع الكلمات اللازمة لإدراج نتائج وقت التحليل في القيم الحرفية. وقد ذُكرت هنا للاكتمال. أما القوالب الأربعة التي ستكتبها، وهي twice وrepeat-quot وcompose-many وeach-literal، فكلها تعريفات MACRO:.
أنت مسؤول عن صيانة مجموعة أدوات توليد الكود في محطة إشارات. كل ماكرو هو استنسل، أي قالب صغير يستخدمه المصرّف ليطبع كودًا متخصّصًا عند كل موقع استدعاء.
أنت تكتب الماكروات الأربعة فقط. يعرّف ملف الاختبار كلمات تغليف صغيرة تستدعي الماكروات التي تكتبها (لا يمكن ملاحظة الماكرو إلا من خلال الكود الذي يولّده)، ثم يتحقّق من نتائج الأغلفة في وقت التشغيل. اترك الأغلفة كما هي.
في كل مهمة، يستقبل جسم الماكرو المُدخلات الحرفية لموقع الاستدعاء على المكدّس، وعليه أن يترك اقتباسًا واحدًا. وتوضّح أسطر «يُترجم إلى» الكودَ الخطي الذي ينبغي أن يولّده اقتباسك.
يأخذ twice اقتباسًا حرفيًّا ويُرجع اقتباسًا يُشغّل الأصل مرّتين على التوالي.
! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ] -> must build [ 1 + 1 + ]
5 inc-twice . ! => 7
يأخذ repeat-quot اقتباسًا حرفيًّا وعددًا حرفيًّا n، ويُرجع اقتباسًا يُشغّل الأصل n من المرّات على التوالي.
وعندما يكون n = 0 يُرجع الاقتباس الفارغ [ ].
! : inc-thrice ( x -- y ) [ 1 + ] 3 repeat-quot ;
! body receives [ 1 + ] 3 -> must build [ 1 + 1 + 1 + ]
5 inc-thrice . ! => 8
يأخذ compose-many مصفوفة حرفية من الاقتباسات ويُرجع اقتباسًا واحدًا يُشغّل كلًّا منها بالترتيب. والمصفوفة الفارغة تُرجع الاقتباس الفارغ.
! : pipeline ( x -- y ) { [ 1 + ] [ 2 * ] [ 1 - ] } compose-many ;
! body receives { [ 1 + ] [ 2 * ] [ 1 - ] } -> must build [ 1 + 2 * 1 - ]
5 pipeline . ! => 11
يأخذ each-literal متتالية حرفية واقتباسًا. ويُرجع اقتباسًا يدفع، لكل عنصر من عناصر المتتالية، ذلك العنصر ثم يُشغّل الاقتباس: تكرار يحدث في وقت التصريف، فيكون الكود المولَّد خطيًّا.
! : sum-three ( -- n ) 0 { 10 20 30 } [ + ] each-literal ;
! body receives { 10 20 30 } [ + ] -> must build [ 10 + 20 + 30 + ]
sum-three . ! => 60
هذا أكثر الماكروات الأربعة بنيوية: فطول المتتالية وقيم عناصرها معًا يشكّلان الكود المولَّد. وستأخذك التلميحات في بناءه خطوة بخطوة.
سجّل في Exercism لتتعلّم وتتقن Factor عبر 47 مفهومًا163 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.