#!/usr/bin/env jruby require 'optparse' require 'jruby' opts = {} options = { :print_source => false, :print_sexp => false, :print_ast => true, :print_ir => false, :print_pass => nil, :pretty_ir => false, :dot_format => false } OptionParser.new do |opts| opts.banner = "Usage: #{$0} [options]" opts.on('-d', '--dot', 'Display as dot data') do |h| options[:dot_format] = true end opts.on('-h', '--help', 'Display this help') do |h| puts opts exit true end opts.on('-i', '--ir', 'Dump all IR passes without executing') do |h| options[:print_ir] = true end opts.on('-p', '--pass passes_list', 'Dump IR after running a pass') do |pass| options[:print_pass] = pass end opts.on('-f', '--formatted-ir', 'Pretty printer for IR (without CFG)') do |f| options[:pretty_ir] = f end opts.on('-s', '--sexp', 'Display the S-Expression for the AST') do |t| options[:print_sexp] = true end opts.on('--source', 'Display the source') do |s| options[:print_source] = true end opts.on('--no-ast', 'Do not print out the AST for this (only useful with -s)') do |a| options[:print_ast] = false end opts.on('-e exp', '--expression') do |e| options[:expression] = e end end.parse! if ARGV.length > 1 abort "You may only specify one script (see --help)" elsif ARGV.length == 1 if options[:expression] abort "-e and a script is not a valid combination (see --help)" end options[:expression] = File.read(ARGV.shift) elsif ! options.has_key?(:expression) abort "No script specified (see --help)" end if options[:print_ir] && options[:print_pass] abort "-p and -i is not valid. Use only one of them (see --help)" end $indent_string = " " def indexes(string, lindex, rindex) lindex = string.index("(", lindex) if lindex != nil rindex = string.index(")", rindex) if rindex != nil return lindex, rindex end def indent(string) depth = -1 lindex, rindex = indexes(string, 0, 0) while (lindex != nil || rindex != nil) if (lindex != nil && lindex < rindex) depth += 1 string[lindex, 1] = "\n#{$indent_string * depth}" else depth -= 1 string[rindex, 1] = "\n" end lindex, rindex = indexes(string, lindex, rindex) end string.gsub(/,\s*$/, '').squeeze("\n") end if options[:print_source] puts "Source:" puts options[:expression] puts end module DotGraph def self.dot_label(node) extra = case node when org.jruby.ast.StrNode then ": '#{node.value}'" when org.jruby.ast.FixnumNode then ": #{node.value}" when org.jruby.ast.FloatNode then ": #{node.value}" when org.jruby.ast.types.INameNode then ": #{node.name}" else "" end "#{short_name(node)}#{extra}" end def self.short_name(node) node.class.name.split("::")[-1].gsub("Node", "") end def self.dot_node_def(node) %Q{#{node.hash} [label="#{dot_label(node)}"];\n} end def self.dot(defs, parent) defs[parent.hash] = dot_node_def(parent) "".tap do |str| parent.child_nodes.each do |child| str << "#{parent.hash} -> #{child.hash};\n" str << dot(defs, child) end end end def self.print_graph(root_node) defs = {} graph_section = DotGraph.dot(defs, root_node) puts "digraph AST {" puts defs.values.join('') puts graph_section puts "}" end end root = JRuby.parse(options[:expression]) if options[:print_ast] if options[:dot_format] DotGraph.print_graph(root) else print "AST:" puts indent(root.to_string) puts end end def print_passes_on(scope, passes) if !scope.kind_of? org.jruby.ir.IRClosure passes.each { |pass| pass.run(scope) } end scope.lexical_scopes.each do |child_scope| child_scope.prepare_for_compilation print_passes_on(child_scope, passes) end end def print_pass_on(scope, pass) if !scope.kind_of? org.jruby.ir.IRClosure pass.run(scope) end scope.lexical_scopes.each do |child_scope| print_pass_on(child_scope, pass) end end def ir_setup(root) runtime = JRuby::runtime manager = runtime.ir_manager JRuby::IR.compiler_debug = true builder = org.jruby.ir.IRBuilder scope = builder.build_root(manager, root).scope scope.prepare_for_compilation passes = manager.get_compiler_passes(scope) [scope, passes] end module IRPrettyPrinter def self.pretty_ir(scope, indent="") i = 0 pretty_str = scope.instrs.map do |instr| f_str = "%s%3i\s\s%s" % [indent, i, instr] i += 1 f_str end pretty_str = [indent + scope.to_s] + pretty_str scope.lexical_scopes.each do |lex_scope| pretty_str += pretty_ir(lex_scope, indent + "\s" * 4) end pretty_str end def self.print_ir(scope) instrs = pretty_ir(scope) instrs.each do |instr| puts instr end end end if options[:pretty_ir] scope, passes = ir_setup(root) puts "IR:" IRPrettyPrinter.print_ir(scope) end if options[:print_pass] scope, passes = ir_setup(root) pass_name = options[:print_pass] pass = passes.find do |p| p.java_class.to_s.include?(pass_name.to_s) end print_pass_on(scope, pass) end if options[:print_ir] scope, passes = ir_setup(root) print_passes_on(scope, passes) end if options[:print_sexp] puts "SEXP:" puts org.jruby.ast.util.SexpMaker.create(root) end