yadriggy 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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')