RubyToC 1.0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,134 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ require 'test_sexp_processor'
4
+ require 'typed_sexp_processor'
5
+ require 'test/unit'
6
+
7
+ # Fake test classes:
8
+
9
+ class TestTypedSexp < TestSexp
10
+
11
+ def setup
12
+ super
13
+ @sexp.sexp_type = Type.str
14
+ end
15
+
16
+ def test_new_nested_typed
17
+ @sexp = TypedSexp.new(:lasgn, "var", TypedSexp.new(:str, "foo", Type.str), Type.str)
18
+ assert_equal('t(:lasgn, "var", t(:str, "foo", Type.str), Type.str)',
19
+ @sexp.inspect)
20
+ end
21
+ def test_sexp_type
22
+ assert_equal(Type.str, @sexp.sexp_type)
23
+ end
24
+
25
+ def test_sexp_type=
26
+ assert_equal(Type.str, @sexp.sexp_type)
27
+ # FIX: we can't set sexp_type a second time, please expand tests
28
+ @sexp._set_sexp_type 24
29
+ assert_equal(24, @sexp.sexp_type)
30
+ end
31
+
32
+ def test_sexp_type_array_homo
33
+ @sexp = t(:array, t(:lit, 1, Type.long),
34
+ t(:lit, 2, Type.long))
35
+ assert_equal(Type.homo, @sexp.sexp_type)
36
+ assert_equal([Type.long, Type.long], @sexp.sexp_types)
37
+ end
38
+
39
+ def test_sexp_type_array_hetero
40
+ @sexp = t(:array, t(:lit, 1, Type.long),
41
+ t(:str, "foo", Type.str))
42
+ assert_equal(Type.hetero, @sexp.sexp_type)
43
+ assert_equal([Type.long, Type.str], @sexp.sexp_types)
44
+ end
45
+
46
+ def test_sexp_type_array_nested
47
+ @sexp = t(:array, t(:lit, 1, Type.long),
48
+ t(:array, t(:lit, 1, Type.long)))
49
+ assert_equal(Type.hetero, @sexp.sexp_type)
50
+ assert_equal([Type.long, Type.homo], @sexp.sexp_types)
51
+ end
52
+
53
+ def test__set_sexp_type
54
+ assert_equal Type.str, @sexp.sexp_type
55
+ @sexp._set_sexp_type Type.bool
56
+ assert_equal Type.bool, @sexp.sexp_type
57
+ end
58
+
59
+ def test_sexp_types
60
+ assert_raises(RuntimeError) do
61
+ @sexp.sexp_types
62
+ end
63
+
64
+ @sexp = t(:array, t(:lit, 1, Type.long), t(:str, "blah", Type.str))
65
+
66
+ assert_equal([Type.long, Type.str], @sexp.sexp_types)
67
+ end
68
+
69
+ def test_equals_sexp
70
+ sexp2 = t(1, 2, 3, Type.str)
71
+ assert_equal(@sexp, sexp2)
72
+ end
73
+
74
+ def test_equals_not_body_typed
75
+ sexp2 = t(1, 2, 5)
76
+ sexp2.sexp_type = Type.str
77
+ assert_not_equal(@sexp, sexp2)
78
+ end
79
+
80
+ def test_equals_not_type
81
+ sexp2 = t(1, 2, 3)
82
+ sexp2.sexp_type = Type.long
83
+ assert_not_equal(@sexp, sexp2)
84
+ end
85
+
86
+ def test_equals_sexp_typed
87
+ sexp2 = t(1, 2, 3)
88
+ sexp2.sexp_type = Type.str
89
+ assert_equal(@sexp, sexp2)
90
+ end
91
+
92
+ def test_equals_array_typed
93
+ # can't use assert_equals because it uses array as receiver
94
+ assert_not_equal(@sexp, [1, 2, 3, Type.str],
95
+ "Sexp must not be equal to equivalent array")
96
+ # both directions just in case
97
+ assert_not_equal([1, 2, 3, Type.str], @sexp,
98
+ "Sexp must not be equal to equivalent array")
99
+ end
100
+
101
+ def test_pretty_print_typed
102
+ util_pretty_print("t(Type.str)",
103
+ t(Type.str))
104
+ util_pretty_print("t(:a, Type.long)",
105
+ t(:a, Type.long))
106
+ util_pretty_print("t(:a, :b, Type.long)",
107
+ t(:a, :b, Type.long))
108
+ util_pretty_print("t(:a, t(:b, Type.long), Type.str)",
109
+ t(:a, t(:b, Type.long), Type.str))
110
+ end
111
+
112
+ def test_to_s_typed
113
+ k = @sexp_class
114
+ n = k.name[0].chr.downcase
115
+ assert_equal("#{n}(Type.long)",
116
+ k.new(Type.long).inspect)
117
+ assert_equal("#{n}(:a, Type.long)",
118
+ k.new(:a, Type.long).inspect)
119
+ assert_equal("#{n}(:a, :b, Type.long)",
120
+ k.new(:a, :b, Type.long).inspect)
121
+ assert_equal("#{n}(:a, #{n}(:b, Type.long), Type.str)",
122
+ k.new(:a, k.new(:b, Type.long), Type.str).inspect)
123
+ end
124
+
125
+ def test_to_a
126
+ @sexp = t(1, 2, 3)
127
+ assert_equal([1, 2, 3], @sexp.to_a)
128
+ end
129
+
130
+ def test_to_a_typed
131
+ assert_equal([1, 2, 3, Type.str], @sexp.to_a)
132
+ end
133
+
134
+ end
data/translate.rb ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ begin require 'rubygems' rescue LoadError end
4
+ require 'parse_tree'
5
+ require 'ruby_to_c'
6
+
7
+ old_classes = []
8
+ ObjectSpace.each_object(Class) do |klass|
9
+ old_classes << klass
10
+ end
11
+
12
+ ARGV.each do |name|
13
+ require name
14
+ end
15
+
16
+ new_classes = []
17
+ ObjectSpace.each_object(Class) do |klass|
18
+ new_classes << klass
19
+ end
20
+
21
+ new_classes -= old_classes
22
+ new_classes = [ eval($c) ] if defined? $c
23
+
24
+ rubytoc = RubyToC.translator
25
+
26
+ code = ParseTree.new(false).parse_tree(*new_classes).map do |klass|
27
+ rubytoc.process(klass)
28
+ end # rescue nil
29
+
30
+ puts rubytoc.processors.last.preamble
31
+ puts code.join("\n\n")
data/type.rb ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ require 'pp'
4
+ require 'type_checker'
5
+
6
+ old_classes = []
7
+ ObjectSpace.each_object(Class) do |klass|
8
+ old_classes << klass
9
+ end
10
+
11
+ ARGV.each do |name|
12
+ require name
13
+ end
14
+
15
+ new_classes = []
16
+ ObjectSpace.each_object(Class) do |klass|
17
+ new_classes << klass
18
+ end
19
+
20
+ new_classes -= old_classes
21
+ new_classes = [ eval($c) ] if defined? $c
22
+
23
+ parser = ParseTree.new(false)
24
+ rewriter = Rewriter.new
25
+ type_checker = TypeChecker.new
26
+
27
+ new_classes.each do |klass|
28
+ sexp = parser.parse_tree klass
29
+ sexp.each do |exp|
30
+ exp = type_checker.process(rewriter.process(exp))
31
+ pp exp.to_a
32
+ end
33
+ end
data/type_checker.rb ADDED
@@ -0,0 +1,922 @@
1
+
2
+ require 'pp'
3
+ begin require 'rubygems' rescue LoadError end
4
+ require 'parse_tree'
5
+ require 'sexp_processor'
6
+ require 'rewriter'
7
+ require 'support'
8
+
9
+ # TODO: calls to sexp_type should probably be replaced w/ better Sexp API
10
+
11
+ ##
12
+ # TypeChecker bootstrap table.
13
+ #
14
+ # Default type signatures to help the TypeChecker figure out the correct types
15
+ # for methods that it might not otherwise encounter.
16
+ #
17
+ # The format is:
18
+ # :method_name => [[:reciever_type, :args_type_1, ..., :return_type], ...]
19
+
20
+ $bootstrap = {
21
+ :< => [[:long, :long, :bool],],
22
+ :<= => [[:long, :long, :bool],],
23
+ :== => [[:long, :long, :bool],],
24
+ :> => [[:long, :long, :bool],],
25
+ :>= => [[:long, :long, :bool],],
26
+
27
+ :+ => ([
28
+ [:long, :long, :long],
29
+ [:str, :str, :str],
30
+ ]),
31
+ :- => [[:long, :long, :long],],
32
+ :* => [[:long, :long, :long],],
33
+
34
+ # polymorphics:
35
+ :nil? => [[:value, :bool],],
36
+ :to_s => [[:long, :str],], # HACK - should be :value, :str
37
+ :to_i => [[:long, :long],], # HACK - should be :value, :str
38
+ :puts => [[:void, :str, :void],],
39
+ :print => [[:void, :str, :void],],
40
+
41
+ :[] => ([
42
+ [:long_list, :long, :long],
43
+ [:str, :long, :long],
44
+ ]),
45
+
46
+ # TODO: get rid of these
47
+ :case_equal_str => [[:str, :str, :bool],],
48
+ :case_equal_long => [[:long, :long, :bool],],
49
+ }
50
+
51
+ ##
52
+ # TypeChecker inferences types for sexps using type unification.
53
+ #
54
+ # TypeChecker expects sexps rewritten with Rewriter, and outputs TypedSexps.
55
+ #
56
+ # Nodes marked as 'unsupported' do not do correct type-checking of all the
57
+ # pieces of the node. They generate possibly incorrect output, that is all.
58
+
59
+ class TypeChecker < SexpProcessor
60
+
61
+ ##
62
+ # Environment containing local variables
63
+
64
+ attr_reader :env
65
+
66
+ ##
67
+ # The global environment contains global variables and constants.
68
+
69
+ attr_reader :genv
70
+
71
+ ##
72
+ # Function table
73
+
74
+ attr_reader :functions
75
+
76
+ ##
77
+ # Utility method that translates a class and optional method name to
78
+ # a type checked sexp. Mostly used for testing.
79
+
80
+ def translate(klass, method = nil)
81
+ @@parser = ParseTree.new(false) unless defined? @@parser
82
+ @@rewriter = Rewriter.new unless defined? @@rewriter
83
+ sexp = @@parser.parse_tree_for_method klass, method
84
+ sexp = @@rewriter.process sexp
85
+ self.process sexp
86
+ end
87
+
88
+ ##
89
+ # Utility method that translates a class and optional method name to
90
+ # a type checked sexp. Mostly used for testing.
91
+
92
+ def self.translate(klass, method = nil)
93
+ self.new.translate(klass, method)
94
+ end
95
+
96
+ ##
97
+ # Yet another utility method - this time the official one, although
98
+ # we don't like the implementation at this stage.
99
+
100
+ def self.process(klass, method=nil)
101
+ processor = self.new
102
+ rewriter = Rewriter.new
103
+ sexp = ParseTree.new.parse_tree(klass, method)
104
+ sexp = [sexp] unless Array === sexp.first
105
+
106
+ result = []
107
+ sexp.each do |exp|
108
+ # TODO: we need a composite processor to chain these cleanly
109
+ sexp = rewriter.process(exp)
110
+ result << processor.process(sexp)
111
+ end
112
+
113
+ result
114
+ end
115
+
116
+ def initialize # :nodoc:
117
+ super
118
+ @env = Environment.new
119
+ @genv = Environment.new
120
+ @functions = FunctionTable.new
121
+ self.auto_shift_type = true
122
+ self.strict = true
123
+ self.expected = TypedSexp
124
+
125
+ bootstrap
126
+ end
127
+
128
+ ##
129
+ # Runs the bootstrap stage, which runs over +$bootstrap+ and
130
+ # converts each entry into a full fledged method signature
131
+ # registered in the type checker. This is where the basic knowledge
132
+ # for lower level types (in C) comes from.
133
+
134
+ def bootstrap
135
+ @genv.add :$stdin, Type.file
136
+ @genv.add :$stdout, Type.file
137
+ @genv.add :$stderr, Type.file
138
+
139
+ ObjectSpace.each_object(Class) do |klass|
140
+ next if klass.name =~ /::/ # only 2 classes is core, but many others
141
+ @genv.add klass.name.intern, Type.fucked
142
+ end
143
+
144
+ $bootstrap.each do |name,signatures|
145
+ # FIX: Using Type.send because it must go through method_missing, not new
146
+ signatures.each do |signature|
147
+ lhs_type = Type.send(signature[0])
148
+ return_type = Type.send(signature[-1])
149
+ arg_types = signature[1..-2].map { |t| Type.send(t) }
150
+ @functions.add_function(name, Type.function(lhs_type, arg_types, return_type))
151
+ end
152
+ end
153
+ end
154
+
155
+ ##
156
+ # Logical and unifies its two arguments, then returns a bool sexp.
157
+
158
+ def process_and(exp)
159
+ rhs = process exp.shift
160
+ lhs = process exp.shift
161
+
162
+ rhs_type = rhs.sexp_type
163
+ lhs_type = lhs.sexp_type
164
+
165
+ rhs_type.unify lhs_type
166
+ rhs_type.unify Type.bool
167
+
168
+ return t(:and, rhs, lhs, Type.bool)
169
+ end
170
+
171
+ ##
172
+ # Args list adds each variable to the local variable table with unknown
173
+ # types, then returns an untyped args list of name/type pairs.
174
+
175
+ def process_args(exp)
176
+ formals = t(:args)
177
+ types = []
178
+
179
+ until exp.empty? do
180
+ arg = exp.shift
181
+ type = Type.unknown
182
+ @env.add arg, type
183
+ formals << t(arg, type)
184
+ types << type
185
+ end
186
+
187
+ return formals
188
+ end
189
+
190
+ ##
191
+ # Arg list stuff
192
+
193
+ def process_arglist(exp)
194
+ args = process_array exp
195
+ args[0] = :arglist
196
+
197
+ args
198
+ end
199
+
200
+ ##
201
+ # Array processes each item in the array, then returns an untyped sexp.
202
+
203
+ def process_array(exp)
204
+ types = []
205
+ vars = t(:array)
206
+ until exp.empty? do
207
+ var = process exp.shift
208
+ vars << var
209
+ types << var.sexp_type
210
+ end
211
+ vars
212
+ end
213
+
214
+ ##
215
+ # Attrasgn processes its rhs and lhs, then returns an untyped sexp.
216
+ #--
217
+ # TODO rewrite this in Rewriter
218
+ # echo "self.blah=7" | parse_tree_show -f
219
+ # => [:attrasgn, [:self], :blah=, [:array, [:lit, 7]]]
220
+
221
+ def process_attrasgn(exp)
222
+ rhs = process exp.shift
223
+ name = exp.shift
224
+ lhs = process exp.shift
225
+
226
+ # TODO: since this is an ivar, we need to figger out their var system. :/
227
+ return t(:attrasgn, rhs, name, lhs)
228
+ end
229
+
230
+ ##
231
+ # Begin processes the body, then returns an untyped sexp.
232
+
233
+ def process_begin(exp)
234
+ body = process exp.shift
235
+ # shouldn't be anything to unify
236
+ return t(:begin, body)
237
+ end
238
+
239
+ ##
240
+ # Block processes each sexp in the block, then returns an unknown-typed
241
+ # sexp.
242
+
243
+ def process_block(exp)
244
+ nodes = t(:block, Type.unknown)
245
+ until exp.empty? do
246
+ nodes << process(exp.shift)
247
+ end
248
+ nodes
249
+ end
250
+
251
+ ##
252
+ # Block arg is currently unsupported. Returns an unmentionably-typed
253
+ # sexp.
254
+ #--
255
+ # TODO do something more sensible
256
+
257
+ def process_block_arg(exp)
258
+ t(:block_arg, exp.shift, Type.fucked)
259
+ end
260
+
261
+ ##
262
+ # Block pass is currently unsupported. Returns a typed sexp.
263
+ #--
264
+ # TODO: we might want to look at rewriting this into a call variation.
265
+ # echo "class E; def e(&b); blah(&b); end; end" | parse_tree_show
266
+ # => [:defn, :e, [:scope, [:block, [:args], [:block_arg, :b], [:block_pass, [:lvar, :b], [:fcall, :blah]]]]]
267
+
268
+ def process_block_pass(exp)
269
+ block = process exp.shift
270
+ call = process exp.shift
271
+ t(:block_pass, block, call)
272
+ end
273
+
274
+ ##
275
+ # Call unifies the actual function paramaters against the formal function
276
+ # paramaters, if a function type signature already exists in the function
277
+ # table. If no type signature for the function name exists, the function is
278
+ # added to the function list.
279
+ #
280
+ # Returns a sexp returned to the type of the function return value, or
281
+ # unknown if it has not yet been determined.
282
+
283
+ def process_call(exp)
284
+ lhs = process exp.shift # can be nil
285
+ name = exp.shift
286
+ args = process exp.shift
287
+
288
+ arg_types = if args.nil? then
289
+ []
290
+ else
291
+ if args.first == :arglist then
292
+ args.sexp_types
293
+ elsif args.first == :splat then
294
+ [args.sexp_type]
295
+ else
296
+ raise "That's not a Ruby Sexp you handed me, I'm freaking out on: #{args.inspect}"
297
+ end
298
+ end
299
+
300
+ if name == :=== then
301
+ rhs = args[1]
302
+ raise "lhs of === may not be nil" if lhs.nil?
303
+ raise "rhs of === may not be nil" if rhs.nil?
304
+ raise "Help! I can't figure out what kind of #=== comparison to use" if
305
+ lhs.sexp_type.unknown? and rhs.sexp_type.unknown?
306
+ equal_type = lhs.sexp_type.unknown? ? rhs.sexp_type : lhs.sexp_type
307
+ name = "case_equal_#{equal_type.list_type}".intern
308
+ end
309
+
310
+ return_type = Type.unknown
311
+ lhs_type = lhs.nil? ? Type.unknown : lhs.sexp_type # TODO: maybe void instead of unknown
312
+
313
+ function_type = Type.function(lhs_type, arg_types, return_type)
314
+ @functions.unify(name, function_type) do
315
+ @functions.add_function(name, function_type)
316
+ $stderr.puts "\nWARNING: function #{name} called w/o being defined. Registering #{function_type.inspect}" if $DEBUG
317
+ end
318
+ return_type = function_type.list_type.return_type
319
+
320
+ return t(:call, lhs, name, args, return_type)
321
+ end
322
+
323
+ ##
324
+ # Class adds the class name to the global environment, processes all of the
325
+ # methods in the class. Returns a zclass-typed sexp.
326
+
327
+ def process_class(exp)
328
+ name = exp.shift
329
+ superclass = exp.shift
330
+
331
+ @genv.add name, Type.zclass
332
+
333
+ result = t(:class, Type.zclass)
334
+ result << name
335
+ result << superclass
336
+
337
+ @env.scope do
338
+ # HACK: not sure this is the right place, maybe genv instead?
339
+ klass = eval(name.to_s) # HACK do proper lookup - ugh
340
+ klass.constants.each do |c|
341
+ const_type = case klass.const_get(c)
342
+ when Fixnum then
343
+ Type.long
344
+ when String then
345
+ Type.str
346
+ else
347
+ Type.unknown
348
+ end
349
+ @env.add c.intern, const_type
350
+ end
351
+
352
+ until exp.empty? do
353
+ result << process(exp.shift)
354
+ end
355
+ end
356
+
357
+ return result
358
+ end
359
+
360
+ ##
361
+ # Colon 2 returns a zclass-typed sexp
362
+
363
+ def process_colon2(exp) # (Module::Class/Module)
364
+ name = exp.shift
365
+ return t(:colon2, name, Type.zclass)
366
+ end
367
+
368
+ ##
369
+ # Colon 3 returns a zclass-typed sexp
370
+
371
+ def process_colon3(exp) # (::OUTER_CONST)
372
+ name = exp.shift
373
+ return t(:colon2, name, Type.const)
374
+ end
375
+
376
+ ##
377
+ # Const looks up the type of the const in the global environment, then
378
+ # returns a sexp of that type.
379
+ #
380
+ # Const is partially unsupported.
381
+ #--
382
+ # TODO :const isn't supported anywhere.
383
+
384
+ def process_const(exp)
385
+ c = exp.shift
386
+ if c.to_s =~ /^[A-Z]/ then
387
+ # TODO: validate that it really is a const?
388
+ type = @genv.lookup(c) rescue @env.lookup(c)
389
+ return t(:const, c, type)
390
+ else
391
+ raise "I don't know what to do with const #{c.inspect}. It doesn't look like a class."
392
+ end
393
+ raise "need to finish process_const in #{self.class}"
394
+ end
395
+
396
+ ##
397
+ # Class variables are currently unsupported. Returns an unknown-typed sexp.
398
+ #--
399
+ # TODO support class variables
400
+
401
+ def process_cvar(exp)
402
+ # TODO: we should treat these as globals and have them in the top scope
403
+ name = exp.shift
404
+ return t(:cvar, name, Type.unknown)
405
+ end
406
+
407
+ ##
408
+ # Class variable assignment
409
+ #--
410
+ # TODO support class variables
411
+
412
+ def process_cvasgn(exp)
413
+ name = exp.shift
414
+ val = process exp.shift
415
+ return t(:cvasgn, name, val, Type.unknown)
416
+ end
417
+
418
+ ##
419
+ # Dynamic variable assignment adds the unknown type to the local
420
+ # environment then returns an unknown-typed sexp.
421
+
422
+ def process_dasgn_curr(exp)
423
+ name = exp.shift
424
+ type = Type.unknown
425
+ @env.add name, type # HACK lookup before adding like lasgn
426
+
427
+ return t(:dasgn_curr, name, type)
428
+ end
429
+
430
+ ##
431
+ # Defined? processes the body, then returns a bool-typed sexp.
432
+
433
+ def process_defined(exp)
434
+ thing = process exp.shift
435
+ return t(:defined, thing, Type.bool)
436
+ end
437
+
438
+ ##
439
+ # Defn adds the formal argument types to the local environment and attempts
440
+ # to unify itself against the function table. If no function exists in the
441
+ # function table, defn adds itself.
442
+ #
443
+ # Defn returns a function-typed sexp.
444
+
445
+ def process_defn(exp)
446
+ name = exp.shift
447
+ unprocessed_args = exp.shift
448
+ args = body = function_type = nil
449
+
450
+ @env.scope do
451
+ args = process unprocessed_args
452
+
453
+ begin
454
+ body = process exp.shift
455
+ rescue TypeError => err
456
+ puts "Error in method #{name}, trying to unify, whole body blew out"
457
+ raise
458
+ end
459
+
460
+ # Function might already have been defined by a :call node.
461
+ # TODO: figure out the receiver type? Is that possible at this stage?
462
+ function_type = Type.function Type.unknown, args.sexp_types, Type.unknown
463
+ @functions.unify(name, function_type) do
464
+ @functions.add_function(name, function_type)
465
+ $stderr.puts "\nWARNING: Registering function #{name}: #{function_type.inspect}" if $DEBUG
466
+
467
+ end
468
+ end
469
+
470
+ return_type = function_type.list_type.return_type
471
+
472
+ # Drill down and find all return calls, unify each one against the
473
+ # registered function return value. That way they all have to
474
+ # return the same type. If we don't end up finding any returns,
475
+ # set the function return type to void.
476
+
477
+ return_count = 0
478
+ body.each_of_type(:return) do |sub_exp|
479
+ begin
480
+ return_type.unify sub_exp[1].sexp_type
481
+ return_count += 1
482
+ rescue TypeError => err
483
+ puts "Error in method #{name}, trying to unify #{sub_exp.inspect} against #{return_type.inspect}"
484
+ raise
485
+ end
486
+ end
487
+ if return_count == 0 then
488
+ begin
489
+ return_type.unify Type.void
490
+ rescue TypeError => err
491
+ puts "Error in method #{name}, trying to unify #{function_type.inspect} against Type.void"
492
+ raise
493
+ end
494
+ end
495
+
496
+ # TODO: bad API, clean
497
+ raise "wrong" if
498
+ args.sexp_types.size != function_type.list_type.formal_types.size
499
+ args.sexp_types.each_with_index do |type, i|
500
+ type.unify function_type.list_type.formal_types[i]
501
+ end
502
+
503
+ return t(:defn, name, args, body, function_type)
504
+ end
505
+
506
+ ##
507
+ # Dynamic string processes all the elements of the body and returns a
508
+ # string-typed sexp.
509
+
510
+ def process_dstr(exp)
511
+ out = t(:dstr, exp.shift, Type.str)
512
+ until exp.empty? do
513
+ result = process exp.shift
514
+ out << result
515
+ end
516
+ return out
517
+ end
518
+
519
+ ##
520
+ # Dynamic variable lookup looks up the variable in the local environment and
521
+ # returns a sexp of that type.
522
+
523
+ def process_dvar(exp)
524
+ name = exp.shift
525
+ type = @env.lookup name
526
+ return t(:dvar, name, type)
527
+ end
528
+
529
+ ##
530
+ # Ensure processes the res and the ensure, and returns an untyped sexp.
531
+
532
+ def process_ensure(exp)
533
+ res = process exp.shift
534
+ ens = process exp.shift
535
+
536
+ t(:ensure, res, ens)
537
+ end
538
+
539
+ ##
540
+ # DOC
541
+
542
+ def process_error(exp) # :nodoc:
543
+ t(:error, exp.shift)
544
+ end
545
+
546
+ ##
547
+ # False returns a bool-typed sexp.
548
+
549
+ def process_false(exp)
550
+ return t(:false, Type.bool)
551
+ end
552
+
553
+ ##
554
+ # Global variable assignment gets stored in the global assignment.
555
+
556
+ def process_gasgn(exp)
557
+ var = exp.shift
558
+ val = process exp.shift
559
+
560
+ var_type = @genv.lookup var rescue nil
561
+ if var_type.nil? then
562
+ @genv.add var, val.sexp_type
563
+ else
564
+ val.sexp_type.unify var_type
565
+ end
566
+
567
+ return t(:gasgn, var, val, val.sexp_type)
568
+ end
569
+
570
+ ##
571
+ # Global variables get looked up in the global environment. If they are
572
+ # found, a sexp of that type is returned, otherwise the unknown type is
573
+ # added to the global environment and an unknown-typed sexp is returned.
574
+
575
+ def process_gvar(exp)
576
+ name = exp.shift
577
+ type = @genv.lookup name rescue nil
578
+ if type.nil? then
579
+ type = Type.unknown
580
+ @genv.add name, type
581
+ end
582
+ return t(:gvar, name, type)
583
+ end
584
+
585
+ ##
586
+ # Hash (inline hashes) are not supported. Returns an unmentionably-typed
587
+ # sexp.
588
+ #--
589
+ # TODO support inline hashes
590
+
591
+ def process_hash(exp)
592
+ result = t(:hash, Type.fucked)
593
+ until exp.empty? do
594
+ result << process(exp.shift)
595
+ end
596
+ return result
597
+ end
598
+
599
+ ##
600
+ # Instance variable assignment is currently unsupported. Does no
601
+ # unification and returns an untyped sexp
602
+
603
+ def process_iasgn(exp)
604
+ var = exp.shift
605
+ val = process exp.shift
606
+
607
+ var_type = @env.lookup var rescue nil
608
+ if var_type.nil? then
609
+ @env.add var, val.sexp_type
610
+ else
611
+ val.sexp_type.unify var_type
612
+ end
613
+
614
+ return t(:iasgn, var, val, val.sexp_type)
615
+ end
616
+
617
+ ##
618
+ # If unifies the condition against the bool type, then unifies the return
619
+ # types of the then and else expressions against each other. Returns a sexp
620
+ # typed the same as the then and else expressions.
621
+
622
+ def process_if(exp)
623
+ cond_exp = process exp.shift
624
+ then_exp = process exp.shift
625
+ else_exp = process exp.shift rescue nil # might be empty
626
+
627
+ cond_exp.sexp_type.unify Type.bool
628
+ begin
629
+ then_exp.sexp_type.unify else_exp.sexp_type unless then_exp.nil? or else_exp.nil?
630
+ rescue TypeError => err
631
+ puts "Error unifying #{then_exp.inspect} with #{else_exp.inspect}"
632
+ raise
633
+ end
634
+
635
+ # FIX: at least document this
636
+ type = then_exp.sexp_type unless then_exp.nil?
637
+ type = else_exp.sexp_type unless else_exp.nil?
638
+
639
+ return t(:if, cond_exp, then_exp, else_exp, type)
640
+ end
641
+
642
+ ##
643
+ # Iter unifies the dynamic variables against the call args (dynamic
644
+ # variables are used in the iter body) and returns a void-typed sexp.
645
+
646
+ def process_iter(exp)
647
+ call_exp = process exp.shift
648
+ dargs_exp = process exp.shift
649
+ body_exp = process exp.shift
650
+
651
+ lhs = call_exp[1] # FIX
652
+ Type.unknown_list.unify lhs.sexp_type # force a list type, lhs must be Enum
653
+ Type.new(lhs.sexp_type.list_type).unify dargs_exp.sexp_type # pull out type
654
+
655
+ return t(:iter, call_exp, dargs_exp, body_exp, Type.void)
656
+ end
657
+
658
+ ##
659
+ # Instance variables are currently unsupported. Returns an unknown-typed
660
+ # sexp.
661
+ #--
662
+ # TODO support instance variables
663
+
664
+ def process_ivar(exp)
665
+ name = exp.shift
666
+
667
+ var_type = @env.lookup name rescue nil
668
+ if var_type.nil? then
669
+ var_type = Type.unknown
670
+ @env.add name, var_type
671
+ end
672
+
673
+ return t(:ivar, name, var_type)
674
+ end
675
+
676
+ ##
677
+ # Local variable assignment unifies the variable type from the environment
678
+ # with the assignment expression, and returns a sexp of that type. If there
679
+ # is no local variable in the environment, one is added with the type of the
680
+ # assignment expression and a sexp of that type is returned.
681
+
682
+ def process_lasgn(exp)
683
+ name = exp.shift
684
+ arg_exp = nil
685
+ arg_type = nil
686
+ var_type = @env.lookup name rescue nil
687
+
688
+ sub_exp = exp.shift
689
+ sub_exp_type = sub_exp.first
690
+ arg_exp = process sub_exp
691
+
692
+ # if we've got an array in there, unify everything in it.
693
+ if sub_exp_type == :array then
694
+ arg_type = arg_exp.sexp_types
695
+ arg_type = arg_type.inject(Type.unknown) do |t1, t2|
696
+ t1.unify t2
697
+ end
698
+ arg_type = arg_type.dup # singleton type
699
+ arg_type.list = true
700
+ else
701
+ arg_type = arg_exp.sexp_type
702
+ end
703
+
704
+ if var_type.nil? then
705
+ @env.add name, arg_type
706
+ var_type = arg_type
707
+ else
708
+ var_type.unify arg_type
709
+ end
710
+
711
+ return t(:lasgn, name, arg_exp, var_type)
712
+ end
713
+
714
+ ##
715
+ # Literal values return a sexp typed to match the literal expression.
716
+
717
+ def process_lit(exp)
718
+ value = exp.shift
719
+ type = nil
720
+
721
+ case value
722
+ when Fixnum then
723
+ type = Type.long
724
+ when Float then
725
+ type = Type.float
726
+ when Symbol then
727
+ type = Type.symbol
728
+ else
729
+ raise "Bug! no: Unknown literal #{value}:#{value.class}"
730
+ end
731
+
732
+ return t(:lit, value, type)
733
+ end
734
+
735
+ ##
736
+ # Local variables get looked up in the local environment and a sexp of that
737
+ # type is returned.
738
+
739
+ def process_lvar(exp)
740
+ name = exp.shift
741
+ t = @env.lookup name
742
+ return t(:lvar, name, t)
743
+ end
744
+
745
+ ##
746
+ # Nil returns a value-typed sexp.
747
+
748
+ def process_nil(exp)
749
+ # don't do a fucking thing until... we have something to do
750
+ # HACK: wtf to do here? (what type is nil?!?!)
751
+ return t(:nil, Type.value)
752
+ end
753
+
754
+ ##
755
+ # Not unifies the type of its expression against bool, then returns a
756
+ # bool-typed sexp.
757
+
758
+ def process_not(exp)
759
+ thing = process exp.shift
760
+ thing.sexp_type.unify Type.bool
761
+ return t(:not, thing, Type.bool)
762
+ end
763
+
764
+ ##
765
+ # ||= operator is currently unsupported. Returns an untyped sexp.
766
+ #--
767
+ # TODO support ||=
768
+
769
+ def process_op_asgn_or(exp)
770
+ ivar = process exp.shift
771
+ iasgn = process exp.shift
772
+ body = process exp.shift
773
+ # TODO: probably need to unify all three? or at least the first two...
774
+ return t(:op_asgn_or, ivar, iasgn, body)
775
+ end
776
+
777
+ ##
778
+ # Or unifies the left and right hand sides with bool, then returns a
779
+ # bool-typed sexp.
780
+
781
+ def process_or(exp)
782
+ rhs = process exp.shift
783
+ lhs = process exp.shift
784
+
785
+ rhs_type = rhs.sexp_type
786
+ lhs_type = lhs.sexp_type
787
+
788
+ rhs_type.unify lhs_type
789
+ rhs_type.unify Type.bool
790
+
791
+ return t(:or, rhs, lhs, Type.bool)
792
+ end
793
+
794
+ ##
795
+ # Rescue body returns an unknown-typed sexp.
796
+
797
+ def process_resbody(exp)
798
+ o1 = process exp.shift
799
+ o2 = process exp.shift
800
+ o3 = exp.empty? ? nil : process(exp.shift)
801
+
802
+ result = t(:resbody, Type.unknown) # void?
803
+ result << o1
804
+ result << o2 unless o2.nil?
805
+ result << o3 unless o3.nil?
806
+
807
+ return result
808
+ end
809
+
810
+ ##
811
+ # Rescue unifies the begin, rescue and ensure types, and returns an untyped
812
+ # sexp.
813
+ #--
814
+ # FIX isn't used anywhere
815
+
816
+ def process_rescue(exp)
817
+ # TODO: I think there is also an else stmt. Should make it
818
+ # mandatory, not optional.
819
+ # TODO: test me
820
+ try_block = process exp.shift
821
+ rescue_block = process exp.shift
822
+ ensure_block = process exp.shift
823
+
824
+ try_type = try_block.sexp_type
825
+ rescue_type = rescue_block.sexp_type
826
+ ensure_type = ensure_block.sexp_type # FIX: not sure if I should unify
827
+
828
+ try_type.unify rescue_type
829
+ try_type.unify ensure_type
830
+
831
+ return t(:rescue, try_block, rescue_block, ensure_block, try_type)
832
+ end
833
+
834
+ ##
835
+ # Return returns a void typed sexp.
836
+
837
+ def process_return(exp)
838
+ result = t(:return, Type.void) # TODO why void - cuz this is a keyword
839
+ result << process(exp.shift) unless exp.empty?
840
+ return result
841
+ end
842
+
843
+ ##
844
+ # Scope returns a void-typed sexp.
845
+
846
+ def process_scope(exp)
847
+ return t(:scope, Type.void) if exp.empty?
848
+
849
+ body = process exp.shift
850
+
851
+ return t(:scope, body, Type.void)
852
+ end
853
+
854
+ ##
855
+ # Self is currently unsupported. Returns an unknown-typed sexp.
856
+ #--
857
+ # TODO support self
858
+
859
+ def process_self(exp)
860
+ return t(:self, Type.unknown)
861
+ end
862
+
863
+ ##
864
+ # Splat is currently unsupported. Returns an unknown-typed sexp.
865
+ #--
866
+ # TODO support splat, maybe like :array?
867
+
868
+ def process_splat(exp)
869
+ value = process exp.shift
870
+ return t(:splat, value, Type.unknown) # TODO: probably value_list?
871
+ end
872
+
873
+ ##
874
+ # String literal returns a string-typed sexp.
875
+
876
+ def process_str(exp)
877
+ return t(:str, exp.shift, Type.str)
878
+ end
879
+
880
+ ##
881
+ # Super is currently unsupported. Returns an unknown-typed sexp.
882
+ #--
883
+ # TODO support super
884
+
885
+ def process_super(exp)
886
+ args = process exp.shift
887
+ # TODO try to look up the method in our superclass?
888
+ return t(:super, args, Type.unknown)
889
+ end
890
+
891
+ ##
892
+ # True returns a bool-typed sexp.
893
+
894
+ def process_true(exp)
895
+ return t(:true, Type.bool)
896
+ end
897
+
898
+ ##
899
+ # While unifies the condition with bool, then returns an untyped sexp.
900
+
901
+ def process_while(exp)
902
+ cond = process exp.shift
903
+ body = process exp.shift
904
+ is_precondition = exp.shift
905
+ Type.bool.unify cond.sexp_type
906
+ return t(:while, cond, body, is_precondition)
907
+ end
908
+
909
+ ##
910
+ # Yield is currently unsupported. Returns a unmentionably-typed sexp.
911
+
912
+ def process_yield(exp)
913
+ result = t(:yield, Type.fucked)
914
+ until exp.empty? do
915
+ result << process(exp.shift)
916
+ end
917
+ return result
918
+ end
919
+
920
+ end
921
+
922
+