yadriggy 1.1.0 → 1.2.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.
@@ -0,0 +1,90 @@
1
+ # Copyright (C) 2018- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'yadriggy'
4
+
5
+ module Yadriggy
6
+ module Py
7
+ # The import statement in Python.
8
+ class Import
9
+ @@src = ''
10
+ @@state = 0
11
+
12
+ # @api private
13
+ def self.source
14
+ src = @@src
15
+ @@src = ''
16
+ @@state = 0
17
+ src
18
+ end
19
+
20
+ # `import` keyword.
21
+ # @param [String|Symbol] name a module name etc.
22
+ def import(name)
23
+ if @@state == 1 || @@state == 2
24
+ @@src << ', ' << name.to_s
25
+ @@state = 1
26
+ elsif @@state == 3
27
+ @@src << ' import ' << name.to_s
28
+ @@state = 1
29
+ else
30
+ Import.error('import')
31
+ end
32
+ self
33
+ end
34
+
35
+ # `as` keyword.
36
+ # @param [String|Symbol] name an alias.
37
+ def as(name)
38
+ if @@state == 1
39
+ @@src << ' as ' << name.to_s
40
+ @@state = 2
41
+ else
42
+ Import.error('as')
43
+ end
44
+ self
45
+ end
46
+
47
+ # `import` keyword.
48
+ # @param [String|Symbol] name a module name.
49
+ def self.import(name)
50
+ error('import') if @@state == 3
51
+ @@src << "\nimport " << name.to_s
52
+ @@state = 1
53
+ Import.new
54
+ end
55
+
56
+ # `from` keyword.
57
+ # @param [String|Symbol] name a module name.
58
+ def self.from(name)
59
+ error('from') if @@state == 3
60
+ @@src << "\nfrom " << name.to_s
61
+ @@state = 3
62
+ Import.new
63
+ end
64
+
65
+ # @api private
66
+ def self.error(name)
67
+ self.source
68
+ raise RuntimeError.new("bad call to Import\##{name}")
69
+ end
70
+ end
71
+
72
+ # Convenience module.
73
+ # Use this module by including it.
74
+ module PyImport
75
+ # `import` statement.
76
+ # @param [String|Symbol] name a module name.
77
+ # @return [Import]
78
+ def pyimport(name)
79
+ Import.import(name)
80
+ end
81
+
82
+ # `from ... import` statement.
83
+ # @param [String|Symbol] name a module name.
84
+ # @return [Import]
85
+ def pyfrom(name)
86
+ Import.from(name)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,62 @@
1
+ # Copyright (C) 2018- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'yadriggy/py/python'
4
+
5
+ module Yadriggy
6
+ module Py
7
+ class PyTypeChecker < RubyTypeChecker
8
+ def initialize()
9
+ super(Py::Syntax)
10
+ @free_variables = Hash.new
11
+ end
12
+
13
+ # @return [Hash<Object,String>] all free variables. A hash table from
14
+ # values to their free variable names.
15
+ def references
16
+ @free_variables
17
+ end
18
+
19
+ # Makes the references set empty. The references set is a set of constants
20
+ # returned by {#references}.
21
+ #
22
+ def clear_references
23
+ @free_variables = Hash.new
24
+ end
25
+
26
+ rule(Name) do
27
+ type = proceed(ast, type_env)
28
+ collect_free_variables(ast, type)
29
+ type
30
+ end
31
+
32
+ # Collect free variables.
33
+ # @param [Name|VariableCall] an_ast
34
+ def collect_free_variables(an_ast, type)
35
+ unless InstanceType.role(type).nil?
36
+ obj = type.object
37
+ unless obj.is_a?(Numeric) || obj.is_a?(String) || obj.is_a?(Symbol) || obj.is_a?(Module)
38
+ @free_variables[obj] = an_ast.name
39
+ end
40
+ end
41
+ end
42
+
43
+ # Computes the type of the {Call} expression
44
+ # by searching the receiver class for the called method.
45
+ # If the method is not found or the method is provided by
46
+ # `Object` or its super class, {DynType} is returned.
47
+ #
48
+ # This overrides the super's method but if the called method is not
49
+ # found, it returns DynType; it does not raise an error.
50
+ def lookup_ruby_classes(type_env, arg_types, recv_type, method_name)
51
+ begin
52
+ mth = Type.get_instance_method_object(recv_type, method_name)
53
+ rescue CheckError
54
+ return DynType
55
+ end
56
+ return DynType if mth.owner > Object
57
+ new_tenv = type_env.new_base_tenv(recv_type.exact_type)
58
+ get_return_type(ast, mth, new_tenv, arg_types)
59
+ end
60
+ end # class PyTypeChecker
61
+ end # module Py
62
+ end
@@ -0,0 +1,130 @@
1
+ # Copyright (C) 2018- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'pycall'
4
+ require 'yadriggy'
5
+ require 'yadriggy/py/codegen'
6
+ require 'yadriggy/py/py_typechecker'
7
+ require 'yadriggy/py/import'
8
+
9
+ module Yadriggy
10
+ module Py
11
+
12
+ Syntax = Yadriggy.define_syntax do
13
+ expr = Name | Number | Super | Binary | Unary | ternary |
14
+ StringLiteral | Lambda |
15
+ ArrayLiteral | Paren | lambda_call | fun_call | ArrayRef | HashLiteral
16
+ stmnt = Return | ForLoop | Loop | if_stmnt | Break |
17
+ BeginEnd | Def | ModuleDef
18
+ exprs = Exprs | stmnt | expr
19
+
20
+ Name = { name: String }
21
+ Number = { value: Numeric }
22
+ VariableCall = Name
23
+ InstanceVariable = nil
24
+ GlobalVariable = nil
25
+ Reserved = Name
26
+ Const = Name
27
+ Binary = { left: expr, op: Symbol, right: expr }
28
+ ArrayRef = { array: expr, indexes: expr }
29
+ ArrayRefField = ArrayRef
30
+ Assign = { left: [expr] | expr, op: Symbol,
31
+ right: [expr] | expr }
32
+ Dots = Binary
33
+ Unary = { op: Symbol, operand: expr }
34
+ StringLiteral = { value: String }
35
+ ArrayLiteral = { elements: ForLoop | [ expr ] }
36
+ Paren = { expression: expr }
37
+ HashLiteral = { pairs: [ (expr|Label|SymbolLiteral) * expr ] }
38
+ Return = { values: [ expr ] }
39
+ ForLoop = {vars: [ Identifier ], set: expr, body: exprs }
40
+ Loop = { op: :while, cond: expr, body: exprs }
41
+ Break = { values: nil }
42
+ if_stmnt = Conditional + { op: :if, cond: expr, then: exprs,
43
+ all_elsif: [expr * exprs], else: (exprs) }
44
+ ternary = Conditional + { op: :ifop, cond: expr, then: expr,
45
+ all_elsif: nil, else: expr }
46
+ Parameters = { params: [ Identifier ],
47
+ optionals: [ Identifier * expr ],
48
+ rest_of_params: (Identifier),
49
+ params_after_rest: [ Identifier ],
50
+ keywords: [ Label * expr ],
51
+ rest_of_keywords: (Identifier),
52
+ block_param: (Identifier) }
53
+ Block = Parameters + { body: exprs }
54
+ Lambda = Block + { body: expr } # -> (x) { x + 1 }
55
+ lambda_name = { name: "lambda" }
56
+ lambda_call = Call + { receiver: nil, op: nil, name: lambda_name,
57
+ args: nil, block_arg: nil, block: Block }
58
+ fun_call = Call + { receiver: (expr), op: (Symbol), name: Identifier,
59
+ args: [ expr ], block_arg: nil, block: nil }
60
+ Command = fun_call
61
+ Exprs = { expressions: [ exprs ] }
62
+ Rescue = { types: [ Const | ConstPathRef ],
63
+ parameter: (Identifier),
64
+ body: (exprs), nested_rescue: (Rescue),
65
+ else: (exprs), ensure: (exprs) }
66
+ BeginEnd = { body: exprs, rescue: (Rescue) }
67
+ Def = Parameters +
68
+ { singular: (expr), name: Identifier, body: exprs,
69
+ rescue: (Rescue) }
70
+ ModuleDef = { name: Const | ConstPathRef, body: exprs,
71
+ rescue: (Rescue) }
72
+ ClassDef = ModuleDef +
73
+ { superclass: (Const | ConstPathRef) }
74
+ end
75
+
76
+ def self.run(&blk)
77
+ ast = Yadriggy::reify(blk)
78
+ Syntax.raise_error unless Syntax.check(ast.tree)
79
+ checker = PyTypeChecker.new
80
+ checker.typecheck(ast.tree.body)
81
+ PyCall.exec(Import.source)
82
+ init_free_variables(checker)
83
+ gen = CodeGen.new(Printer.new, checker)
84
+ ast.astrees.each {|t| gen.print(t) unless t == ast }
85
+ last_expr = generate_except_last(ast.tree.body, gen)
86
+ PyCall.exec(gen.printer.output)
87
+ unless last_expr.nil?
88
+ PyCall.eval(CodeGen.new(Printer.new, checker).print(last_expr).printer.output)
89
+ end
90
+ end
91
+
92
+ def self.init_free_variables(checker)
93
+ unless checker.references.empty?
94
+ gen = CodeGen.new(Printer.new, checker)
95
+ args = gen.print_free_vars_initializer
96
+ PyCall.exec(gen.printer.output)
97
+ f = PyCall.eval(CodeGen::FreeVarInitName)
98
+ f.call(args)
99
+ end
100
+ end
101
+
102
+ def self.generate_except_last(ast, gen)
103
+ if expr_or_subtype(ast)
104
+ ast
105
+ elsif ast.is_a?(Exprs) && expr_or_subtype(ast.expressions[-1])
106
+ ast.expressions[0...-1].each do |e|
107
+ gen.print(e)
108
+ gen.newline
109
+ end
110
+ ast.expressions[-1]
111
+ else
112
+ gen.print(ast)
113
+ nil
114
+ end
115
+ end
116
+
117
+ def self.expr_or_subtype(ast)
118
+ if ast.nil? || ast.is_a?(Assign)
119
+ false
120
+ else
121
+ usertype = ast.usertype
122
+ if usertype == :fun_call
123
+ ast.name.name != 'print'
124
+ else
125
+ usertype == :expr || usertype == :lambda_call || usertype == :ternary
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,29 +1,26 @@
1
1
  # Copyright (C) 2017- Shigeru Chiba. All rights reserved.
2
2
 
3
3
  require 'yadriggy/typecheck'
4
- require 'set'
5
4
 
6
5
  module Yadriggy
7
- # Type checker for Ruby
6
+ # Type checker for Ruby.
7
+ # Most values are typed as DynType but local variables
8
+ # are identified. So `type(ast)` returns
9
+ # a {LocalVarType} object if `ast` is of a local variable.
10
+ # A {LocalVarType} is a {Type} object that represents not
11
+ # only the value's type but also the fact that the value
12
+ # comes from a local variable.
13
+ #
14
+ # The type of a free variable is the type of the current value
15
+ # of that variable. {#type} returns an {InstanceType} object.
16
+ #
17
+ # This checker also attempts to recursivly trace a call-graph
18
+ # to reify and type-check the ASTs of the called methods.
8
19
  #
9
20
  class RubyTypeChecker < TypeChecker
10
21
  def initialize(syntax=nil)
11
22
  super()
12
23
  @syntax = syntax
13
- @referred_objects = Set.new
14
- end
15
-
16
- # @return [Set<Object>] all the constants that the type-checked code has
17
- # referred to. Numbers and classes are excluced.
18
- def references()
19
- @referred_objects
20
- end
21
-
22
- # Makes the references set empty. The references set is a set of constants
23
- # returned by {#references}.
24
- #
25
- def clear_references
26
- @referred_objects = Set.new
27
24
  end
28
25
 
29
26
  # Typing rules
@@ -33,38 +30,56 @@ module Yadriggy
33
30
  # the current value of that variable and gives the value type to that AST.
34
31
 
35
32
  rule(Assign) do
36
- rtype = type(ast.right)
37
- ltype = type(ast.left)
38
- if ast.op != :'=' # if op is += etc.
39
- LocalVarType.role(ltype)&.definition = ast.left
40
- ltype
41
- elsif ast.left.is_a?(IdentifierOrCall)
42
- vtype = type_env.bound_name?(ast.left)
43
- if vtype.nil?
44
- bind_local_var(type_env, ast.left, rtype)
33
+ if ast.left.is_a?(Array)
34
+ type_multi_assign(ast.left, ast.op, ast.right)
35
+ else
36
+ ast_right = ast.right
37
+ if ast_right.is_a?(Array)
38
+ ast_right.each {|e| type(e) }
39
+ type_assign(ast.left, ast.op, DynType)
45
40
  else
46
- type_assert(rtype <= vtype, 'invalid assignment')
47
- LocalVarType.role(vtype)&.definition = ast.left
48
- vtype
41
+ type_assign(ast.left, ast.op, type(ast_right))
49
42
  end
50
- else
51
- ltype
52
43
  end
53
44
  end
54
45
 
55
- rule(Name) do
56
- get_name_type(ast, type_env)
46
+ # @api private
47
+ # @param [Array(ASTnode)] ast_left left operand
48
+ #
49
+ def type_multi_assign(ast_left, ast_op, ast_right)
50
+ if ast_right.is_a?(Array)
51
+ rtypes = ast_right.map {|e| type(e) }
52
+ ast_left.each_with_index do |v, i|
53
+ type_assign(v, ast_op,
54
+ i < rtypes.size ? rtypes[i] : RubyClass::NilClass)
55
+ end
56
+ else
57
+ type(ast_right)
58
+ ast_left.each_with_index {|v, i| type_assign(v, ast_op, DynType) }
59
+ end
60
+ DynType
57
61
  end
58
62
 
59
- rule(Const) do
60
- type = get_name_type(ast, type_env)
61
- unless InstanceType.role(type).nil?
62
- obj = type.object
63
- unless obj.is_a?(Numeric) || obj.is_a?(Module)
64
- @referred_objects << obj
63
+ # @api private
64
+ # @param [ASTnode] ast_left the left operand
65
+ # @param [Type] rtype the type of the right operand.
66
+ def type_assign(ast_left, ast_op, rtype)
67
+ ltype = type(ast_left)
68
+ if ast_op != :'=' # if op is += etc.
69
+ LocalVarType.role(ltype)&.definition = ast_left
70
+ elsif ast_left.is_a?(IdentifierOrCall)
71
+ vtype = type_env.bound_name?(ast_left)
72
+ if vtype.nil?
73
+ bind_local_var(type_env, ast_left, DynType)
74
+ else
75
+ LocalVarType.role(vtype)&.definition = ast_left
65
76
  end
66
77
  end
67
- type
78
+ DynType
79
+ end
80
+
81
+ rule(Name) do
82
+ get_name_type(ast, type_env)
68
83
  end
69
84
 
70
85
  # Gets the type of a given name, which may be a local variable
@@ -122,11 +137,13 @@ module Yadriggy
122
137
 
123
138
  rule(Unary) do
124
139
  type(ast.operand)
140
+ DynType
125
141
  end
126
142
 
127
143
  rule(Binary) do
128
144
  type(ast.right)
129
145
  type(ast.left)
146
+ DynType
130
147
  end
131
148
 
132
149
  rule(Dots) do
@@ -151,6 +168,7 @@ module Yadriggy
151
168
  end
152
169
 
153
170
  rule(ArrayLiteral) do
171
+ ast.elements.each {|t| type(t) }
154
172
  RubyClass::Array
155
173
  end
156
174
 
@@ -159,6 +177,7 @@ module Yadriggy
159
177
  end
160
178
 
161
179
  rule(HashLiteral) do
180
+ ast.pairs.each {|kv| type(kv[1]) }
162
181
  RubyClass::Hash
163
182
  end
164
183
 
@@ -191,17 +210,27 @@ module Yadriggy
191
210
  # get_return_type().
192
211
  #
193
212
  rule(Call) do
194
- method_name = ast.name.to_sym
213
+ # f.() is equivalent to f.call()
214
+ method_name = ast.name ? ast.name.to_sym : 'call'
195
215
  if method_name == :lambda
216
+ type_args_and_block(ast)
196
217
  RubyClass::Proc
197
218
  elsif method_name == :raise
219
+ type_args_and_block(ast)
198
220
  RubyClass::Exception
199
221
  else
200
222
  get_call_expr_type(ast, type_env, method_name)
201
223
  end
202
224
  end
203
225
 
226
+ def type_args_and_block(call_ast)
227
+ call_ast.args.each {|t| type(t) }
228
+ type(call_ast.block)
229
+ end
230
+
204
231
  rule(ArrayRef) do
232
+ type(ast.array)
233
+ ast.indexes.each {|t| type(t) }
205
234
  DynType
206
235
  end
207
236
 
@@ -225,6 +254,7 @@ module Yadriggy
225
254
 
226
255
  rule(ForLoop) do
227
256
  ast.vars.each {|v| bind_local_var(type_env, v, DynType) }
257
+ type(ast.set)
228
258
  type(ast.body)
229
259
  DynType
230
260
  end
@@ -311,6 +341,7 @@ module Yadriggy
311
341
  end
312
342
  type_assert_subsume(mtype.result, res_t, 'bad result type')
313
343
  end
344
+ bind_local_var(type_env, ast.name, mtype)
314
345
  mtype
315
346
  end
316
347
 
@@ -379,6 +410,13 @@ module Yadriggy
379
410
  # @return [ResultType] the type of the resulting value.
380
411
  def get_call_expr_type(call_ast, type_env, method_name)
381
412
  arg_types = call_ast.args.map {|t| type(t) }
413
+ get_call_expr_type_with_argtypes(call_ast, type_env, method_name,
414
+ arg_types)
415
+ end
416
+
417
+ # @api private
418
+ def get_call_expr_type_with_argtypes(call_ast, type_env, method_name,
419
+ arg_types)
382
420
  type(call_ast.block_arg)
383
421
  type(call_ast.block)
384
422
 
@@ -413,7 +451,10 @@ module Yadriggy
413
451
  end
414
452
  end
415
453
 
416
- # @private
454
+ # @api private
455
+ # Attempts to find a method by {TypeChecker#typedef}, which
456
+ # searches the method table in this typechecker.
457
+ #
417
458
  def lookup_builtin(recv_type, method_name)
418
459
  et = recv_type.exact_type
419
460
  if DynType == et
@@ -428,7 +469,15 @@ module Yadriggy
428
469
  end
429
470
  end
430
471
 
431
- # @private
472
+ # Computes the type of the {Call} expression
473
+ # by searching the receiver class for the called method.
474
+ #
475
+ # @param [TypeEnv] type_env a type environment.
476
+ # @param [Array<Type>] arg_types the types of the actual arguments.
477
+ # @param [Type] recv_type the receiver type.
478
+ # @param [String|Symbol] method_name the name of the called method.
479
+ # @raise [CheckError] if the method is not found in the receiver class.
480
+ # @return [ResultType] the result type.
432
481
  def lookup_ruby_classes(type_env, arg_types, recv_type, method_name)
433
482
  begin
434
483
  mth = Type.get_instance_method_object(recv_type, method_name)
@@ -443,14 +492,17 @@ module Yadriggy
443
492
  # It returns a {ResultType}.
444
493
  #
445
494
  # Override this method to delimit reification. The implementation
446
- # in this class reifies any method. The method does not have to
495
+ # in this class reifies any method. If its source code is not found,
496
+ # {#get_return_type} reports an error.
497
+ #
498
+ # This method {#get_return_type} does not have to
447
499
  # return a ResultType, which can be used in a later phase to
448
500
  # obtain the invoked method.
449
501
  # This method is invoked by rule(Call). See rule(Call) for more details.
450
502
  #
451
503
  # @param [Call] an_ast the Call node.
452
504
  # @param [Proc|Method|UnboundMethod] mthd the method invoked by an_ast.
453
- # if `mthd` is nil, {get_return_type} reports an error.
505
+ # if `mthd` is nil, {#get_return_type} reports an error.
454
506
  # @param [TypeEnv] new_tenv a type environment.
455
507
  # @param [Array<Type>] arg_types the types of the actual arguments.
456
508
  # @return [ResultType] the result type.
@@ -458,7 +510,6 @@ module Yadriggy
458
510
  m_ast = an_ast.root.reify(mthd)
459
511
  type_assert_false(m_ast.nil?, "no source code: for #{mthd}")
460
512
  (@syntax.check(m_ast.tree) || @syntax.raise_error) if @syntax
461
-
462
513
  mtype = MethodType.role(type(m_ast.tree, new_tenv))
463
514
  type_assert(mtype, 'not a method type')
464
515
  type_assert_params(mtype.params, arg_types, 'argument type mismatch')