Requirements
------------
* Ruby 1.8.4
* RubyNode (http://rubynode.rubyforge.org/\)
Duplication of effort?
Maybe a bit. But as I said, it started as a toy project to learn about Ruby's internals.
Do you know about ParseTree and Ruby2C?
Yes.
But RubyNode was just a by-product of Ruby2CExtension and it has a different interface than ParseTree.
And Ruby2CExtension is not like Ruby2C, it's more like ZenObfuscate.
Dominik
MMm. I'll be honest, I was kind of disappointed. I was hoping for a response like "Yes, but ParseTree has <X> limitation or uses <Y> style, and RubyNode has a <Z> implementation" :). Ah well.
Then you should have asked for a comparison ;-). Anyway:
When I started working on Ruby2CExtension (around February 2006), I intended to work with Ruby 1.9 and ParseTree didn't support 1.9 back then. So I looked for alternatives.
I first found Ripper, which works in 1.9, but doesn't return the exact node tree. For example "1+1" produces something like [:program, [[:binary, int(1), :+, int(1)]]], while with RubyNode you get [:call, {:mid=>:+, :recv=>[:lit, {:lit=>1}], :args=>[:array, [[:lit, {:lit=>1}]]]}]. And Ripper also is work in progress and has some bigger problems for example with here documents.
Then I found Nodewrap, which worked really nice. But as I progressed with Ruby2CExtension I found that Nodewrap had some problems with some node types, I sent some patches to Paul Brannan and he was actually working on a new release, but didn't have enough time.
So I finally wrote my own node tree accessing library. It had the following design goals:
-as simple as possible, easily maintainable
-read only (because I saw that Nodewrap had to jump through lots of hoops to allow write access)
-compatible to different Ruby versions
-low level and high level access
-get as much information about the node types as possible by parsing Ruby source code
Because of the last point RubyNode is not easily gemifyable, but on the other hand it should easily adapt to changes in Ruby even without changing the RubyNode source code (at least it should never segfault).
Some things that RubyNode can do and ParseTree currently cannot:
-Low level access:
ParseTree only gives you the s-exps, with RubyNode you can get the flags field, the line number and the filename of the node. You can also get each the raw long value of each union if you really want, and so on.
-Access node trees of procs
-Parse arbitrary strings of Ruby code to node trees without evaling them:
ParseTree only allows evaling code and then only provides access to method node trees, with RubyNode you can just do:
irb(main):001:0> pp "p 1;class A; def foo;42;end;end;p 2".parse_to_nodes.transform
[:block,
[[:fcall, {:mid=>:p, :args=>[:array, [[:lit, {:lit=>1}]]]}],
[:class,
{:body=>
[:scope,
{:next=>
[:defn,
{:mid=>:foo,
:defn=>
[:scope,
{:next=>
[:block,
[[:args, {:rest=>-1, :opt=>false, :cnt=>0}],
[:lit, {:lit=>42}]]],
:rval=>false,
:tbl=>nil}],
:noex=>2}],
:rval=>false,
:tbl=>nil}],
:super=>false,
:cpath=>[:colon2, {:mid=>:A, :head=>false}]}],
[:fcall, {:mid=>:p, :args=>[:array, [[:lit, {:lit=>2}]]]}]]]
This feature is actually quite simple and I think it should be added to ParseTree.
As you can see, the pretty printed node tree above is a bit verbose, but the hashes are IMO much more flexible and nicer than accessing the attributes by position.
RubyNode doesn't have an equivalent to SexpProcessor, but it is easy enough to make your own, as I did for Ruby2CExtension. Example:
class NodeProcessor
def process(node)
case node
when false
"Qnil"
else
begin
send("process_#{node.first}", node.last)
rescue
# handle
end
end
end
def process_class(hash)
# ...
end
# ...
end
I hope this answers all your questions.
And while I am at it: Ruby2CExtension vs. ZenObfuscate:
I can't really compare them because I don't have access to ZenObfuscate, but from the announcement:
- Known Limitations
There are issues with what the obfuscator can translate to C
and as a result you may need to modify your code in order to
translate it. Usually this is a pretty straightforward and
simple task. We do a good job of translating static ruby to
its equivalent C, but not all ruby has an equivalent in C.
- Only translates methods in classes and modules, not
freestanding code.
Ruby2CExtension translates free standing code.
- Explicit returns are required in all methods.
Ruby2CExtension doesn't require those.
- Temporary: Conditional logic (including ?
may not be on the
right hand side of an assignment.
No problem in Ruby2CExtension
- Temporaryish: Exception handling and generic block closures
currently don't translate.
They do in Ruby2CExtension (except for some things described at http://ruby2cext.rubyforge.org/limitations.html#section10\)
- Some expressions in ruby we don't currently do, but could
upon request, where some other ruby expressions will never
translate.
I am not sure what Ryan means here, but Ruby2CExtension can translate arbitrary Ruby expressions (again except for things described at http://ruby2cext.rubyforge.org/limitations.html\)
And Ruby2CExtension is free.
Dominik
···
On Tue, 20 Jun 2006 07:42:31 +0200, Logan Capaldo <logancapaldo@gmail.com> wrote:
On Jun 17, 2006, at 2:58 PM, Dominik Bathon wrote:
On Sat, 17 Jun 2006 19:10:17 +0200, Logan Capaldo >> <logancapaldo@gmail.com> wrote:
On Jun 17, 2006, at 10:31 AM, Dominik Bathon wrote: