Project

sirop

0.01
The project is in a healthy, maintained state
Sirop: Ruby code rewriter
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 5.22.0

Runtime

~> 0.27.0
 Project Readme

Sirop

Sirop is a Ruby gem for manipulating Ruby source code. Sirop is very young, so the following information might be incomplete, out of date, or simply wrong!

Use Cases

Some of the use cases addressed by Sirop are:

  • Compile DSLs into optimized Ruby code. This is especially interesting for HTML templating DSLs in libraries like Phlex, Papercraft etc. Example
  • Get the source of a given block or method.
  • Rewrite parts of Ruby code, for implementing Ruby macros (and why not?).

Limitations

  • Sirop supports Ruby 3.2 or newer.
  • Sirop can be used only on blocks and methods defined in a file, so cannot really be used on dynamically eval'd Ruby code, or in an IRB/Pry session.

Getting the AST/source of a Ruby proc or method

To get the AST of a proc or a method, use Sirop.to_ast:

# for a proc
mul = ->(x, y) { x * y }
Sirop.to_ast(mul) #=> ...

# for a method
def foo; :bar; end
Sirop.to_ast(method(:foo)) #=> ...

To get the source of a proc or a method, use Sirop.to_source:

mul = ->(x, y) { x * y }
Sirop.to_source(mul) #=> "->(x, y) { x * y }"

def foo; :bar; end
Sirop.to_source(method(:foo)) #=> "def foo; :bar; end"

Rewriting Ruby code

You can consult the DSL compiler example. This example intercepts method calls by defining a visit_call_node method:

# Annotated with some explanations
def visit_call_node(node)
  # don't rewrite if the call has a receiver
  return super if node.receiver

  # set HTML location start
  @html_location_start ||= node.location
  # get method arguments...
  inner_text, attrs = tag_args(node)
  # and block
  block = node.block

  # emit HTML tag according to given arguments
  if inner_text
    emit_tag_open(node, attrs)
    emit_tag_inner_text(inner_text)
    emit_tag_close(node)
  elsif block
    emit_tag_open(node, attrs)
    visit(block.body)
    emit_tag_close(node)
  else
    emit_tag_open_close(node, attrs)
  end
  # set HTML location end
  @html_location_end = node.location
end

Future directions

  • Implement a macro expander with support for quote/unquote:

    trace_macro = Sirop.macro do |ast|
      source = Sirop.to_source(ast)
      quote do
        result = unquote(ast)
        puts "The result of #{source} is: #{result}"
        result
      end
    end
    
    def add(x, y)
      trace(x + y)
    end
    
    Sirop.expand_macros(method(:add), trace: trace_macro)
  • Implement a DSL compiler with hooks for easier usage in DSL libraries.

Contributing

We gladly welcome contributions from anyone! Some areas that need work currently are:

Please feel free to contribute PR's and issues