/macroruby

A very brief and very simple experiment with macros in Ruby.

Primary LanguageRuby

macroruby
=========

A very brief and very simple experiment with macros in Ruby.

Pros:
  * Easy to understand; easy to write new macros
  * User is shielded from different ASTs; no s-exp walking needed
  * Runs with 1.8, 1.9, and jruby (i.e. does not require ParseTree)
  * Errors during macro expansion are verbose and understandable
  * Macros can be recursive and/or mutually referential

Cons:
  * This is a toy
  * Requires invoking macroruby in place of ruby
  * Error messages outside of macro errors have little context
  * Code must be syntax-compatible with ruby_parser

Instructions
============

Write a method which starts with "macro_".  At parse time, the method
is extracted from the source then compiled individually without the
"macro_" prefix.

All arguments passed to a macro are strings.  The first argument is
always the receiver, even for implicit-self invocations, e.g. f().
The arguments are the literal/verbatim expressions which appear as
part of the invocation call.

The output of a macro is a string containing the ruby code to be
inserted at the point of invocation.

A macro ignores a passed block, if one is given.

Examples
========

Setting $DEBUG=true inside macroruby generates the following verbose
output.

% ./macroruby swap.rb
------------------------------------------------------------
* original code:

def macro_swap(receiver, x, y)
  tmp = MacroRuby.gen_string
  %{
    lambda {
      #{tmp} = #{x}
      #{x} = #{y}
      #{y} = #{tmp}
    }.call
  }
end

a = 33
b = 44
swap(a, b)
p a  #=> 44
p b  #=> 33
------------------------------------------------------------
* transformed code:
# do nothing
a = 33
b = 44
lambda do
  __gen_string__47845892465281419903__1 = a
  a = b
  b = __gen_string__47845892465281419903__1
end.call
p(a)
p(b)
------------------------------------------------------------
* run:
44
33

% ./macroruby locals_to_hash.rb 
------------------------------------------------------------
* original code:

def macro_locals_to_hash(receiver, *args)
  args.inject("{") { |acc, arg|
    acc << ":#{arg}=>#{arg},"
  } << "}"
end

a = 33
b = 44
h = locals_to_hash(a, b)
p h  # => { :a => 33, :b => 44 }
------------------------------------------------------------
* transformed code:
# do nothing
a = 33
b = 44
h = { :a => (a), :b => (b) }
p(h)
------------------------------------------------------------
* run:
{:a=>33, :b=>44}

% ./macroruby recursive.rb 
------------------------------------------------------------
* original code:

def macro_fib(receiver, n_str)
  n = n_str.to_i
  if n == 1 or n == 2
    "1"
  else
    %{
      fib(#{n - 1}) + fib(#{n - 2})
    }
  end
end

p fib(1)
p fib(2)
p fib(3)
p fib(4)
p fib(5)
p fib(6)
p fib(7)
------------------------------------------------------------
* transformed code:
# do nothing
p(1)
p(1)
p((1 + 1))
p(((1 + 1) + 1))
p((((1 + 1) + 1) + (1 + 1)))
p(((((1 + 1) + 1) + (1 + 1)) + ((1 + 1) + 1)))
p((((((1 + 1) + 1) + (1 + 1)) + ((1 + 1) + 1)) + (((1 + 1) + 1) + (1 + 1))))
------------------------------------------------------------
* run:
1
1
2
3
5
8
13