Macros
Mirah supports macros for compile time code generation. Macros are functions that operate on nodes of the parsed Mirah AST at compile time and return a new node for the tree. In this way, they are much more similar (especially in expressivity) to macros in Lisp than those implemented by a C pre-processor (which simply operate on text strings).
Macros are defined by prefixing a function definition with “macro”, and macro functions must return an instance of Node.
Examples
Ruby style attr_reader/attr_writer macros
class Foo
macro def attr_reader(name_node, type)
name = name_node.string_value
quote {
def `name`
@`name`
end
}
end
macro def attr_writer(name_node, type)
name = name_node.string_value
quote do
def `"#{name}_set"`(value:`type`)
@`name` = value
end
end
end
end
class Bar < Foo
attr_reader :size, :int
attr_writer :size, :int
end
b = Bar.new
b.size = 4
puts "b has size #{b.size}"
There are two basic mechanisms to notice in these examples: quote
and the backtick unquote operators. quote
takes a block and returns it’s AST representation (a Node) while the backticks allow you to “unquote” expressions in the block so that they will be replaced with whatever was passed to the macro. So when the parser sees a macro call (e.g. attr_reader :size, :int
) it replaces it with the quoted block to produce something like:
def size:int
@size
end
This all happens at compile-time, meaning your code runs as fast as if you had written out the boilerplate yourself, (e.g. no startup penalty)
Another example–renaming methods so they have java friendlier names:
macro def foo?
quote { isFoo }
end
class A
def foo?
true
end
end
puts A.new.foo? # outputs true