houndstooth 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,698 @@
|
|
1
|
+
module Houndstooth
|
2
|
+
module Instructions
|
3
|
+
# A variable. Every instruction assigns its results to a variable.
|
4
|
+
# This does NOT necessarily correlate to a Ruby variable - these variables are temporary,
|
5
|
+
# and only created to "simplify" Ruby code. For example, "2 + 6" would become:
|
6
|
+
# $1 = 2
|
7
|
+
# $2 = 6
|
8
|
+
# $3 = $1 + $2
|
9
|
+
# $1, $2 and $3 are variables.
|
10
|
+
class Variable
|
11
|
+
# The globally unique ID of this variable.
|
12
|
+
# @return [Integer]
|
13
|
+
attr_reader :id
|
14
|
+
|
15
|
+
# If this variable corresponds to a Ruby variable of some kind, that variable's
|
16
|
+
# identifier. This doesn't need to be an exact reference, since it's only used for
|
17
|
+
# display.
|
18
|
+
# @return [String, nil]
|
19
|
+
attr_accessor :ruby_identifier
|
20
|
+
|
21
|
+
# Create a new variable with a unique ID, optionally with a Ruby identifier.
|
22
|
+
def initialize(ruby_identifier=nil)
|
23
|
+
@@next_id ||= 1
|
24
|
+
@id = @@next_id
|
25
|
+
@@next_id += 1
|
26
|
+
|
27
|
+
@ruby_identifier = ruby_identifier
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_assembly
|
31
|
+
"$#{id}" + (ruby_identifier ? "(#{ruby_identifier})" : "")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# A block of sequential instructions, which can optionally introduce a new variable scope.
|
36
|
+
class InstructionBlock
|
37
|
+
# The instructions in the block.
|
38
|
+
# @return [<Instruction>]
|
39
|
+
attr_reader :instructions
|
40
|
+
|
41
|
+
# The Ruby variables in the scope introduced at this block, or nil if this block doesn't
|
42
|
+
# introduce a scope. All of these variables must have a `ruby_identifier` for
|
43
|
+
# resolution.
|
44
|
+
# @return [<Variable>, nil]
|
45
|
+
attr_reader :scope
|
46
|
+
|
47
|
+
# The parameters of this block, if it is a method block or a function definition.
|
48
|
+
# @return [<Variable>, nil]
|
49
|
+
attr_reader :parameters
|
50
|
+
|
51
|
+
# Any type refinements introduced exclusively within this block. These will be shadowed
|
52
|
+
# by type changes, and are not considered when e.g. constructing unions from branches.
|
53
|
+
# These are used to implement the "magic" refinement of uncertain types, such as unions.
|
54
|
+
# @return [{Variable => TypeInstance}]
|
55
|
+
attr_reader :type_refinements
|
56
|
+
|
57
|
+
# The instruction which this block belongs to.
|
58
|
+
# @return [Instruction, nil]
|
59
|
+
attr_reader :parent
|
60
|
+
|
61
|
+
# The environment in which this block exists. Only needs to be set during type checking.
|
62
|
+
# @return [Environment]
|
63
|
+
attr_accessor :environment
|
64
|
+
|
65
|
+
def initialize(instructions: nil, has_scope:, parameters: nil, parent:)
|
66
|
+
@instructions = instructions || []
|
67
|
+
@scope = has_scope ? [] : nil
|
68
|
+
@parameters = parameters || []
|
69
|
+
@parent = parent
|
70
|
+
@type_refinements = {}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the type of a variable at a particular instruction, either by reference or
|
74
|
+
# index. If the type is not known, returns nil.
|
75
|
+
# @param [Variable] var
|
76
|
+
# @param [Instruction, Integer] ins
|
77
|
+
# @return [Type, nil]
|
78
|
+
def variable_type_at(var, ins, strictly_before: false)
|
79
|
+
index =
|
80
|
+
if ins.is_a?(Integer)
|
81
|
+
ins
|
82
|
+
else
|
83
|
+
instructions.index { |i| i.equal? ins } or raise 'invalid instruction ref'
|
84
|
+
end
|
85
|
+
|
86
|
+
index -= 1 if strictly_before
|
87
|
+
|
88
|
+
# Look for an instruction with a typechange for this variable
|
89
|
+
until index < 0
|
90
|
+
# Is this a relevant typechange for this variable?
|
91
|
+
if instructions[index].result == var && !instructions[index].type_change.nil?
|
92
|
+
# We found a typechange! Return it
|
93
|
+
return instructions[index].type_change
|
94
|
+
end
|
95
|
+
|
96
|
+
# If the instruction is a conditional...
|
97
|
+
if instructions[index].is_a?(ConditionalInstruction)
|
98
|
+
# Find types for this variable in both branches, starting from the end
|
99
|
+
tbi = instructions[index].true_branch.instructions.last
|
100
|
+
fbi = instructions[index].false_branch.instructions.last
|
101
|
+
|
102
|
+
true_t = instructions[index].true_branch.variable_type_at(var, tbi) \
|
103
|
+
|| environment.resolve_type('::NilClass').instantiate
|
104
|
+
false_t = instructions[index].false_branch.variable_type_at(var, fbi) \
|
105
|
+
|| environment.resolve_type('::NilClass').instantiate
|
106
|
+
|
107
|
+
# The type is a union of both sides
|
108
|
+
return Houndstooth::Environment::UnionType.new([true_t, false_t]).simplify
|
109
|
+
end
|
110
|
+
|
111
|
+
# If the instruction is a send, and it has a block...
|
112
|
+
if instructions[index].is_a?(SendInstruction) && instructions[index].method_block
|
113
|
+
# Check type of variable within block
|
114
|
+
block_last_ins = instructions[index].method_block.instructions.last
|
115
|
+
block_t = instructions[index].method_block.variable_type_at(var, block_last_ins)
|
116
|
+
|
117
|
+
# Check type before this send
|
118
|
+
before_t = variable_type_at(var, index - 1)
|
119
|
+
|
120
|
+
# The type is a union of both sides
|
121
|
+
return Houndstooth::Environment::UnionType.new([before_t, block_t]).simplify
|
122
|
+
end
|
123
|
+
|
124
|
+
# Move onto the previous instruction
|
125
|
+
index -= 1
|
126
|
+
end
|
127
|
+
|
128
|
+
# Is there a type refinement for this variable?
|
129
|
+
return type_refinements[var] if type_refinements[var]
|
130
|
+
|
131
|
+
# Check the parent if we have one
|
132
|
+
parent&.block&.variable_type_at(var, parent, strictly_before: true)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Identical to `variable_type_at`, but throws an exception on a missing type.
|
136
|
+
def variable_type_at!(var, ins)
|
137
|
+
variable_type_at(var, ins) or raise "assertion failed: missing type for #{var.to_assembly}"
|
138
|
+
end
|
139
|
+
|
140
|
+
# Gets the return type of this block - i.e. the type of its last variable assignment.
|
141
|
+
# If the type is not known, returns nil.
|
142
|
+
# @return [Type, nil]
|
143
|
+
def return_type
|
144
|
+
variable_type_at(instructions.last.result, instructions.last)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Identical to `return_type`, but throws an exception on a missing type.
|
148
|
+
def return_type!
|
149
|
+
return_type or raise "assertion failed: missing return type"
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the `Variable` instance by its Ruby identifier, for either a local variable
|
153
|
+
# or a method block/definition parameter.
|
154
|
+
# If the variable isn't present in this scope, it will look up scopes until it is found.
|
155
|
+
# If `create` is set, the variable will be created in the closest scope if it doesn't
|
156
|
+
# exist.
|
157
|
+
# If the variable couldn't be found (and `create` is not set), throws an exception,
|
158
|
+
# since this represents a mismatch in Ruby's state and our state.
|
159
|
+
#
|
160
|
+
# @param [String] name
|
161
|
+
# @param [Boolean] create
|
162
|
+
# @param [InstructionBlock, nil] highest_scope
|
163
|
+
# @return [Variable]
|
164
|
+
def resolve_local_variable(name, create:, highest_scope: nil)
|
165
|
+
if !scope.nil?
|
166
|
+
highest_scope = self if highest_scope.nil?
|
167
|
+
|
168
|
+
var = scope.find { |v| v.ruby_identifier == name }
|
169
|
+
return var if !var.nil?
|
170
|
+
|
171
|
+
var = parameters.find { |v| v.ruby_identifier == name }
|
172
|
+
return var if !var.nil?
|
173
|
+
end
|
174
|
+
|
175
|
+
if !parent.nil?
|
176
|
+
parent.block.resolve_local_variable(name, create: create, highest_scope: highest_scope)
|
177
|
+
else
|
178
|
+
# Variable doesn't exist, create in highest scope
|
179
|
+
if create
|
180
|
+
new_var = Variable.new(name)
|
181
|
+
highest_scope.scope << new_var
|
182
|
+
new_var
|
183
|
+
else
|
184
|
+
raise "local variable #{name} doesn't exist in any block scope"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_assembly
|
190
|
+
s = instructions.map { |ins| ins.to_assembly }.join("\n")
|
191
|
+
s = "| #{parameters.map(&:to_assembly).join(", ")} |\n#{s}" if parameters.any?
|
192
|
+
if type_refinements.any?
|
193
|
+
type_refinements.each do |var, type|
|
194
|
+
s = "refine #{var.to_assembly} -> #{type.rbs}\n#{s}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
s
|
199
|
+
end
|
200
|
+
|
201
|
+
def walk(&blk)
|
202
|
+
blk.(self)
|
203
|
+
instructions.each do |instruction|
|
204
|
+
instruction.walk(&blk)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Recusively marks all instructions of this block as const-considered.
|
209
|
+
def mark_const_considered
|
210
|
+
instructions.each do |ins|
|
211
|
+
ins.mark_const_considered
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# A minimal instruction in a sequence, translated from Ruby code.
|
217
|
+
# @abstract
|
218
|
+
class Instruction
|
219
|
+
# The block which this instruction resides in.
|
220
|
+
# @return [InstructionBlock]
|
221
|
+
attr_accessor :block
|
222
|
+
|
223
|
+
# The node which this instruction was derived from.
|
224
|
+
# @return [SemanticNode]
|
225
|
+
attr_accessor :node
|
226
|
+
|
227
|
+
# The variable which the result of this instruction is assigned to.
|
228
|
+
# @return [Variable]
|
229
|
+
attr_accessor :result
|
230
|
+
|
231
|
+
# If this instruction changes the type of the `result` variable, the new type.
|
232
|
+
# To discover the type of a variable, you can traverse previous instructions and look
|
233
|
+
# for the most recent type change (or, for things like conditional branches, combine
|
234
|
+
# the type changes on the two conditional branches).
|
235
|
+
# @return [Type, nil]
|
236
|
+
attr_accessor :type_change
|
237
|
+
|
238
|
+
# If true, this instruction was observed by the interpreter, and could have been
|
239
|
+
# executed given the correct control flow path.
|
240
|
+
# Used for checking the constraints of const-required methods later in the type checker.
|
241
|
+
attr_accessor :const_considered
|
242
|
+
alias const_considered? const_considered
|
243
|
+
|
244
|
+
def initialize(block:, node:, type_change: nil, generate_result: true)
|
245
|
+
@block = block
|
246
|
+
@node = node
|
247
|
+
@result = generate_result ? Variable.new : nil
|
248
|
+
@type_change = type_change
|
249
|
+
@const_considered = false
|
250
|
+
end
|
251
|
+
|
252
|
+
def to_assembly
|
253
|
+
return "#{verbose_assembly_prefix}#{result.to_assembly}" \
|
254
|
+
+ (self.type_change ? " -> #{self.type_change.rbs}" : "") \
|
255
|
+
+ " = " \
|
256
|
+
# Print ????? if not overridden
|
257
|
+
+ (self.class == Instruction ? "?????" : "")
|
258
|
+
end
|
259
|
+
|
260
|
+
def verbose_assembly_prefix
|
261
|
+
if $cli_options[:verbose_instructions]
|
262
|
+
cc_prefix = const_considered ? '[cc] ' : ''
|
263
|
+
loc = node.ast_node.loc.expression
|
264
|
+
"#{cc_prefix}[at #{loc.source_buffer.name}:#{loc.line}:#{loc.column}] "
|
265
|
+
else
|
266
|
+
''
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def walk(&blk)
|
271
|
+
blk.(self)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Marks this instruction, and any child blocks recursively, as const-considered.
|
275
|
+
def mark_const_considered
|
276
|
+
self.const_considered = true
|
277
|
+
end
|
278
|
+
|
279
|
+
protected
|
280
|
+
|
281
|
+
def assembly_indent(str)
|
282
|
+
str.split("\n").map { |line| " #{line}" }.join("\n")
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# An instruction which simply assigns the result variable to another (or the same) variable.
|
287
|
+
# Unlike most instructions, this will not create a new implicit variable - this is used to
|
288
|
+
# generate references to existing variables. For example:
|
289
|
+
#
|
290
|
+
# x = 3
|
291
|
+
# puts x
|
292
|
+
#
|
293
|
+
# The `x` argument needs to generate an instruction, so generates the following:
|
294
|
+
#
|
295
|
+
# $1(x) = 3
|
296
|
+
# $1(x) = existing $1(x)
|
297
|
+
# puts $1(x)
|
298
|
+
#
|
299
|
+
# The `result` and `variable` are different properties so that the `result` can be replaced
|
300
|
+
# by future generations, for example:
|
301
|
+
#
|
302
|
+
# x = 3
|
303
|
+
# y = x
|
304
|
+
#
|
305
|
+
# Would become:
|
306
|
+
#
|
307
|
+
# $1(x) = 3
|
308
|
+
# $2(y) = existing $1(x)
|
309
|
+
#
|
310
|
+
class AssignExistingInstruction < Instruction
|
311
|
+
def initialize(block:, node:, result:, variable:)
|
312
|
+
super(block: block, node: node, generate_result: false)
|
313
|
+
@result = result
|
314
|
+
@variable = variable
|
315
|
+
end
|
316
|
+
|
317
|
+
# The variable to assign to the result.
|
318
|
+
# @return [Variable]
|
319
|
+
attr_accessor :variable
|
320
|
+
|
321
|
+
def to_assembly
|
322
|
+
"#{super}existing #{variable.to_assembly}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# An instruction which assigns a literal value.
|
327
|
+
class LiteralInstruction < Instruction
|
328
|
+
# The constant value being assigned.
|
329
|
+
# @return [Integer, Boolean, Float, String, Symbol, nil]
|
330
|
+
attr_accessor :value
|
331
|
+
|
332
|
+
def initialize(block:, node:, value:)
|
333
|
+
super(block: block, node: node)
|
334
|
+
@value = value
|
335
|
+
end
|
336
|
+
|
337
|
+
def to_assembly
|
338
|
+
"#{super}literal #{value.inspect}"
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# An argument to a `SendInstruction`.
|
343
|
+
# @abstract
|
344
|
+
class Argument
|
345
|
+
# The variable used for the argument's value.
|
346
|
+
# @return [Variable]
|
347
|
+
attr_accessor :variable
|
348
|
+
|
349
|
+
def initialize(variable)
|
350
|
+
@variable = variable
|
351
|
+
end
|
352
|
+
|
353
|
+
def to_assembly
|
354
|
+
variable.to_assembly
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# A standard, singular, positional argument.
|
359
|
+
class PositionalArgument < Argument; end
|
360
|
+
|
361
|
+
# A singular keyword argument.
|
362
|
+
class KeywordArgument < Argument
|
363
|
+
# The keyword.
|
364
|
+
# @return [String]
|
365
|
+
attr_accessor :name
|
366
|
+
|
367
|
+
def initialize(variable, name:)
|
368
|
+
super(variable)
|
369
|
+
@name = name.to_s
|
370
|
+
end
|
371
|
+
|
372
|
+
def to_assembly
|
373
|
+
"#{name}: #{variable.to_assembly}"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# A method call on an object.
|
378
|
+
class SendInstruction < Instruction
|
379
|
+
# TODO: splats
|
380
|
+
|
381
|
+
# The target of the method call.
|
382
|
+
# @return [Variable]
|
383
|
+
attr_accessor :target
|
384
|
+
|
385
|
+
# The name of the method to call.
|
386
|
+
# @return [Symbol]
|
387
|
+
attr_accessor :method_name
|
388
|
+
|
389
|
+
# The arguments to this call.
|
390
|
+
# @return [<Argument>]
|
391
|
+
attr_accessor :arguments
|
392
|
+
|
393
|
+
# The block passed to this call.
|
394
|
+
# @return [InstuctionBlock, nil]
|
395
|
+
attr_accessor :method_block
|
396
|
+
|
397
|
+
# If true, this isn't really a send, but instead a super call. The `target` and
|
398
|
+
# `method_name` of this instance should be ignored.
|
399
|
+
# It'll probably be possible to resolve this later, and make it a standard method call,
|
400
|
+
# since we'll statically know the superclass.
|
401
|
+
# @return [Boolean]
|
402
|
+
attr_accessor :super_call
|
403
|
+
|
404
|
+
# Any type arguments passed alongside this method call. When initially built, these
|
405
|
+
# may be strings, as the instruction builder doesn't have access to the environment to
|
406
|
+
# parse a type. They will be parsed and resolved to types later.
|
407
|
+
# @return [<String, Type>]
|
408
|
+
attr_accessor :type_arguments
|
409
|
+
|
410
|
+
def initialize(block:, node:, target:, method_name:, arguments: nil, method_block: nil, super_call: false, type_arguments: nil)
|
411
|
+
super(block: block, node: node)
|
412
|
+
@target = target
|
413
|
+
@method_name = method_name
|
414
|
+
@arguments = arguments || []
|
415
|
+
@method_block = method_block
|
416
|
+
@super_call = super_call
|
417
|
+
@type_arguments = type_arguments || []
|
418
|
+
end
|
419
|
+
|
420
|
+
def to_assembly
|
421
|
+
args = arguments.map { |a| a.to_assembly }.join(", ")
|
422
|
+
|
423
|
+
if super_call
|
424
|
+
"#{super}send_super #{args}"
|
425
|
+
else
|
426
|
+
"#{super}send #{target.to_assembly} #{method_name} (#{args})"
|
427
|
+
end \
|
428
|
+
+ (type_arguments.any? ? " typeargs [#{
|
429
|
+
type_arguments.map { |t| t.is_a?(String) ? "<unparsed> #{t}" : t.rbs }.join(', ')
|
430
|
+
}]" : '') \
|
431
|
+
+ (method_block ? " block\n#{assembly_indent(method_block.to_assembly)}\nend" : '')
|
432
|
+
end
|
433
|
+
|
434
|
+
def mark_const_considered
|
435
|
+
super
|
436
|
+
|
437
|
+
# If this has a block, mark it as const-considered too
|
438
|
+
method_block&.mark_const_considered
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# An instruction which assigns the value of `self`.
|
443
|
+
class SelfInstruction < Instruction
|
444
|
+
def to_assembly
|
445
|
+
"#{super}self"
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
# Convert something to a string for string or symbol interpolation.
|
450
|
+
# This is not the same as just calling #to_s. When an object is interpolated, Ruby tries
|
451
|
+
# calling #to_s first, and if it returns anything other than a string it uses a default
|
452
|
+
# implementation.
|
453
|
+
# See: https://stackoverflow.com/questions/25488902/what-happens-when-you-use-string-interpolation-in-ruby
|
454
|
+
class ToStringInstruction < Instruction
|
455
|
+
# The target to convert.
|
456
|
+
# @return [Variable]
|
457
|
+
attr_accessor :target
|
458
|
+
|
459
|
+
def initialize(block:, node:, target:)
|
460
|
+
super(block: block, node: node)
|
461
|
+
@target = target
|
462
|
+
end
|
463
|
+
|
464
|
+
def to_assembly
|
465
|
+
"#{super}to_string #{target.to_assembly}"
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
# Execute one of two blocks for a final result, based on a condition.
|
470
|
+
class ConditionalInstruction < Instruction
|
471
|
+
# The condition to evaluate.
|
472
|
+
# @return [Variable]
|
473
|
+
attr_accessor :condition
|
474
|
+
|
475
|
+
# The block to execute if true.
|
476
|
+
# @return [InstructionBlock]
|
477
|
+
attr_accessor :true_branch
|
478
|
+
|
479
|
+
# The block to execute if false.
|
480
|
+
# @return [InstructionBlock]
|
481
|
+
attr_accessor :false_branch
|
482
|
+
|
483
|
+
def initialize(block:, node:, condition:, true_branch:, false_branch:)
|
484
|
+
super(block: block, node: node)
|
485
|
+
@condition = condition
|
486
|
+
@true_branch = true_branch
|
487
|
+
@false_branch = false_branch
|
488
|
+
end
|
489
|
+
|
490
|
+
def to_assembly
|
491
|
+
super +
|
492
|
+
"if #{condition.to_assembly}\n" \
|
493
|
+
"#{assembly_indent(true_branch.to_assembly)}\n" \
|
494
|
+
"else\n" \
|
495
|
+
"#{assembly_indent(false_branch.to_assembly)}\n" \
|
496
|
+
"end"
|
497
|
+
end
|
498
|
+
|
499
|
+
def walk(&blk)
|
500
|
+
super
|
501
|
+
true_branch.walk(&blk)
|
502
|
+
false_branch.walk(&blk)
|
503
|
+
end
|
504
|
+
|
505
|
+
def mark_const_considered
|
506
|
+
super
|
507
|
+
true_branch.mark_const_considered
|
508
|
+
false_branch.mark_const_considered
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
# An access of the base constant value. For example, `::A` accesses `A` from the base.
|
513
|
+
class ConstantBaseAccessInstruction < Instruction
|
514
|
+
def to_assembly
|
515
|
+
"#{super}constbase"
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
# An access of a constant value, class, or module.
|
520
|
+
class ConstantAccessInstruction < Instruction
|
521
|
+
# A variable of the target from which to access this constant.
|
522
|
+
# If `nil`, accesses from the current context.
|
523
|
+
# @return [Variable, nil]
|
524
|
+
attr_accessor :target
|
525
|
+
|
526
|
+
# The name of the constant to access.
|
527
|
+
# @return [Symbol]
|
528
|
+
attr_accessor :name
|
529
|
+
|
530
|
+
# Any type arguments passed alongside this constant access. When initially built, these
|
531
|
+
# may be strings, as the instruction builder doesn't have access to the environment to
|
532
|
+
# parse a type. They will be parsed and resolved to types later.
|
533
|
+
# @return [<String, Type>]
|
534
|
+
attr_accessor :type_arguments
|
535
|
+
|
536
|
+
def initialize(block:, node:, name:, target:, type_arguments: nil)
|
537
|
+
super(block: block, node: node)
|
538
|
+
@name = name
|
539
|
+
@target = target
|
540
|
+
@type_arguments = type_arguments || []
|
541
|
+
end
|
542
|
+
|
543
|
+
def to_assembly
|
544
|
+
"#{super}const #{target&.to_assembly || '(here)'} #{name}" \
|
545
|
+
+ (type_arguments.any? ? " typeargs [#{
|
546
|
+
type_arguments.map { |t| t.is_a?(String) ? "<unparsed> #{t}" : t.rbs }.join(', ')
|
547
|
+
}]" : '')
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
# A definition of a new class or module.
|
552
|
+
class TypeDefinitionInstruction < Instruction
|
553
|
+
# The name of the item being defined.
|
554
|
+
# @return [Symbol]
|
555
|
+
attr_accessor :name
|
556
|
+
|
557
|
+
# The kind of definition: either :class or :module.
|
558
|
+
# @return [Symbol]
|
559
|
+
attr_accessor :kind
|
560
|
+
|
561
|
+
# The constant on which the type is being defined.
|
562
|
+
# If `nil`, defines on the current context.
|
563
|
+
# @return [Variable, nil]
|
564
|
+
attr_accessor :target
|
565
|
+
|
566
|
+
# The superclass of this type, if it's a class.
|
567
|
+
# @return [Variable, nil]
|
568
|
+
attr_accessor :superclass
|
569
|
+
|
570
|
+
# The block to execute to build the type definition.
|
571
|
+
# @return [InstructionBlock]
|
572
|
+
attr_accessor :body
|
573
|
+
|
574
|
+
def initialize(block:, node:, name:, kind:, target:, superclass:, body:)
|
575
|
+
super(block: block, node: node)
|
576
|
+
@name = name
|
577
|
+
@kind = kind
|
578
|
+
@target = target
|
579
|
+
@superclass = superclass
|
580
|
+
@body = body
|
581
|
+
end
|
582
|
+
|
583
|
+
def to_assembly
|
584
|
+
super +
|
585
|
+
"typedef #{kind} #{name} on #{target&.to_assembly || '(here)'}#{superclass ? " super #{superclass.to_assembly}" : ''}\n" \
|
586
|
+
"#{assembly_indent(body.to_assembly)}\n" \
|
587
|
+
"end"
|
588
|
+
end
|
589
|
+
|
590
|
+
def walk(&blk)
|
591
|
+
super
|
592
|
+
body.walk(&blk)
|
593
|
+
end
|
594
|
+
|
595
|
+
def mark_const_considered
|
596
|
+
super
|
597
|
+
body.mark_const_considered
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
# A definition of a new method.
|
602
|
+
class MethodDefinitionInstruction < Instruction
|
603
|
+
# The name of the item being defined.
|
604
|
+
# @return [Symbol]
|
605
|
+
attr_accessor :name
|
606
|
+
|
607
|
+
# The target on which the method is being defined.
|
608
|
+
# If `nil`, the method is defined on instances of `self`.
|
609
|
+
# @return [Variable, nil]
|
610
|
+
attr_accessor :target
|
611
|
+
|
612
|
+
# The body of this method definition.
|
613
|
+
# @return [InstructionBlock]
|
614
|
+
attr_accessor :body
|
615
|
+
|
616
|
+
def initialize(block:, node:, name:, target:, body:)
|
617
|
+
super(block: block, node: node)
|
618
|
+
@name = name
|
619
|
+
@target = target
|
620
|
+
@body = body
|
621
|
+
end
|
622
|
+
|
623
|
+
def to_assembly
|
624
|
+
super +
|
625
|
+
"methoddef #{name} on #{target&.to_assembly || '(implicit)'}\n" \
|
626
|
+
"#{assembly_indent(body.to_assembly)}\n" \
|
627
|
+
"end"
|
628
|
+
end
|
629
|
+
|
630
|
+
def walk(&blk)
|
631
|
+
super
|
632
|
+
body.walk(&blk)
|
633
|
+
end
|
634
|
+
|
635
|
+
def mark_const_considered
|
636
|
+
# We _don't_ walk into the body here! The interpreter won't visit the body
|
637
|
+
# (TODO: What about when the user can define their own const-required methods?)
|
638
|
+
super
|
639
|
+
end
|
640
|
+
|
641
|
+
# Given a self type, resolves the method in the environment which is being defined here,
|
642
|
+
# and the type on which the method is defined.
|
643
|
+
def resolve_method_and_type(self_type, env)
|
644
|
+
# Look up this method in the environment
|
645
|
+
# Where's it defined? The only allowed explicit target currently is `self`, so if
|
646
|
+
# that's given...
|
647
|
+
if !target.nil?
|
648
|
+
# ...then it's defined on `self`
|
649
|
+
inner_self_type = self_type
|
650
|
+
method = inner_self_type.resolve_instance_method(name, env)
|
651
|
+
else
|
652
|
+
# Otherwise it's defined on the instance of `self`
|
653
|
+
inner_self_type = env.resolve_type(self_type.type.uneigen).instantiate
|
654
|
+
method = inner_self_type.resolve_instance_method(name, env)
|
655
|
+
end
|
656
|
+
|
657
|
+
[method, inner_self_type]
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
class InstanceVariableReadInstruction < Instruction
|
662
|
+
# The name of the instance variable to be read.
|
663
|
+
# @return [String]
|
664
|
+
attr_accessor :name
|
665
|
+
|
666
|
+
def initialize(block:, node:, name:)
|
667
|
+
super(block: block, node: node)
|
668
|
+
@name = name
|
669
|
+
end
|
670
|
+
|
671
|
+
def to_assembly
|
672
|
+
super +
|
673
|
+
"@read #{name}"
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
class InstanceVariableWriteInstruction < Instruction
|
678
|
+
# The name of the instance variable to be written to.
|
679
|
+
# @return [String]
|
680
|
+
attr_accessor :name
|
681
|
+
|
682
|
+
# The variable which will be written into the instance variable.
|
683
|
+
# @return [Variable]
|
684
|
+
attr_accessor :value
|
685
|
+
|
686
|
+
def initialize(block:, node:, name:, value:)
|
687
|
+
super(block: block, node: node)
|
688
|
+
@name = name
|
689
|
+
@value = value
|
690
|
+
end
|
691
|
+
|
692
|
+
def to_assembly
|
693
|
+
super +
|
694
|
+
"@write #{name} #{value.to_assembly}"
|
695
|
+
end
|
696
|
+
end
|
697
|
+
end
|
698
|
+
end
|