CoolFace
Datasetpublic

timlawrenz/gnn-ruby-code-study

GNN Ruby Code Study Systematic study of Graph Neural Network architectures for Ruby code complexity prediction and generation. Paper: Graph Neural Networks for Ruby Code Complexity Prediction and Generation: A Systematic Architecture Study Dataset 22,452 Ruby methods parsed into AST graphs with 74-dimensional node features. Split Samples File Train 19,084 dataset/train.jsonl Validation 3,368 dataset/val.jsonl Each JSONL record contains:… See the full description on the dataset page: https://huggingface.co/datasets/timlawrenz/gnn-ruby-code-study.

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes61downloads
pretty_print_ast.rb955 linesDownload Raw Back to scripts
1#!/usr/bin/env ruby2# frozen_string_literal: true3 4require 'json'5require 'parser/current'6 7# Pretty-printing script to convert AST JSON back to formatted Ruby code8#9# This script takes the JSON representation of an AST (as used in the project's dataset)10# and converts it back into formatted Ruby code that is syntactically equivalent11# to the original source.12 13class ASTPrettyPrinter14  # Main method to convert AST JSON to pretty-printed Ruby code15  #16  # @param ast_json [String, Hash] JSON string or parsed hash representing the AST17  # @return [String] Pretty-printed Ruby code18  def self.ast_to_ruby(ast_json)19    ast_hash = ast_json.is_a?(String) ? JSON.parse(ast_json) : ast_json20    new.convert_node(ast_hash)21  end22 23  # Convert a single AST node to Ruby code24  #25  # @param node [Hash, String, nil] AST node to convert26  # @param indent_level [Integer] Current indentation level27  # @return [String] Ruby code representation28  def convert_node(node, indent_level = 0)29    return '' if node.nil?30    return node.to_s if node.is_a?(String) || node.is_a?(Numeric)31 32    return '' unless node.is_a?(Hash) && node['type']33 34    case node['type']35    when 'def'36      convert_def(node, indent_level)37    when 'args'38      convert_args(node)39    when 'begin'40      convert_begin(node, indent_level)41    when 'send'42      convert_send(node)43    when 'block'44      convert_block(node, indent_level)45    when 'self'46      'self'47    when 'nil'48      'nil'49    when 'true'50      'true'51    when 'false'52      'false'53    when 'str'54      convert_string(node)55    when 'int'56      convert_int(node)57    when 'float'58      convert_float(node)59    when 'sym'60      convert_symbol(node)61    when 'lvar'62      convert_lvar(node)63    when 'ivar'64      convert_ivar(node)65    when 'cvar'66      convert_cvar(node)67    when 'gvar'68      convert_gvar(node)69    when 'const'70      convert_const(node)71    when 'if'72      convert_if(node, indent_level)73    when 'unless'74      convert_unless(node, indent_level)75    when 'while'76      convert_while(node, indent_level)77    when 'until'78      convert_until(node, indent_level)79    when 'for'80      convert_for(node, indent_level)81    when 'case'82      convert_case(node, indent_level)83    when 'when'84      convert_when(node, indent_level)85    when 'return'86      convert_return(node)87    when 'break'88      convert_break(node)89    when 'next'90      convert_next(node)91    when 'yield'92      convert_yield(node)93    when 'and'94      convert_and(node)95    when 'or'96      convert_or(node)97    when 'not'98      convert_not(node)99    when 'array'100      convert_array(node)101    when 'hash'102      convert_hash(node)103    when 'pair'104      convert_pair(node)105    when 'splat'106      convert_splat(node)107    when 'kwsplat'108      convert_kwsplat(node)109    when 'class'110      convert_class(node, indent_level)111    when 'module'112      convert_module(node, indent_level)113    when 'sclass'114      convert_sclass(node, indent_level)115    when 'lvasgn'116      convert_lvasgn(node)117    when 'ivasgn'118      convert_ivasgn(node)119    when 'cvasgn'120      convert_cvasgn(node)121    when 'gvasgn'122      convert_gvasgn(node)123    when 'casgn'124      convert_casgn(node)125    when 'dstr'126      convert_dstr(node)127    when 'dnum'128      convert_dnum(node)129    when 'regexp'130      convert_regexp(node)131    when 'resbody'132      convert_resbody(node, indent_level)133    when 'ensure'134      convert_ensure(node, indent_level)135    when 'retry'136      'retry'137    when 'kwarg'138      convert_kwarg(node)139    when 'kwoptarg'140      convert_kwoptarg(node)141    when 'kwrestarg'142      convert_kwrestarg(node)143    when 'blockarg'144      convert_blockarg(node)145    when 'optarg'146      convert_optarg(node)147    when 'restarg'148      convert_restarg(node)149    when 'arg'150      convert_arg(node)151    else152      # Fallback for unknown node types153      convert_unknown(node)154    end155  end156 157  private158 159  def indent(level)160    '  ' * level161  end162 163  def convert_def(node, indent_level)164    children = node['children'] || []165    method_name = children[0]166    args_node = children[1]167    body_node = children[2]168 169    result = "#{indent(indent_level)}def #{method_name}"170    171    if args_node && args_node['children'] && !args_node['children'].empty?172      result += "(#{convert_node(args_node)})"173    end174    175    result += "\n"176    177    if body_node178      body_code = convert_node(body_node, indent_level + 1)179      result += body_code unless body_code.strip.empty?180    end181    182    result += "\n#{indent(indent_level)}end"183    result184  end185 186  def convert_args(node)187    children = node['children'] || []188    children.map { |child| convert_node(child) }.join(', ')189  end190 191  def convert_begin(node, indent_level)192    children = node['children'] || []193    children.map { |child| 194      code = convert_node(child, indent_level)195      # Add indentation if the code doesn't already have it196      if code.start_with?(indent(indent_level))197        code198      else199        "#{indent(indent_level)}#{code}"200      end201    }.join("\n")202  end203 204  def convert_send(node)205    children = node['children'] || []206    receiver = children[0]207    method_name = children[1]208    args = children[2..-1]209 210    result = ''211    212    if receiver213      result += convert_node(receiver)214      215      # Special case for array access216      if method_name.to_s == '[]'217        result += "[#{args.map { |arg| convert_node(arg) }.join(', ')}]"218        return result219      end220      221      result += '.'222    end223    224    if method_name.to_s.end_with?('=') && args && !args.empty?225      # For assignment methods, handle specially226      base_name = method_name.to_s.chomp('=')227      result += "#{base_name} = #{args.map { |arg| convert_node(arg) }.join(', ')}"228    else229      # Regular method calls230      result += method_name.to_s231      232      if args && !args.empty?233        # For common methods like raise, don't use parentheses234        if should_omit_parentheses?(method_name.to_s)235          result += " #{args.map { |arg| convert_node(arg) }.join(', ')}"236        else237          result += "(#{args.map { |arg| convert_node(arg) }.join(', ')})"238        end239      end240    end241    242    result243  end244 245  def should_omit_parentheses?(method_name)246    # Common methods that look better without parentheses247    common_methods = %w[raise puts print p require require_relative include extend]248    # Operators that should not have parentheses249    operators = %w[+ - * / % ** == != < > <= >= <=> === =~ !~ << >> & | ^ ~ && ||]250    251    common_methods.include?(method_name) || operators.include?(method_name)252  end253 254  def convert_block(node, indent_level)255    children = node['children'] || []256    call_node = children[0]257    block_args = children[1]258    block_body = children[2]259 260    result = convert_node(call_node)261    262    # Determine if we should use do...end (for multi-line) or { } (for single-line)263    use_do_end = should_use_do_end_block?(block_body)264    265    if use_do_end266      # Multi-line do...end block267      if block_args && block_args['children'] && !block_args['children'].empty?268        result += " do |#{convert_node(block_args)}|\n"269      else270        result += " do\n"271      end272 273      if block_body274        body_code = convert_node(block_body, indent_level + 1)275        result += body_code276        result += "\n"277      end278 279      result += "#{indent(indent_level)}end"280    else281      # Single-line { } block282      if block_args && block_args['children'] && !block_args['children'].empty?283        result += " { |#{convert_node(block_args)}|"284      else285        result += " {"286      end287 288      if block_body289        body_code = convert_node(block_body)290        result += " #{body_code} "291      end292 293      result += "}"294    end295    296    result297  end298 299  def should_use_do_end_block?(block_body)300    return false unless block_body301    302    # Use do...end for multi-statement blocks303    if block_body['type'] == 'begin'304      children = block_body['children'] || []305      return children.length > 1306    end307    308    # Use do...end for complex single statements (control flow, etc.)309    case block_body['type']310    when 'if', 'unless', 'case', 'while', 'until', 'for', 'def', 'class', 'module'311      true312    else313      false314    end315  end316 317  def convert_string(node)318    children = node['children'] || []319    "\"#{children[0]}\""320  end321 322  def convert_int(node)323    children = node['children'] || []324    children[0].to_s325  end326 327  def convert_float(node)328    children = node['children'] || []329    children[0].to_s330  end331 332  def convert_symbol(node)333    children = node['children'] || []334    ":#{children[0]}"335  end336 337  def convert_lvar(node)338    children = node['children'] || []339    children[0].to_s340  end341 342  def convert_ivar(node)343    children = node['children'] || []344    "@#{children[0]}"345  end346 347  def convert_cvar(node)348    children = node['children'] || []349    "@@#{children[0]}"350  end351 352  def convert_gvar(node)353    children = node['children'] || []354    "$#{children[0]}"355  end356 357  def convert_const(node)358    children = node['children'] || []359    scope = children[0]360    const_name = children[1]361    362    result = ""363    if scope364      result += "#{convert_node(scope)}::"365    end366    result += const_name.to_s367    result368  end369 370  def convert_lvasgn(node)371    children = node['children'] || []372    var_name = children[0]373    value = children[1]374    375    if value376      "#{var_name} = #{convert_node(value)}"377    else378      var_name.to_s379    end380  end381 382  def convert_ivasgn(node)383    children = node['children'] || []384    var_name = children[0]385    value = children[1]386    387    if value388      "@#{var_name} = #{convert_node(value)}"389    else390      "@#{var_name}"391    end392  end393 394  def convert_cvasgn(node)395    children = node['children'] || []396    var_name = children[0]397    value = children[1]398    399    if value400      "@@#{var_name} = #{convert_node(value)}"401    else402      "@@#{var_name}"403    end404  end405 406  def convert_gvasgn(node)407    children = node['children'] || []408    var_name = children[0]409    value = children[1]410    411    if value412      "$#{var_name} = #{convert_node(value)}"413    else414      "$#{var_name}"415    end416  end417 418  def convert_casgn(node)419    children = node['children'] || []420    scope = children[0]421    const_name = children[1]422    value = children[2]423    424    result = ""425    if scope426      result += "#{convert_node(scope)}::"427    end428    result += const_name.to_s429    430    if value431      result += " = #{convert_node(value)}"432    end433    434    result435  end436 437  def convert_dstr(node)438    children = node['children'] || []439    result = "\""440    441    children.each do |child|442      if child.is_a?(String)443        result += child444      else445        case child['type']446        when 'begin'447          # String interpolation448          result += "\#{#{convert_node(child)}}"449        else450          result += convert_node(child)451        end452      end453    end454    455    result += "\""456    result457  end458 459  def convert_dnum(node)460    children = node['children'] || []461    children[0].to_s462  end463 464  def convert_regexp(node)465    children = node['children'] || []466    pattern = children[0]467    flags = children[1]468    469    result = "/#{convert_node(pattern)}/"470    if flags && flags != 0471      result += convert_regexp_flags(flags)472    end473    result474  end475 476  def convert_regexp_flags(flags)477    flag_str = ""478    flag_str += "i" if flags & 1 != 0   # IGNORECASE479    flag_str += "x" if flags & 2 != 0   # EXTENDED480    flag_str += "m" if flags & 4 != 0   # MULTILINE481    flag_str482  end483 484  def convert_if(node, indent_level)485    children = node['children'] || []486    condition = children[0]487    then_body = children[1]488    else_body = children[2]489 490    result = "#{indent(indent_level)}if #{convert_node(condition)}\n"491    492    if then_body493      result += convert_node(then_body, indent_level + 1)494      result += "\n"495    end496    497    if else_body498      result += "#{indent(indent_level)}else\n"499      result += convert_node(else_body, indent_level + 1)500      result += "\n"501    end502    503    result += "#{indent(indent_level)}end"504    result505  end506 507  def convert_unless(node, indent_level)508    children = node['children'] || []509    condition = children[0]510    then_body = children[1]511    else_body = children[2]512 513    result = "#{indent(indent_level)}unless #{convert_node(condition)}\n"514    515    if then_body516      result += convert_node(then_body, indent_level + 1)517      result += "\n"518    end519    520    if else_body521      result += "#{indent(indent_level)}else\n"522      result += convert_node(else_body, indent_level + 1)523      result += "\n"524    end525    526    result += "#{indent(indent_level)}end"527    result528  end529 530  def convert_return(node)531    children = node['children'] || []532    if children.empty?533      'return'534    else535      "return #{children.map { |child| convert_node(child) }.join(', ')}"536    end537  end538 539  def convert_break(node)540    children = node['children'] || []541    if children.empty?542      'break'543    else544      "break #{children.map { |child| convert_node(child) }.join(', ')}"545    end546  end547 548  def convert_next(node)549    children = node['children'] || []550    if children.empty?551      'next'552    else553      "next #{children.map { |child| convert_node(child) }.join(', ')}"554    end555  end556 557  def convert_yield(node)558    children = node['children'] || []559    if children.empty?560      'yield'561    else562      "yield(#{children.map { |child| convert_node(child) }.join(', ')})"563    end564  end565 566  def convert_and(node)567    children = node['children'] || []568    left = convert_node_with_precedence(children[0], 'and')569    right = convert_node_with_precedence(children[1], 'and')570    "#{left} && #{right}"571  end572 573  def convert_or(node)574    children = node['children'] || []575    left = convert_node_with_precedence(children[0], 'or')576    right = convert_node_with_precedence(children[1], 'or')577    "#{left} || #{right}"578  end579 580  def convert_node_with_precedence(node, parent_op)581    return convert_node(node) unless node.is_a?(Hash) && node['type']582    583    # Define operator precedence (higher number = higher precedence)584    precedence = {585      'or' => 1,   # ||586      'and' => 2   # &&587    }588    589    node_op = node['type']590    591    # Add parentheses if the child operator has lower precedence than parent592    if precedence[node_op] && precedence[parent_op] && 593       precedence[node_op] < precedence[parent_op]594      "(#{convert_node(node)})"595    else596      convert_node(node)597    end598  end599 600  def convert_not(node)601    children = node['children'] || []602    "!#{convert_node(children[0])}"603  end604 605  def convert_array(node)606    children = node['children'] || []607    "[#{children.map { |child| convert_node(child) }.join(', ')}]"608  end609 610  def convert_hash(node)611    children = node['children'] || []612    "{ #{children.map { |child| convert_node(child) }.join(', ')} }"613  end614 615  def convert_pair(node)616    children = node['children'] || []617    key = children[0]618    value = children[1]619    620    # Use modern syntax for symbol keys (key: value instead of :key => value)621    if key && key['type'] == 'sym'622      key_name = key['children'][0]623      "#{key_name}: #{convert_node(value)}"624    else625      "#{convert_node(key)} => #{convert_node(value)}"626    end627  end628 629  def convert_splat(node)630    children = node['children'] || []631    "*#{convert_node(children[0])}"632  end633 634  def convert_kwsplat(node)635    children = node['children'] || []636    "**#{convert_node(children[0])}"637  end638 639  def convert_arg(node)640    children = node['children'] || []641    children[0].to_s642  end643 644  def convert_optarg(node)645    children = node['children'] || []646    name = children[0]647    default = children[1]648    "#{name} = #{convert_node(default)}"649  end650 651  def convert_restarg(node)652    children = node['children'] || []653    if children.empty?654      '*'655    else656      "*#{children[0]}"657    end658  end659 660  def convert_kwarg(node)661    children = node['children'] || []662    "#{children[0]}:"663  end664 665  def convert_kwoptarg(node)666    children = node['children'] || []667    name = children[0]668    default = children[1]669    "#{name}: #{convert_node(default)}"670  end671 672  def convert_kwrestarg(node)673    children = node['children'] || []674    if children.empty?675      '**'676    else677      "**#{children[0]}"678    end679  end680 681  def convert_blockarg(node)682    children = node['children'] || []683    "&#{children[0]}"684  end685 686  def convert_class(node, indent_level)687    children = node['children'] || []688    name = children[0]689    superclass = children[1]690    body = children[2]691 692    result = "#{indent(indent_level)}class #{convert_node(name)}"693    694    if superclass695      result += " < #{convert_node(superclass)}"696    end697    698    result += "\n"699    700    if body701      result += convert_node(body, indent_level + 1)702      result += "\n"703    end704    705    result += "#{indent(indent_level)}end"706    result707  end708 709  def convert_module(node, indent_level)710    children = node['children'] || []711    name = children[0]712    body = children[1]713 714    result = "#{indent(indent_level)}module #{convert_node(name)}\n"715    716    if body717      result += convert_node(body, indent_level + 1)718      result += "\n"719    end720    721    result += "#{indent(indent_level)}end"722    result723  end724 725  def convert_while(node, indent_level)726    children = node['children'] || []727    condition = children[0]728    body = children[1]729 730    result = "#{indent(indent_level)}while #{convert_node(condition)}\n"731    732    if body733      result += convert_node(body, indent_level + 1)734      result += "\n"735    end736    737    result += "#{indent(indent_level)}end"738    result739  end740 741  def convert_until(node, indent_level)742    children = node['children'] || []743    condition = children[0]744    body = children[1]745 746    result = "#{indent(indent_level)}until #{convert_node(condition)}\n"747    748    if body749      result += convert_node(body, indent_level + 1)750      result += "\n"751    end752    753    result += "#{indent(indent_level)}end"754    result755  end756 757  def convert_case(node, indent_level)758    children = node['children'] || []759    expr = children[0]760    branches = children[1..-1]761 762    result = "#{indent(indent_level)}case #{convert_node(expr)}\n"763    764    # Separate when branches from else clause765    when_branches = []766    else_clause = nil767    768    branches.each do |branch|769      if branch && branch['type'] == 'when'770        when_branches << branch771      elsif branch && branch['type'] != 'when'772        # This is likely the else clause (any non-when node at the end)773        else_clause = branch774      end775    end776    777    # Process when branches778    when_branches.each do |when_node|779      result += convert_node(when_node, indent_level)780      result += "\n"781    end782    783    # Process else clause if present784    if else_clause785      result += "#{indent(indent_level)}else\n"786      result += convert_node(else_clause, indent_level + 1)787      result += "\n"788    end789    790    result += "#{indent(indent_level)}end"791    result792  end793 794  def convert_when(node, indent_level)795    children = node['children'] || []796    conditions = children[0..-2]797    body = children[-1]798 799    result = "#{indent(indent_level)}when #{conditions.map { |cond| convert_node(cond) }.join(', ')}\n"800    801    if body802      result += convert_node(body, indent_level + 1)803    end804    805    result806  end807 808  def convert_for(node, indent_level)809    children = node['children'] || []810    var = children[0]811    collection = children[1]812    body = children[2]813 814    result = "#{indent(indent_level)}for #{convert_node(var)} in #{convert_node(collection)}\n"815    816    if body817      result += convert_node(body, indent_level + 1)818      result += "\n"819    end820    821    result += "#{indent(indent_level)}end"822    result823  end824 825  def convert_sclass(node, indent_level)826    children = node['children'] || []827    expr = children[0]828    body = children[1]829 830    result = "#{indent(indent_level)}class << #{convert_node(expr)}\n"831    832    if body833      result += convert_node(body, indent_level + 1)834      result += "\n"835    end836    837    result += "#{indent(indent_level)}end"838    result839  end840 841  def convert_rescue(node, indent_level)842    children = node['children'] || []843    body = children[0]844    rescue_clauses = children[1..-2]845    else_clause = children[-1]846 847    result = ""848    849    if body850      result += convert_node(body, indent_level)851      result += "\n"852    end853    854    rescue_clauses.each do |rescue_clause|855      if rescue_clause && rescue_clause['type'] == 'resbody'856        result += convert_resbody(rescue_clause, indent_level)857        result += "\n"858      end859    end860    861    if else_clause862      result += "#{indent(indent_level)}else\n"863      result += convert_node(else_clause, indent_level + 1)864      result += "\n"865    end866    867    result868  end869 870  def convert_resbody(node, indent_level)871    children = node['children'] || []872    exception_types = children[0]873    exception_var = children[1]874    body = children[2]875 876    result = "#{indent(indent_level)}rescue"877    878    if exception_types && exception_types['children'] && !exception_types['children'].empty?879      result += " #{exception_types['children'].map { |type| convert_node(type) }.join(', ')}"880    end881    882    if exception_var883      result += " => #{convert_node(exception_var)}"884    end885    886    result += "\n"887    888    if body889      result += convert_node(body, indent_level + 1)890    end891    892    result893  end894 895  def convert_ensure(node, indent_level)896    children = node['children'] || []897    body = children[0]898    ensure_body = children[1]899 900    result = ""901    902    if body903      result += convert_node(body, indent_level)904      result += "\n"905    end906    907    result += "#{indent(indent_level)}ensure\n"908    909    if ensure_body910      result += convert_node(ensure_body, indent_level + 1)911      result += "\n"912    end913    914    result915  end916 917  def convert_unknown(node)918    # Fallback: try to handle unknown nodes gracefully919    children = node['children'] || []920    if children.empty?921      node['type']922    else923      "#{node['type']}(#{children.map { |child| convert_node(child) }.join(', ')})"924    end925  end926end927 928# Command-line interface929if __FILE__ == $0930  begin931    if ARGV.empty? && !$stdin.tty?932      # Read from stdin when no arguments and input is piped933      input = $stdin.read934    elsif ARGV.length == 1935      if ARGV[0] == '-'936        # Explicit stdin937        input = $stdin.read938      else939        # Read from file940        input = File.read(ARGV[0])941      end942    else943      puts "Usage: #{$0} <ast_json_file>"944      puts "   or: echo '<ast_json>' | #{$0}"945      puts "   or: #{$0} -"946      exit 1947    end948 949    result = ASTPrettyPrinter.ast_to_ruby(input)950    puts result951  rescue => e952    $stderr.puts "Error: #{e.message}"953    exit 1954  end955end