yadriggy 1.0.0
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.
- 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
|