Project

macros

0.01
No commit activity in last 3 years
No release in over 3 years
Macros for Ruby
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 3.0

Runtime

 Project Readme

Macros

Macros for Ruby

Install

gem install macros

Usage

Any source file that contains macro definitions, or that requires macro expansion, should be loaded with Macros.require.

require 'macros'

Macros.require 'my_macros'
Macros.require 'my_code_using_macros'

Macros look like normal method definitions, but they are defined inside a Macros do ; end block.

# my_macros.rb

Macros do
  # Replace the +output+ method with the +puts+ method
  def with_output(ast)
    treemap(ast) do |node|
      if smatch? node, s(:send, nil, :output)
        s(:send, nil, :puts, *node.children.drop(2))
      else
        node
      end
    end
  end
end

Now this code

# my_code_using_macros.rb

with_output do
  output "foo"
  output "bar"
end

Will be transformed into

puts "foo"
puts "bar"

AST basics

AST stands for Abstract Syntax Tree, a way of representing the structure of code as data.

The macro will receive an instance of Parser::AST::Node, and must return an instance of Parser::AST::Node. A Node consists of a type and zero or more children. You can use Macros.parse and Macros.unparse to experiment.

For example

obj.do_thing(x, y)

parses to

s(:send,
  s(:send, nil, :obj), :do_thing,
  s(:send, nil, :x),
  s(:send, nil, :y))

Helpers

Inside the macro definition the following convenience functions are available:

s(type, *children)

Construct an AST node of given type, with specific children.

node?(n)

Is the given object an AST node?

treemap(node, &tranform)

Similar to Enumerable#map, but performs a full tree walk, passing any AST::Node to the block.

treefilter(node, &pred)

Returns an array of any node in the tree that satisfies the predicate

sfind(node, spec)

spec is an Array of symbols, integers, and arrays. It is used a bit like XPath or CSS locators.

node = s(:def, :a_name, s(:args, s(:arg, :x), s(:arg, :y)))
sfind(node, [:def, 1, :args, [:arg, 0]])
# => [:x, y]

smatch?(node, pattern)

Checks if the node matches the "pattern"

node = s(:def, :a_name, s(:args, s(:arg, :x), s(:arg, :y)))
smatch?(node, s(:def, :a_name))
# => true

Is this a joke?

Well, it works, but it's a toy. Working with Ruby syntax trees is pretty awkward, and macros can easily lead to a mess. You have been warned!

License

© Arne Brasseur 2015

Mozilla Public License Version 2.0. See LICENSE file.