unparser¶ ↑
Generate equivalent source for ASTs from parser.
The following constraints apply:
-
No support for macruby extensions
-
Only support for the modern AST format
-
Only support for Ruby >= 2.5
Notable Users:
-
mutant - Code review engine via mutation testing.
-
ruby-next - Ruby Syntax Backports.
-
May other reverse-dependencies.
(if you want your tool to be mentioned here please PR the addition with a TLDR of your use case).
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.
Usage¶ ↑
require 'parser/current' require 'unparser' ast = Unparser.parse('your(ruby(code))') Unparser.unparse(ast) # => 'your(ruby(code))'
To preserve the comments from the source:
require 'parser/current' require 'unparser' ast, comments = Unparser.parse_with_comments('your(ruby(code)) # with comments') Unparser.unparse(ast, comments) # => 'your(ruby(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) end end include YourHelper node = s(:def, :foo, s(:args, s(:arg, :x) ), s(:send, 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' node = Unparser.parse(<<~'RUBY') %w[foo bar] RUBY generated = Unparser.unparse(node) # ["foo", "bar"], NOT %w[foo bar] ! code == generated # false, not identical code Unparser.parse(generated) == node # true, but identical AST
Summary: unparser does not reproduce your source! It produces equivalent source.
Ruby Versions:¶ ↑
Unparsers primay reason for existance is mutant and its supported Ruby-Versions.
Basically: All non EOL MRI releases.
If you need to generate Ruby Syntax outside of this band feel free to contact me (email in gemspec).
Testing:¶ ↑
Unparser
currently successfully round trips almost all ruby code around. Using MRI-2.5.x. 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.
Limitations:¶ ↑
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.
A fix is possible as with latest updates the parser gem carries the information.
Example:
Original-Source:
# -*- encoding: binary -*- "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF"
Original-AST:
(str "\x98v\xAB\xCDE2\xEF\x01\x01#Eg\x89\xAB\xCD\xEF")
Generated-Source:
"\x98v\xAB\xCDE2\xEF\x01\x01#Eg\x89\xAB\xCD\xEF"
Generated-AST:
(str "\x98v\xAB\xCDE2\xEF\u0001\u0001#Eg\x89\xAB\xCD\xEF")
Diff:
@@ -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")
Installation¶ ↑
Install the gem unparser
via your prefered method.
People¶ ↑
Various people contributed to this repository. See Contributors.
Included Libraries¶ ↑
For dependency reduction reasons unparser ships vendored (and reduced) versions of:
Contributing¶ ↑
-
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.
License¶ ↑
See LICENSE file.