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,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
|