Skip to main content

Sage-Lisp Preprocessor Macros

·229 words·2 mins
Documentation - This article is part of a series.
Part 15: This Article
The Sage Preprocessor: Sage also provides a preprocessor to write AST-based macros. These macros are written in Lisp, and operate on the AST before the code is handed to the compiler. This lets you write compile-time code that configures the compiler, or rewrites your own code before it is compiled!

Here’s a simple demonstration of a macro that squares constant integers before they are compiled:

// Use the `#![...]` syntax to start a compile time directive.
// Our directive is a function definition, which will be used as
// a macro. It takes an AST node `x`, and returns a new AST node
// that squares the constant integer.
#![(defun square_sage_const(x) {
    (define n x@"ConstExpr"@"Int")
    ["ConstExpr" ["Int" (* n n)]]
})]

// Now we can use the macro to square our constant integer!
println(#[square_sage_const] 5);
Output:
25

You can write entire programs that compile and run at compile-time – the possibilities are endless! If you want to inspect the AST of a value being compiled, you can define a macro that simply prints the AST-node and returns it.

#![{
    (println "Why not just use Sage as a Sage-Lisp interpreter?")

    (defun fact(n) {
        (if n <= 1
            {1}
            {n * (fact n - 1)})
    })

    (println (fact 5))
}]
Output:
Why not just use Sage as a Sage-Lisp interpreter?
120

You can learn more about Sage-Lisp at the repository here.

Documentation - This article is part of a series.
Part 15: This Article