yadriggy 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +108 -0
- data/Rakefile +10 -0
- data/lib/yadriggy.rb +32 -0
- data/lib/yadriggy/algebra.rb +497 -0
- data/lib/yadriggy/ast.rb +1839 -0
- data/lib/yadriggy/ast_location.rb +73 -0
- data/lib/yadriggy/ast_value.rb +428 -0
- data/lib/yadriggy/c.rb +11 -0
- data/lib/yadriggy/c/c.rb +220 -0
- data/lib/yadriggy/c/codegen.rb +481 -0
- data/lib/yadriggy/c/config.rb +51 -0
- data/lib/yadriggy/c/ctype.rb +118 -0
- data/lib/yadriggy/c/ctypecheck.rb +449 -0
- data/lib/yadriggy/c/ffi.rb +301 -0
- data/lib/yadriggy/c/opencl.rb +458 -0
- data/lib/yadriggy/c/program.rb +86 -0
- data/lib/yadriggy/c1.rb +10 -0
- data/lib/yadriggy/checker.rb +216 -0
- data/lib/yadriggy/eval.rb +200 -0
- data/lib/yadriggy/eval_all.rb +159 -0
- data/lib/yadriggy/pretty_print.rb +492 -0
- data/lib/yadriggy/printer.rb +82 -0
- data/lib/yadriggy/ruby_typecheck.rb +468 -0
- data/lib/yadriggy/ruby_typeinfer.rb +335 -0
- data/lib/yadriggy/source_code.rb +168 -0
- data/lib/yadriggy/syntax.rb +524 -0
- data/lib/yadriggy/type.rb +754 -0
- data/lib/yadriggy/typecheck.rb +277 -0
- data/lib/yadriggy/version.rb +5 -0
- data/yadriggy.gemspec +33 -0
- metadata +149 -0
@@ -0,0 +1,335 @@
|
|
1
|
+
# Copyright (C) 2017- Shigeru Chiba. All rights reserved.
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'yadriggy/ruby_typecheck'
|
5
|
+
|
6
|
+
module Yadriggy
|
7
|
+
|
8
|
+
# Type checker for Ruby with type inference.
|
9
|
+
#
|
10
|
+
class RubyTypeInferer < RubyTypeChecker
|
11
|
+
|
12
|
+
# Binds a local variable name to a type.
|
13
|
+
# @param [TypeEnv] env a type environment.
|
14
|
+
# @param [ASTnode] ast a local variable name.
|
15
|
+
# @param [Type] var_type a type.
|
16
|
+
# @param [Boolean] is_def true if the variable is initialized there.
|
17
|
+
def bind_local_var(env, ast, var_type, is_def=true)
|
18
|
+
unless var_type.nil?
|
19
|
+
t = if UnionType.role(var_type)
|
20
|
+
ts = UnionType.role(var_type).types
|
21
|
+
UnionType.new(ts.map {|t| to_non_instance_type(t) })
|
22
|
+
else
|
23
|
+
ins_t = InstanceType.role(var_type)
|
24
|
+
to_non_instance_type(var_type)
|
25
|
+
end
|
26
|
+
lvt = LocalVarType.new(t.copy(OptionalRole), is_def ? ast : nil)
|
27
|
+
env.bind_name(ast, lvt)
|
28
|
+
@typetable[ast] = lvt
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @private
|
33
|
+
# When the initial value of a variable is an InstanceType,
|
34
|
+
# the type of the variable has to be a RubyCass type
|
35
|
+
# corresponding to that instance type.
|
36
|
+
# The variable type can be set to that InstanceType only when
|
37
|
+
# it is guaranteed that the value of the variable is never changed
|
38
|
+
# later.
|
39
|
+
def to_non_instance_type(t)
|
40
|
+
ins_t = InstanceType.role(t)
|
41
|
+
if ins_t.nil?
|
42
|
+
t
|
43
|
+
else
|
44
|
+
ins_t.supertype
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Note that obj.name += ... is regarded as obj.name=(obj.name + ...).
|
49
|
+
#
|
50
|
+
rule(Assign) do
|
51
|
+
rtype = type(ast.right)
|
52
|
+
left_expr = ast.left
|
53
|
+
if ast.op != :'=' # if op is += etc.
|
54
|
+
ltype = type(left_expr)
|
55
|
+
LocalVarType.role(ltype)&.definition = left_expr
|
56
|
+
ltype
|
57
|
+
elsif left_expr.is_a?(IdentifierOrCall)
|
58
|
+
vtype = type_env.bound_name?(left_expr)
|
59
|
+
if vtype.nil? # if a new name is found,
|
60
|
+
method_name = left_expr.name + '='
|
61
|
+
if is_attr_accessor?(ast, type_env, method_name.to_sym) # self.name=()?
|
62
|
+
call_expr = Call.make(name: method_name, args: [ ast.right ])
|
63
|
+
get_call_expr_type(call_expr, type_env, method_name)
|
64
|
+
else
|
65
|
+
bind_local_var(type_env, left_expr, rtype)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
type_assert(rtype <= vtype, 'incompatible assignment type')
|
69
|
+
LocalVarType.role(vtype)&.definition = left_expr
|
70
|
+
vtype
|
71
|
+
end
|
72
|
+
elsif left_expr.is_a?(Call) && left_expr.op == :'.' # obj.name=()?
|
73
|
+
method_name = left_expr.name.name + '='
|
74
|
+
call_expr = Call.make(receiver: left_expr.receiver,
|
75
|
+
name: method_name, args: [ ast.right ],
|
76
|
+
parent: ast.parent)
|
77
|
+
get_call_expr_type(call_expr, type_env, method_name)
|
78
|
+
elsif left_expr.is_a?(InstanceVariable) # @var = ..., @@cvar = ..., @var += ...
|
79
|
+
get_instance_variable_type(type_env.context, left_expr, true,
|
80
|
+
InstanceType.role(rtype)&.supertype || rtype)
|
81
|
+
elsif left_expr.is_a?(GlobalVariable)
|
82
|
+
get_instance_variable_type(:global_variables, left_expr, true,
|
83
|
+
InstanceType.role(rtype)&.supertype || rtype)
|
84
|
+
else
|
85
|
+
type(left_expr) # a[i] = ..., <expr> = ... or <expr> += ...
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @private
|
90
|
+
def is_attr_accessor?(expr, tenv, name)
|
91
|
+
self_t = type_env.context
|
92
|
+
!self_t.nil? &&
|
93
|
+
(self_t.method_defined?(name) ||
|
94
|
+
self_t.private_method_defined?(name))
|
95
|
+
end
|
96
|
+
|
97
|
+
rule(Name) do
|
98
|
+
tenv = type_env
|
99
|
+
name_ast = ast
|
100
|
+
type = tenv.bound_name?(name_ast)
|
101
|
+
if type
|
102
|
+
type
|
103
|
+
else
|
104
|
+
method_name = name_ast.to_sym
|
105
|
+
if is_attr_accessor?(name_ast, tenv, method_name.to_sym)
|
106
|
+
call_expr = Call.make(name: method_name, parent: name_ast.parent)
|
107
|
+
get_call_expr_type(call_expr, tenv, method_name)
|
108
|
+
else
|
109
|
+
# this name is a free variable.
|
110
|
+
v = name_ast.value
|
111
|
+
if v == Undef
|
112
|
+
DynType
|
113
|
+
else
|
114
|
+
InstanceType.new(v)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
rule(Const) do
|
121
|
+
v = ast.value
|
122
|
+
if v == Undef
|
123
|
+
DynType
|
124
|
+
else
|
125
|
+
InstanceType.new(v)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
rule(GlobalVariable) do
|
130
|
+
v = ast.value
|
131
|
+
if v == Undef
|
132
|
+
get_instance_variable_type(:global_variables, ast, false, DynType)
|
133
|
+
else
|
134
|
+
get_instance_variable_type(:global_variables, ast, true, RubyClass[v.class])
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
rule(InstanceVariable) do
|
139
|
+
v = ast.value
|
140
|
+
key = type_env.context
|
141
|
+
if v == Undef
|
142
|
+
get_instance_variable_type(key, ast, false, DynType)
|
143
|
+
else
|
144
|
+
get_instance_variable_type(key, ast, true, RubyClass[v.class])
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Obtains the type of the given instance variable `ivar` declared
|
149
|
+
# in the given class (i.e. module) or the instance object `key`.
|
150
|
+
# If the type of `ivar` is not defined, `value_type` is recorded
|
151
|
+
# as its type.
|
152
|
+
#
|
153
|
+
# @param [Module|Object] key the key when looking into the typedef table.
|
154
|
+
# @param [InstanceVariable] ivar an instance variable.
|
155
|
+
# @param [Boolean] is_valid_type true if `value_type` is valid.
|
156
|
+
# @param [Type] value_type the type suggested for `ivar`.
|
157
|
+
# @return [Type] the type of `ivar`.
|
158
|
+
def get_instance_variable_type(key, ivar, is_valid_type, value_type)
|
159
|
+
td = add_typedef(key)
|
160
|
+
ivar_t = td[ivar]
|
161
|
+
if ivar_t.nil?
|
162
|
+
td[ivar] = value_type
|
163
|
+
else
|
164
|
+
type_assert_subsume(ivar_t, value_type,
|
165
|
+
"bad type value for #{ivar.name}") if is_valid_type
|
166
|
+
ivar_t
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# +@, -@, !, ~, not
|
171
|
+
rule(Unary) do
|
172
|
+
expr_t = type(ast.expr)
|
173
|
+
op = ast.op
|
174
|
+
if op == :! || op == :not
|
175
|
+
RubyClass::Boolean
|
176
|
+
else
|
177
|
+
if (op == :~ && expr_t <= RubyClass::Integer) ||
|
178
|
+
((op == :+@ || op == :-@) && expr_t <= RubyClass::Numeric)
|
179
|
+
expr_t
|
180
|
+
else
|
181
|
+
call_expr = Call.make(receiver: ast.expr, name: op,
|
182
|
+
parent: ast.parent)
|
183
|
+
get_call_expr_type(call_expr, type_env, op)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
rule(Binary) do
|
189
|
+
right_t = type(ast.right)
|
190
|
+
left_t = type(ast.left)
|
191
|
+
binary_type(ast, right_t, left_t)
|
192
|
+
end
|
193
|
+
|
194
|
+
# @private
|
195
|
+
def binary_type(bin_expr, right_t, left_t)
|
196
|
+
op = bin_expr.op
|
197
|
+
case op
|
198
|
+
when :'&&', :'||', :and, :or # not overridable
|
199
|
+
return UnionType.new([right_t, left_t])
|
200
|
+
when :>, :>=, :<, :<=, :==, :===, :!=
|
201
|
+
if left_t <= RubyClass::Numeric
|
202
|
+
return RubyClass::Boolean
|
203
|
+
end
|
204
|
+
when :**, :*, :/, :%, :+, :-
|
205
|
+
if left_t <= RubyClass::Numeric
|
206
|
+
if left_t <= RubyClass::Float || right_t <= RubyClass::Float
|
207
|
+
return RubyClass::Float
|
208
|
+
else
|
209
|
+
return RubyClass::Integer
|
210
|
+
end
|
211
|
+
end
|
212
|
+
when :<<, :>>, :&, :|, :^
|
213
|
+
return RubyClass::Integer if left_t <= RubyClass::Integer
|
214
|
+
# when :=~, :!~, :<=>
|
215
|
+
end
|
216
|
+
|
217
|
+
if left_t <= RubyClass::String
|
218
|
+
if op == :% || op == :+ || op == :<<
|
219
|
+
return RubyClass::String
|
220
|
+
elsif op == :=~ || op == :<=>
|
221
|
+
return UnionType.new(RubyClass::Integer, RubyClass::NilClass)
|
222
|
+
elsif op == :!~
|
223
|
+
return RubyClass::Boolean
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
call_expr = Call.make(receiver: bin_expr.left, name: op,
|
228
|
+
args: [bin_expr.right], parent: bin_expr.parent)
|
229
|
+
return get_call_expr_type(call_expr, type_env, op)
|
230
|
+
end
|
231
|
+
|
232
|
+
rule(Dots) do
|
233
|
+
CompositeType.new(RubyClass::Range, type(ast.left))
|
234
|
+
end
|
235
|
+
|
236
|
+
rule(ArrayLiteral) do
|
237
|
+
ele = ast.elements
|
238
|
+
if 0 < ele.size && ele.size < 17
|
239
|
+
t = type(ele[0])
|
240
|
+
et = InstanceType.role(t)&.supertype || t
|
241
|
+
if ele.all? {|e| type(e) <= et }
|
242
|
+
CompositeType.new(RubyClass::Array, et)
|
243
|
+
else
|
244
|
+
RubyClass::Array
|
245
|
+
end
|
246
|
+
else
|
247
|
+
RubyClass::Array
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Variable access or a method call without arguments.
|
252
|
+
# This implementation invokes the method if the expression is
|
253
|
+
# a method call. It returns a InstanceType containing the
|
254
|
+
# resulting value.
|
255
|
+
#
|
256
|
+
rule(VariableCall) do
|
257
|
+
type = type_env.bound_name?(ast)
|
258
|
+
if type
|
259
|
+
type
|
260
|
+
else
|
261
|
+
call_expr = Call.make(name: ast.name, parent: ast.parent)
|
262
|
+
get_call_expr_type(call_expr, type_env, call_expr.name.to_sym)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# @private
|
267
|
+
# Overrides {RubyTypeChecker#get_return_type}.
|
268
|
+
#
|
269
|
+
def get_return_type(an_ast, mthd, new_tenv, arg_types)
|
270
|
+
m_ast = an_ast.root.reify(mthd)
|
271
|
+
type_assert_false(m_ast.nil?, "no source code: for #{mthd}")
|
272
|
+
(@syntax.check(m_ast.tree) || @syntax.raise_error) if @syntax
|
273
|
+
|
274
|
+
m_ast.tree.params.each_with_index do |p, i|
|
275
|
+
bind_local_var(new_tenv, p, arg_types[i])
|
276
|
+
end
|
277
|
+
|
278
|
+
nparams = m_ast.tree.params.length
|
279
|
+
m_ast.tree.optionals.each_with_index do |p, i|
|
280
|
+
bind_local_var(new_tenv, p, arg_types[nparams + i])
|
281
|
+
end
|
282
|
+
|
283
|
+
mtype = MethodType.role(type(m_ast.tree, new_tenv))
|
284
|
+
type_assert(mtype, 'not a method type')
|
285
|
+
type_assert_params(mtype.params, arg_types, 'argument type mismatch')
|
286
|
+
mtype.result
|
287
|
+
end
|
288
|
+
|
289
|
+
rule(ArrayRef) do
|
290
|
+
array_t = CompositeType.role(type(ast.array))
|
291
|
+
if array_t&.ruby_class == Array
|
292
|
+
array_t.first_arg
|
293
|
+
else
|
294
|
+
DynType
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
rule(ForLoop) do
|
299
|
+
set_type = type(ast.set)
|
300
|
+
var_type = CompositeType.role(set_type)&.first_arg
|
301
|
+
ast.vars.each do |v|
|
302
|
+
bind_local_var(type_env, v, var_type.nil? ? DynType : var_type)
|
303
|
+
end
|
304
|
+
type(ast.body)
|
305
|
+
DynType
|
306
|
+
end
|
307
|
+
|
308
|
+
rule(Def) do
|
309
|
+
ptypes = ast.params.map do |v|
|
310
|
+
t = type_env.bound_name?(v.name)
|
311
|
+
t || DynType
|
312
|
+
end
|
313
|
+
|
314
|
+
ptypes = ptypes + ast.optionals.map do |v|
|
315
|
+
t = type_env.bound_name?(v[0].name)
|
316
|
+
t || type(v[1])
|
317
|
+
end
|
318
|
+
|
319
|
+
mtype = MethodType.new(ast, ptypes, DynType)
|
320
|
+
type_assert_later do
|
321
|
+
s = type_env.new_tenv
|
322
|
+
type_parameters(ast, s) # ignore parameter types
|
323
|
+
body_t = type(ast.body, s)
|
324
|
+
res_t = if ast.rescue.nil?
|
325
|
+
body_t
|
326
|
+
else
|
327
|
+
UnionType.make(body_t, type(ast.rescue, s))
|
328
|
+
end
|
329
|
+
type_assert_subsume(mtype.result, res_t, 'bad result type')
|
330
|
+
end
|
331
|
+
mtype
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# Copyright (C) 2017- Shigeru Chiba. All rights reserved.
|
2
|
+
|
3
|
+
require 'ripper'
|
4
|
+
require 'pry'
|
5
|
+
|
6
|
+
module Yadriggy
|
7
|
+
# @private
|
8
|
+
# Retrieves source code in the S-expression style.
|
9
|
+
class SourceCode
|
10
|
+
|
11
|
+
# Gets an S-expression.
|
12
|
+
#
|
13
|
+
def self.get_sexp(proc)
|
14
|
+
return nil unless proc.is_a?(Proc) || proc.is_a?(Method) ||
|
15
|
+
proc.is_a?(UnboundMethod)
|
16
|
+
|
17
|
+
file_name, line = proc.source_location
|
18
|
+
return nil if file_name.nil?
|
19
|
+
src = if file_name == "(pry)" then read_pry_history
|
20
|
+
else File.read(file_name) end
|
21
|
+
prog = Ripper.sexp(src)
|
22
|
+
prog && [file_name, find_sexp(prog, line)]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.read_pry_history
|
26
|
+
cmds = Pry.commands
|
27
|
+
|
28
|
+
# The line number seems wrong if the source code is in pry_history.
|
29
|
+
# To correct the line number, a blank line is added to source.
|
30
|
+
source = "\n"
|
31
|
+
|
32
|
+
lineno = 0
|
33
|
+
lineno1 = Pry.history.original_lines
|
34
|
+
File.foreach(Pry.config.history.file) do |line|
|
35
|
+
lineno += 1
|
36
|
+
if lineno > lineno1
|
37
|
+
if cmds.select {|k,v| v.matches?(line) }.empty?
|
38
|
+
source << line
|
39
|
+
else
|
40
|
+
# source << "\n"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
source
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.min(a, b)
|
48
|
+
if a < b then a else b end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.max(a, b)
|
52
|
+
if a > b then a else b end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.find_sexp(prog, line)
|
56
|
+
find_sexp2(prog, line, [1, nil])
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param [Array] current the current location `[line, block]`
|
60
|
+
def self.find_sexp2(prog, line, current)
|
61
|
+
if prog.nil? || !prog.is_a?(Array)
|
62
|
+
return nil
|
63
|
+
else
|
64
|
+
t = prog[0]
|
65
|
+
if t == :@ident || t == :@tstring_content || t == :@const ||
|
66
|
+
t == :@int || t == :@float || t == :@kw || t == :@label ||
|
67
|
+
t == :@gvar || t == :@CHAR
|
68
|
+
#current[0] = prog[2][0]
|
69
|
+
current_line = prog[2][0]
|
70
|
+
if line < current_line && !current[1].nil?
|
71
|
+
return current[1]
|
72
|
+
else
|
73
|
+
current[0] = current_line
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
else
|
77
|
+
is_block = (t == :brace_block || t == :do_block ||
|
78
|
+
t == :def || t == :defs || t == :lambda)
|
79
|
+
if is_block && line == current[0] || def_at?(line, t, prog)
|
80
|
+
return prog
|
81
|
+
else
|
82
|
+
current[1] = nil
|
83
|
+
prog.each do |e|
|
84
|
+
r = find_sexp2(e, line, current)
|
85
|
+
return r unless r.nil?
|
86
|
+
end
|
87
|
+
if is_block
|
88
|
+
if line <= current[0]
|
89
|
+
return prog
|
90
|
+
else
|
91
|
+
current[1] = prog
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.def_at?(line, t, prog)
|
103
|
+
(t == :def || t == :defs) &&
|
104
|
+
prog[1].is_a?(Array) && prog[1][0] == :@ident &&
|
105
|
+
prog[1][2][0] == line
|
106
|
+
end
|
107
|
+
|
108
|
+
class Cons
|
109
|
+
include Enumerable
|
110
|
+
attr_accessor :head, :tail
|
111
|
+
|
112
|
+
def initialize(head, tail=nil)
|
113
|
+
@head = head
|
114
|
+
@tail = tail
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.list(*elements)
|
118
|
+
list = nil
|
119
|
+
elements.reverse_each do |e|
|
120
|
+
list = Cons.new(e, list)
|
121
|
+
end
|
122
|
+
list
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.append!(lst1, lst2)
|
126
|
+
if lst1 == nil
|
127
|
+
lst2
|
128
|
+
elsif lst2 == nil
|
129
|
+
lst1
|
130
|
+
else
|
131
|
+
p = lst1
|
132
|
+
while p.tail != nil
|
133
|
+
p = p.tail
|
134
|
+
end
|
135
|
+
p.tail = lst2
|
136
|
+
lst1
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def size()
|
141
|
+
size = 0
|
142
|
+
list = self
|
143
|
+
while list != nil
|
144
|
+
list = list.tail
|
145
|
+
size += 1
|
146
|
+
end
|
147
|
+
size
|
148
|
+
end
|
149
|
+
|
150
|
+
def each()
|
151
|
+
list = self
|
152
|
+
while list != nil
|
153
|
+
yield list.head
|
154
|
+
list = list.tail
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def fold(acc)
|
159
|
+
list = self
|
160
|
+
while list != nil
|
161
|
+
acc = yield acc, list.head
|
162
|
+
list = list.tail
|
163
|
+
end
|
164
|
+
acc
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|