الماكرو


الماكرو هو مجموعة من الأوامر (مجموعة تعليمات) يمكن تكرارها بسهولة في أي مكان من البرنامج. أو بمعنى آخر هو مجموعة تعليمات يتم إعطاؤها اسماً بحيث كلما واجه المترجم هذا الاسم يستبدله بمجموعة التعليمات.

يتم تعريف الماكرو بالشكل التالي:

عرف اسم_الماكرو: ماكرو [معطيات] {
  // متن_الماكرو
}
def macro_name: macro [parameters] {
  // macro_body
}

أو بالصيغة المختصرة:

ماكرو اسم_الماكرو [معطيات] {
  // متن الماكرو
}
macro macro_name [parameters] {
  // macro_body
}

حيث أن:
اسم_الماكرو (macro_name): اسم يستخدم لاحقًا لاستدعاء الماكرو.
ماكرو (macro): كلمة مفتاحية.
معطيات (parameters): معطيات الماكرو التي سيتم استخدامها (تعويضها) في متن الماكرو.
متن_الماكرو (macro_body): متن الماكرو حيث سنضع التعليمات البرمجية (في حال كانت التعليمات عبارة عن تعليمة واحدة يمكنك الاستغناء عن القوسين الحاصرين).

المثال الأول:
هنا سنعرف ماكرو باسم `_أمد_` (LIMIT) وسيحمل القيمة 5. وبالتالي أينما واجه المترجم اسم هذا الماكرو سيدرك أننا نقصد القيمة 5.

اشمل "مـتم/طـرفية"؛
ماكرو _أمد_ 5؛ // تعريف ماكرو بدون وسطاء
مـتم.طـرفية.اطبع("قيمة _أمد_ هي %d\ج"، _أمد_[])؛
// قيمة _أمد_ هي 5
import "Srl/Console.alusus";
macro LIMIT 5; // تعريف ماكرو بدون وسطاء
Srl.Console.print("The value of LIMIT is %d\n", LIMIT[]);
//The value of LIMIT is 5

المثال الثاني:
سنستخدم الآن الماكرو لحساب مساحة مستطيل.

اشمل "مـتم/طـرفية"؛
ماكرو _مساحة_ [س، ص] س * ص؛
عرف ا: صحيح = 4؛
عرف ب: صحيح = 9؛
عرف مساحة: صحيح؛
مساحة = _مساحة_[ا، ب]؛
مـتم.طـرفية.اطبع("مساحة المستطيل هي: %d\ج"، مساحة)؛
// مساحة المستطيل هي: 36
import "Srl/Console.alusus";
macro AREA [x,y] x*y;
def a: int = 4;
def b: int = 9;
def area: int;
area = AREA[a, b];
Srl.Console.print("Area of rectangle is: %d\n", area);
// Area of rectangle is: 36

من البرنامج أعلاه يمكننا أن نرى أنه كلما وجد المترجم [x, y]AREA في البرنامج فإنه يستبدلها بمتن الماكرو بعد استعاضة المعطيات ما يقابلها، أي x * y.

المثال الثالث:

اشمل "مـتم/طـرفية"؛
اشمل "مـتم/نـص"؛
استخدم مـتم؛
عرف ن: نـص ="ماكرو..";
ماكرو اطبع_نصا [م] طـرفية.اطبع("%s"، م.صوان)؛
اطبع_نصا[ن]؛ // ماكرو..
import "Srl/Console.alusus";
import "Srl/String.alusus";
use Srl;
def s: String = "Macro..";
macro printString [s] Console.print("%s", s.buf);
printString[s]; // Macro...

لاحظ أنه عند استخدام المتغيرات في الماكرو لانقوم بتحديد صنفها (ولايمكننا ذلك)، وإنما يتم تحديده تلقائياً.

ملاحظات:

  1. في لغة الأسُس إذا عرفت ماكرو داخل مجال معين وحاولت استخدامه داخل مجال آخر فسيمنعك المترجم من ذلك.
  2. يمكن في لغة الأسُس تعريف عدة ماكروهات بنفس الاسم طالما أنها معرفة ضمن مجالات مختلفة.

الماكروهات المتسلسلة


  • تسمى وحدات الماكرو داخل وحدات الماكرو بوحدات الماكرو المتسلسلة.
  • في وحدات الماكرو المتسلسلة يتم أولاً توسيع الماكرو الأصلي ثم توسيع الماكرو الفرعي.

مثال:

اشمل "مـتم/طـرفية"؛
ماكرو _انستجرام_ _متابعون_[]؛
ماكرو _متابعون_ 100؛
مـتم.طـرفية.اطبع("لدى الأسس %d متابعا على انستجرام\ج"، _انستجرام_[])؛
// لدى الأسس 100 متابعا على انستجرام
import "Srl/Console.alusus";
macro INSTAGRAM FOLLOWERS[];
macro FOLLOWERS 100;
Srl.Console.print("Alusus has %dK followers on Instagram\n", INSTAGRAM[]);
// Alusus has 100K followers on Instagram

الفرق بين ماكرو لغة السي والأسُس


يمكنك الاطلاع على الفرق بين ماكرو لغة الأسُس وماكرو لغة السي من هنا

ميزات الماكرو


على الرغم من التشابه الكبير بين وحدات الماكرو و الدوال إلا أن هناك بعض الحالات والفوائد التي قد تجعلنا نستخدم الماكرو، منها:

  1. الماكروهات تسمى أيضاً بالمُختصرات، وبالتالي فهي مفيدة عندما يتم تكرار التعليمات البرمجية، كما أن تعريفها واستدعائها سهل.

  2. في كثير من الحالات تكون سرعة تنفيذه أفضل من سرعة تنفيذ الدوال لأن معالجته تتم أثناء الترجمة ولا تُحول إلى عملية استدعاء أثناء التنفيذ كما هو الحال مع الدالة.

  3. المتغيرات التي يتم تعريفها داخل الماكرو تكون مرئية بمجرد استدعاء الماكرو (يمكن الوصول لها ضمن المجال الذي تم فيه استدعاء الماكرو ﻷنه يقوم بكتابة متنه (body) في المكان الذي يتم استدعاؤه فيه) أما في الدوال فهذا غير ممكن لأن المتغيرات التي يتم تعريفها داخل مجال الدالة تبقى محصورة ضمنها (متغيرات محلية)،مثال:
    اشمل "مـتم/طـرفية"؛
    ماكرو عرف_س { عرف س: صحيح = 9؛ }
    عرف_س[]؛
    مـتم.طـرفية.اطبع("%d"، س)؛ // 9
    
    import "Srl/Console.alusus";
    macro defX { def x: int = 9; }
    defX[]
    Srl.Console.print("%d", x); // 9
    
    لاحظ كيف أنه بمجرد استدعاء الماكرو أصبحنا قادرين على استخدام المتغير س.

  4. عند التعامل مع الماكرو فنحن لا نحتاج لتحديد صنف المعطيات، على عكس الدوال التي نحتاج إلى الإعلان عن أصناف معطياتها بشكل صريح.
    لاحظ المثال التالي:
    اشمل "مـتم/طـرفية"؛
    استخدم مـتم.طـرفية؛
    ماكرو تربيع [س] { س * س }
    اطبع("%d\ج"، تربيع[5])؛ // 25
    اطبع("%0.2f\ج"، تربيع[5.5]~مثل[عائم[64]])؛ // 30.25
    
    import "Srl/Console.alusus"
    use Srl.Console;
    macro square [x] { x * x }
    print("%d\n", square[5]); // 25
    print("%0.2f\n", square[5.5]~cast[Float[64]]); // 30.25
    
    هنا يمكننا استخدام الماكرو `ماكرو` (square) مع أي صنف يقبل المؤثر *.

  5. شفرة الماكرو تنفذ ضمن المجال الذي يستدعيه وبالتالي يمكن للماكرو الولوج مباشرة إلى متغيرات عرفت في ذلك المجال حتى لو كانت متغيرات محلية وكذلك يمكنه رؤية نفس العناصر التي يمكن لذلك المجال رؤيتها. هذه الميزة مفيدة في كثير من الحالات مثل المعالجات التمهيدية.
    لنفترض أنه لدينا الكود البسيط التالي:
    اشمل "مـتم/طـرفية"؛
    ماكرو تربيع [س] { س * س }
    دالة د () {
      عرف س: صحيح = 9؛
      مـتم.طـرفية.اطبع("%d"، تربيع[س])؛
    }
    د()؛ // 81
    
    import "Srl/Console.alusus";
    macro square[x] { x * x }
    function f () {
      def x: int = 9;
      Srl.Console.print("%d", square[x]);
    }
    f() // 81
    
    الآن لو أزلت المعطى من الماكرو وافترضت أن الدالة يجب أن تعرف المتغير فهذا لا يمكنك فعله بالدوال. أي:
    اشمل "مـتم/طـرفية"؛
    ماكرو تربيع { س * س }
    دالة د () {
      عرف س: صحيح = 9؛
      مـتم.طـرفية.اطبع("%d"، تربيع[])؛
    }
    د()؛ // 81
    
    import "Srl/Console.alusus";
    macro square { x * x }
    function f () {
      def x: int = 9;
      Srl.Console.print("%d", square[]);
    }
    f(); // 81