RubyToC 1.0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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