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