houndstooth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +49 -0
- data/README.md +99 -0
- data/bin/houndstooth.rb +183 -0
- data/fuzz/cases/x.rb +8 -0
- data/fuzz/cases/y.rb +8 -0
- data/fuzz/cases/z.rb +22 -0
- data/fuzz/ruby.dict +64 -0
- data/fuzz/run +21 -0
- data/lib/houndstooth/environment/builder.rb +260 -0
- data/lib/houndstooth/environment/type_parser.rb +149 -0
- data/lib/houndstooth/environment/types/basic/type.rb +85 -0
- data/lib/houndstooth/environment/types/basic/type_instance.rb +54 -0
- data/lib/houndstooth/environment/types/compound/union_type.rb +72 -0
- data/lib/houndstooth/environment/types/defined/base_defined_type.rb +23 -0
- data/lib/houndstooth/environment/types/defined/defined_type.rb +137 -0
- data/lib/houndstooth/environment/types/defined/pending_defined_type.rb +14 -0
- data/lib/houndstooth/environment/types/method/method.rb +79 -0
- data/lib/houndstooth/environment/types/method/method_type.rb +144 -0
- data/lib/houndstooth/environment/types/method/parameters.rb +53 -0
- data/lib/houndstooth/environment/types/method/special_constructor_method.rb +15 -0
- data/lib/houndstooth/environment/types/special/instance_type.rb +9 -0
- data/lib/houndstooth/environment/types/special/self_type.rb +9 -0
- data/lib/houndstooth/environment/types/special/type_parameter_placeholder.rb +38 -0
- data/lib/houndstooth/environment/types/special/untyped_type.rb +11 -0
- data/lib/houndstooth/environment/types/special/void_type.rb +12 -0
- data/lib/houndstooth/environment/types.rb +3 -0
- data/lib/houndstooth/environment.rb +74 -0
- data/lib/houndstooth/errors.rb +53 -0
- data/lib/houndstooth/instructions.rb +698 -0
- data/lib/houndstooth/interpreter/const_internal.rb +148 -0
- data/lib/houndstooth/interpreter/objects.rb +142 -0
- data/lib/houndstooth/interpreter/runtime.rb +309 -0
- data/lib/houndstooth/interpreter.rb +7 -0
- data/lib/houndstooth/semantic_node/control_flow.rb +218 -0
- data/lib/houndstooth/semantic_node/definitions.rb +253 -0
- data/lib/houndstooth/semantic_node/identifiers.rb +308 -0
- data/lib/houndstooth/semantic_node/keywords.rb +45 -0
- data/lib/houndstooth/semantic_node/literals.rb +226 -0
- data/lib/houndstooth/semantic_node/operators.rb +126 -0
- data/lib/houndstooth/semantic_node/parameters.rb +108 -0
- data/lib/houndstooth/semantic_node/send.rb +349 -0
- data/lib/houndstooth/semantic_node/super.rb +12 -0
- data/lib/houndstooth/semantic_node.rb +119 -0
- data/lib/houndstooth/stdlib.rb +6 -0
- data/lib/houndstooth/type_checker.rb +462 -0
- data/lib/houndstooth.rb +53 -0
- data/spec/ast_to_node_spec.rb +889 -0
- data/spec/environment_spec.rb +323 -0
- data/spec/instructions_spec.rb +291 -0
- data/spec/integration_spec.rb +785 -0
- data/spec/interpreter_spec.rb +170 -0
- data/spec/self_spec.rb +7 -0
- data/spec/spec_helper.rb +50 -0
- data/test/ruby_interpreter_test.rb +162 -0
- data/types/stdlib.htt +170 -0
- metadata +110 -0
@@ -0,0 +1,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
|