Class: RubyToAnsiC

Inherits:
SexpProcessor
  • Object
show all
Defined in:
lib/ruby_to_ansi_c.rb

Overview

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.

NOT SUPPORTED: (keep in sync w/ initialize)

:begin, :block_arg, :case, :dstr, :rescue, :self, :super, :when

Direct Known Subclasses

RubyToRubyC

Constant Summary collapse

VERSION =
'1.0.0-beta-5'
METHOD_MAP =

Function definition

{ 
  :| => "or",
  :& => "and",
  :^ => "xor",
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRubyToAnsiC

:nodoc:



160
161
162
163
164
165
166
167
168
169
170
# File 'lib/ruby_to_ansi_c.rb', line 160

def initialize # :nodoc:
  super
  @env = Environment.new
  self.auto_shift_type = true
  self.unsupported = [:alias, :alloca, :argscat, :argspush, :attrasgn, :attrset, :back_ref, :begin, :block_arg, :block_pass, :bmethod, :break, :case, :cdecl, :cfunc, :colon2, :colon3, :cref, :cvasgn, :cvdecl, :dasgn, :defined, :defs, :dmethod, :dot2, :dot3, :dregx, :dregx_once, :dstr, :dsym, :dxstr, :ensure, :evstr, :fbody, :fcall, :flip2, :flip3, :for, :gasgn, :ifunc, :last, :masgn, :match, :match2, :match3, :memo, :method, :module, :newline, :next, :nth_ref, :op_asgn1, :op_asgn2, :op_asgn_and, :opt_n, :postexe, :redo, :resbody, :rescue, :retry, :sclass, :self, :splat, :super, :svalue, :to_ary, :undef, :until, :valias, :vcall, :when, :xstr, :yield, :zarray, :zsuper]

  self.strict = true
  self.expected = String

  @prototypes = []
end

Instance Attribute Details

#envObject (readonly)

Provides access to the variable scope.



70
71
72
# File 'lib/ruby_to_ansi_c.rb', line 70

def env
  @env
end

#prototypesObject (readonly)

Provides access to the method signature prototypes that are needed at the top of the C file.



76
77
78
# File 'lib/ruby_to_ansi_c.rb', line 76

def prototypes
  @prototypes
end

Class Method Details

.c_type(typ) ⇒ Object

Returns a textual version of a C type that corresponds to a sexp type.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ruby_to_ansi_c.rb', line 36

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

.translate(klass, method = nil) ⇒ Object

Front-end utility method for translating an entire class or a specific method from that class.



123
124
125
126
127
128
129
130
131
132
# File 'lib/ruby_to_ansi_c.rb', line 123

def self.translate(klass, method=nil)
  # REFACTOR: rename to self.process
  unless method.nil? then
    self.translator.process(ParseTree.new(false).parse_tree_for_method(klass, method))
  else
    ParseTree.new.parse_tree(klass).map do |k|
      self.translator.process(ParseTree.new.parse_tree(klass))
    end
  end
end

.translate_all_of(klass) ⇒ Object

(Primary) Front-end utility method for translating an entire class. Has special error handlers that convert errors into C++ comments (//…).



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/ruby_to_ansi_c.rb', line 139

def self.translate_all_of(klass)
  result = []

  # HACK: make CompositeSexpProcessor have a registered error handler
  klass.instance_methods(false).sort.each do |method|
    result << 
      begin
        self.translate(klass, method)
      rescue UnsupportedNodeError => err
        "// NOTE: #{err} in #{klass}##{method}"
      rescue UnknownNodeError => err
        "// ERROR: #{err} in #{klass}##{method}: #{ParseTree.new.parse_tree_for_method(klass, method).inspect}"
      rescue Exception => err
        "// ERROR: #{err} in #{klass}##{method}: #{ParseTree.new.parse_tree_for_method(klass, method).inspect} #{err.backtrace.join(', ')}"
      end
  end

  prototypes =  self.translator.processors[-1].prototypes
  "#{prototypes.join('')}\n\n#{result.join("\n\n")}"
end

.translatorObject

Lazy initializer for the composite RubytoC translator chain.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/ruby_to_ansi_c.rb', line 96

def self.translator
  unless defined? @translator then
    @translator = CompositeSexpProcessor.new
    @translator << Rewriter.new
    @translator << TypeChecker.new
    @translator << R2CRewriter.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

Instance Method Details

#no(exp) ⇒ Object

TODO: remove me



28
29
30
# File 'lib/ruby_to_ansi_c.rb', line 28

def no(exp) # :nodoc:
  raise "no: #{caller[0].split[1]} #{exp.inspect}"
end

#preambleObject

Provides a (rather bogus) preamble. Put your includes and defines here. It really should be made to be much more clean and extendable.



83
84
85
86
87
88
89
90
91
# File 'lib/ruby_to_ansi_c.rb', line 83

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

#process_and(exp) ⇒ Object

Logical And. Nothing exciting here



175
176
177
178
179
180
# File 'lib/ruby_to_ansi_c.rb', line 175

def process_and(exp)
  lhs = process exp.shift
  rhs = process exp.shift

  return "#{lhs} && #{rhs}"
end

#process_arglist(exp) ⇒ Object

Arglist is used by call arg lists.



185
186
187
# File 'lib/ruby_to_ansi_c.rb', line 185

def process_arglist(exp)
  return process_array(exp)
end

#process_args(exp) ⇒ Object

Argument List including variable types.



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/ruby_to_ansi_c.rb', line 192

def process_args(exp)
  args = []

  until exp.empty? do
    arg = exp.shift

#       p arg
#       p arg.sexp_type
#       p arg.first
#       p self.class
#       p arg.sexp_type.class
#       p TypeMap.methods.sort
#       p c_type(arg.sexp_type)

    args << "#{self.class.c_type(arg.sexp_type)} #{arg.first}"
  end

  return "(#{args.join ', '})"
end

#process_array(exp) ⇒ Object

Array is used as call arg lists and as initializers for variables.



215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/ruby_to_ansi_c.rb', line 215

def process_array(exp)
  code = []

  until exp.empty? do
    code << process(exp.shift) 
  end

  s = code.join ', '
  s = "rb_ary_new()" if s.empty? # HACK

  return s
end

#process_block(exp) ⇒ Object

Block doesn’t have an analog in C, except maybe as a functions’s outer braces.



232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/ruby_to_ansi_c.rb', line 232

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

#process_call(exp) ⇒ Object

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.



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/ruby_to_ansi_c.rb', line 252

def process_call(exp)
  receiver = exp.shift
  name = exp.shift

  receiver_type = Type.unknown
  unless receiver.nil? then
    receiver_type = receiver.sexp_type
  end
  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? then
      args = receiver
    else
      args = "#{receiver}, #{args}"
    end

    return "#{name}(#{args})"
  end
end

#process_class(exp) ⇒ Object

DOC



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/ruby_to_ansi_c.rb', line 300

def process_class(exp)
  name = exp.shift
  superklass = exp.shift

  result = []

  result << "// class #{name}"

  until exp.empty? do
    # HACK: cheating!
    klass = name
    method = exp[1]
    result << process(exp.shift)
  end

  return result.join("\n\n")
end

#process_const(exp) ⇒ Object



318
319
320
321
# File 'lib/ruby_to_ansi_c.rb', line 318

def process_const(exp)
  name = exp.shift
  return name.to_s
end

#process_cvar(exp) ⇒ Object

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



330
331
332
333
334
# File 'lib/ruby_to_ansi_c.rb', line 330

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

#process_dasgn_curr(exp) ⇒ Object

Iterator variables.

TODO: check to see if this is the least bit relevant anymore. We might have rewritten them all.



342
343
344
345
346
# File 'lib/ruby_to_ansi_c.rb', line 342

def process_dasgn_curr(exp)
  var = exp.shift
  @env.add var.to_sym, exp.sexp_type
  return var.to_s
end

#process_defn(exp) ⇒ Object



357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/ruby_to_ansi_c.rb', line 357

def process_defn(exp)
  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.sexp_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

#process_dummy(exp) ⇒ Object

Generic handler. Ignore me, I’m not here.

TODO: nuke dummy nodes by using new SexpProcessor rewrite rules.



376
377
378
# File 'lib/ruby_to_ansi_c.rb', line 376

def process_dummy(exp)
  process_block(exp).chomp
end

#process_dvar(exp) ⇒ Object

Dynamic variables, should be the same as lvar at this stage.

TODO: remove / rewrite?



385
386
387
388
389
# File 'lib/ruby_to_ansi_c.rb', line 385

def process_dvar(exp)
  var = exp.shift
  @env.add var.to_sym, exp.sexp_type
  return var.to_s
end

#process_error(exp) ⇒ Object

DOC



394
395
396
# File 'lib/ruby_to_ansi_c.rb', line 394

def process_error(exp)
  return exp.shift
end

#process_false(exp) ⇒ Object

False. Pretty straightforward.



401
402
403
# File 'lib/ruby_to_ansi_c.rb', line 401

def process_false(exp)
       return "0"
end

#process_gvar(exp) ⇒ Object

Global variables, evil but necessary.

TODO: get the case statement out by using proper bootstrap in genv.



410
411
412
413
414
415
416
417
418
419
# File 'lib/ruby_to_ansi_c.rb', line 410

def process_gvar(exp)
  name = exp.shift
  type = exp.sexp_type
  case name
  when :$stderr then
    "stderr"
  else
    raise "Bug! Unhandled gvar #{name.inspect} (type = #{type})"
  end
end

#process_hash(exp) ⇒ Object

Hash values, currently unsupported, but plans are in the works.



424
425
426
# File 'lib/ruby_to_ansi_c.rb', line 424

def process_hash(exp)
  no(exp)
end

#process_iasgn(exp) ⇒ Object

Instance Variable Assignment



431
432
433
434
435
# File 'lib/ruby_to_ansi_c.rb', line 431

def process_iasgn(exp)
  name = exp.shift
  val = process exp.shift
  "self->#{name.to_s.sub(/^@/, '')} = #{val}"
end

#process_if(exp) ⇒ Object

Conditional statements

TODO: implementation is ugly as hell… PLEASE try to clean



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/ruby_to_ansi_c.rb', line 442

def process_if(exp)
  cond_part = process exp.shift

  result = "if (#{cond_part})"

  then_block = ! exp.first.nil? && exp.first.first == :block
  then_part  = process exp.shift
  else_block = ! exp.first.nil? && exp.first.first == :block
  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

#process_iter(exp) ⇒ Object

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.

Raises:

  • (UnsupportedNodeError)


481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/ruby_to_ansi_c.rb', line 481

def process_iter(exp)
  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
    call = 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

#process_ivar(exp) ⇒ Object

Instance Variable Access



508
509
510
511
# File 'lib/ruby_to_ansi_c.rb', line 508

def process_ivar(exp)
  name = exp.shift
  "self->#{name.to_s.sub(/^@/, '')}"
end

#process_lasgn(exp) ⇒ Object

Assignment to a local variable.

TODO: figure out array issues and clean up.



518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/ruby_to_ansi_c.rb', line 518

def process_lasgn(exp)
  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.sexp_type
  @env.add var.to_sym, exp_type
  var_type = self.class.c_type exp_type

  if exp_type.list? then
    assert_type args, :array

    raise "array must be of one type" unless args.sexp_type == Type.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.sexp_types.empty? ? 'void *' : self.class.c_type(args.sexp_types.first)

    args.shift # :arglist
    out << "#{var} = (#{array_type}) malloc(sizeof(#{array_type}) * #{args.length});\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

#process_lit(exp) ⇒ Object

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?



560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/ruby_to_ansi_c.rb', line 560

def process_lit(exp)
  # TODO what about floats and big numbers?

  value = exp.shift
  sexp_type = exp.sexp_type
  case sexp_type
  when Type.long, Type.float then
    return value.to_s
  when Type.symbol then
    return value.to_s.inspect
  else
    raise "Bug! no: Unknown literal #{value}:#{value.class}"
  end
end

#process_lvar(exp) ⇒ Object

Local variable



578
579
580
581
582
583
# File 'lib/ruby_to_ansi_c.rb', line 578

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

#process_nil(exp) ⇒ Object

Nil, currently ruby nil, not C NULL (0).



588
589
590
# File 'lib/ruby_to_ansi_c.rb', line 588

def process_nil(exp)
  return "NULL"
end

#process_not(exp) ⇒ Object

Nil, currently ruby nil, not C NULL (0).



595
596
597
598
# File 'lib/ruby_to_ansi_c.rb', line 595

def process_not(exp)
  term = process exp.shift
  return "!(#{term})"
end

#process_op_asgn_or(exp) ⇒ Object

Or assignment (||=), currently unsupported, but only because of laziness.



604
605
606
# File 'lib/ruby_to_ansi_c.rb', line 604

def process_op_asgn_or(exp)
  no(exp)
end

#process_or(exp) ⇒ Object

Logical or. Nothing exciting here



611
612
613
614
615
616
# File 'lib/ruby_to_ansi_c.rb', line 611

def process_or(exp)
  lhs = process exp.shift
  rhs = process exp.shift

  return "#{lhs} || #{rhs}"
end

#process_return(exp) ⇒ Object

Return statement. Nothing exciting here



621
622
623
# File 'lib/ruby_to_ansi_c.rb', line 621

def process_return(exp)
  return "return #{process exp.shift}"
end

#process_scope(exp) ⇒ Object

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?



631
632
633
634
635
636
637
638
639
640
641
642
# File 'lib/ruby_to_ansi_c.rb', line 631

def process_scope(exp)
  declarations = []
  body = nil
  @env.scope do
    body = process exp.shift unless exp.empty?
    @env.current.sort_by { |v,t| v.to_s }.each do |var, var_type|
      var_type = self.class.c_type var_type
      declarations << "#{var_type} #{var};\n"
    end
  end
  return "{\n#{declarations}#{body}}"
end

#process_str(exp) ⇒ Object

Strings. woot.



647
648
649
650
# File 'lib/ruby_to_ansi_c.rb', line 647

def process_str(exp)
  s = exp.shift.gsub(/\n/, '\\n')
  return "\"#{s}\""
end

#process_true(exp) ⇒ Object

Truth… what is truth?



655
656
657
# File 'lib/ruby_to_ansi_c.rb', line 655

def process_true(exp)
  return "1"
end

#process_while(exp) ⇒ Object

While block. Nothing exciting here.



662
663
664
665
666
667
668
669
670
# File 'lib/ruby_to_ansi_c.rb', line 662

def process_while(exp)
  cond = process exp.shift
  body = process exp.shift
  body += ";" unless body =~ /;/
  is_precondition = exp.shift
  code = "while (#{cond}) {\n#{body.strip}\n}"
  code = "{\n#{body.strip}\n} while (#{cond})" unless is_precondition
  return code
end