To make things easier on
Ruby has separate nodes for each of its three method call syntaxes: a NODE_CALL is built for
obj.call
, a NODE_FCALL is built for call(args)
and a NODE_VCALL is built for a plain call
. These we've collapsed into a single call node that uses nil for unused fields.A method definition is a NODE_DEFN which contains a NODE_SCOPE, which may or may not contain a NODE_BLOCK, but in either case contains a NODE_ARGS to hold the list of arguments to the method. We've pulled the args up to be a direct child of the defn node.
NODE_CASE and NODE_WHEN are rewritten as nested if statements.
We still have some cleanups to be made that will make writing an interpreter easier, arg lists are held inside a NODE_ARGS, which could be flattened out inline, for example in a NODE_CALL:
[:call, [:gvar, "$stderr"], "fputs", [:array, [:str, "blah"]]]
Is currently rewritten to:
[:call, "fputs", [:gvar, "$stderr"], [:array, [:str, "blah"]]]
But could be rewritten to:
[:call, "fputs", [:gvar, "$stderr"], [:str, "blah"]]
Or:
[:call, "fputs", [:gvar, "$stderr"], 1, [:str, "blah"]]
Note that after rewriting a NODE_VCALL currently looks like:
[:call, "puts", nil, nil]
And a NODE_FCALL looks like:
[:call, "puts", nil, [:array, [:str, "blah"]]]
Now, why is Ruby's AST so complicated? Well, an RNode contains some flags, a string, and three fields. With only three fields some trade-offs needed to be made, and I imagine the designers traded memory for simplicity.
Since we have not yet written an interpreter for our rewritten Sexp, some of the magic of eval.c may take on more clarity.