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.

Direct Known Subclasses

RubyToRubyC

Constant Summary collapse

VERSION =
'1.1.0'
METHOD_MAP =

Function definition

{ # TODO: steal map from ZenTest
  :| => "or",
  :& => "and",
  :^ => "xor",
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRubyToAnsiC

:nodoc:



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

def initialize # :nodoc:
  super
  @env = ::R2CEnvironment.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, :hash, :ifunc, :last, :masgn, :match, :match2, :match3, :memo, :method, :module, :newline, :next, :nth_ref, :op_asgn_or, :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

  @statics = []
  @prototypes = []
end

Instance Attribute Details

#envObject (readonly)

Provides access to the variable scope.



74
75
76
# File 'lib/ruby_to_ansi_c.rb', line 74

def env
  @env
end

#prototypesObject (readonly)

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



80
81
82
# File 'lib/ruby_to_ansi_c.rb', line 80

def prototypes
  @prototypes
end

#staticsObject (readonly)

Provides a place to put things at the file scope. Be smart, make them static (hence the name).



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

def statics
  @statics
end

Class Method Details

.c_type(typ) ⇒ Object

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



34
35
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
# 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

.translatorObject

Lazy initializer for the composite RubytoC translator chain.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# 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

Instance Method Details

#no(exp) ⇒ Object

TODO: remove me



26
27
28
# File 'lib/ruby_to_ansi_c.rb', line 26

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.



87
88
89
90
91
92
93
94
95
# 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

#process_and(exp) ⇒ Object

Logical And. Nothing exciting here



139
140
141
142
143
144
# 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

#process_arglist(exp) ⇒ Object

Arglist is used by call arg lists.



149
150
151
152
# File 'lib/ruby_to_ansi_c.rb', line 149

def process_arglist(exp)
  return '' if exp.empty?
  return process_array(exp)
end

#process_args(exp) ⇒ Object

Argument List including variable types.



157
158
159
160
161
162
163
164
165
166
167
168
169
# 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

#process_array(exp) ⇒ Object

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



174
175
176
177
178
179
180
181
182
183
184
185
# 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

#process_block(exp) ⇒ Object

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



191
192
193
194
195
196
197
198
199
200
201
202
# 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

#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.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# 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

#process_class(exp) ⇒ Object

DOC



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# 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

#process_const(exp) ⇒ Object

Constants, must be pre-defined in the global env for ansi c.



277
278
279
280
# File 'lib/ruby_to_ansi_c.rb', line 277

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



289
290
291
292
293
# 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

#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.



301
302
303
304
305
# 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

#process_defn(exp) ⇒ Object

TODO: audit against obfuscator



316
317
318
319
320
321
322
323
324
325
326
327
328
# 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

#process_defx(exp) ⇒ Object

TODO: audit against obfuscator



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

def process_defx(exp) # TODO: audit against obfuscator
  return process_defn(exp)
end

#process_dummy(exp) ⇒ Object

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

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



339
340
341
# File 'lib/ruby_to_ansi_c.rb', line 339

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?



348
349
350
351
352
# 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

#process_error(exp) ⇒ Object

DOC - TODO: what is this?!?



357
358
359
# File 'lib/ruby_to_ansi_c.rb', line 357

def process_error(exp)
  return exp.shift
end

#process_false(exp) ⇒ Object

False. Pretty straightforward.



364
365
366
# File 'lib/ruby_to_ansi_c.rb', line 364

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.



375
376
377
378
379
380
381
382
383
384
# 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

#process_iasgn(exp) ⇒ Object

Instance Variable Assignment



389
390
391
392
393
# 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

#process_if(exp) ⇒ Object

Conditional statements

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



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# 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

#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)


437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# 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

#process_ivar(exp) ⇒ Object

Instance Variable Access



465
466
467
468
# File 'lib/ruby_to_ansi_c.rb', line 465

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.



475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# 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

#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?



517
518
519
520
521
522
523
524
525
526
527
528
529
530
# 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

#process_lvar(exp) ⇒ Object

Local variable



535
536
537
538
539
540
# 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

#process_nil(exp) ⇒ Object

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



549
550
551
# File 'lib/ruby_to_ansi_c.rb', line 549

def process_nil(exp)
  return "NULL"
end

#process_not(exp) ⇒ Object

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



556
557
558
559
# File 'lib/ruby_to_ansi_c.rb', line 556

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

#process_or(exp) ⇒ Object

Logical or. Nothing exciting here



564
565
566
567
568
569
# 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

#process_return(exp) ⇒ Object

Return statement. Nothing exciting here



574
575
576
# File 'lib/ruby_to_ansi_c.rb', line 574

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?



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# 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

#process_static(exp) ⇒ Object

A bogus ruby sexp type for generating static variable declarations



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

def process_static(exp)
  return exp.shift
end

#process_str(exp) ⇒ Object

Strings. woot.



610
611
612
# File 'lib/ruby_to_ansi_c.rb', line 610

def process_str(exp)
  return exp.shift.inspect
end

#process_true(exp) ⇒ Object

Truth… what is truth?



619
620
621
# File 'lib/ruby_to_ansi_c.rb', line 619

def process_true(exp)
  return "1"
end

#process_while(exp) ⇒ Object

While block. Nothing exciting here.



626
627
628
629
630
631
632
633
634
635
636
# 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

#with_scopeObject



638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# 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