RubyToC 1.0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/ruby_to_c.rb ADDED
@@ -0,0 +1,680 @@
1
+
2
+ $TESTING = false unless defined? $TESTING
3
+
4
+ begin require 'rubygems' rescue LoadError end
5
+ require 'parse_tree'
6
+ require 'sexp_processor'
7
+ require 'composite_sexp_processor'
8
+ require 'type_checker'
9
+ require 'rewriter'
10
+ require 'pp'
11
+
12
+ ##
13
+ # Maps a sexp type to a C counterpart.
14
+
15
+ module TypeMap
16
+
17
+ ##
18
+ # Returns a textual version of a C type that corresponds to a sexp
19
+ # type.
20
+
21
+ def c_type(typ)
22
+ base_type =
23
+ case typ.type.contents # HACK this is breaking demeter
24
+ when :float then
25
+ "double"
26
+ when :long then
27
+ "long"
28
+ when :str then
29
+ "str"
30
+ when :symbol then
31
+ "symbol"
32
+ when :bool then # TODO: subject to change
33
+ "VALUE"
34
+ when :void then
35
+ "void"
36
+ when :homo then
37
+ "void *" # HACK
38
+ when :value, :unknown then
39
+ "VALUE"
40
+ # HACK: uncomment this and fix the above when you want to have good tests
41
+ # when :unknown then
42
+ # raise "You should not have unknown types by now!"
43
+ else
44
+ raise "Bug! Unknown type #{typ.inspect} in c_type"
45
+ end
46
+
47
+ base_type += "_array" if typ.list?
48
+
49
+ base_type
50
+ end
51
+
52
+ module_function :c_type # if $TESTING
53
+
54
+ end
55
+
56
+ ##
57
+ # The whole point of this project! RubyToC is an actually very simple
58
+ # SexpProcessor that does the final conversion from Sexp to C code.
59
+ # This class has more unsupported nodes than any other (on
60
+ # purpose--we'd like TypeChecker and friends to be as generally useful
61
+ # as possible), and as a result, supports a very small subset of ruby.
62
+ #
63
+ # NOT SUPPORTED: (keep in sync w/ initialize)
64
+ #
65
+ # :begin, :block_arg, :case, :dstr, :rescue, :self, :super, :when
66
+
67
+ class RubyToC < SexpProcessor
68
+
69
+ VERSION = '1.0.0-beta-4'
70
+
71
+ # TODO: remove me
72
+ def no(exp) # :nodoc:
73
+ raise "no: #{caller[0].split[1]} #{exp.inspect}"
74
+ end
75
+
76
+ include TypeMap
77
+
78
+ ##
79
+ # Provides access to the variable scope.
80
+
81
+ attr_reader :env
82
+
83
+ ##
84
+ # Provides access to the method signature prototypes that are needed
85
+ # at the top of the C file.
86
+
87
+ attr_reader :prototypes
88
+
89
+ ##
90
+ # Provides a (rather bogus) preamble. Put your includes and defines
91
+ # here. It really should be made to be much more clean and
92
+ # extendable.
93
+
94
+ def preamble
95
+ "// BEGIN METARUBY PREAMBLE
96
+ #include <ruby.h>
97
+ #define RB_COMPARE(x, y) (x) == (y) ? 0 : (x) < (y) ? -1 : 1
98
+ typedef char * str;
99
+ typedef struct { unsigned long length; long * contents; } long_array;
100
+ typedef struct { unsigned long length; str * contents; } str_array;
101
+ #define case_equal_long(x, y) ((x) == (y))
102
+ // END METARUBY PREAMBLE
103
+ " + self.prototypes.join('')
104
+ end
105
+
106
+ ##
107
+ # Lazy initializer for the composite RubytoC translator chain.
108
+
109
+ def self.translator
110
+ unless defined? @@translator then
111
+ @@translator = CompositeSexpProcessor.new
112
+ @@translator << Rewriter.new
113
+ @@translator << TypeChecker.new
114
+ @@translator << R2CRewriter.new
115
+ @@translator << RubyToC.new
116
+ @@translator.on_error_in(:defn) do |processor, exp, err|
117
+ result = processor.expected.new
118
+ case result
119
+ when Array then
120
+ result << :error
121
+ end
122
+ msg = "// ERROR: #{err.class}: #{err}"
123
+ msg += " in #{exp.inspect}" unless exp.nil? or $TESTING
124
+ msg += " from #{caller.join(', ')}" unless $TESTING
125
+ result << msg
126
+ result
127
+ end
128
+ end
129
+ @@translator
130
+ end
131
+
132
+ ##
133
+ # Front-end utility method for translating an entire class or a
134
+ # specific method from that class.
135
+
136
+ def self.translate(klass, method=nil)
137
+ # REFACTOR: rename to self.process
138
+ unless method.nil? then
139
+ self.translator.process(ParseTree.new(false).parse_tree_for_method(klass, method))
140
+ else
141
+ ParseTree.new.parse_tree(klass).map do |k|
142
+ self.translator.process(ParseTree.new.parse_tree(klass))
143
+ end
144
+ end
145
+ end
146
+
147
+ ##
148
+ # (Primary) Front-end utility method for translating an entire
149
+ # class. Has special error handlers that convert errors into C++
150
+ # comments (//...).
151
+
152
+ def self.translate_all_of(klass)
153
+ result = []
154
+
155
+ # HACK: make CompositeSexpProcessor have a registered error handler
156
+ klass.instance_methods(false).sort.each do |method|
157
+ result <<
158
+ begin
159
+ self.translate(klass, method)
160
+ rescue UnsupportedNodeError => err
161
+ "// NOTE: #{err} in #{klass}##{method}"
162
+ rescue UnknownNodeError => err
163
+ "// ERROR: #{err} in #{klass}##{method}: #{ParseTree.new.parse_tree_for_method(klass, method).inspect}"
164
+ rescue Exception => err
165
+ "// ERROR: #{err} in #{klass}##{method}: #{ParseTree.new.parse_tree_for_method(klass, method).inspect} #{err.backtrace.join(', ')}"
166
+ end
167
+ end
168
+
169
+ prototypes = self.translator.processors[-1].prototypes
170
+ "#{prototypes.join('')}\n\n#{result.join("\n\n")}"
171
+ end
172
+
173
+ def initialize # :nodoc:
174
+ super
175
+ @env = Environment.new
176
+ self.auto_shift_type = true
177
+ self.unsupported = [ :begin, :block_arg, :case, :dstr, :rescue, :self, :super, :when, ]
178
+ self.strict = true
179
+ self.expected = String
180
+
181
+ @prototypes = []
182
+ end
183
+
184
+ ##
185
+ # Logical And. Nothing exciting here
186
+
187
+ def process_and(exp)
188
+ lhs = process exp.shift
189
+ rhs = process exp.shift
190
+
191
+ return "#{lhs} && #{rhs}"
192
+ end
193
+
194
+ ##
195
+ # Argument List including variable types.
196
+
197
+ def process_args(exp)
198
+ args = []
199
+
200
+ until exp.empty? do
201
+ arg = exp.shift
202
+
203
+ # p arg
204
+ # p arg.sexp_type
205
+ # p arg.first
206
+ # p self.class
207
+ # p arg.sexp_type.class
208
+ # p TypeMap.methods.sort
209
+ # p c_type(arg.sexp_type)
210
+
211
+ args << "#{c_type(arg.sexp_type)} #{arg.first}"
212
+ end
213
+
214
+ return "(#{args.join ', '})"
215
+ end
216
+
217
+ ##
218
+ # Arglist is used by call arg lists.
219
+
220
+ def process_arglist(exp)
221
+ return process_array(exp)
222
+ end
223
+
224
+ ##
225
+ # Array is used as call arg lists and as initializers for variables.
226
+
227
+ def process_array(exp)
228
+ code = []
229
+
230
+ until exp.empty? do
231
+ code << process(exp.shift)
232
+ end
233
+
234
+ s = code.join ', '
235
+ s = "[]" if s.empty?
236
+
237
+ return s
238
+ end
239
+
240
+ ##
241
+ # Block doesn't have an analog in C, except maybe as a functions's
242
+ # outer braces.
243
+
244
+ def process_block(exp)
245
+ code = []
246
+ until exp.empty? do
247
+ code << process(exp.shift)
248
+ end
249
+
250
+ body = code.join(";\n")
251
+ body += ";" unless body =~ /[;}]\Z/
252
+ body += "\n"
253
+
254
+ return body
255
+ end
256
+
257
+ ##
258
+ # Call, both unary and binary operators and regular function calls.
259
+ #
260
+ # TODO: This needs a lot of work. We've cheated with the case
261
+ # statement below. We need a real function signature lookup like we
262
+ # have in R2CRewriter.
263
+
264
+ def process_call(exp)
265
+ receiver = exp.shift
266
+ name = exp.shift
267
+
268
+ receiver_type = Type.unknown
269
+ unless receiver.nil? then
270
+ receiver_type = receiver.sexp_type
271
+ end
272
+ receiver = process receiver
273
+
274
+ case name
275
+ # TODO: these need to be numerics
276
+ # emacs gets confused by :/ below, need quotes to fix indentation
277
+ when :==, :<, :>, :<=, :>=, :-, :+, :*, :"/", :% then
278
+ args = process exp.shift[1]
279
+ return "#{receiver} #{name} #{args}"
280
+ when :<=>
281
+ args = process exp.shift[1]
282
+ return "RB_COMPARE(#{receiver}, #{args})"
283
+ when :equal?
284
+ args = process exp.shift
285
+ return "#{receiver} == #{args}" # equal? == address equality
286
+ when :[]
287
+ if receiver_type.list? then
288
+ args = process exp.shift
289
+ return "#{receiver}.contents[#{args}]"
290
+ else
291
+ # FIX: not sure about this one... hope for the best.
292
+ args = process exp.shift
293
+ return "#{receiver}[#{args}]"
294
+ end
295
+ else
296
+ args = process exp.shift
297
+ name = "NIL_P" if name == :nil?
298
+
299
+ if receiver.nil? and args.nil? then
300
+ args = ""
301
+ elsif receiver.nil? then
302
+ # nothing to do
303
+ elsif args.nil? then
304
+ args = receiver
305
+ else
306
+ args = "#{receiver}, #{args}"
307
+ end
308
+
309
+ return "#{name}(#{args})"
310
+ end
311
+ end
312
+
313
+ ##
314
+ # DOC
315
+
316
+ def process_class(exp)
317
+ name = exp.shift
318
+ superklass = exp.shift
319
+
320
+ result = []
321
+
322
+ result << "// class #{name}"
323
+
324
+ until exp.empty? do
325
+ # HACK: cheating!
326
+ klass = name
327
+ method = exp[1]
328
+ result << process(exp.shift)
329
+ end
330
+
331
+ return result.join("\n\n")
332
+ end
333
+
334
+ def process_const(exp)
335
+ name = exp.shift
336
+ return name.to_s
337
+ end
338
+
339
+ ##
340
+ # Constants, must be defined in the global env.
341
+ #
342
+ # TODO: This will cause a lot of errors with the built in classes
343
+ # until we add them to the bootstrap phase.
344
+ # HACK: what is going on here??? We have NO tests for this node
345
+
346
+ def process_cvar(exp)
347
+ # TODO: we should treat these as globals and have them in the top scope
348
+ name = exp.shift
349
+ return name.to_s
350
+ end
351
+
352
+ ##
353
+ # Iterator variables.
354
+ #
355
+ # TODO: check to see if this is the least bit relevant anymore. We
356
+ # might have rewritten them all.
357
+
358
+ def process_dasgn_curr(exp)
359
+ var = exp.shift
360
+ @env.add var.to_sym, exp.sexp_type
361
+ return var.to_s
362
+ end
363
+
364
+ ##
365
+ # Function definition
366
+
367
+ def process_defn(exp)
368
+
369
+ name = exp.shift
370
+ args = process exp.shift
371
+ body = process exp.shift
372
+ function_type = exp.sexp_type
373
+
374
+ ret_type = c_type function_type.list_type.return_type
375
+
376
+ @prototypes << "#{ret_type} #{name}#{args};\n"
377
+ "#{ret_type}\n#{name}#{args} #{body}"
378
+ end
379
+
380
+ ##
381
+ # Generic handler. Ignore me, I'm not here.
382
+ #
383
+ # TODO: nuke dummy nodes by using new SexpProcessor rewrite rules.
384
+
385
+ def process_dummy(exp)
386
+ process_block(exp).chomp
387
+ end
388
+
389
+ ##
390
+ # Dynamic variables, should be the same as lvar at this stage.
391
+ #
392
+ # TODO: remove / rewrite?
393
+
394
+ def process_dvar(exp)
395
+ var = exp.shift
396
+ @env.add var.to_sym, exp.sexp_type
397
+ return var.to_s
398
+ end
399
+
400
+ ##
401
+ # DOC
402
+
403
+ def process_error(exp)
404
+ return exp.shift
405
+ end
406
+
407
+ ##
408
+ # False. Pretty straightforward. Currently we output ruby Qfalse
409
+
410
+ def process_false(exp)
411
+ return "Qfalse"
412
+ end
413
+
414
+ ##
415
+ # Global variables, evil but necessary.
416
+ #
417
+ # TODO: get the case statement out by using proper bootstrap in genv.
418
+
419
+ def process_gvar(exp)
420
+ name = exp.shift
421
+ type = exp.sexp_type
422
+ case name
423
+ when :$stderr then
424
+ "stderr"
425
+ else
426
+ raise "Bug! Unhandled gvar #{name.inspect} (type = #{type})"
427
+ end
428
+ end
429
+
430
+ ##
431
+ # Hash values, currently unsupported, but plans are in the works.
432
+
433
+ def process_hash(exp)
434
+ no(exp)
435
+ end
436
+
437
+ ##
438
+ # Instance Variable Assignment
439
+
440
+ def process_iasgn(exp)
441
+ name = exp.shift
442
+ val = process exp.shift
443
+ "self->#{name.to_s.sub(/^@/, '')} = #{val}"
444
+ end
445
+
446
+ ##
447
+ # Conditional statements
448
+ #
449
+ # TODO: implementation is ugly as hell... PLEASE try to clean
450
+
451
+ def process_if(exp)
452
+ cond_part = process exp.shift
453
+
454
+ result = "if (#{cond_part})"
455
+
456
+ then_block = ! exp.first.nil? && exp.first.first == :block
457
+ then_part = process exp.shift
458
+ else_block = ! exp.first.nil? && exp.first.first == :block
459
+ else_part = process exp.shift
460
+
461
+ then_part = "" if then_part.nil?
462
+ else_part = "" if else_part.nil?
463
+
464
+ result += " {\n"
465
+
466
+ then_part = then_part.join(";\n") if Array === then_part
467
+ then_part += ";" unless then_part =~ /[;}]\Z/
468
+ # HACK: um... deal with nil correctly (see unless support)
469
+ result += then_part.to_s # + ";"
470
+ result += ";" if then_part.nil?
471
+ result += "\n" unless result =~ /\n\Z/
472
+ result += "}"
473
+
474
+ if else_part != "" then
475
+ result += " else {\n"
476
+ else_part = else_part.join(";\n") if Array === else_part
477
+ else_part += ";" unless else_part =~ /[;}]\Z/
478
+ result += else_part
479
+ result += "\n}"
480
+ end
481
+
482
+ result
483
+ end
484
+
485
+ ##
486
+ # Iterators for loops. After rewriter nearly all iter nodes
487
+ # should be able to be interpreted as a for loop. If not, then you
488
+ # are doing something not supported by C in the first place.
489
+
490
+ def process_iter(exp)
491
+ out = []
492
+ @env.scope do
493
+ enum = exp[0][1][1] # HACK ugly
494
+ call = process exp.shift
495
+ var = process(exp.shift).intern # semi-HACK-y
496
+ body = process exp.shift
497
+ index = "index_#{var}"
498
+
499
+ body += ";" unless body =~ /[;}]\Z/
500
+ body.gsub!(/\n\n+/, "\n")
501
+
502
+ out << "unsigned long #{index};"
503
+ out << "for (#{index} = 0; #{index} < #{enum}.length; ++#{index}) {"
504
+ out << "#{c_type @env.lookup(var)} #{var} = #{enum}.contents[#{index}];"
505
+ out << body
506
+ out << "}"
507
+ end
508
+
509
+ return out.join("\n")
510
+ end
511
+
512
+ ##
513
+ # Instance Variable Access
514
+
515
+ def process_ivar(exp)
516
+ name = exp.shift
517
+ "self->#{name.to_s.sub(/^@/, '')}"
518
+ end
519
+
520
+ ##
521
+ # Assignment to a local variable.
522
+ #
523
+ # TODO: figure out array issues and clean up.
524
+
525
+ def process_lasgn(exp)
526
+ out = ""
527
+
528
+ var = exp.shift
529
+ value = exp.shift
530
+ # grab the size of the args, if any, before process converts to a string
531
+ arg_count = 0
532
+ arg_count = value.length - 1 if value.first == :array
533
+ args = value
534
+
535
+ exp_type = exp.sexp_type
536
+ @env.add var.to_sym, exp_type
537
+ var_type = c_type exp_type
538
+
539
+ if exp_type.list? then
540
+ assert_type args, :array
541
+
542
+ raise "array must be of one type" unless args.sexp_type == Type.homo
543
+
544
+ # HACK: until we figure out properly what to do w/ zarray
545
+ # before we know what its type is, we will default to long.
546
+ array_type = args.sexp_types.empty? ? 'long' : c_type(args.sexp_types.first)
547
+
548
+ args.shift
549
+ out << "#{var}.length = #{arg_count};\n"
550
+ out << "#{var}.contents = (#{array_type}*) malloc(sizeof(#{array_type}) * #{var}.length);\n"
551
+ args.each_with_index do |o,i|
552
+ out << "#{var}.contents[#{i}] = #{process o};\n"
553
+ end
554
+ else
555
+ out << "#{var} = #{process args}"
556
+ end
557
+
558
+ out.sub!(/;\n\Z/, '')
559
+
560
+ return out
561
+ end
562
+
563
+ ##
564
+ # Literals, numbers for the most part. Will probably cause
565
+ # compilation errors if you try to translate bignums and other
566
+ # values that don't have analogs in the C world. Sensing a pattern?
567
+
568
+ def process_lit(exp)
569
+ # TODO what about floats and big numbers?
570
+
571
+ value = exp.shift
572
+ sexp_type = exp.sexp_type
573
+ case sexp_type
574
+ when Type.long, Type.float then
575
+ return value.to_s
576
+ when Type.symbol then
577
+ return ":" + value.to_s
578
+ else
579
+ raise "Bug! no: Unknown literal #{value}:#{value.class}"
580
+ end
581
+ end
582
+
583
+ ##
584
+ # Local variable
585
+
586
+ def process_lvar(exp)
587
+ name = exp.shift
588
+ # do nothing at this stage, var should have been checked for
589
+ # existance already.
590
+ return name.to_s
591
+ end
592
+
593
+ ##
594
+ # Nil, currently ruby nil, not C NULL (0).
595
+
596
+ def process_nil(exp)
597
+ return "Qnil"
598
+ end
599
+
600
+ ##
601
+ # Nil, currently ruby nil, not C NULL (0).
602
+
603
+ def process_not(exp)
604
+ term = process exp.shift
605
+ return "!(#{term})"
606
+ end
607
+
608
+ ##
609
+ # Or assignment (||=), currently unsupported, but only because of
610
+ # laziness.
611
+
612
+ def process_op_asgn_or(exp)
613
+ no(exp)
614
+ end
615
+
616
+ ##
617
+ # Logical or. Nothing exciting here
618
+
619
+ def process_or(exp)
620
+ lhs = process exp.shift
621
+ rhs = process exp.shift
622
+
623
+ return "#{lhs} || #{rhs}"
624
+ end
625
+
626
+ ##
627
+ # Return statement. Nothing exciting here
628
+
629
+ def process_return(exp)
630
+ return "return #{process exp.shift}"
631
+ end
632
+
633
+ ##
634
+ # Scope has no real equivalent in C-land, except that like
635
+ # process_block above. We put variable declarations here before the
636
+ # body and use this as our opportunity to open a variable
637
+ # scope. Crafty, no?
638
+
639
+ def process_scope(exp)
640
+ declarations = []
641
+ body = nil
642
+ @env.scope do
643
+ body = process exp.shift unless exp.empty?
644
+ @env.current.sort_by { |v,t| v.to_s }.each do |var, var_type|
645
+ var_type = c_type var_type
646
+ declarations << "#{var_type} #{var};\n"
647
+ end
648
+ end
649
+ return "{\n#{declarations}#{body}}"
650
+ end
651
+
652
+ ##
653
+ # Strings. woot.
654
+
655
+ def process_str(exp)
656
+ s = exp.shift.gsub(/\n/, '\\n')
657
+ return "\"#{s}\""
658
+ end
659
+
660
+ ##
661
+ # Truth... what is truth? In this case, Qtrue.
662
+
663
+ def process_true(exp)
664
+ return "Qtrue"
665
+ end
666
+
667
+ ##
668
+ # While block. Nothing exciting here.
669
+
670
+ def process_while(exp)
671
+ cond = process exp.shift
672
+ body = process exp.shift
673
+ body += ";" unless body =~ /;/
674
+ is_precondition = exp.shift
675
+ code = "while (#{cond}) {\n#{body.strip}\n}"
676
+ code = "{\n#{body.strip}\n} while (#{cond})" unless is_precondition
677
+ return code
678
+ end
679
+
680
+ end