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/History.txt +69 -0
- data/Makefile +49 -0
- data/Manifest.txt +98 -0
- data/README.txt +75 -0
- data/demo/char.rb +13 -0
- data/demo/factorial.rb +11 -0
- data/demo/hello.rb +11 -0
- data/demo/misc.rb +25 -0
- data/demo/newarray.rb +11 -0
- data/demo/strcat.rb +12 -0
- data/rewrite.rb +32 -0
- data/rewriter.rb +356 -0
- data/ruby_to_c.rb +680 -0
- data/support.rb +317 -0
- data/test_all.rb +9 -0
- data/test_extras.rb +143 -0
- data/test_rewriter.rb +292 -0
- data/test_ruby_to_c.rb +533 -0
- data/test_support.rb +525 -0
- data/test_type_checker.rb +838 -0
- data/test_typed_sexp_processor.rb +134 -0
- data/translate.rb +31 -0
- data/type.rb +33 -0
- data/type_checker.rb +922 -0
- data/typed_sexp_processor.rb +88 -0
- data/validate.sh +49 -0
- data/zcomparable.rb +300 -0
- metadata +74 -0
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
|