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,148 @@
1
+ module Houndstooth::Interpreter
2
+ # Provides definitions for `#!const internal` methods in the standard library.
3
+ class ConstInternal
4
+ def initialize(env:)
5
+ @method_definitions = {}
6
+
7
+ # Initialize adding methods
8
+ add = ->(this, other, **_) do
9
+ InterpreterObject.from_value(
10
+ value: this.unwrap_primitive_value + other.unwrap_primitive_value,
11
+ env: env,
12
+ )
13
+ end
14
+ ['::Numeric', '::Integer', '::Float', '::String'].each do |t|
15
+ @method_definitions[env.resolve_type(t).resolve_instance_method(:+, env)] = add
16
+ end
17
+
18
+ # to_sym
19
+ @method_definitions[env.resolve_type('::String').resolve_instance_method(:to_sym, env)] =
20
+ ->(this, **_) do
21
+ InterpreterObject.from_value(value: this.unwrap_primitive_value.to_sym, env: env)
22
+ end
23
+
24
+ # times
25
+ @method_definitions[env.resolve_type('::Integer').resolve_instance_method(:times, env)] =
26
+ ->(this, call_block:, **_) do
27
+ this.unwrap_primitive_value.times do |i|
28
+ call_block.([InterpreterObject.from_value(value: i, env: env)])
29
+ end
30
+
31
+ this
32
+ end
33
+
34
+ # Array.new
35
+ @method_definitions[env.resolve_type('::Array').eigen.resolve_instance_method(:new, env)] =
36
+ ->(this, **_) do
37
+ InterpreterObject.from_value(value: [], env: env)
38
+ end
39
+
40
+ # Array#<<
41
+ @method_definitions[env.resolve_type('::Array').resolve_instance_method(:<<, env)] =
42
+ ->(this, item, **_) do
43
+ this.unwrap_primitive_value << item
44
+ this
45
+ end
46
+
47
+ # Array#each
48
+ @method_definitions[env.resolve_type('::Array').resolve_instance_method(:each, env)] =
49
+ ->(this, call_block:, **_) do
50
+ items = this.unwrap_primitive_value
51
+ items.each do |item|
52
+ call_block.([item])
53
+ end
54
+
55
+ this
56
+ end
57
+
58
+ # puts and print
59
+ kernel = env.resolve_type('::Kernel').eigen
60
+ @method_definitions[kernel.resolve_instance_method(:puts, env)] = ->(_, obj, **_) do
61
+ $const_printed = true
62
+ puts obj.ruby_inspect
63
+ end
64
+ @method_definitions[kernel.resolve_instance_method(:print, env)] = ->(_, obj, **_) do
65
+ $const_printed = true
66
+ print obj.ruby_inspect
67
+ end
68
+
69
+ # attr_reader
70
+ @method_definitions[env.resolve_type('::Class').eigen.resolve_instance_method(:attr_reader, env)] =
71
+ ->(this, name, type_arguments:, **_) do
72
+ t = type_arguments['T']
73
+ t = t.substitute_type_parameters(nil, type_arguments)
74
+
75
+ env.resolve_type(this.type.uneigen).instance_methods << Houndstooth::Environment::Method.new(
76
+ name.unwrap_primitive_value,
77
+ [
78
+ Houndstooth::Environment::MethodType.new(
79
+ return_type: t,
80
+ )
81
+ ]
82
+ )
83
+ InterpreterObject.from_value(value: nil, env: env)
84
+ end
85
+
86
+ # attr_writer
87
+ @method_definitions[env.resolve_type('::Class').eigen.resolve_instance_method(:attr_writer, env)] =
88
+ ->(this, name, type_arguments:, **_) do
89
+ t = type_arguments['T']
90
+ t = t.substitute_type_parameters(nil, type_arguments)
91
+
92
+ env.resolve_type(this.type.uneigen).instance_methods << Houndstooth::Environment::Method.new(
93
+ "#{name.unwrap_primitive_value}=".to_sym,
94
+ [
95
+ Houndstooth::Environment::MethodType.new(
96
+ positional: [
97
+ Houndstooth::Environment::PositionalParameter.new(:value, t)
98
+ ],
99
+ )
100
+ ]
101
+ )
102
+ InterpreterObject.from_value(value: nil, env: env)
103
+ end
104
+
105
+ # define_method
106
+ @method_definitions[env.resolve_type('::Class').eigen.resolve_instance_method(:define_method, env)] =
107
+ ->(this, name, type_arguments:, **_) do
108
+ # Get all argument types and return type
109
+ arg_types = (type_arguments.length - 1).times.map do |i|
110
+ type_arguments["A#{i + 1}"].substitute_type_parameters(nil, type_arguments)
111
+ end
112
+ return_type = type_arguments['R'].substitute_type_parameters(nil, type_arguments)
113
+
114
+ # Create method
115
+ env.resolve_type(this.type.uneigen).instance_methods << Houndstooth::Environment::Method.new(
116
+ name.unwrap_primitive_value,
117
+ [
118
+ Houndstooth::Environment::MethodType.new(
119
+ positional: arg_types.map.with_index do |t, i|
120
+ Houndstooth::Environment::PositionalParameter.new(
121
+ "__anon_param_#{i}",
122
+ t,
123
+ )
124
+ end,
125
+ return_type: return_type,
126
+ )
127
+ ]
128
+ )
129
+ InterpreterObject.from_value(value: nil, env: env)
130
+ end
131
+
132
+ # Various env-changing methods are a no-op for now
133
+ nil_method = ->(*_, **_) { InterpreterObject.from_value(value: nil, env: env) }
134
+ [:private, :protected].each do |m|
135
+ @method_definitions[env.resolve_type('::Class').eigen.resolve_instance_method(m, env)] = nil_method
136
+ end
137
+ end
138
+
139
+ # @return [Environment]
140
+ attr_accessor :env
141
+
142
+ # A hash of the method definitions. Access with the environment's method reference, call the
143
+ # given proc with the self value and argument values, and it will return a new object as the
144
+ # result.
145
+ # @return [{Method => Proc}]
146
+ attr_accessor :method_definitions
147
+ end
148
+ end
@@ -0,0 +1,142 @@
1
+ module Houndstooth::Interpreter
2
+ # An instance of a defined type.
3
+ class InterpreterObject
4
+ def initialize(type:, env:, primitive_value: nil)
5
+ @type = type
6
+ @env = env
7
+ @instance_variables = {}
8
+ @primitive_value = primitive_value || [false, nil]
9
+ end
10
+
11
+ # The type which this is an instance of.
12
+ # @return [DefinedType]
13
+ attr_accessor :type
14
+
15
+ # The type environment in which this object exists.
16
+ # @return [Environment]
17
+ attr_accessor :env
18
+
19
+ # The instance variables on this instance.
20
+ # @return [{String => InterpreterObject}]
21
+ attr_accessor :instance_variables
22
+
23
+ # The primitive value on this instance, if it has one.
24
+ # A tuple of the form [present, value].
25
+ # @return [(Boolean, Object)]
26
+ attr_accessor :primitive_value
27
+
28
+ def unwrap_primitive_value
29
+ present, value = primitive_value
30
+
31
+ default =
32
+ case type
33
+ when env.resolve_type('::Integer')
34
+ 0
35
+ when env.resolve_type('::String')
36
+ ''
37
+ when env.resolve_type('::Boolean')
38
+ false
39
+ when env.resolve_type('::Float')
40
+ 0.0
41
+ when env.resolve_type('::Symbol')
42
+ :""
43
+ when env.resolve_type('::Array')
44
+ []
45
+
46
+ # These always have particular values, so just return immediately and ignore
47
+ # whatever the value might be set to
48
+ when env.resolve_type('::NilClass')
49
+ return nil
50
+ when env.resolve_type('::FalseClass')
51
+ return false
52
+ when env.resolve_type('::TrueClass')
53
+ return true
54
+
55
+ else
56
+ raise 'internal error: tried to unwrapp primitive where type isn\'t primitive'
57
+ end
58
+
59
+ if present
60
+ value
61
+ else
62
+ default
63
+ end
64
+ end
65
+
66
+ def self.from_value(value:, env:)
67
+ case value
68
+ when Integer
69
+ t, prim = env.resolve_type('Integer'), [true, value]
70
+ when String
71
+ t, prim = env.resolve_type('String'), [true, value]
72
+ when Float
73
+ t, prim = env.resolve_type('Float'), [true, value]
74
+ when Symbol
75
+ t, prim = env.resolve_type('Symbol'), [true, value]
76
+ when Array
77
+ t, prim = env.resolve_type('Array'), [true, value]
78
+ when nil
79
+ t = env.resolve_type('NilClass')
80
+ when false
81
+ t = env.resolve_type('FalseClass')
82
+ when true
83
+ t = env.resolve_type('TrueClass')
84
+ else
85
+ raise 'internal error: could not convert primitive into instance'
86
+ end
87
+
88
+ new(
89
+ type: t,
90
+ env: env,
91
+ primitive_value: prim,
92
+ )
93
+ end
94
+
95
+ def falsey?
96
+ type == env.resolve_type('NilClass') || type == env.resolve_type('FalseClass')
97
+ end
98
+
99
+ def truthy?
100
+ !falsey?
101
+ end
102
+
103
+ def inspect
104
+ result = "#<interpreter object: #{type.rbs}"
105
+ if primitive_value[0]
106
+ result += " #{unwrap_primitive_value}"
107
+ else
108
+ if instance_variables.any?
109
+ result += " #{instance_variables.map { |var, val| "#{var}=#{val}" }}"
110
+ end
111
+ end
112
+ result += ">"
113
+
114
+ result
115
+ end
116
+ alias to_s inspect
117
+
118
+ def ruby_inspect
119
+ # Special cases
120
+ if type == env.resolve_type('NilClass')
121
+ return ''
122
+ elsif type == env.resolve_type('TrueClass')
123
+ return 'true'
124
+ elsif type == env.resolve_type('FalseClass')
125
+ return 'false'
126
+ end
127
+
128
+ if primitive_value.first
129
+ primitive_value = unwrap_primitive_value
130
+ if primitive_value.is_a?(Array)
131
+ "[" + primitive_value.map { |value| value.ruby_inspect }.join(", ") + "]"
132
+ else
133
+ primitive_value.to_s
134
+ end
135
+ else
136
+ # Don't know how to handle this, call back to #<interpreter object: ...>
137
+ # representation
138
+ inspect
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,309 @@
1
+ require_relative 'const_internal'
2
+
3
+ module Houndstooth::Interpreter
4
+ class Runtime
5
+ def initialize(env:)
6
+ @variables = {}
7
+ @env = env
8
+ @const_internal = ConstInternal.new(env: env)
9
+ end
10
+
11
+ # @return [{Variable => InterpreterObject}]
12
+ attr_accessor :variables
13
+
14
+ # @return [Environment]
15
+ attr_accessor :env
16
+
17
+ # @return [ConstInternal]
18
+ attr_accessor :const_internal
19
+
20
+ def execute_block(block, lexical_context:, self_type:, self_object:, type_arguments:)
21
+ block.instructions.each do |inst|
22
+ execute_instruction(inst, lexical_context: lexical_context, self_type: self_type, self_object: self_object, type_arguments: type_arguments)
23
+ end
24
+ end
25
+
26
+ def execute_instruction(ins, lexical_context:, self_type:, self_object:, type_arguments:)
27
+ case ins
28
+ when Houndstooth::Instructions::ConstantBaseAccessInstruction
29
+ # There's no object we can use to represent the constant base, so let's go with a
30
+ # special symbol - it'll make it very obvious if it's being used somewhere it
31
+ # shouldn't
32
+ result_value = :constant_base
33
+
34
+ when Houndstooth::Instructions::ConstantAccessInstruction
35
+ if ins.target
36
+ # TODO: will only work with types, not actual constants
37
+ target_value = variables[ins.target]
38
+ if target_value == :constant_base
39
+ target = Houndstooth::Environment::BaseDefinedType.new
40
+ else
41
+ target = target_value.type
42
+ end
43
+ else
44
+ target = lexical_context
45
+ end
46
+ resolved = env.resolve_type(ins.name.to_s, type_context: env.resolve_type(target.uneigen))
47
+
48
+ if resolved.nil?
49
+ Houndstooth::Errors::Error.new(
50
+ "No constant named `#{ins.name}` on `#{target.rbs}`",
51
+ [[ins.node.ast_node.loc.expression, "no such constant"]]
52
+ ).push
53
+
54
+ # Abandon
55
+ variables[ins.result] = InterpreterObject.from_value(value: nil, env: env)
56
+ return
57
+ end
58
+
59
+ result_value = InterpreterObject.new(type: resolved.eigen, env: env)
60
+
61
+ when Houndstooth::Instructions::TypeDefinitionInstruction
62
+ if ins.target
63
+ Houndstooth::Errors::Error.new(
64
+ "namespace targets visited by interpreter are not supported",
65
+ [[node.ast_node.loc.expression, 'break up "class A::B" into separate definitions']],
66
+ ).push
67
+ end
68
+
69
+ # Because it can't be overridden (not yet supported above), we know that the type
70
+ # is going to be defined on the lexical context
71
+ type_being_defined = env.resolve_type("#{lexical_context.uneigen}::#{ins.name}").eigen
72
+ type_being_defined_inst = type_being_defined.instantiate
73
+
74
+ execute_block(
75
+ ins.body,
76
+ lexical_context: type_being_defined,
77
+ self_type: type_being_defined_inst,
78
+ self_object: InterpreterObject.new(
79
+ type: type_being_defined,
80
+ env: env,
81
+ ),
82
+ type_arguments: type_arguments,
83
+ )
84
+
85
+ result_value = variables[ins.body.instructions.last.result]
86
+
87
+ when Houndstooth::Instructions::MethodDefinitionInstruction
88
+ method, _ = ins.resolve_method_and_type(self_type, env)
89
+
90
+ # Associate instruction block with environment
91
+ method.instruction_block = ins.body
92
+
93
+ # Return name of defined method
94
+ result_value = InterpreterObject.from_value(value: ins.name, env: env)
95
+
96
+ when Houndstooth::Instructions::LiteralInstruction
97
+ result_value = InterpreterObject.from_value(value: ins.value, env: env)
98
+
99
+ when Houndstooth::Instructions::SelfInstruction
100
+ result_value = self_object
101
+
102
+ when Houndstooth::Instructions::AssignExistingInstruction
103
+ result_value = variables[ins.variable]
104
+
105
+ when Houndstooth::Instructions::SendInstruction
106
+ target = variables[ins.target]
107
+ meth = target.type.resolve_instance_method(ins.method_name, env)
108
+ args = ins.arguments.map do |arg|
109
+ if arg.is_a?(Houndstooth::Instructions::PositionalArgument)
110
+ variables[arg.variable]
111
+ else
112
+ raise 'internal error: unimplemented arg should\'ve been caught earlier than interpreter'
113
+ end
114
+ end
115
+
116
+ if meth.nil?
117
+ Houndstooth::Errors::Error.new(
118
+ "`#{target}` has no method named `#{ins.method_name}`",
119
+ [[ins.node.ast_node.loc.expression, 'no such method']],
120
+ ).push
121
+
122
+ # Abandon
123
+ variables[ins.result] = InterpreterObject.from_value(value: nil, env: env)
124
+ return
125
+ end
126
+
127
+ if meth.const.nil?
128
+ Houndstooth::Errors::Error.new(
129
+ "Cannot call non-const method `#{meth.name}` on `#{target}` from const context",
130
+ [[ins.node.ast_node.loc.expression, 'call is not const']],
131
+ ).push
132
+
133
+ # Abandon
134
+ variables[ins.result] = InterpreterObject.from_value(value: nil, env: env)
135
+ return
136
+ end
137
+
138
+ # Parse type arguments
139
+ type_args = Houndstooth::Environment::TypeParser.parse_type_arguments(env, ins.type_arguments, type_arguments)
140
+ .map { |t| t.substitute_type_parameters(nil, type_arguments) }
141
+
142
+ # Crudely find the best signature based on the number of type parameters - this is
143
+ # extremely wonky!
144
+ type_params = meth.signatures.find { |sig| sig.type_parameters.length == type_args.length }&.type_parameters
145
+ raise 'internal error: no const signature matching number of type parameters' if type_params.nil?
146
+ type_arguments = type_arguments.merge(type_params.zip(type_args).to_h)
147
+
148
+ if meth.const_internal?
149
+ # Look up, call, and set result
150
+ begin
151
+ definition = const_internal.method_definitions[meth]
152
+ raise "internal error: `#{meth.name}` on `#{target}` is missing const-internal definition" if definition.nil?
153
+
154
+ # It's OK for const-internal definitions to just execute the block directly
155
+ # on this runtime, since the instruction generator will have sorted out any
156
+ # local scoping with overlapping names for us
157
+ result_value = definition.(
158
+ target, *args,
159
+ type_arguments: type_arguments,
160
+ call_block: ->(args) do
161
+ # `call_method`'s arguments expect Variables... but we don't have
162
+ # any here! The value came from a magical internal source.
163
+ # Instead, we can just make up some new ones; it'll be fine!
164
+ args = args.map do |arg|
165
+ var = Houndstooth::Instructions::Variable.new
166
+ variables[var] = arg
167
+ Houndstooth::Instructions::PositionalArgument.new(var)
168
+ end
169
+
170
+ call_method(
171
+ block: ins.method_block,
172
+ arguments: args,
173
+ loc: ins.node.ast_node.loc,
174
+ type_arguments: type_arguments,
175
+ target: self_object,
176
+ lexical_context: lexical_context,
177
+ )
178
+ end,
179
+ )
180
+ rescue => e
181
+ raise e if $cli_options[:fatal_interpreter]
182
+
183
+ Houndstooth::Errors::Error.new(
184
+ "Interpreter runtime error: #{e}\n " \
185
+ "(run with --fatal-interpreter to exit with backtrace on first error)",
186
+ [[ins.node.ast_node.loc.expression, 'occurred within this call']],
187
+ ).push
188
+
189
+ # Abandon
190
+ variables[ins.result] = InterpreterObject.from_value(value: nil, env: env)
191
+ return
192
+ end
193
+ else
194
+ # Check there's actually a definition
195
+ if meth.instruction_block.nil?
196
+ Houndstooth::Errors::Error.new(
197
+ "`#{meth.name}` on `#{target}` has not been defined yet",
198
+ [[ins.node.ast_node.loc.expression, 'no definition of method called here']],
199
+ ).push
200
+
201
+ # Abandon
202
+ variables[ins.result] = InterpreterObject.from_value(value: nil, env: env)
203
+ return
204
+ end
205
+
206
+ call_result = call_method(
207
+ block: meth.instruction_block,
208
+ arguments: ins.arguments,
209
+ loc: ins.node.ast_node.loc,
210
+ type_arguments: type_arguments,
211
+ target: target,
212
+ lexical_context: lexical_context,
213
+ )
214
+ if call_result == false
215
+ # Abandon
216
+ variables[ins.result] = InterpreterObject.from_value(value: nil, env: env)
217
+ else
218
+ result_value = call_result
219
+ end
220
+ end
221
+
222
+ when Houndstooth::Instructions::ConditionalInstruction
223
+ condition = variables[ins.condition]
224
+ if condition.truthy?
225
+ execute_block(ins.true_branch, lexical_context: lexical_context, self_type: self_type, self_object: self_object, type_arguments: type_arguments)
226
+ result_value = variables[ins.true_branch.instructions.last.result]
227
+ else
228
+ execute_block(ins.false_branch, lexical_context: lexical_context, self_type: self_type, self_object: self_object, type_arguments: type_arguments)
229
+ result_value = variables[ins.false_branch.instructions.last.result]
230
+ end
231
+
232
+ when Houndstooth::Instructions::ToStringInstruction
233
+ obj = variables[ins.target]
234
+ result_value = obj.ruby_inspect
235
+ result_value = InterpreterObject.from_value(value: result_value, env: env)
236
+ else
237
+ raise "internal error: don't know how to interpret #{ins.class.name}"
238
+ end
239
+
240
+ variables[ins.result] = result_value
241
+ end
242
+
243
+ # Finds instructions at the very top level of a program to execute, and executes them. Not
244
+ # all instructions in the given block will be executed.
245
+ def execute_from_top_level(block)
246
+ # Run the interpreter on top-level definitions
247
+ # TODO: ...and also particular marked top-level sends (see notes in Obsidian)
248
+ # Because we don't allow definitions to have targets yet, this is fine - any nodes used to
249
+ # build up to the definition do not matter
250
+ instructions_to_interpret = []
251
+ block.instructions.each do |ins|
252
+ if ins.is_a?(Houndstooth::Instructions::TypeDefinitionInstruction)
253
+ instructions_to_interpret << ins
254
+ end
255
+ end
256
+ instructions_to_interpret.each do |ins|
257
+ ins.mark_const_considered
258
+ execute_instruction(
259
+ ins,
260
+ self_object: nil, # TODO
261
+ self_type: nil, # TODO
262
+ lexical_context: Houndstooth::Environment::BaseDefinedType.new,
263
+ type_arguments: {},
264
+ )
265
+ end
266
+ end
267
+
268
+ def call_method(block:, arguments:, loc:, type_arguments:, target:, lexical_context:)
269
+ # Create a new runtime frame to call this method
270
+ child_runtime = Runtime.new(env: env)
271
+
272
+ # Populate arguments
273
+ if arguments.length != block.parameters.length
274
+ Houndstooth::Errors::Error.new(
275
+ "Wrong number of arguments to interpreter method call",
276
+ [[loc.expression, "got #{arguments.length}, expected #{block.parameters.length}"]],
277
+ ).push
278
+
279
+ # Abandon
280
+ return false
281
+ end
282
+
283
+ arguments.zip(block.parameters).each do |arg, param_var|
284
+ unless arg.is_a?(Houndstooth::Instructions::PositionalArgument)
285
+ Houndstooth::Errors::Error.new(
286
+ "Unsupported argument type used - only positional arguments are supported",
287
+ [[loc.expression, 'unsupported']],
288
+ ).push
289
+
290
+ # Abandon
291
+ return false
292
+ end
293
+
294
+ # Inject variables into the runtime to set
295
+ child_runtime.variables[param_var] = variables[arg.variable]
296
+ end
297
+
298
+ # Execute body and set return value
299
+ child_runtime.execute_block(
300
+ block,
301
+ lexical_context: lexical_context,
302
+ self_object: target,
303
+ self_type: target.type,
304
+ type_arguments: type_arguments,
305
+ )
306
+ child_runtime.variables[block.instructions.last.result]
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,7 @@
1
+ module Houndstooth
2
+ module Interpreter; end
3
+ end
4
+
5
+ require_relative 'interpreter/objects'
6
+ require_relative 'interpreter/runtime'
7
+ require_relative 'interpreter/const_internal'