houndstooth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +11 -0
  6. data/Gemfile.lock +49 -0
  7. data/README.md +99 -0
  8. data/bin/houndstooth.rb +183 -0
  9. data/fuzz/cases/x.rb +8 -0
  10. data/fuzz/cases/y.rb +8 -0
  11. data/fuzz/cases/z.rb +22 -0
  12. data/fuzz/ruby.dict +64 -0
  13. data/fuzz/run +21 -0
  14. data/lib/houndstooth/environment/builder.rb +260 -0
  15. data/lib/houndstooth/environment/type_parser.rb +149 -0
  16. data/lib/houndstooth/environment/types/basic/type.rb +85 -0
  17. data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
  18. data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
  19. data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
  20. data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
  21. data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
  22. data/lib/houndstooth/environment/types/method/method.rb +79 -0
  23. data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
  24. data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
  25. data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
  26. data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
  27. data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
  28. data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
  29. data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
  30. data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
  31. data/lib/houndstooth/environment/types.rb +3 -0
  32. data/lib/houndstooth/environment.rb +74 -0
  33. data/lib/houndstooth/errors.rb +53 -0
  34. data/lib/houndstooth/instructions.rb +698 -0
  35. data/lib/houndstooth/interpreter/const_internal.rb +148 -0
  36. data/lib/houndstooth/interpreter/objects.rb +142 -0
  37. data/lib/houndstooth/interpreter/runtime.rb +309 -0
  38. data/lib/houndstooth/interpreter.rb +7 -0
  39. data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
  40. data/lib/houndstooth/semantic_node/definitions.rb +253 -0
  41. data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
  42. data/lib/houndstooth/semantic_node/keywords.rb +45 -0
  43. data/lib/houndstooth/semantic_node/literals.rb +226 -0
  44. data/lib/houndstooth/semantic_node/operators.rb +126 -0
  45. data/lib/houndstooth/semantic_node/parameters.rb +108 -0
  46. data/lib/houndstooth/semantic_node/send.rb +349 -0
  47. data/lib/houndstooth/semantic_node/super.rb +12 -0
  48. data/lib/houndstooth/semantic_node.rb +119 -0
  49. data/lib/houndstooth/stdlib.rb +6 -0
  50. data/lib/houndstooth/type_checker.rb +462 -0
  51. data/lib/houndstooth.rb +53 -0
  52. data/spec/ast_to_node_spec.rb +889 -0
  53. data/spec/environment_spec.rb +323 -0
  54. data/spec/instructions_spec.rb +291 -0
  55. data/spec/integration_spec.rb +785 -0
  56. data/spec/interpreter_spec.rb +170 -0
  57. data/spec/self_spec.rb +7 -0
  58. data/spec/spec_helper.rb +50 -0
  59. data/test/ruby_interpreter_test.rb +162 -0
  60. data/types/stdlib.htt +170 -0
  61. metadata +110 -0
@@ -0,0 +1,218 @@
1
+ module Houndstooth::SemanticNode
2
+ # Used to group a sequence of nodes into one node - for example, when the body of a method
3
+ # definition contains more than one statement.
4
+ #
5
+ # In an ideal world, this class wouldn't exist, and instead we'd use an array everywhere it's
6
+ # possible for multiple nodes to exist. However, it turns out bodies are valid virtually
7
+ # everywhere! The following are valid snippets of Ruby code...
8
+ #
9
+ # - 1 + (x = 2; x * x)
10
+ # - something((a; b; c), (d; e; f))
11
+ # - class (s = :IO; Object.const_get(s))::Something; end
12
+ class Body < Base
13
+ # @return [<SemanticNode>]
14
+ attr_accessor :nodes
15
+
16
+ register_ast_converter :begin do |ast_node|
17
+ if ast_node.to_a.length == 1
18
+ from_ast(ast_node.to_a.first)
19
+ else
20
+ Body.new(
21
+ ast_node: ast_node,
22
+
23
+ # Use a flat map so that we can flatten inner Body nodes into this one
24
+ nodes: ast_node.to_a.flat_map do |ast_node|
25
+ sem_node = from_ast(ast_node)
26
+ if sem_node.is_a?(Body)
27
+ sem_node.nodes
28
+ else
29
+ [sem_node]
30
+ end
31
+ end
32
+ )
33
+ end
34
+ end
35
+
36
+ def to_instructions(block)
37
+ # A body could signify a new scope, but not always, so we'll let the upper node in the
38
+ # tree create one if needed
39
+
40
+ if nodes.any?
41
+ nodes&.each do |node|
42
+ node.to_instructions(block)
43
+ end
44
+ else
45
+ block << I::LiteralInstruction.new(block: block, node: self, value: nil)
46
+ end
47
+ end
48
+ end
49
+
50
+ # A conditional with true and false branches, used to represent `if` statements, ternary
51
+ # conditionals, and `case/when` constructs.
52
+ class Conditional < Base
53
+ # @return [SemanticNode]
54
+ attr_accessor :condition
55
+
56
+ # @return [SemanticNode]
57
+ attr_accessor :true_branch
58
+
59
+ # @return [SemanticNode, nil]
60
+ attr_accessor :false_branch
61
+
62
+ register_ast_converter :if do |ast_node|
63
+ condition, true_branch, false_branch = ast_node.to_a.map { from_ast(_1) if _1 }
64
+
65
+ Conditional.new(
66
+ ast_node: ast_node,
67
+ condition: condition,
68
+ true_branch: true_branch,
69
+ false_branch: false_branch,
70
+ )
71
+ end
72
+
73
+ register_ast_converter :case do |ast_node|
74
+ subject, *ast_whens, else_case = *ast_node
75
+
76
+ subject = from_ast(subject)
77
+ whens = ast_whens.map { |w| w.to_a.map { from_ast(_1) if _1 } } # [[value, body], ...]
78
+ else_case = from_ast(else_case) if else_case
79
+
80
+ # Convert into assignment and conditional chain
81
+ fabricated_subject_var = LocalVariable.fabricate
82
+ fabricated_subject_var_asgn = VariableAssignment.new(
83
+ ast_node: nil,
84
+ target: fabricated_subject_var,
85
+ value: subject,
86
+ )
87
+
88
+ # Add each `when` as the false branch of the previous one
89
+ root_conditional = nil
90
+ last_conditional = nil
91
+
92
+ whens.each.with_index do |_when, i|
93
+ value, body = *_when
94
+
95
+ this_conditional = Conditional.new(
96
+ ast_node: ast_whens[i],
97
+
98
+ # `when x` is equivalent to `x === subject`
99
+ condition: Send.new(
100
+ ast_node: ast_whens[i],
101
+
102
+ target: value,
103
+ method: :===,
104
+ arguments: [PositionalArgument.new(fabricated_subject_var)],
105
+ ),
106
+ true_branch: body,
107
+ false_branch: nil,
108
+ )
109
+
110
+ if last_conditional
111
+ last_conditional.false_branch = this_conditional
112
+ last_conditional = this_conditional
113
+ else
114
+ root_conditional = this_conditional
115
+ last_conditional = this_conditional
116
+ end
117
+ end
118
+
119
+ # It is syntactically enforced that a `case` will have at least one `when`, so this is safe
120
+ last_conditional.false_branch = else_case
121
+
122
+ Body.new(
123
+ ast_node: ast_node,
124
+ nodes: [
125
+ fabricated_subject_var_asgn,
126
+ root_conditional,
127
+ ]
128
+ )
129
+ end
130
+
131
+ def to_instructions(block)
132
+ condition.to_instructions(block)
133
+ ci = I::ConditionalInstruction.new(
134
+ block: block,
135
+ node: self,
136
+ condition: block.instructions.last.result,
137
+ true_branch: nil,
138
+ false_branch: nil,
139
+ )
140
+ ci.true_branch =
141
+ I::InstructionBlock.new(has_scope: false, parent: ci).tap do |blk|
142
+ true_branch.to_instructions(blk)
143
+ end
144
+ ci.false_branch =
145
+ I::InstructionBlock.new(has_scope: false, parent: ci).tap do |blk|
146
+ if false_branch.nil?
147
+ blk.instructions << I::LiteralInstruction.new(block: blk, node: self, value: nil)
148
+ else
149
+ false_branch.to_instructions(blk)
150
+ end
151
+ end
152
+ block.instructions << ci
153
+ end
154
+ end
155
+
156
+ # A while loop.
157
+ #
158
+ # TODO: It's possible this can be desugared into Kernel.loop { break unless condition; body }
159
+ class While < Base
160
+ # @return [SemanticNode]
161
+ attr_accessor :condition
162
+
163
+ # @return [SemanticNode]
164
+ attr_accessor :body
165
+
166
+ register_ast_converter :while do |ast_node|
167
+ condition, body = ast_node.to_a.map { from_ast(_1) if _1 }
168
+
169
+ While.new(
170
+ ast_node: ast_node,
171
+ condition: condition,
172
+ body: body,
173
+ )
174
+ end
175
+ end
176
+
177
+ # A mixin for defining expressions which affect the control of their enclosing contexts, e.g.
178
+ # `return` and `break`. These all take one optional arguments, so we can deduplicate their
179
+ # definitions.
180
+ module ControlExpressionMixin
181
+ def control_exp_mixin(type)
182
+ # @return [SemanticNode, nil]
183
+ attr_accessor :value
184
+
185
+ register_ast_converter type do |ast_node|
186
+ if ast_node.to_a.length > 1
187
+ value = ArrayLiteral.new(
188
+ ast_node: ast_node,
189
+ nodes: ast_node.to_a.map { from_ast(_1) },
190
+ )
191
+ else
192
+ value = ast_node.to_a.first
193
+ value = from_ast(value) if value
194
+ end
195
+
196
+ self.new(ast_node: ast_node, value: value)
197
+ end
198
+ end
199
+ end
200
+
201
+ # A return expression.
202
+ class Return < Base
203
+ extend ControlExpressionMixin
204
+ control_exp_mixin :return
205
+ end
206
+
207
+ # A break expression.
208
+ class Break < Base
209
+ extend ControlExpressionMixin
210
+ control_exp_mixin :break
211
+ end
212
+
213
+ # A next expression.
214
+ class Next < Base
215
+ extend ControlExpressionMixin
216
+ control_exp_mixin :next
217
+ end
218
+ end
@@ -0,0 +1,253 @@
1
+ module Houndstooth::SemanticNode
2
+ # A method definition. Used for both standard method definitions (`def x()`) or definitions
3
+ # on a singleton (`def something.x()`).
4
+ class MethodDefinition < Base
5
+ # @return [Symbol]
6
+ attr_accessor :name
7
+
8
+ # @return [Parameters]
9
+ attr_accessor :parameters
10
+
11
+ # @return [SemanticNode, nil]
12
+ attr_accessor :target
13
+
14
+ # @return [SemanticNode]
15
+ attr_accessor :body
16
+
17
+ register_ast_converter :def do |ast_node|
18
+ name, parameters, body = *ast_node
19
+ comments = shift_comments(ast_node)
20
+
21
+ body = from_ast(body) if body
22
+ parameters = from_ast(parameters)
23
+
24
+ MethodDefinition.new(
25
+ ast_node: ast_node,
26
+ comments: comments,
27
+
28
+ name: name,
29
+ body: body,
30
+ parameters: parameters,
31
+ target: nil,
32
+ )
33
+ end
34
+
35
+ register_ast_converter :defs do |ast_node|
36
+ target, name, parameters, body = *ast_node
37
+ comments = shift_comments(ast_node)
38
+
39
+ target = from_ast(target)
40
+ body = from_ast(body) if body
41
+ parameters = from_ast(parameters)
42
+
43
+ MethodDefinition.new(
44
+ ast_node: ast_node,
45
+ comments: comments,
46
+
47
+ name: name,
48
+ body: body,
49
+ parameters: parameters,
50
+ target: target,
51
+ )
52
+ end
53
+
54
+ def to_instructions(block)
55
+ if target
56
+ target.to_instructions(block)
57
+ target_var = block.instructions.last.result
58
+ else
59
+ target_var = nil
60
+ end
61
+
62
+ mdi = I::MethodDefinitionInstruction.new(
63
+ node: self,
64
+ block: block,
65
+ name: name,
66
+ target: target_var,
67
+ body: nil,
68
+ )
69
+ mdi.body =
70
+ I::InstructionBlock.new(has_scope: true, parent: mdi).tap do |blk|
71
+ if !parameters.add_to_instruction_block(blk)
72
+ block.instructions << I::LiteralInstruction.new(node: self, block: block, value: nil)
73
+ return
74
+ end
75
+
76
+ if body
77
+ body.to_instructions(blk)
78
+ else
79
+ blk.instructions << I::LiteralInstruction.new(node: self, block: block, value: nil)
80
+ end
81
+ end
82
+ block.instructions << mdi
83
+ end
84
+ end
85
+
86
+ module TypeDefinitionMixin
87
+ def type_definition_instructions(block, kind)
88
+ # Generate the name as instructions, but remove the last one to discover the name
89
+ # (Because the name will lead up to the target, e.g. class A::B)
90
+ # We measure how many instructions are generated to figure out if there was actually a
91
+ # leading path
92
+ instruction_count = block.instructions.length
93
+ name.to_instructions(block)
94
+ instruction_count = block.instructions.length - instruction_count
95
+ unless block.instructions.last.is_a?(I::ConstantAccessInstruction)
96
+ Houndstooth::Errors::Error.new(
97
+ "Type name must be a constant",
98
+ [[name.loc.expression, "unsupported"]]
99
+ ).push
100
+ return
101
+ end
102
+ type_name = block.instructions.pop.name.to_sym
103
+ if instruction_count > 1
104
+ type_target = block.instructions.last&.result
105
+ else
106
+ type_target = nil
107
+ end
108
+
109
+ if kind == :class
110
+ # Generate superclass, or if there isn't one, use Object
111
+ if superclass
112
+ superclass.to_instructions(block)
113
+ type_superclass = block.instructions.last.result
114
+ else
115
+ block.instructions << I::ConstantBaseAccessInstruction.new(block: block, node: self)
116
+ block.instructions << I::ConstantAccessInstruction.new(
117
+ block: block,
118
+ node: self,
119
+ target: block.instructions.last.result,
120
+ name: :Object,
121
+ )
122
+ type_superclass = block.instructions.last.result
123
+ end
124
+ end
125
+
126
+ # Build type
127
+ tdi = I::TypeDefinitionInstruction.new(
128
+ block: block,
129
+ node: self,
130
+ name: type_name,
131
+ kind: kind,
132
+ target: type_target,
133
+ superclass: type_superclass,
134
+ body: nil,
135
+ )
136
+ tdi.body =
137
+ I::InstructionBlock.new(has_scope: true, parent: tdi).tap do |blk|
138
+ body&.to_instructions(blk)
139
+ end
140
+
141
+ block.instructions << tdi
142
+ end
143
+ end
144
+
145
+ # A class definition.
146
+ class ClassDefinition < Base
147
+ # @return [SemanticNode]
148
+ attr_accessor :name
149
+
150
+ # @return [SemanticNode, nil]
151
+ attr_accessor :superclass
152
+
153
+ # @return [SemanticNode, nil]
154
+ attr_accessor :body
155
+
156
+ register_ast_converter :class do |ast_node|
157
+ name, superclass, body = *ast_node
158
+ comments = shift_comments(ast_node)
159
+
160
+ name = from_ast(name)
161
+ superclass = from_ast(superclass) if superclass
162
+ body = from_ast(body) if body
163
+
164
+ ClassDefinition.new(
165
+ ast_node: ast_node,
166
+ comments: comments,
167
+
168
+ name: name,
169
+ superclass: superclass,
170
+ body: body,
171
+ )
172
+ end
173
+
174
+ include TypeDefinitionMixin
175
+ def to_instructions(block)
176
+ type_definition_instructions(block, :class)
177
+ end
178
+ end
179
+
180
+ # A singleton class accessor: `class << x`.
181
+ class SingletonClass < Base
182
+ # @return [SemanticNode]
183
+ attr_accessor :target
184
+
185
+ # @return [SemanticNode, nil]
186
+ attr_accessor :body
187
+
188
+ register_ast_converter :sclass do |ast_node|
189
+ target, body = *ast_node
190
+
191
+ target = from_ast(target)
192
+ body = from_ast(body) if body
193
+
194
+ SingletonClass.new(
195
+ ast_node: ast_node,
196
+ target: target,
197
+ body: body,
198
+ )
199
+ end
200
+ end
201
+
202
+ # A module definition.
203
+ class ModuleDefinition < Base
204
+ # @return [SemanticNode]
205
+ attr_accessor :name
206
+
207
+ # @return [SemanticNode, nil]
208
+ attr_accessor :body
209
+
210
+ register_ast_converter :module do |ast_node|
211
+ name, body = *ast_node
212
+ comments = shift_comments(ast_node)
213
+
214
+ name = from_ast(name)
215
+ body = from_ast(body) if body
216
+
217
+ ModuleDefinition.new(
218
+ ast_node: ast_node,
219
+ comments: comments,
220
+
221
+ name: name,
222
+ body: body,
223
+ )
224
+ end
225
+
226
+ include TypeDefinitionMixin
227
+ def to_instructions(block)
228
+ type_definition_instructions(block, :module)
229
+ end
230
+ end
231
+
232
+ # An alias.
233
+ #
234
+ # Aliases are usually between statically-named methods given with just identifiers, but they
235
+ # can also be between methods named with dynamic symbols, and even between global variables.
236
+ class Alias < Base
237
+ # @return [SemanticNode]
238
+ attr_accessor :from
239
+
240
+ # @return [SemanticNode]
241
+ attr_accessor :to
242
+
243
+ register_ast_converter :alias do |ast_node|
244
+ to, from = ast_node.to_a.map { from_ast(_1) }
245
+
246
+ Alias.new(
247
+ ast_node: ast_node,
248
+ from: from,
249
+ to: to,
250
+ )
251
+ end
252
+ end
253
+ end