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 =

HACK version should be 1.0.0.beta.7, but rubygems sucks

'1.0.0.7'
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:



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

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.



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

def env
  @env
end

#prototypesObject (readonly)

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



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

def prototypes
  @prototypes
end

#staticsObject (readonly)

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



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

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.



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
64
# File 'lib/ruby_to_ansi_c.rb', line 35

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.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ruby_to_ansi_c.rb', line 101

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



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

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.



88
89
90
91
92
93
94
95
96
# File 'lib/ruby_to_ansi_c.rb', line 88

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



140
141
142
143
144
145
# File 'lib/ruby_to_ansi_c.rb', line 140

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.



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

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

#process_args(exp) ⇒ Object

Argument List including variable types.



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

def process_args(exp)
  args = []

  until exp.empty? do
    arg = exp.shift
    name = arg.first.to_s.sub(/^\*/, '').intern
    type = arg.sexp_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.



175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/ruby_to_ansi_c.rb', line 175

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.



192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/ruby_to_ansi_c.rb', line 192

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.



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

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



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

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.



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

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



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

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.



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

def process_dasgn_curr(exp) # TODO: audit against obfuscator
  var = exp.shift
  @env.add var.to_sym, exp.sexp_type
  return var.to_s
end

#process_defn(exp) ⇒ Object

TODO: audit against obfuscator



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

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.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_defx(exp) ⇒ Object

TODO: audit against obfuscator



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

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.



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

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?



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

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 - TODO: what is this?!?



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

def process_error(exp)
  return exp.shift
end

#process_false(exp) ⇒ Object

False. Pretty straightforward.



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

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.



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

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_iasgn(exp) ⇒ Object

Instance Variable Assignment



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

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



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
435
436
437
# File 'lib/ruby_to_ansi_c.rb', line 405

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)


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

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

    p exp

    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



474
475
476
477
# File 'lib/ruby_to_ansi_c.rb', line 474

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.



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
513
514
515
516
517
518
519
520
# File 'lib/ruby_to_ansi_c.rb', line 484

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.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
# TODO: look into alloca
    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?



527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/ruby_to_ansi_c.rb', line 527

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 # HACK wrong! write test!
  else
    raise "Bug! no: Unknown literal #{value}:#{value.class}"
  end
end

#process_lvar(exp) ⇒ Object

Local variable



545
546
547
548
549
550
# File 'lib/ruby_to_ansi_c.rb', line 545

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



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

def process_nil(exp)
  return "NULL"
end

#process_not(exp) ⇒ Object

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



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

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

#process_or(exp) ⇒ Object

Logical or. Nothing exciting here



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

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



584
585
586
# File 'lib/ruby_to_ansi_c.rb', line 584

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?



594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
# File 'lib/ruby_to_ansi_c.rb', line 594

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



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

def process_static(exp)
  return exp.shift
end

#process_str(exp) ⇒ Object

Strings. woot.



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

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

#process_true(exp) ⇒ Object

Truth… what is truth?



629
630
631
# File 'lib/ruby_to_ansi_c.rb', line 629

def process_true(exp)
  return "1"
end

#process_while(exp) ⇒ Object

While block. Nothing exciting here.



636
637
638
639
640
641
642
643
644
645
646
# File 'lib/ruby_to_ansi_c.rb', line 636

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



648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
# File 'lib/ruby_to_ansi_c.rb', line 648

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