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.2'
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(exp, _src = nil, _timeout = nil) ⇒ Object



136
137
138
# File 'lib/ruby_to_ansi_c.rb', line 136

def process exp, _src = nil, _timeout = nil
  super(exp)
end

#process_and(exp) ⇒ Object

Logical And. Nothing exciting here



143
144
145
146
147
148
# File 'lib/ruby_to_ansi_c.rb', line 143

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.



153
154
155
156
# File 'lib/ruby_to_ansi_c.rb', line 153

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

#process_args(exp) ⇒ Object

Argument List including variable types.



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

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.



178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ruby_to_ansi_c.rb', line 178

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.



195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/ruby_to_ansi_c.rb', line 195

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.



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
253
254
255
256
# File 'lib/ruby_to_ansi_c.rb', line 215

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



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/ruby_to_ansi_c.rb', line 261

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.



281
282
283
284
# File 'lib/ruby_to_ansi_c.rb', line 281

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



293
294
295
296
297
# File 'lib/ruby_to_ansi_c.rb', line 293

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.



305
306
307
308
309
# File 'lib/ruby_to_ansi_c.rb', line 305

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



320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/ruby_to_ansi_c.rb', line 320

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



334
335
336
# File 'lib/ruby_to_ansi_c.rb', line 334

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.



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

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?



352
353
354
355
356
# File 'lib/ruby_to_ansi_c.rb', line 352

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



361
362
363
# File 'lib/ruby_to_ansi_c.rb', line 361

def process_error(exp)
  return exp.shift
end

#process_false(exp) ⇒ Object

False. Pretty straightforward.



368
369
370
# File 'lib/ruby_to_ansi_c.rb', line 368

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.



379
380
381
382
383
384
385
386
387
388
# File 'lib/ruby_to_ansi_c.rb', line 379

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



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

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



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
431
432
433
434
# File 'lib/ruby_to_ansi_c.rb', line 404

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)


441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/ruby_to_ansi_c.rb', line 441

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



469
470
471
472
# File 'lib/ruby_to_ansi_c.rb', line 469

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.



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
511
512
# File 'lib/ruby_to_ansi_c.rb', line 479

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.join.sub(/;\n\Z/, '')
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?



519
520
521
522
523
524
525
526
527
528
529
530
531
532
# File 'lib/ruby_to_ansi_c.rb', line 519

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



537
538
539
540
541
542
# File 'lib/ruby_to_ansi_c.rb', line 537

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



551
552
553
# File 'lib/ruby_to_ansi_c.rb', line 551

def process_nil(exp)
  return "NULL"
end

#process_not(exp) ⇒ Object

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



558
559
560
561
# File 'lib/ruby_to_ansi_c.rb', line 558

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

#process_or(exp) ⇒ Object

Logical or. Nothing exciting here



566
567
568
569
570
571
# File 'lib/ruby_to_ansi_c.rb', line 566

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



576
577
578
# File 'lib/ruby_to_ansi_c.rb', line 576

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?



586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/ruby_to_ansi_c.rb', line 586

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



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

def process_static(exp)
  return exp.shift
end

#process_str(exp) ⇒ Object

Strings. woot.



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

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

#process_true(exp) ⇒ Object

Truth… what is truth?



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

def process_true(exp)
  return "1"
end

#process_while(exp) ⇒ Object

While block. Nothing exciting here.



628
629
630
631
632
633
634
635
636
637
638
# File 'lib/ruby_to_ansi_c.rb', line 628

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



640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
# File 'lib/ruby_to_ansi_c.rb', line 640

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.join
    end
  end

  return declarations, result
end