houndstooth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +11 -0
  6. data/Gemfile.lock +49 -0
  7. data/README.md +99 -0
  8. data/bin/houndstooth.rb +183 -0
  9. data/fuzz/cases/x.rb +8 -0
  10. data/fuzz/cases/y.rb +8 -0
  11. data/fuzz/cases/z.rb +22 -0
  12. data/fuzz/ruby.dict +64 -0
  13. data/fuzz/run +21 -0
  14. data/lib/houndstooth/environment/builder.rb +260 -0
  15. data/lib/houndstooth/environment/type_parser.rb +149 -0
  16. data/lib/houndstooth/environment/types/basic/type.rb +85 -0
  17. data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
  18. data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
  19. data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
  20. data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
  21. data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
  22. data/lib/houndstooth/environment/types/method/method.rb +79 -0
  23. data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
  24. data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
  25. data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
  26. data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
  27. data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
  28. data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
  29. data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
  30. data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
  31. data/lib/houndstooth/environment/types.rb +3 -0
  32. data/lib/houndstooth/environment.rb +74 -0
  33. data/lib/houndstooth/errors.rb +53 -0
  34. data/lib/houndstooth/instructions.rb +698 -0
  35. data/lib/houndstooth/interpreter/const_internal.rb +148 -0
  36. data/lib/houndstooth/interpreter/objects.rb +142 -0
  37. data/lib/houndstooth/interpreter/runtime.rb +309 -0
  38. data/lib/houndstooth/interpreter.rb +7 -0
  39. data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
  40. data/lib/houndstooth/semantic_node/definitions.rb +253 -0
  41. data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
  42. data/lib/houndstooth/semantic_node/keywords.rb +45 -0
  43. data/lib/houndstooth/semantic_node/literals.rb +226 -0
  44. data/lib/houndstooth/semantic_node/operators.rb +126 -0
  45. data/lib/houndstooth/semantic_node/parameters.rb +108 -0
  46. data/lib/houndstooth/semantic_node/send.rb +349 -0
  47. data/lib/houndstooth/semantic_node/super.rb +12 -0
  48. data/lib/houndstooth/semantic_node.rb +119 -0
  49. data/lib/houndstooth/stdlib.rb +6 -0
  50. data/lib/houndstooth/type_checker.rb +462 -0
  51. data/lib/houndstooth.rb +53 -0
  52. data/spec/ast_to_node_spec.rb +889 -0
  53. data/spec/environment_spec.rb +323 -0
  54. data/spec/instructions_spec.rb +291 -0
  55. data/spec/integration_spec.rb +785 -0
  56. data/spec/interpreter_spec.rb +170 -0
  57. data/spec/self_spec.rb +7 -0
  58. data/spec/spec_helper.rb +50 -0
  59. data/test/ruby_interpreter_test.rb +162 -0
  60. data/types/stdlib.htt +170 -0
  61. metadata +110 -0
@@ -0,0 +1,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