Macros


A macro is a set of commands (statements) that can be repeated anywhere in the program. In other words, it is a set of commands that are given a name, and whenever the compiler encounters that name it replaces it with those set of commands.

Macros are defined in the following way:

def macro_name: macro [parameters] {
  // macro_body
}

Or in the shorter form:

macro macro_name [parameters] {
  // macro_body
}

Where:
macro_name: a name used later to call the macro.
macro: a keyword.
parameters: macro parameters that will be used (substituted) in macro body.
macro_body: macro body where we will put the programming statements (in case there is only one statement we could omit the curly brackets).

Example 1:
We will define a macro named `LIMIT` and it will hold the value 5. So whenever the compiler reach the name of this macro it will replace it with 5.

import "Srl/Console.alusus";
macro LIMIT 5; // a macro without parameters.
Srl.Console.print("The value of LIMIT is %d\n", LIMIT[]);
//The value of LIMIT is 5

Example 2:
We will use a macro to calculate the area of a rectangle.

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

In the example above we can see that each time the compiler encounters `AREA[x, y]` it replaces it with the body of the macro after substituting the arguments x and y with the parameters provided to the macro.

Example 3:

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...

Note that when using variables in macros we don't specify its type (and we can't do this); the compiler will simply substitute the macro name with the macro body during preprocessing, so it doesn't need to know the types of the arguments.

Notes:

  1. In Alusus if you define a macro inside some scope and try to use inside another scope the compiler will prevent you from that.
  2. It is possible in Alusus to define multiple macros with the same name as long as they are defined in different scopes.

Nested Macros


  • Macros inside another macros are called nested macros.
  • In nested macros, first the original macro is extended (replace its name with its body) then the inner one.

Example:

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

Comparison with C Macros


You can read about the differences between Alusus macros and C macros here

Benefits of Using Macros


Macros are similer to functions, but there exists cases where functions fall short and macros are needed instead:

  1. Variables defined inside a macro are accessible from outside the macro, i.e. from the scope where the macro is used. For example:
    import "Srl/Console.alusus";
    macro defX { def x: int = 9; }
    defX[]
    Srl.Console.print("%d", x); // 9
    
    Note how that when we called the macro we became able to use the variable `x`.
  2. Macro arguments are not tied to a certain data type, so they can be used to apply the functionality on different data types without needing to write multiple versions of the macro. For example:
    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
    
    Here we can use the macro `square` with any type that accepts the operator `*`.
  3. A macro's code is executed inside the scope that called it, so the macro can directly access variables defined in that scope even if they are local variables, and can see the elements that this scope can see.
    Assume that we have the following simple code:
    import "Srl/Console.alusus";
    macro square[x] { x * x }
    function f () {
      def x: int = 9;
      Srl.Console.print("%d", square[x]);
    }
    f() // 81
    
    If we remove the parameters from the macro and assume that the function should define that variable, the macro will still work. This is not possible with functions.
    import "Srl/Console.alusus";
    macro square { x * x }
    function f () {
      def x: int = 9;
      Srl.Console.print("%d", square[]);
    }
    f(); // 81
    
  4. In some cases a macro will result in better performance than a function since its body will be inlined so there will not be a function call at run time.