# Metaprogramming Puck has rich metaprogramming support, heavily inspired by Nim. Many features that would have to be at the compiler level in most languages (error propagation `?`, `std.fmt.print`, `?`, `!`, `->` type sugar, `=>` closure sugar, `async`/`await`) are instead implemented as macros within the standard library. Macros take in fragments of the AST within their scope, transform them with arbitrary compile-time code, and spit back out transformed AST fragments to be injected and checked for validity. This is similar to what the Lisp family of languages do. It has a number of benefits: there is no separate metaprogramming language, it is syntactically and semantically hygienic, and the underlying framework can be reused for all kinds of compile-time code execution. By keeping an intentionally minimal AST, some things not possible to express in literal code may be expressible in the AST: in particular, bindings can be injected in many places they could not be injected in ordinarily. (A minimal AST also has the benefit of being quite predictable.) ## Scope Macros may not change Puck's syntax: the syntax is flexible enough. They have the same scope as other routines, that is: **function scope**: takes the arguments within or following a function call ```puck macro print(params: varargs) = var res = Call("write", [stdout]) for param in params do res.params.add(param) print(1, 2, 3, 4) print "hello", " ", "world", "!" ``` **block scope**: takes the expression following a colon as a single argument ```puck macro my_macro(body) my_macro 1 2 3 4 ``` **operator scope**: takes one or two parameters either as an infix (two parameters) or a postfix (one parameter) operator ```puck # operators are restricted to punctuation macro +=(a, b) = Call("=", [a, Call("+", [a, b])]) a += b ``` ## Usage Macros typically take a list of parameters *without* types, but they optionally may be given a type to constrain the usage of a macro. Regardless: as macros operate at compile time, their parameters are not instances of a type, but rather an `Expr` expression representing a portion of the *abstract syntax tree*. Similarly, macros always return an `Expr` to be injected into the abstract syntax tree despite the usual absence of an explicit return type, but the return type may be specified to additionally typecheck the returned `Expr`. ```puck ``` As macros operate at compile time, they may not inspect the *values* that their parameters evaluate to. However, parameters may be marked `const`: in which case they will be treated like parameters in functions: as values. (note constant parameters may be written as `const[T]` or `const T`.) ```puck macro ?[T, E](self: Result[T, E]) = quote match `self` of Okay(x) then x of Error(e) then return Error(e) func meow: Result[bool, ref Err] = let a = stdin.get()? ``` ## Quoting The `quote` macro is special. It takes in literal code and returns that code **as the AST**. Within quoted data, backticks may be used to break out in order to evaluate and inject arbitrary code: though the code must evaluate to an expression of type `Expr`. Thus, quoting is *structured*: one cannot simply quote any arbitrary section. Quoting is very powerful: most macros are implemented using it. ```puck ``` The `Expr` type is available from `std.ast`, as are many helpers, and combined they provide the construction of arbitrary syntax trees (indeed, `quote` relies on and emits types of it). It is a `union` type with its variants directly corresponding to the variants of the internal AST of Puck. ```puck ``` Construction of macros can be difficult: and so several helpers are provided to ease debugging. The `Debug` and `Display` interfaces are implemented for abstract syntax trees: `dbg` will print a representation of the passed syntax tree as an object, and `print` will print a best-effort representation as literal code. Together with `quote` and optionally with `const`, these can be used to quickly get the representation of arbitrary code.