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.
@@ -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
+