Turn ruby AST into equivalent ruby source
Clone or download
Latest commit 7d22ef2 Jul 18, 2018
Failed to load latest commit information.
bin Remove redundant encoding comments Sep 10, 2015
config Fix latest violations found by Devtools Jan 22, 2017
lib Add emitters for s(:__LINE__) and s(:__FILE__) Jul 18, 2018
spec Add emitters for s(:__LINE__) and s(:__FILE__) Jul 18, 2018
.rspec Add warnings flag to rspec config Apr 20, 2014
.rubocop.yml Fix rubocop config format Jun 8, 2014
Changelog.md Bump to v0.2.8 Jul 18, 2018
Gemfile Remove mutant git source Jan 25, 2016
Gemfile.lock Relax version requirements on parser gem to allow using parser v2.5.0 Mar 8, 2018
LICENSE Add LICENSE Jun 28, 2013
Rakefile Fix latest violations found by Devtools Jan 22, 2017
circle.yml Fix circle config to build latest ruby Apr 13, 2015
unparser.gemspec Bump to v0.2.8 Jul 18, 2018



Build Status Dependency Status Code Climate Gem Version

Generate equivalent source for ASTs from whitequarks parser. Excluding the macruby extensions the parser gem implemnents on top of ruby syntax.

Excluding the MacRuby / RubyMotion extensions the parser gem implemenents on top of MRI Ruby syntax starting with parser release 2.3. If you feel the need to get them supported, contact me.

This library is able to reproduce 100% of Ruby 2.1 - 2.3 syntax. Including its own source code.

It serves well for mutant mutators and the in-memory vendoring for self hosting, and other tooling.

Public API:

While unparser is in the 0.x versions its public API can change any moment. I recommend to use ~> 0.x.y style version constraints that should give the best mileage.


require 'unparser'
Unparser.unparse(your_ast) # => "the code"

To preserve the comments from the source:

require 'parser/current'
require 'unparser'
ast, comments = Parser::CurrentRuby.parse_with_comments(your_source)
Unparser.unparse(ast, comments) # => "the code # with comments"

Passing in manually constructed AST:

require 'parser/current'
require 'unparser'

module YourHelper
  def s(type, *children)
    Parser::AST::Node.new(type, children)

include YourHelper

node = s(:def,
    s(:arg, :x)
    s(:lvar, :x),
    s(:int, 3)

Unparser.unparse(node) # => "def foo(x)\n  x + 3\nend"

Note: DO NOT attempt to pass in nodes generated via AST::Sexp#s, these ones return API incompatible AST::Node instances, unparser needs Parser::AST::Node instances.

Equivalent vs identical:

require 'unparser'

code = <<-RUBY
%w(foo bar)

node = Parser::CurrentRuby.parse(code)

generated = Unparser.unparse(node) # ["foo", "bar"], NOT %w(foo bar) !

code == generated                            # false, not identical code
Parser::CurrentRuby.parse(generated) == node # true, but identical AST

Summary: unparser does not reproduce your source! It produces equivalent source.


Unparser currently successfully round trips almost all ruby code around. Using MRI-2.0.0. If there is a non round trippable example that is NOT subjected to known Limitations. please report a bug.

On CI unparser is currently tested against rubyspec with minor excludes.


Source parsed with magic encoding headers other than UTF-8 and that have literal strings. where parts can be represented in UTF-8 will fail to get reproduced.

Please note: If you are on 1.9.3 or any 1.9 mode ruby and use UTF-8 encoded source via the magic encoding header: Unparser does not reproduce these.

A fix might be possible and requires some guessing or parser metadata the raw AST does not carry.



# -*- encoding: binary -*-



(str "\x98v\xAB\xCDE2\xEF\x01\x01#Eg\x89\xAB\xCD\xEF")




(str "\x98v\xAB\xCDE2\xEF\u0001\u0001#Eg\x89\xAB\xCD\xEF")


@@ -1,2 +1,2 @@
-(str "\x98v\xAB\xCDE2\xEF\x01\x01#Eg\x89\xAB\xCD\xEF")
+(str "\x98v\xAB\xCDE2\xEF\u0001\u0001#Eg\x89\xAB\xCD\xEF")


Install the gem unparser via your prefered method.


Various people contributed to this repository. See Contributors.


  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not mess with Rakefile or version (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
  • Send me a pull request. Bonus points for topic branches.


See LICENSE file.