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