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