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