houndstooth 0.1.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 +5 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +49 -0
- data/README.md +99 -0
- data/bin/houndstooth.rb +183 -0
- data/fuzz/cases/x.rb +8 -0
- data/fuzz/cases/y.rb +8 -0
- data/fuzz/cases/z.rb +22 -0
- data/fuzz/ruby.dict +64 -0
- data/fuzz/run +21 -0
- data/lib/houndstooth/environment/builder.rb +260 -0
- data/lib/houndstooth/environment/type_parser.rb +149 -0
- data/lib/houndstooth/environment/types/basic/type.rb +85 -0
- data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
- data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
- data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
- data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
- data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
- data/lib/houndstooth/environment/types/method/method.rb +79 -0
- data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
- data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
- data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
- data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
- data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
- data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
- data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
- data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
- data/lib/houndstooth/environment/types.rb +3 -0
- data/lib/houndstooth/environment.rb +74 -0
- data/lib/houndstooth/errors.rb +53 -0
- data/lib/houndstooth/instructions.rb +698 -0
- data/lib/houndstooth/interpreter/const_internal.rb +148 -0
- data/lib/houndstooth/interpreter/objects.rb +142 -0
- data/lib/houndstooth/interpreter/runtime.rb +309 -0
- data/lib/houndstooth/interpreter.rb +7 -0
- data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
- data/lib/houndstooth/semantic_node/definitions.rb +253 -0
- data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
- data/lib/houndstooth/semantic_node/keywords.rb +45 -0
- data/lib/houndstooth/semantic_node/literals.rb +226 -0
- data/lib/houndstooth/semantic_node/operators.rb +126 -0
- data/lib/houndstooth/semantic_node/parameters.rb +108 -0
- data/lib/houndstooth/semantic_node/send.rb +349 -0
- data/lib/houndstooth/semantic_node/super.rb +12 -0
- data/lib/houndstooth/semantic_node.rb +119 -0
- data/lib/houndstooth/stdlib.rb +6 -0
- data/lib/houndstooth/type_checker.rb +462 -0
- data/lib/houndstooth.rb +53 -0
- data/spec/ast_to_node_spec.rb +889 -0
- data/spec/environment_spec.rb +323 -0
- data/spec/instructions_spec.rb +291 -0
- data/spec/integration_spec.rb +785 -0
- data/spec/interpreter_spec.rb +170 -0
- data/spec/self_spec.rb +7 -0
- data/spec/spec_helper.rb +50 -0
- data/test/ruby_interpreter_test.rb +162 -0
- data/types/stdlib.htt +170 -0
- metadata +110 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
module Houndstooth::SemanticNode
|
2
|
+
# A set of parameters accepted by a method definition or block.
|
3
|
+
class Parameters < Base
|
4
|
+
register_ast_converter :args do |ast_node|
|
5
|
+
parameters = Parameters.new(
|
6
|
+
ast_node: ast_node,
|
7
|
+
positional_parameters: [],
|
8
|
+
optional_parameters: [],
|
9
|
+
keyword_parameters: [],
|
10
|
+
optional_keyword_parameters: [],
|
11
|
+
rest_parameter: nil,
|
12
|
+
rest_keyword_parameter: nil,
|
13
|
+
block_parameter: nil,
|
14
|
+
only_proc_parameter: false,
|
15
|
+
has_forward_parameter: false,
|
16
|
+
)
|
17
|
+
|
18
|
+
ast_node.to_a.each do |arg|
|
19
|
+
case arg.type
|
20
|
+
when :arg
|
21
|
+
parameters.positional_parameters << arg.to_a.first
|
22
|
+
when :kwarg
|
23
|
+
parameters.keyword_parameters << arg.to_a.first
|
24
|
+
when :optarg
|
25
|
+
name, value = *arg
|
26
|
+
parameters.optional_parameters << [name, from_ast(value)]
|
27
|
+
when :kwoptarg
|
28
|
+
name, value = *arg
|
29
|
+
parameters.optional_keyword_parameters << [name, from_ast(value)]
|
30
|
+
when :restarg
|
31
|
+
parameters.rest_parameter = arg.to_a.first
|
32
|
+
when :kwrestarg
|
33
|
+
parameters.rest_keyword_parameter = arg.to_a.first
|
34
|
+
when :procarg0
|
35
|
+
parameters.only_proc_parameter = true
|
36
|
+
when :blockarg
|
37
|
+
parameters.block_parameter = arg.to_a.first
|
38
|
+
when :forward_arg
|
39
|
+
parameters.has_forward_parameter = true
|
40
|
+
else
|
41
|
+
Houndstooth::Errors::Error.new(
|
42
|
+
"Unsupported argument type",
|
43
|
+
[[arg.loc.expression, "unsupported"]]
|
44
|
+
).push
|
45
|
+
next nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
parameters
|
50
|
+
end
|
51
|
+
|
52
|
+
# True if this block of parameters takes the magic "progarc0", which in blocks can represent
|
53
|
+
# all parameters given in an array.
|
54
|
+
# @return [Boolean]
|
55
|
+
attr_accessor :only_proc_parameter
|
56
|
+
|
57
|
+
# @return [<Symbol>]
|
58
|
+
attr_accessor :positional_parameters
|
59
|
+
|
60
|
+
# @return [<(Symbol, SemanticNode)>]
|
61
|
+
attr_accessor :optional_parameters
|
62
|
+
|
63
|
+
# @return [<Symbol>]
|
64
|
+
attr_accessor :keyword_parameters
|
65
|
+
|
66
|
+
# @return [<(Symbol, SemanticNode)>]
|
67
|
+
attr_accessor :optional_keyword_parameters
|
68
|
+
|
69
|
+
# @return [Symbol, nil]
|
70
|
+
attr_accessor :rest_parameter
|
71
|
+
|
72
|
+
# @return [Symbol, nil]
|
73
|
+
attr_accessor :rest_keyword_parameter
|
74
|
+
|
75
|
+
# @return [Symbol, nil]
|
76
|
+
attr_accessor :block_parameter
|
77
|
+
|
78
|
+
# True if this method has a `...` parameter.
|
79
|
+
# @return [Boolean]
|
80
|
+
attr_accessor :has_forward_parameter
|
81
|
+
|
82
|
+
def add_to_instruction_block(block)
|
83
|
+
if optional_parameters.any? ||
|
84
|
+
keyword_parameters.any? ||
|
85
|
+
optional_keyword_parameters.any? ||
|
86
|
+
rest_parameter ||
|
87
|
+
rest_keyword_parameter ||
|
88
|
+
has_forward_parameter ||
|
89
|
+
block_parameter ||
|
90
|
+
only_proc_parameter
|
91
|
+
|
92
|
+
# Replace call with a nil
|
93
|
+
Houndstooth::Errors::Error.new(
|
94
|
+
"Only required positional parameters are supported",
|
95
|
+
[[ast_node.loc.expression, "unsupported parameters in block"]]
|
96
|
+
).push
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
|
100
|
+
# Create parameters on this block
|
101
|
+
positional_parameters.each do |name|
|
102
|
+
block.parameters << I::Variable.new(name.to_s)
|
103
|
+
end
|
104
|
+
|
105
|
+
return true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,349 @@
|
|
1
|
+
module Houndstooth::SemanticNode
|
2
|
+
# A method call, called a 'send' internally by Ruby and its parser, hence its name here.
|
3
|
+
class Send < Base
|
4
|
+
# @return [SemanticNode, nil]
|
5
|
+
attr_accessor :target
|
6
|
+
|
7
|
+
# @return [Symbol]
|
8
|
+
attr_accessor :method
|
9
|
+
|
10
|
+
# @return [<SemanticNode>]
|
11
|
+
attr_accessor :arguments
|
12
|
+
|
13
|
+
# @return [Boolean]
|
14
|
+
attr_accessor :safe_navigation
|
15
|
+
|
16
|
+
# If true, this isn't really a send, but instead a super call. The `target` and `method`
|
17
|
+
# of this instance should be ignored.
|
18
|
+
# @return [Boolean]
|
19
|
+
attr_accessor :super_call
|
20
|
+
|
21
|
+
# @return [Block, nil]
|
22
|
+
attr_accessor :block
|
23
|
+
|
24
|
+
register_ast_converter :send do |ast_node, multiple_assignment_lhs: false|
|
25
|
+
target, method, *arguments_nodes = *ast_node
|
26
|
+
|
27
|
+
# Let the target shift comments first!
|
28
|
+
# This is because you can break onto newlines on the dots if you need to apply a comment
|
29
|
+
# to another node.
|
30
|
+
#
|
31
|
+
# Say you need to apply a magic comment to all of the three sends in a chain:
|
32
|
+
#
|
33
|
+
# a.b.c
|
34
|
+
#
|
35
|
+
# Appying to the first send in the chain (the "deepest target") allows you to do this:
|
36
|
+
#
|
37
|
+
# # Comment A
|
38
|
+
# a
|
39
|
+
# # Comment B
|
40
|
+
# .b
|
41
|
+
# # Comment C
|
42
|
+
# .c
|
43
|
+
#
|
44
|
+
# Rather than what you've have to do if they apply to the end of the chain:
|
45
|
+
#
|
46
|
+
# # Comment A
|
47
|
+
# _a = a
|
48
|
+
# # Comment B
|
49
|
+
# _b = a.b
|
50
|
+
# # Comment C
|
51
|
+
# _c = b.c
|
52
|
+
#
|
53
|
+
target = from_ast(target) if target
|
54
|
+
comments = shift_comments(ast_node)
|
55
|
+
|
56
|
+
if multiple_assignment_lhs
|
57
|
+
next Send.new(
|
58
|
+
ast_node: ast_node,
|
59
|
+
comments: comments,
|
60
|
+
|
61
|
+
target: target,
|
62
|
+
method: method,
|
63
|
+
arguments: [PositionalArgument.new(MagicPlaceholder.new)],
|
64
|
+
safe_navigation: false,
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
if arguments_nodes.last&.type == :kwargs
|
69
|
+
arguments = arguments_nodes[0...-1].map { PositionalArgument.new(from_ast(_1)) }
|
70
|
+
arguments.concat(arguments_nodes.last.to_a.map do |kwarg|
|
71
|
+
next [:_, nil] if kwarg.type == :kwsplat
|
72
|
+
|
73
|
+
unless kwarg.type == :pair
|
74
|
+
Houndstooth::Errors::Error.new(
|
75
|
+
"Expected keyword argument list to contain only pairs",
|
76
|
+
[[kwarg.loc.expression, "did not parse as a pair"]]
|
77
|
+
).push
|
78
|
+
next nil
|
79
|
+
end
|
80
|
+
|
81
|
+
name, value = *kwarg.to_a.map { from_ast(_1) }
|
82
|
+
KeywordArgument.new(value, name: name)
|
83
|
+
end)
|
84
|
+
else
|
85
|
+
arguments = arguments_nodes.map { PositionalArgument.new(from_ast(_1)) }
|
86
|
+
end
|
87
|
+
|
88
|
+
Send.new(
|
89
|
+
ast_node: ast_node,
|
90
|
+
comments: comments,
|
91
|
+
|
92
|
+
target: target,
|
93
|
+
method: method,
|
94
|
+
arguments: arguments,
|
95
|
+
safe_navigation: false,
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
register_ast_converter :csend do |ast_node, multiple_assignment_lhs: false|
|
100
|
+
# Convert this csend into a send
|
101
|
+
equivalent_send_node = Parser::AST::Node.new(:send, ast_node, location: ast_node.location)
|
102
|
+
|
103
|
+
# Convert that into a semantic node and set the safe flag
|
104
|
+
send = from_ast(equivalent_send_node, multiple_assignment_lhs: multiple_assignment_lhs)
|
105
|
+
send.safe_navigation = true
|
106
|
+
|
107
|
+
send
|
108
|
+
end
|
109
|
+
|
110
|
+
register_ast_converter :block do |ast_node|
|
111
|
+
send_ast_node, args_ast_node, block_body = *ast_node
|
112
|
+
|
113
|
+
# Parse the `send`, we'll set block properties afterwards
|
114
|
+
send = from_ast(send_ast_node)
|
115
|
+
send.ast_node = ast_node
|
116
|
+
|
117
|
+
send.block = Block.new(
|
118
|
+
ast_node: ast_node,
|
119
|
+
parameters: from_ast(args_ast_node),
|
120
|
+
body: block_body.nil? ? Body.new(ast_node: ast_node) : from_ast(block_body)
|
121
|
+
)
|
122
|
+
|
123
|
+
send
|
124
|
+
end
|
125
|
+
|
126
|
+
# Numblocks are just converted into regular blocks with the same parameter names, e.g.:
|
127
|
+
#
|
128
|
+
# array.map { _1 + 1 }
|
129
|
+
#
|
130
|
+
# Becomes:
|
131
|
+
#
|
132
|
+
# array.map { |_1| _1 + 1 }
|
133
|
+
#
|
134
|
+
register_ast_converter :numblock do |ast_node|
|
135
|
+
send_ast_node, args_count, block_body = *ast_node
|
136
|
+
|
137
|
+
# Parse the `send`, we'll set block properties afterwards
|
138
|
+
send = from_ast(send_ast_node)
|
139
|
+
send.ast_node = ast_node
|
140
|
+
|
141
|
+
# Build a fake set of parameters for the block
|
142
|
+
# We need to respect the "procarg0" semantics by checking if there's only 1 parameter
|
143
|
+
if args_count == 1
|
144
|
+
parameters = Parameters.new(
|
145
|
+
ast_node: block_body,
|
146
|
+
positional_parameters: [],
|
147
|
+
optional_parameters: [],
|
148
|
+
keyword_parameters: [],
|
149
|
+
optional_keyword_parameters: [],
|
150
|
+
rest_parameter: nil,
|
151
|
+
rest_keyword_parameter: nil,
|
152
|
+
only_proc_parameter: true,
|
153
|
+
)
|
154
|
+
else
|
155
|
+
parameters = Parameters.new(
|
156
|
+
ast_node: block_body,
|
157
|
+
positional_parameters: args_count.times.map { |i| :"_#{i + 1}" },
|
158
|
+
optional_parameters: [],
|
159
|
+
keyword_parameters: [],
|
160
|
+
optional_keyword_parameters: [],
|
161
|
+
rest_parameter: nil,
|
162
|
+
rest_keyword_parameter: nil,
|
163
|
+
only_proc_parameter: false,
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
send.block = Block.new(
|
168
|
+
ast_node: ast_node,
|
169
|
+
parameters: parameters,
|
170
|
+
body: from_ast(block_body)
|
171
|
+
)
|
172
|
+
|
173
|
+
send
|
174
|
+
end
|
175
|
+
|
176
|
+
# Supers are virtually identical to method calls in terms of the arguments they can take.
|
177
|
+
register_ast_converter :super do |ast_node|
|
178
|
+
# Convert this super into a fake send node
|
179
|
+
equivalent_send_node = Parser::AST::Node.new(
|
180
|
+
:send,
|
181
|
+
[
|
182
|
+
# Target
|
183
|
+
nil,
|
184
|
+
|
185
|
+
# Method
|
186
|
+
:super__NOT_A_REAL_METHOD,
|
187
|
+
|
188
|
+
# Arguments
|
189
|
+
*ast_node
|
190
|
+
],
|
191
|
+
location: ast_node.location
|
192
|
+
)
|
193
|
+
|
194
|
+
# Convert that into a semantic node and set the super flag
|
195
|
+
send = from_ast(equivalent_send_node)
|
196
|
+
send.super_call = true
|
197
|
+
|
198
|
+
send
|
199
|
+
end
|
200
|
+
|
201
|
+
def to_instructions(block)
|
202
|
+
# Generate instructions for the method's target
|
203
|
+
# If it doesn't have one, then it's implicitly `self`
|
204
|
+
if target
|
205
|
+
target.to_instructions(block)
|
206
|
+
else
|
207
|
+
block.instructions << I::SelfInstruction.new(block: block, node: self)
|
208
|
+
end
|
209
|
+
target_variable = block.instructions.last.result
|
210
|
+
|
211
|
+
type_arguments = get_type_arguments
|
212
|
+
|
213
|
+
# If this call uses save navigation, we want to wrap everything else in a conditional
|
214
|
+
# which checks the target isn't nil
|
215
|
+
# (If safe navigation bails from a call because the target is nil, the arguments don't
|
216
|
+
# get evaluated either)
|
217
|
+
if safe_navigation
|
218
|
+
# Generates:
|
219
|
+
# $1 = ...target...
|
220
|
+
# if $2.nil?
|
221
|
+
# nil
|
222
|
+
# else
|
223
|
+
# $1.method
|
224
|
+
# end
|
225
|
+
block.instructions << I::SendInstruction.new(
|
226
|
+
block: block,
|
227
|
+
node: self,
|
228
|
+
target: target_variable,
|
229
|
+
method_name: :nil?,
|
230
|
+
)
|
231
|
+
|
232
|
+
true_blk = I::InstructionBlock.new(has_scope: false, parent: block)
|
233
|
+
true_blk.instructions << I::LiteralInstruction.new(block: true_blk, node: self, value: nil)
|
234
|
+
block.instructions << I::ConditionalInstruction.new(
|
235
|
+
block: block,
|
236
|
+
node: self,
|
237
|
+
condition: block.instructions.last.result,
|
238
|
+
true_branch: true_blk,
|
239
|
+
false_branch: I::InstructionBlock.new(has_scope: false, parent: block),
|
240
|
+
)
|
241
|
+
|
242
|
+
# Replace the working instruction block with the false branch, so we insert the
|
243
|
+
# actual send in there
|
244
|
+
block = block.instructions.last.false_branch
|
245
|
+
end
|
246
|
+
|
247
|
+
# Evaluate arguments
|
248
|
+
ins_args = arguments.map do |arg|
|
249
|
+
case arg
|
250
|
+
when PositionalArgument
|
251
|
+
arg.node.to_instructions(block)
|
252
|
+
I::PositionalArgument.new(block.instructions.last.result)
|
253
|
+
when KeywordArgument
|
254
|
+
if arg.name.is_a?(SymbolLiteral) && arg.name.components.length == 1 && arg.name.components.first.is_a?(String)
|
255
|
+
arg.node.to_instructions(block)
|
256
|
+
I::KeywordArgument.new(
|
257
|
+
block.instructions.last.result,
|
258
|
+
name: arg.name.components.first,
|
259
|
+
)
|
260
|
+
else
|
261
|
+
Houndstooth::Errors::Error.new(
|
262
|
+
"Keyword argument keys must be non-interpolated symbol literals",
|
263
|
+
[[arg.name.ast_node.loc.expression, "invalid key"]]
|
264
|
+
).push
|
265
|
+
|
266
|
+
block.instructions << I::LiteralInstruction.new(block: block, node: arg.name, value: nil)
|
267
|
+
I::KeywordArgument.new(
|
268
|
+
block.instructions.last.result,
|
269
|
+
name: "__non_symbol_key_error_#{(rand * 10000).to_i}",
|
270
|
+
)
|
271
|
+
end
|
272
|
+
else
|
273
|
+
raise "unknown node argument type: #{arg}"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Insert send instruction
|
278
|
+
si = I::SendInstruction.new(
|
279
|
+
block: block,
|
280
|
+
node: self,
|
281
|
+
target: target_variable,
|
282
|
+
method_name: method,
|
283
|
+
arguments: ins_args,
|
284
|
+
super_call: super_call,
|
285
|
+
type_arguments: type_arguments,
|
286
|
+
)
|
287
|
+
|
288
|
+
# Build up method block
|
289
|
+
if self.block
|
290
|
+
si.method_block =
|
291
|
+
I::InstructionBlock.new(has_scope: true, parent: si).tap do |blk|
|
292
|
+
if !self.block.parameters.add_to_instruction_block(blk)
|
293
|
+
block.instructions << I::LiteralInstruction.new(node: self, block: block, value: nil)
|
294
|
+
return
|
295
|
+
end
|
296
|
+
|
297
|
+
# Create body
|
298
|
+
self.block.body.to_instructions(blk)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
block.instructions << si
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# A block passed to a `Send`.
|
307
|
+
class Block < Base
|
308
|
+
# @return [Parameters]
|
309
|
+
attr_accessor :parameters
|
310
|
+
|
311
|
+
# @return [SemanticNode]
|
312
|
+
attr_accessor :body
|
313
|
+
end
|
314
|
+
|
315
|
+
# An argument to a `Send`.
|
316
|
+
# @abstract
|
317
|
+
class Argument
|
318
|
+
# The node for the argument's value.
|
319
|
+
# @return [SemanticNode]
|
320
|
+
attr_accessor :node
|
321
|
+
|
322
|
+
def initialize(node)
|
323
|
+
@node = node
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# A standard, singular, positional argument.
|
328
|
+
class PositionalArgument < Argument; end
|
329
|
+
|
330
|
+
# A singular keyword argument.
|
331
|
+
class KeywordArgument < Argument
|
332
|
+
# The keyword.
|
333
|
+
# @return [SemanticNode]
|
334
|
+
attr_accessor :name
|
335
|
+
|
336
|
+
def initialize(node, name:)
|
337
|
+
super(node)
|
338
|
+
@name = name
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# A special argument which may appear in the arguments to a `Send`, when arguments have been
|
343
|
+
# forwarded from the enclosing method into it.
|
344
|
+
class ForwardedArguments < Base
|
345
|
+
register_ast_converter :forwarded_args do |ast_node|
|
346
|
+
ForwardedArguments.new(ast_node: ast_node)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Houndstooth::SemanticNode
|
2
|
+
# An implicit super call, without parentheses. This will forward arguments to the superclass'
|
3
|
+
# method automatically.
|
4
|
+
class ImplicitSuper < Base
|
5
|
+
register_ast_converter :zsuper do |ast_node|
|
6
|
+
self.new(ast_node: ast_node)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# ...Where's `ExplicitSuper`?
|
11
|
+
# We use `Send` for that, since the parameters are largely the same!
|
12
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'parser/ruby30'
|
2
|
+
|
3
|
+
# Accuracy to Ruby 3
|
4
|
+
LEGACY_MODES = %i[lambda procarg0 encoding arg_inside_procarg0 forward_arg kwargs match_pattern]
|
5
|
+
LEGACY_MODES.each do |mode|
|
6
|
+
Parser::Builders::Default.send :"emit_#{mode}=", true
|
7
|
+
end
|
8
|
+
Parser::Builders::Default.emit_index = false
|
9
|
+
Parser::Builders::Default.emit_lambda = false
|
10
|
+
|
11
|
+
# Useful resource: https://docs.rs/lib-ruby-parser/3.0.12/lib_ruby_parser/index.html
|
12
|
+
# Based on whitequark/parser so gives good idea of what node types to expect
|
13
|
+
|
14
|
+
module Houndstooth::SemanticNode
|
15
|
+
# Shorthand for use by #to_instructions implementations
|
16
|
+
I = Houndstooth::Instructions
|
17
|
+
|
18
|
+
class Base
|
19
|
+
# @return [Parser::AST::Node]
|
20
|
+
attr_accessor :ast_node
|
21
|
+
|
22
|
+
# @return [<Parser::Source::Comment>]
|
23
|
+
attr_accessor :comments
|
24
|
+
|
25
|
+
def initialize(ast_node:, **kwargs)
|
26
|
+
@comments = []
|
27
|
+
@ast_node = ast_node
|
28
|
+
|
29
|
+
kwargs.each do |k, v|
|
30
|
+
send :"#{k}=", v
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.from_ast(ast_node, **options)
|
35
|
+
converter = @@ast_converters[ast_node.type]
|
36
|
+
|
37
|
+
if converter.nil?
|
38
|
+
Houndstooth::Errors::Error.new(
|
39
|
+
"Unsupported AST node type #{ast_node.type}",
|
40
|
+
[[ast_node.loc.expression, "unsupported"]]
|
41
|
+
).push
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
converter.(ast_node, **options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.register_ast_converter(*types, &block)
|
49
|
+
@@ast_converters ||= {}
|
50
|
+
types.each do |type|
|
51
|
+
@@ast_converters[type] = block
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: shouldn't use a global!!
|
56
|
+
def self.shift_comments(ast_node)
|
57
|
+
# TODO: don't pick *any* comment before this one, only ones on their own line
|
58
|
+
# In this case:
|
59
|
+
# x = 2 # foo
|
60
|
+
# y
|
61
|
+
# We shouldn't match the `# foo` comment to the `y` Send
|
62
|
+
|
63
|
+
if ast_node.type == :send && ast_node.location.respond_to?(:selector) && ast_node.location.selector
|
64
|
+
# Use name of the method as position reference, if available
|
65
|
+
reference_location = ast_node.location.selector
|
66
|
+
else
|
67
|
+
# Not sure what this is, just use the very start of the expression
|
68
|
+
reference_location = ast_node.location.expression
|
69
|
+
end
|
70
|
+
|
71
|
+
comments = []
|
72
|
+
comments << $comments.shift \
|
73
|
+
while $comments.first && $comments.first.location.expression < reference_location
|
74
|
+
comments
|
75
|
+
end
|
76
|
+
|
77
|
+
# Converts this semantic node into a sequence of equivalent instructions, and adds them to
|
78
|
+
# the given instruction block.
|
79
|
+
# It is expected that, after this call returns, the variable assigned by the final
|
80
|
+
# instruction in the block has an equivalent result to evaluating this expression.
|
81
|
+
# @param [InstructionBlock] block
|
82
|
+
def to_instructions(block)
|
83
|
+
raise "#to_instructions not implemented for #{self.class.name}"
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
# Extracts type arguments from comments as strings.
|
89
|
+
def get_type_arguments
|
90
|
+
comments
|
91
|
+
.select { |c| c.text.start_with?('#!arg ') }
|
92
|
+
.map do |c|
|
93
|
+
unless /^#!arg\s+(.+)\s*$/ === c.text
|
94
|
+
Houndstooth::Errors::Error.new(
|
95
|
+
"Malformed #!arg definition",
|
96
|
+
[[c.loc.expression, "invalid"]]
|
97
|
+
).push
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
$1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.from_ast(...)
|
107
|
+
Base.from_ast(...)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
require_relative 'semantic_node/parameters'
|
112
|
+
require_relative 'semantic_node/control_flow'
|
113
|
+
require_relative 'semantic_node/operators'
|
114
|
+
require_relative 'semantic_node/identifiers'
|
115
|
+
require_relative 'semantic_node/keywords'
|
116
|
+
require_relative 'semantic_node/literals'
|
117
|
+
require_relative 'semantic_node/send'
|
118
|
+
require_relative 'semantic_node/definitions'
|
119
|
+
require_relative 'semantic_node/super'
|