class RubyToAnsiC
The whole point of this project! RubyToC is an actually very simple SexpProcessor that does the final conversion from Sexp
to C code. This class has more unsupported nodes than any other (on purpose–we'd like TypeChecker
and friends to be as generally useful as possible), and as a result, supports a very small subset of ruby.
Constants
- METHOD_MAP
Function definition
- VERSION
Attributes
Provides access to the variable scope.
Provides access to the method signature prototypes that are needed at the top of the C file.
Provides a place to put things at the file scope. Be smart, make them static (hence the name).
Public Class Methods
Returns a textual version of a C type that corresponds to a sexp type.
# File lib/ruby_to_ansi_c.rb, line 34 def self.c_type(typ) base_type = case typ.type.contents # HACK this is breaking demeter when :float then "double" when :long then "long" when :str then "str" when :symbol then "symbol" when :bool then # TODO: subject to change "bool" when :void then "void" when :homo then "void *" # HACK when :value, :unknown then "void *" # HACK # HACK: uncomment this and fix the above when you want to have good tests # when :unknown then # raise "You should not have unknown types by now!" else raise "Bug! Unknown type #{typ.inspect} in c_type" end base_type += " *" if typ.list? unless typ.unknown? base_type end
Lazy initializer for the composite RubytoC translator chain.
# File lib/ruby_to_ansi_c.rb, line 100 def self.translator unless defined? @translator then @translator = CompositeSexpProcessor.new @translator << Rewriter.new @translator << TypeChecker.new # @translator << CRewriter.new @translator << RubyToAnsiC.new @translator.on_error_in(:defn) do |processor, exp, err| result = processor.expected.new case result when Array then result << :error end msg = "// ERROR: #{err.class}: #{err}" msg += " in #{exp.inspect}" unless exp.nil? or $TESTING msg += " from #{caller.join(', ')}" unless $TESTING result << msg result end end @translator end
Public Instance Methods
Provides a (rather bogus) preamble. Put your includes and defines here. It really should be made to be much more clean and extendable.
# File lib/ruby_to_ansi_c.rb, line 87 def preamble "// BEGIN METARUBY PREAMBLE #include <ruby.h> #define RB_COMPARE(x, y) (x) == (y) ? 0 : (x) < (y) ? -1 : 1 typedef char * str; #define case_equal_long(x, y) ((x) == (y)) // END METARUBY PREAMBLE " + self.prototypes.join('') end
Logical And. Nothing exciting here
# File lib/ruby_to_ansi_c.rb, line 139 def process_and(exp) lhs = process exp.shift rhs = process exp.shift return "#{lhs} && #{rhs}" end
Arglist is used by call arg lists.
# File lib/ruby_to_ansi_c.rb, line 149 def process_arglist(exp) return '' if exp.empty? return process_array(exp) end
Argument List including variable types.
# File lib/ruby_to_ansi_c.rb, line 157 def process_args(exp) args = [] until exp.empty? do arg = exp.shift name = arg.first.to_s.sub(/^\*/, '').intern type = arg.c_type @env.add name, type args << "#{self.class.c_type(type)} #{name}" end return "(#{args.join ', '})" end
Array is used as call arg lists and as initializers for variables.
# File lib/ruby_to_ansi_c.rb, line 174 def process_array(exp) return "rb_ary_new()" if exp.empty? # HACK FIX! not ansi c! code = [] until exp.empty? do code << process(exp.shift) end s = code.join ', ' return s end
Block doesn't have an analog in C, except maybe as a functions's outer braces.
# File lib/ruby_to_ansi_c.rb, line 191 def process_block(exp) code = [] until exp.empty? do code << process(exp.shift) end body = code.join(";\n") body += ";" unless body =~ /[;}]\Z/ body += "\n" return body end
Call, both unary and binary operators and regular function calls.
TODO: This needs a lot of work. We've cheated with the case statement below. We need a real function signature lookup like we have in R2CRewriter.
# File lib/ruby_to_ansi_c.rb, line 211 def process_call(exp) receiver = exp.shift name = exp.shift receiver = process receiver case name # TODO: these need to be numerics # emacs gets confused by :/ below, need quotes to fix indentation when :==, :<, :>, :<=, :>=, :-, :+, :*, :"/", :% then args = process exp.shift[1] return "#{receiver} #{name} #{args}" when :<=> args = process exp.shift[1] return "RB_COMPARE(#{receiver}, #{args})" when :equal? args = process exp.shift return "#{receiver} == #{args}" # equal? == address equality when :[] args = process exp.shift return "#{receiver}[#{args}]" when :nil? exp.clear return receiver.to_s else args = process exp.shift if receiver.nil? and args.nil? then args = "" elsif receiver.nil? then # nothing to do elsif args.nil? or args.empty? then args = receiver else args = "#{receiver}, #{args}" end args = '' if args == 'rb_ary_new()' # HACK return "#{name}(#{args})" end end
DOC
# File lib/ruby_to_ansi_c.rb, line 257 def process_class(exp) name = exp.shift superklass = exp.shift result = [] until exp.empty? do # HACK: cheating! result << process(exp.shift) end result.unshift(*statics) result.unshift "// class #{name} < #{superklass}" return result.join("\n\n") end
Constants, must be pre-defined in the global env for ansi c.
# File lib/ruby_to_ansi_c.rb, line 277 def process_const(exp) name = exp.shift return name.to_s end
Constants, must be defined in the global env.
TODO: This will cause a lot of errors with the built in classes until we add them to the bootstrap phase. HACK: what is going on here??? We have NO tests for this node
# File lib/ruby_to_ansi_c.rb, line 289 def process_cvar(exp) # TODO: we should treat these as globals and have them in the top scope name = exp.shift return name.to_s end
Iterator variables.
TODO: check to see if this is the least bit relevant anymore. We might have rewritten them all.
# File lib/ruby_to_ansi_c.rb, line 301 def process_dasgn_curr(exp) # TODO: audit against obfuscator var = exp.shift @env.add var.to_sym, exp.c_type return var.to_s end
# File lib/ruby_to_ansi_c.rb, line 316 def process_defn(exp) # TODO: audit against obfuscator name = exp.shift name = METHOD_MAP[name] if METHOD_MAP.has_key? name name = name.to_s.sub(/(.*)\?$/, 'is_\1').intern args = process exp.shift body = process exp.shift function_type = exp.c_type ret_type = self.class.c_type function_type.list_type.return_type @prototypes << "#{ret_type} #{name}#{args};\n" "#{ret_type}\n#{name}#{args} #{body}" end
# File lib/ruby_to_ansi_c.rb, line 330 def process_defx(exp) # TODO: audit against obfuscator return process_defn(exp) end
Generic handler. Ignore me, I'm not here.
TODO: nuke dummy nodes by using new SexpProcessor rewrite rules.
# File lib/ruby_to_ansi_c.rb, line 339 def process_dummy(exp) process_block(exp).chomp end
Dynamic variables, should be the same as lvar at this stage.
TODO: remove / rewrite?
# File lib/ruby_to_ansi_c.rb, line 348 def process_dvar(exp) var = exp.shift @env.add var.to_sym, exp.c_type return var.to_s end
DOC - TODO: what is this?!?
# File lib/ruby_to_ansi_c.rb, line 357 def process_error(exp) return exp.shift end
False. Pretty straightforward.
# File lib/ruby_to_ansi_c.rb, line 364 def process_false(exp) return "0" end
Global variables, evil but necessary.
TODO: get the case statement out by using proper bootstrap in genv.
# File lib/ruby_to_ansi_c.rb, line 375 def process_gvar(exp) name = exp.shift type = exp.c_type case name when :$stderr then "stderr" else raise "Bug! Unhandled gvar #{name.inspect} (type = #{type})" end end
Instance Variable Assignment
# File lib/ruby_to_ansi_c.rb, line 389 def process_iasgn(exp) name = exp.shift val = process exp.shift "self->#{name.to_s.sub(/^@/, '')} = #{val}" end
Conditional statements
TODO: implementation is ugly as hell… PLEASE try to clean
# File lib/ruby_to_ansi_c.rb, line 400 def process_if(exp) cond_part = process exp.shift result = "if (#{cond_part})" then_part = process exp.shift else_part = process exp.shift then_part = "" if then_part.nil? else_part = "" if else_part.nil? result += " {\n" then_part = then_part.join(";\n") if Array === then_part then_part += ";" unless then_part =~ /[;}]\Z/ # HACK: um... deal with nil correctly (see unless support) result += then_part.to_s # + ";" result += ";" if then_part.nil? result += "\n" unless result =~ /\n\Z/ result += "}" if else_part != "" then result += " else {\n" else_part = else_part.join(";\n") if Array === else_part else_part += ";" unless else_part =~ /[;}]\Z/ result += else_part result += "\n}" end result end
Iterators for loops. After rewriter nearly all iter nodes should be able to be interpreted as a for loop. If not, then you are doing something not supported by C in the first place.
# File lib/ruby_to_ansi_c.rb, line 437 def process_iter(exp) # TODO: audit against obfuscator out = [] # Only support enums in C-land raise UnsupportedNodeError if exp[0][1].nil? # HACK ugly @env.scope do enum = exp[0][1][1] # HACK ugly t(:iter, t(:call, lhs <-- get lhs _ = process exp.shift var = process(exp.shift).intern # semi-HACK-y body = process exp.shift index = "index_#{var}" body += ";" unless body =~ /[;}]\Z/ body.gsub!(/\n\n+/, "\n") out << "unsigned long #{index};" out << "for (#{index} = 0; #{enum}[#{index}] != NULL; ++#{index}) {" out << "#{self.class.c_type @env.lookup(var)} #{var} = #{enum}[#{index}];" out << body out << "}" end return out.join("\n") end
Instance Variable Access
# File lib/ruby_to_ansi_c.rb, line 465 def process_ivar(exp) name = exp.shift "self->#{name.to_s.sub(/^@/, '')}" end
Assignment to a local variable.
TODO: figure out array issues and clean up.
# File lib/ruby_to_ansi_c.rb, line 475 def process_lasgn(exp) # TODO: audit against obfuscator out = "" var = exp.shift value = exp.shift # grab the size of the args, if any, before process converts to a string arg_count = 0 arg_count = value.length - 1 if value.first == :array args = value exp_type = exp.c_type @env.add var.to_sym, exp_type if exp_type.list? then assert_type args, :array raise "array must be of one type" unless args.c_type == CType.homo # HACK: until we figure out properly what to do w/ zarray # before we know what its type is, we will default to long. array_type = args.c_types.empty? ? 'void *' : self.class.c_type(args.c_types.first) args.shift # :arglist # TODO: look into alloca out << "#{var} = (#{array_type}) malloc(sizeof(#{array_type}) * #{arg_count});\n" args.each_with_index do |o,i| out << "#{var}[#{i}] = #{process o};\n" end else out << "#{var} = #{process args}" end out.sub!(/;\n\Z/, '') return out end
Literals, numbers for the most part. Will probably cause compilation errors if you try to translate bignums and other values that don't have analogs in the C world. Sensing a pattern?
# File lib/ruby_to_ansi_c.rb, line 517 def process_lit(exp) # TODO what about floats and big numbers? value = exp.shift c_type = exp.c_type case c_type when CType.long, CType.float then return value.to_s when CType.symbol then return value.to_s.inspect # HACK wrong! write test! else raise "Bug! no: Unknown literal #{value}:#{value.class}" end end
Local variable
# File lib/ruby_to_ansi_c.rb, line 535 def process_lvar(exp) name = exp.shift # do nothing at this stage, var should have been checked for # existance already. return name.to_s end
Nil, currently ruby nil, not C NULL (0).
# File lib/ruby_to_ansi_c.rb, line 549 def process_nil(exp) return "NULL" end
Nil, currently ruby nil, not C NULL (0).
# File lib/ruby_to_ansi_c.rb, line 556 def process_not(exp) term = process exp.shift return "!(#{term})" end
Logical or. Nothing exciting here
# File lib/ruby_to_ansi_c.rb, line 564 def process_or(exp) lhs = process exp.shift rhs = process exp.shift return "#{lhs} || #{rhs}" end
Return statement. Nothing exciting here
# File lib/ruby_to_ansi_c.rb, line 574 def process_return(exp) return "return #{process exp.shift}" end
Scope has no real equivalent in C-land, except that like process_block
above. We put variable declarations here before the body and use this as our opportunity to open a variable scope. Crafty, no?
# File lib/ruby_to_ansi_c.rb, line 584 def process_scope(exp) declarations, body = with_scope do process exp.shift unless exp.empty? end declarations = declarations.reject { |d| d =~ / static_/ } result = [] result << "{" result << declarations.join("\n") unless declarations.empty? result << body.chomp if body result << "}" return result.join("\n") end
A bogus ruby sexp type for generating static variable declarations
# File lib/ruby_to_ansi_c.rb, line 603 def process_static(exp) return exp.shift end
Strings. woot.
# File lib/ruby_to_ansi_c.rb, line 610 def process_str(exp) return exp.shift.inspect end
Truth… what is truth?
# File lib/ruby_to_ansi_c.rb, line 619 def process_true(exp) return "1" end
While block. Nothing exciting here.
# File lib/ruby_to_ansi_c.rb, line 626 def process_while(exp) cond = process exp.shift body = process exp.shift body += ";" unless body =~ /;/ is_precondition = exp.shift if is_precondition then return "while (#{cond}) {\n#{body.strip}\n}" else return "{\n#{body.strip}\n} while (#{cond})" end end
# File lib/ruby_to_ansi_c.rb, line 638 def with_scope declarations = [] result = nil outer_scope = @env.all.keys @env.scope do result = yield @env.current.sort_by { |v,_| v.to_s }.each do |var, (type,val)| next if outer_scope.include? var decl = "#{self.class.c_type type} #{var}" case val when nil then # do nothing when /^\[/ then decl << "#{val}" else decl << " = #{val}" end decl << ';' declarations << decl end end return declarations, result end