macros!

February 17, 2025 ยท View on GitHub

Macros can be defined inside Ascent programs to avoid having to repeat code. These macros can expand to sequences of body or head clauses in Ascent rules.

The syntax resembles (unstable) Rust macros 2.0.

Example: Finding out what languages can be compiled to what languages competently.

type Lang = &'static str;
type Compiler = &'static str;
ascent! {
   relation compiler(Compiler, Lang, Lang);
   relation bad_compiler(Compiler);

   relation can_compile_to(Lang, Lang);

   macro compiler($from: expr, $to: expr) {
      compiler(name, $from, $to), !bad_compiler(name)
   }

   can_compile_to(a, b) <-- compiler!(a, b);
   can_compile_to(a, c) <-- compiler!(a, b), can_compile_to(b, c);
}

Identifiers bound in macro bodies, like name in the example above, remain private to the macro invocation. For example, to find out what languages can be compiled in two steps, we can add the following:

ascent! {
   //...
   relation compiles_in_two_steps(Lang, Lang);
   compiles_in_two_steps(a, c) <-- compiler!(a, b), compiler!(b, c);
}

And it won't require the a -> b compiler to be the same as the b -> c compiler!