#!/usr/local/bin/ruby -w

begin require 'rubygems'; rescue LoadError; end

require 'inline'

##
# ParseTree is a RubyInline-style extension that accesses and
# traverses the internal parse tree created by ruby.
#
#   class Example
#     def blah
#       return 1 + 1
#     end
#   end
#
#   ParseTree.new.parse_tree(Example)
#   => [[:class, :Example, :Object,
#          [:defn,
#            "blah",
#            [:scope,
#              [:block,
#                [:args],
#                [:return, [:call, [:lit, 1], "+", [:array, [:lit, 1]]]]]]]]]

class ParseTree

  VERSION = '1.7.0'

  ##
  # Front end translation method.

  def self.translate(klass_or_str, method=nil)
    pt = self.new(false)
    case klass_or_str
    when String
      pt.parse_tree_for_string(klass_or_str).first
    else
      unless method.nil? then
        if method.to_s =~ /^self\./ then
          method = method.to_s[5..-1].intern
          pt.parse_tree_for_method(klass_or_str, method, true)
        else
          pt.parse_tree_for_method(klass_or_str, method)
        end
      else
        pt.parse_tree(klass_or_str).first
      end
    end
  end

  ##
  # Initializes a ParseTree instance. Includes newline nodes if
  # +include_newlines+ which defaults to +$DEBUG+.

  def initialize(include_newlines=$DEBUG)
    if include_newlines then
      warn "WARNING: include_newlines=true from #{caller[0..9].join(', ')}"
    end
    @include_newlines = include_newlines
  end

  ##
  # Main driver for ParseTree. Returns an array of arrays containing
  # the parse tree for +klasses+.
  #
  # Structure:
  #
  #   [[:class, classname, superclassname, [:defn :method1, ...], ...], ...]
  #
  # NOTE: v1.0 - v1.1 had the signature (klass, meth=nil). This wasn't
  # used much at all and since parse_tree_for_method already existed,
  # it was deemed more useful to expand this method to do multiple
  # classes.

  def parse_tree(*klasses)
    result = []
    klasses.each do |klass|
      # TODO: remove this on v 1.1
      raise "You should call parse_tree_for_method(#{klasses.first}, #{klass}) instead of parse_tree" if Symbol === klass or String === klass
      klassname = klass.name rescue '' # HACK klass.name should never be nil
                                   # Tempfile's DelegateClass(File) seems to
                                   # cause this
      klassname = "UnnamedClass_#{klass.object_id}" if klassname.empty?
      klassname = klassname.to_sym

      code = if Class === klass then
               sc = klass.superclass
               sc_name = ((sc.nil? or sc.name.empty?) ? "nil" : sc.name).intern
               [:class, klassname, [:const, sc_name]]
             else
               [:module, klassname]
             end

      method_names = []
      method_names += klass.instance_methods false
      method_names += klass.private_instance_methods false
      # protected methods are included in instance_methods, go figure!

      method_names.sort.each do |m|
        code << parse_tree_for_method(klass, m.to_sym)
      end

      klass.singleton_methods(false).sort.each do |m|
        code << parse_tree_for_method(klass, m.to_sym, true)
      end

      result << code
    end
    return result
  end

  ##
  # Returns the parse tree for just one +method+ of a class +klass+.
  #
  # Format:
  #
  #   [:defn, :name, :body]

  def parse_tree_for_method(klass, method, is_cls_meth=false)
    $stderr.puts "** parse_tree_for_method(#{klass}, #{method}):" if $DEBUG
    r = parse_tree_for_meth(klass, method.to_sym, @include_newlines, is_cls_meth)
    r[1] = :"self.#{r[1]}" if is_cls_meth
    r
  end

  ##
  # Returns the parse tree for a string +source+.
  #
  # Format:
  #
  #   [[sexps] ... ]

  def parse_tree_for_string(source, filename = nil, line = nil,
                            newlines = false)
    filename ||= '(string)'
    line ||= 1
    return parse_tree_for_str(source, filename, line, newlines)
  end

  if RUBY_VERSION < "1.8.4" then
    inline do |builder|
      builder.add_type_converter("bool", '', '')
      builder.c_singleton "
        bool has_alloca() {
          (void)self;
          #ifdef C_ALLOCA
            return Qtrue;
          #else
            return Qfalse;
          #endif
          }"
    end
  else
    def self.has_alloca
      true
    end
  end


  NODE_NAMES = [
                #  00
                :method, :fbody, :cfunc, :scope, :block,
                :if, :case, :when, :opt_n, :while,
                #  10
                :until, :iter, :for, :break, :next,
                :redo, :retry, :begin, :rescue, :resbody,
                #  20
                :ensure, :and, :or, :not, :masgn,
                :lasgn, :dasgn, :dasgn_curr, :gasgn, :iasgn,
                #  30
                :cdecl, :cvasgn, :cvdecl, :op_asgn1, :op_asgn2,
                :op_asgn_and, :op_asgn_or, :call, :fcall, :vcall,
                #  40
                :super, :zsuper, :array, :zarray, :hash,
                :return, :yield, :lvar, :dvar, :gvar,
                #  50
                :ivar, :const, :cvar, :nth_ref, :back_ref,
                :match, :match2, :match3, :lit, :str,
                #  60
                :dstr, :xstr, :dxstr, :evstr, :dregx,
                :dregx_once, :args, :argscat, :argspush, :splat,
                #  70
                :to_ary, :svalue, :block_arg, :block_pass, :defn,
                :defs, :alias, :valias, :undef, :class,
                #  80
                :module, :sclass, :colon2, :colon3, :cref,
                :dot2, :dot3, :flip2, :flip3, :attrset,
                #  90
                :self, :nil, :true, :false, :defined,
                #  95
                :newline, :postexe, :alloca, :dmethod, :bmethod,
                # 100
                :memo, :ifunc, :dsym, :attrasgn,
                :last
               ]

  if RUBY_VERSION < "1.8.4" then
    NODE_NAMES.delete :alloca unless has_alloca
  end

  if RUBY_VERSION > "1.9" then
    NODE_NAMES.insert NODE_NAMES.index(:hash), :values
    NODE_NAMES.insert NODE_NAMES.index(:defined), :errinfo
    NODE_NAMES.insert NODE_NAMES.index(:last), :prelude, :lambda
    NODE_NAMES.delete :dmethod
    NODE_NAMES[128] = NODE_NAMES.delete :newline
  end

  ############################################################
  # END of rdoc methods
  ############################################################

  inline do |builder|
    builder.add_type_converter("bool", '', '')
    builder.add_type_converter("ID *", '', '')
    builder.add_type_converter("NODE *", '(NODE *)', '(VALUE)')
    builder.include '"intern.h"'
    builder.include '"version.h"'
    builder.include '"rubysig.h"'
    builder.include '"node.h"'
    builder.include '"st.h"'
    builder.include '"env.h"'
    builder.add_compile_flags "-Wall"
    builder.add_compile_flags "-W"
    builder.add_compile_flags "-Wpointer-arith"
    builder.add_compile_flags "-Wcast-qual"
    builder.add_compile_flags "-Wcast-align"
    builder.add_compile_flags "-Wwrite-strings"
    builder.add_compile_flags "-Wmissing-noreturn"
    # NOTE: If you get weird compiler errors like:
    #    dereferencing type-punned pointer will break strict-aliasing rules
    # PLEASE do one of the following:
    # 1) Get me a login on your box so I can repro this and get it fixed.
    # 2) Fix it and send me the patch
    # 3) (quick, but dirty and bad), comment out the following line:
    builder.add_compile_flags "-Werror"
    # NOTE: this flag doesn't work w/ gcc 2.95.x - the FreeBSD default
    # builder.add_compile_flags "-Wno-strict-aliasing"
    # ruby.h screws these up hardcore:
    # builder.add_compile_flags "-Wundef"
    # builder.add_compile_flags "-Wconversion"
    # builder.add_compile_flags "-Wstrict-prototypes"
    # builder.add_compile_flags "-Wmissing-prototypes"
    # builder.add_compile_flags "-Wsign-compare"

    def self.if_version(test, version, str)
      RUBY_VERSION.send(test, version) ? str : ""
    end

    builder.prefix %{
        #define nd_3rd   u3.node
        static unsigned case_level = 0;
    }

    builder.prefix %{
        struct METHOD {
          VALUE klass, rklass;
          VALUE recv;
          ID id, oid;
#{if_version :>, "1.8.2", "int safe_level;"}
          NODE *body;
        };

        struct BLOCK {
          NODE *var;
          NODE *body;
          VALUE self;
          struct FRAME frame;
          struct SCOPE *scope;
          VALUE klass;
          NODE *cref;
          int iter;
          int vmode;
          int flags;
          int uniq;
          struct RVarmap *dyna_vars;
          VALUE orig_thread;
          VALUE wrapper;
          VALUE block_obj;
          struct BLOCK *outer;
          struct BLOCK *prev;
        };
    } unless RUBY_VERSION >= "1.9" # we got matz to add this to env.h

  ##
  # add_to_parse_tree(ary, node, include_newlines, local_variables)

  builder.c_raw %Q@
static void add_to_parse_tree(VALUE ary,
                              NODE * n,
                              VALUE newlines,
                              ID * locals) {
  NODE * volatile node = n;
  NODE * volatile contnode = NULL;
  VALUE old_ary = Qnil;
  VALUE current;
  VALUE node_name;
  static VALUE node_names = Qnil;

  if (NIL_P(node_names)) {
    node_names = rb_const_get_at(rb_const_get_at(rb_cObject,rb_intern("ParseTree")),rb_intern("NODE_NAMES"));
  }

  if (!node) return;

again:

  if (node) {
    node_name = rb_ary_entry(node_names, nd_type(node));
    if (RTEST(ruby_debug)) {
      fprintf(stderr, "%15s: %s%s%s\\n",
        rb_id2name(SYM2ID(node_name)),
        (RNODE(node)->u1.node != NULL ? "u1 " : "   "),
        (RNODE(node)->u2.node != NULL ? "u2 " : "   "),
        (RNODE(node)->u3.node != NULL ? "u3 " : "   "));
    }
  } else {
    node_name = ID2SYM(rb_intern("ICKY"));
  }

  current = rb_ary_new();
  rb_ary_push(ary, current);
  rb_ary_push(current, node_name);

again_no_block:

    switch (nd_type(node)) {

    case NODE_BLOCK:
      if (contnode) {
        add_to_parse_tree(current, node, newlines, locals);
        break;
      }
      contnode = node->nd_next;

      // NOTE: this will break the moment there is a block w/in a block
      old_ary = ary;
      ary = current;
      node = node->nd_head;
      goto again;
      break;

    case NODE_FBODY:
    case NODE_DEFINED:
      add_to_parse_tree(current, node->nd_head, newlines, locals);
      break;

    case NODE_COLON2:
      add_to_parse_tree(current, node->nd_head, newlines, locals);
      rb_ary_push(current, ID2SYM(node->nd_mid));
      break;

    case NODE_MATCH2:
    case NODE_MATCH3:
      add_to_parse_tree(current, node->nd_recv, newlines, locals);
      add_to_parse_tree(current, node->nd_value, newlines, locals);
      break;

    case NODE_BEGIN:
    case NODE_OPT_N:
    case NODE_NOT:
      add_to_parse_tree(current, node->nd_body, newlines, locals);
      break;

    case NODE_IF:
      add_to_parse_tree(current, node->nd_cond, newlines, locals);
      if (node->nd_body) {
        add_to_parse_tree(current, node->nd_body, newlines, locals);
      } else {
        rb_ary_push(current, Qnil);
      }
      if (node->nd_else) {
        add_to_parse_tree(current, node->nd_else, newlines, locals);
      } else {
        rb_ary_push(current, Qnil);
      }
      break;

  case NODE_CASE:
    case_level++;
    if (node->nd_head != NULL) {
      add_to_parse_tree(current, node->nd_head, newlines, locals); /* expr */
    } else {
      rb_ary_push(current, Qnil);
    }
    node = node->nd_body;
    while (node) {
      add_to_parse_tree(current, node, newlines, locals);
      if (nd_type(node) == NODE_WHEN) {                 /* when */
        node = node->nd_next;
      } else {
        break;                                          /* else */
      }
      if (! node) {
        rb_ary_push(current, Qnil);                     /* no else */
      }
    }
    case_level--;
    break;

  case NODE_WHEN:
    if (!case_level) { /* when without case, ie, no expr in case */
      rb_ary_pop(ary); /* reset what current is pointing at */
      node = NEW_CASE(0, node);
      goto again;
    }
    add_to_parse_tree(current, node->nd_head, newlines, locals); /* args */
    if (node->nd_body) {
      add_to_parse_tree(current, node->nd_body, newlines, locals); /* body */
    } else {
      rb_ary_push(current, Qnil);
    }
    break;

  case NODE_WHILE:
  case NODE_UNTIL:
    add_to_parse_tree(current,  node->nd_cond, newlines, locals);
    if (node->nd_body) {
      add_to_parse_tree(current,  node->nd_body, newlines, locals);
    } else {
      rb_ary_push(current, Qnil);
    }
    rb_ary_push(current, node->nd_3rd == 0 ? Qfalse : Qtrue);
    break;

  case NODE_BLOCK_PASS:
    add_to_parse_tree(current, node->nd_body, newlines, locals);
    add_to_parse_tree(current, node->nd_iter, newlines, locals);
    break;

  case NODE_ITER:
  case NODE_FOR:
    add_to_parse_tree(current, node->nd_iter, newlines, locals);
    if (node->nd_var != (NODE *)1
        && node->nd_var != (NODE *)2
        && node->nd_var != NULL) {
      add_to_parse_tree(current, node->nd_var, newlines, locals);
    } else {
      rb_ary_push(current, Qnil);
    }
    add_to_parse_tree(current, node->nd_body, newlines, locals);
    break;

  case NODE_BREAK:
  case NODE_NEXT:
  case NODE_YIELD:
    if (node->nd_stts)
      add_to_parse_tree(current, node->nd_stts, newlines, locals);
    break;

  case NODE_RESCUE:
      add_to_parse_tree(current, node->nd_1st, newlines, locals);
      add_to_parse_tree(current, node->nd_2nd, newlines, locals);
      add_to_parse_tree(current, node->nd_3rd, newlines, locals);
    break;

  // rescue body:
  // begin stmt rescue exception => var; stmt; [rescue e2 => v2; s2;]* end
  // stmt rescue stmt
  // a = b rescue c

  case NODE_RESBODY:
      if (node->nd_3rd) {
        add_to_parse_tree(current, node->nd_3rd, newlines, locals);
      } else {
        rb_ary_push(current, Qnil);
      }
      add_to_parse_tree(current, node->nd_2nd, newlines, locals);
      add_to_parse_tree(current, node->nd_1st, newlines, locals);
    break;

  case NODE_ENSURE:
    add_to_parse_tree(current, node->nd_head, newlines, locals);
    if (node->nd_ensr) {
      add_to_parse_tree(current, node->nd_ensr, newlines, locals);
    }
    break;

  case NODE_AND:
  case NODE_OR:
    add_to_parse_tree(current, node->nd_1st, newlines, locals);
    add_to_parse_tree(current, node->nd_2nd, newlines, locals);
    break;

  case NODE_DOT2:
  case NODE_DOT3:
  case NODE_FLIP2:
  case NODE_FLIP3:
    add_to_parse_tree(current, node->nd_beg, newlines, locals);
    add_to_parse_tree(current, node->nd_end, newlines, locals);
    break;

  case NODE_RETURN:
    if (node->nd_stts)
      add_to_parse_tree(current, node->nd_stts, newlines, locals);
    break;

  case NODE_ARGSCAT:
  case NODE_ARGSPUSH:
    add_to_parse_tree(current, node->nd_head, newlines, locals);
    add_to_parse_tree(current, node->nd_body, newlines, locals);
    break;

  case NODE_CALL:
  case NODE_FCALL:
  case NODE_VCALL:
    if (nd_type(node) != NODE_FCALL)
      add_to_parse_tree(current, node->nd_recv, newlines, locals);
    rb_ary_push(current, ID2SYM(node->nd_mid));
    if (node->nd_args || nd_type(node) != NODE_FCALL)
      add_to_parse_tree(current, node->nd_args, newlines, locals);
    break;

  case NODE_SUPER:
    add_to_parse_tree(current, node->nd_args, newlines, locals);
    break;

  case NODE_BMETHOD:
    {
      struct BLOCK *data;
      Data_Get_Struct(node->nd_cval, struct BLOCK, data);
      add_to_parse_tree(current, data->var, newlines, locals);
      add_to_parse_tree(current, data->body, newlines, locals);
      break;
    }
    break;

#{if_version :>, "1.9", '#if 0'}
  case NODE_DMETHOD:
    {
      struct METHOD *data;
      Data_Get_Struct(node->nd_cval, struct METHOD, data);
      rb_ary_push(current, ID2SYM(data->id));
      add_to_parse_tree(current, data->body, newlines, locals);
      break;
    }
#{if_version :>, "1.9", '#endif'}

  case NODE_METHOD:
    fprintf(stderr, "u1 = %p u2 = %p u3 = %p\\n", node->nd_1st, node->nd_2nd, node->nd_3rd);
    add_to_parse_tree(current, node->nd_3rd, newlines, locals);
    break;

  case NODE_SCOPE:
    add_to_parse_tree(current, node->nd_next, newlines, node->nd_tbl);
    break;

  case NODE_OP_ASGN1:
    add_to_parse_tree(current, node->nd_recv, newlines, locals);
    add_to_parse_tree(current, node->nd_args->nd_2nd, newlines, locals);
    switch (node->nd_mid) {
    case 0:
      rb_ary_push(current, ID2SYM(rb_intern("||")));
      break;
    case 1:
      rb_ary_push(current, ID2SYM(rb_intern("&&")));
      break;
    default:
      rb_ary_push(current, ID2SYM(node->nd_mid));
      break;
    }
    add_to_parse_tree(current, node->nd_args->nd_head, newlines, locals);
    break;

  case NODE_OP_ASGN2:
    add_to_parse_tree(current, node->nd_recv, newlines, locals);
    rb_ary_push(current, ID2SYM(node->nd_next->nd_aid));

    switch (node->nd_next->nd_mid) {
    case 0:
      rb_ary_push(current, ID2SYM(rb_intern("||")));
      break;
    case 1:
      rb_ary_push(current, ID2SYM(rb_intern("&&")));
      break;
    default:
      rb_ary_push(current, ID2SYM(node->nd_next->nd_mid));
      break;
    }

    add_to_parse_tree(current, node->nd_value, newlines, locals);
    break;

  case NODE_OP_ASGN_AND:
  case NODE_OP_ASGN_OR:
    add_to_parse_tree(current, node->nd_head, newlines, locals);
    add_to_parse_tree(current, node->nd_value, newlines, locals);
    break;

  case NODE_MASGN:
    add_to_parse_tree(current, node->nd_head, newlines, locals);
    if (node->nd_args && node->nd_args != (NODE *)-1) {
      add_to_parse_tree(current, node->nd_args, newlines, locals);
    }
    add_to_parse_tree(current, node->nd_value, newlines, locals);
    break;

  case NODE_LASGN:
  case NODE_IASGN:
  case NODE_DASGN:
  case NODE_DASGN_CURR:
  case NODE_CDECL:
  case NODE_CVASGN:
  case NODE_CVDECL:
  case NODE_GASGN:
    rb_ary_push(current, ID2SYM(node->nd_vid));
    add_to_parse_tree(current, node->nd_value, newlines, locals);
    break;

  case NODE_VALIAS:           // u1 u2 (alias $global $global2)
    rb_ary_push(current, ID2SYM(node->u1.id));
    rb_ary_push(current, ID2SYM(node->u2.id));
    break;
  case NODE_ALIAS:            // u1 u2 (alias :blah :blah2)
    add_to_parse_tree(current, node->nd_1st, newlines, locals);
    add_to_parse_tree(current, node->nd_2nd, newlines, locals);
    break;

  case NODE_UNDEF:            // u2    (undef name, ...)
    add_to_parse_tree(current, node->nd_value, newlines, locals);
    break;

  case NODE_COLON3:           // u2    (::OUTER_CONST)
    rb_ary_push(current, ID2SYM(node->u2.id));
    break;

  case NODE_HASH:
    {
      NODE *list;

      list = node->nd_head;
      while (list) {
        add_to_parse_tree(current, list->nd_head, newlines, locals);
        list = list->nd_next;
        if (list == 0)
          rb_bug("odd number list for Hash");
        add_to_parse_tree(current, list->nd_head, newlines, locals);
        list = list->nd_next;
      }
    }
    break;

  case NODE_ARRAY:
      while (node) {
        add_to_parse_tree(current, node->nd_head, newlines, locals);
        node = node->nd_next;
      }
    break;

  case NODE_DSTR:
  case NODE_DSYM:
  case NODE_DXSTR:
  case NODE_DREGX:
  case NODE_DREGX_ONCE:
    {
      NODE *list = node->nd_next;
      rb_ary_push(current, rb_str_new3(node->nd_lit));
      while (list) {
        if (list->nd_head) {
          switch (nd_type(list->nd_head)) {
          case NODE_STR:
            add_to_parse_tree(current, list->nd_head, newlines, locals);
            break;
          case NODE_EVSTR:
            add_to_parse_tree(current, list->nd_head->nd_body, newlines, locals);
            break;
          default:
            add_to_parse_tree(current, list->nd_head, newlines, locals);
            break;
          }
        }
        list = list->nd_next;
      }
    }
    break;

  case NODE_DEFN:
  case NODE_DEFS:
    if (node->nd_defn) {
      if (nd_type(node) == NODE_DEFS)
        add_to_parse_tree(current, node->nd_recv, newlines, locals);
      rb_ary_push(current, ID2SYM(node->nd_mid));
      add_to_parse_tree(current, node->nd_defn, newlines, locals);
    }
    break;

  case NODE_CLASS:
  case NODE_MODULE:
    rb_ary_push(current, ID2SYM((ID)node->nd_cpath->nd_mid));
    if (nd_type(node) == NODE_CLASS) {
      if (node->nd_super) {
        add_to_parse_tree(current, node->nd_super, newlines, locals);
      } else {
        rb_ary_push(current, Qnil);
      }
    }
    add_to_parse_tree(current, node->nd_body, newlines, locals);
    break;

  case NODE_SCLASS:
    add_to_parse_tree(current, node->nd_recv, newlines, locals);
    add_to_parse_tree(current, node->nd_body, newlines, locals);
    break;

  case NODE_ARGS: {
    long arg_count = (long)node->nd_rest;
    if (locals && (node->nd_cnt || node->nd_opt || arg_count != -1)) {
      int i;
      int max_args;
      NODE *optnode;

      max_args = node->nd_cnt;
      for (i = 0; i < max_args; i++) {
        // regular arg names
        rb_ary_push(current, ID2SYM(locals[i + 3]));
      }

      optnode = node->nd_opt;
      while (optnode) {
        // optional arg names
        rb_ary_push(current, ID2SYM(locals[i + 3]));
        i++;
        optnode = optnode->nd_next;
      }

      if (arg_count > 0) {
        // *arg name
        VALUE sym = rb_str_intern(rb_str_plus(rb_str_new2("*"), rb_str_new2(rb_id2name(locals[i + 3]))));
        rb_ary_push(current, sym);
      } else if (arg_count == 0) {
        // nothing to do in this case, empty list
      } else if (arg_count == -1) {
        // nothing to do in this case, handled above
      } else if (arg_count == -2) {
        // nothing to do in this case, no name == no use
      } else {
        rb_raise(rb_eArgError,
                 "not a clue what this arg value is: %ld", arg_count);
      }

      optnode = node->nd_opt;
      // block?
      if (optnode) {
        add_to_parse_tree(current, node->nd_opt, newlines, locals);
      }
    }
  }  break;

  case NODE_LVAR:
  case NODE_DVAR:
  case NODE_IVAR:
  case NODE_CVAR:
  case NODE_GVAR:
  case NODE_CONST:
  case NODE_ATTRSET:
    rb_ary_push(current, ID2SYM(node->nd_vid));
    break;

  case NODE_XSTR:             // u1    (%x{ls})
  case NODE_STR:              // u1
  case NODE_LIT:
    rb_ary_push(current, node->nd_lit);
    break;

  case NODE_MATCH:            // u1 -> [:lit, u1]
    {
      VALUE n = rb_ary_new();
      rb_ary_push(n, ID2SYM(rb_intern("lit")));
      rb_ary_push(n, node->nd_lit);
      rb_ary_push(current, n);
    }
    break;

  case NODE_NEWLINE:
    rb_ary_push(current, INT2FIX(nd_line(node)));
    rb_ary_push(current, rb_str_new2(node->nd_file));

    if (! RTEST(newlines)) rb_ary_pop(ary); // nuke it

    node = node->nd_next;
    goto again;
    break;

  case NODE_NTH_REF:          // u2 u3 ($1) - u3 is local_cnt('~') ignorable?
    rb_ary_push(current, INT2FIX(node->nd_nth));
    break;

  case NODE_BACK_REF:         // u2 u3 ($& etc)
    {
    char c = node->nd_nth;
    rb_ary_push(current, rb_str_intern(rb_str_new(&c, 1)));
    }
    break;

  case NODE_BLOCK_ARG:        // u1 u3 (def x(&b)
    rb_ary_push(current, ID2SYM(node->u1.id));
    break;

  // these nodes are empty and do not require extra work:
  case NODE_RETRY:
  case NODE_FALSE:
  case NODE_NIL:
  case NODE_SELF:
  case NODE_TRUE:
  case NODE_ZARRAY:
  case NODE_ZSUPER:
  case NODE_REDO:
    break;

  case NODE_SPLAT:
  case NODE_TO_ARY:
  case NODE_SVALUE:             // a = b, c
    add_to_parse_tree(current, node->nd_head, newlines, locals);
    break;

  case NODE_ATTRASGN:           // literal.meth = y u1 u2 u3
    // node id node
    if (node->nd_1st == RNODE(1)) {
      add_to_parse_tree(current, NEW_SELF(), newlines, locals);
    } else {
      add_to_parse_tree(current, node->nd_1st, newlines, locals);
    }
    rb_ary_push(current, ID2SYM(node->u2.id));
    add_to_parse_tree(current, node->nd_3rd, newlines, locals);
    break;

  case NODE_EVSTR:
    add_to_parse_tree(current, node->nd_2nd, newlines, locals);
    break;

  case NODE_POSTEXE:            // END { ... }
    // Nothing to do here... we are in an iter block
    break;

  case NODE_CFUNC:
    rb_ary_push(current, INT2FIX(node->nd_cfnc));
    rb_ary_push(current, INT2FIX(node->nd_argc));
    break;

#{if_version :<, "1.9", "#if 0"}
  case NODE_ERRINFO:
  case NODE_VALUES:
  case NODE_PRELUDE:
  case NODE_LAMBDA:
    puts("no worky in 1.9 yet");
    break;
#{if_version :<, "1.9", "#endif"}

  // Nodes we found but have yet to decypher
  // I think these are all runtime only... not positive but...
  case NODE_MEMO:               // enum.c zip
  case NODE_CREF:
  case NODE_IFUNC:
  // #defines:
  // case NODE_LMASK:
  // case NODE_LSHIFT:
  default:
    rb_warn("Unhandled node #%d type '%s'", nd_type(node), rb_id2name(SYM2ID(rb_ary_entry(node_names, nd_type(node)))));
    if (RNODE(node)->u1.node != NULL) rb_warning("unhandled u1 value");
    if (RNODE(node)->u2.node != NULL) rb_warning("unhandled u2 value");
    if (RNODE(node)->u3.node != NULL) rb_warning("unhandled u3 value");
    if (RTEST(ruby_debug)) fprintf(stderr, "u1 = %p u2 = %p u3 = %p\\n", node->nd_1st, node->nd_2nd, node->nd_3rd);
    rb_ary_push(current, INT2FIX(-99));
    rb_ary_push(current, INT2FIX(nd_type(node)));
    break;
  }

 //  finish:
  if (contnode) {
      node = contnode;
      contnode = NULL;
      current = ary;
      ary = old_ary;
      old_ary = Qnil;
      goto again_no_block;
  }
}
@ # end of add_to_parse_tree block

    builder.c %Q{
static VALUE parse_tree_for_meth(VALUE klass, VALUE method, VALUE newlines, VALUE is_cls_meth) {
  VALUE n;
  NODE *node = NULL;
  ID id;
  VALUE result = rb_ary_new();
  VALUE version = rb_const_get_at(rb_cObject,rb_intern("RUBY_VERSION"));

  (void) self; // quell warnings

  if (strcmp(StringValuePtr(version), #{RUBY_VERSION.inspect})) {
    rb_fatal("bad version, %s != #{RUBY_VERSION}\\n", StringValuePtr(version));
  }

  id = rb_to_id(method);
  if (RTEST(is_cls_meth)) { // singleton method
    klass = CLASS_OF(klass);
  }
  if (st_lookup(RCLASS(klass)->m_tbl, id, &n)) {
    node = (NODE*)n;
    rb_ary_push(result, ID2SYM(rb_intern("defn")));
    rb_ary_push(result, ID2SYM(id));
    add_to_parse_tree(result, node->nd_body, newlines, NULL);
  } else {
    rb_ary_push(result, Qnil);
  }

  return result;
}
}

    builder.prefix " extern NODE *ruby_eval_tree_begin; " \
      if RUBY_VERSION < '1.9.0'

    builder.c %Q{
static VALUE parse_tree_for_str(VALUE source, VALUE filename, VALUE line,
                                   VALUE newlines) {
  VALUE tmp;
  VALUE result = rb_ary_new();
  NODE *node = NULL;
  int critical;

  (void) self; // quell warnings

  tmp = rb_check_string_type(filename);
  if (NIL_P(tmp)) {
    filename = rb_str_new2("(string)");
  }

  if (NIL_P(line)) {
    line = LONG2FIX(1);
  }

  newlines = RTEST(newlines);

  ruby_nerrs = 0;
  StringValue(source);
  critical = rb_thread_critical;
  rb_thread_critical = Qtrue;
  ruby_in_eval++;
  node = rb_compile_string(StringValuePtr(filename), source, NUM2INT(line));
  ruby_in_eval--;
  rb_thread_critical = critical;

  if (ruby_nerrs > 0) {
    ruby_nerrs = 0;
#if RUBY_VERSION_CODE < 190
    ruby_eval_tree_begin = 0;
#endif
    rb_exc_raise(ruby_errinfo);
  }

  add_to_parse_tree(result, node, newlines, NULL);

  return result;
}
}

  end # inline call
end # ParseTree class
