taoism 1.0.1 → 1.0.2
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 +4 -4
- data/bin/tao +46 -1
- data/bin/taoism +5 -0
- data/lib/taoism/console.rb +72 -0
- data/lib/taoism/environment.rb +44 -0
- data/lib/taoism/evaluator.rb +385 -0
- data/lib/taoism/lexer.rb +271 -0
- data/lib/taoism/nodes.rb +53 -0
- data/lib/taoism/parser.rb +1324 -0
- data/lib/taoism/runtime.rb +45 -0
- data/lib/taoism/token.rb +7 -0
- data/lib/taoism/token_type.rb +62 -0
- data/lib/taoism/types.rb +20 -0
- data/lib/taoism/version.rb +3 -0
- data/lib/taoism.rb +11 -1
- metadata +35 -16
- data/lib/tao/lexer/scanner/position.rb +0 -7
- data/lib/tao/lexer/scanner/position_tracking.rb +0 -19
- data/lib/tao/lexer/scanner.rb +0 -50
- data/lib/tao/lexer/tokenizer.rb +0 -17
- data/lib/tao/lexer.rb +0 -6
- data/lib/tao/token/types.rb +0 -34
- data/lib/tao/token.rb +0 -8
- data/lib/tao/version.rb +0 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3888b31accff3d78029eaba044b83ef112863b85b2b4c6ee84865f3af918330
|
|
4
|
+
data.tar.gz: ea0fbbb5539fbda01d5f2c40eb8f2a1067e451c6a7cc9c0b16e47c3b981080d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5569c903178515e6f8658be27acbb1868b7517422111bf8a30f0c441e83879d531c4ab52067e1b62b0f7ac22be6d61008fee4021b227d419cd323f5c09584e85
|
|
7
|
+
data.tar.gz: 76776999d05166456d75b43bbf0d62b5ebad747a0271bb17c867b181dbd0d9aef90fd9945b1b74c7388ed3e595c84d440fea7d6370a9e9c40d0bcf5c6aa29045
|
data/bin/tao
CHANGED
|
@@ -1,2 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
require '
|
|
2
|
+
require 'taoism'
|
|
3
|
+
|
|
4
|
+
source = if $stdin.tty?
|
|
5
|
+
files = ARGV.reject { _1.start_with?('-') }
|
|
6
|
+
|
|
7
|
+
if files.empty?
|
|
8
|
+
puts "Tao is a toy language."
|
|
9
|
+
exit 0
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
filepath = files.first
|
|
13
|
+
|
|
14
|
+
if files[1]
|
|
15
|
+
$stderr.puts "skipped: #{files[1..].join(', ')}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if File.exist?(filepath)
|
|
19
|
+
File.read(filepath)
|
|
20
|
+
else
|
|
21
|
+
$stderr.puts "No such file - #{filepath}"
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
ARGF.read
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
begin
|
|
29
|
+
lexer = Taoism::Lexer.new(source)
|
|
30
|
+
parser = Taoism::Parser.new(lexer)
|
|
31
|
+
|
|
32
|
+
ast = parser.parse
|
|
33
|
+
|
|
34
|
+
if parser.errors.empty?
|
|
35
|
+
evaluator = Taoism::Evaluator.new
|
|
36
|
+
evaluator.evaluate(ast)
|
|
37
|
+
else
|
|
38
|
+
parser.errors.each { |e| $stderr.puts e }
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
rescue Taoism::Runtime::Error => err
|
|
42
|
+
$stderr.puts "Error: #{err.message}"
|
|
43
|
+
exit 1
|
|
44
|
+
rescue StandardError => err
|
|
45
|
+
$stderr.puts "Internal error: #{err.message}"
|
|
46
|
+
exit 1
|
|
47
|
+
end
|
data/bin/taoism
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Taoism
|
|
2
|
+
class Console
|
|
3
|
+
PS1 = 'taoism:%03d> '
|
|
4
|
+
PS2 = 'taoism:%03d* '
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@evaluator = Evaluator.new
|
|
8
|
+
@line_no = 1
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
require 'readline'
|
|
13
|
+
|
|
14
|
+
loop do
|
|
15
|
+
input = read_input
|
|
16
|
+
break unless input
|
|
17
|
+
|
|
18
|
+
break if input.strip == 'exit'
|
|
19
|
+
evaluate(input)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def read_input
|
|
24
|
+
ps1 = PS1 % @line_no
|
|
25
|
+
ps2 = PS2 % @line_no
|
|
26
|
+
|
|
27
|
+
input = Readline.readline(ps1, true)
|
|
28
|
+
return if input.nil?
|
|
29
|
+
input = input.chomp
|
|
30
|
+
|
|
31
|
+
had_continuation = false
|
|
32
|
+
|
|
33
|
+
while unclosed?(input)
|
|
34
|
+
had_continuation = true
|
|
35
|
+
line = Readline.readline(ps2, false)
|
|
36
|
+
|
|
37
|
+
break unless line
|
|
38
|
+
input << "\n" << line.chomp
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if had_continuation
|
|
42
|
+
Readline::HISTORY.pop
|
|
43
|
+
Readline::HISTORY << input
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@line_no += 1
|
|
47
|
+
input
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def evaluate(input)
|
|
51
|
+
lexer = Lexer.new(input)
|
|
52
|
+
parser = Parser.new(lexer)
|
|
53
|
+
|
|
54
|
+
ast = parser.parse
|
|
55
|
+
|
|
56
|
+
if parser.errors.empty?
|
|
57
|
+
result = @evaluator.evaluate(ast)
|
|
58
|
+
puts Runtime.repr(result)
|
|
59
|
+
else
|
|
60
|
+
parser.errors.each do |err|
|
|
61
|
+
$stderr.puts "Error: #{err}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
rescue Runtime::Error, Runtime::Return, Runtime::Leave => err
|
|
65
|
+
$stderr.puts "Error: #{err.message}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def unclosed?(input)
|
|
69
|
+
input.count('{') > input.count('}')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Taoism
|
|
2
|
+
class Environment
|
|
3
|
+
def initialize(outer = nil)
|
|
4
|
+
@outer = outer
|
|
5
|
+
@scope = {}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def define(name, value, mutable:)
|
|
9
|
+
if @scope.key?(name)
|
|
10
|
+
raise Runtime::Error, "already defined variable: #{name}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
@scope[name] = { value: value, mutable: mutable }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(name)
|
|
17
|
+
if @scope.key?(name)
|
|
18
|
+
@scope[name][:value]
|
|
19
|
+
elsif @outer
|
|
20
|
+
@outer.get(name)
|
|
21
|
+
else
|
|
22
|
+
raise Runtime::Error, "undefined variable: #{name}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def set(name, value)
|
|
27
|
+
if @scope.key?(name)
|
|
28
|
+
if @scope[name][:mutable]
|
|
29
|
+
@scope[name][:value] = value
|
|
30
|
+
else
|
|
31
|
+
raise Runtime::Error, "cannot assign to immutable variable"
|
|
32
|
+
end
|
|
33
|
+
elsif @outer
|
|
34
|
+
@outer.set(name, value)
|
|
35
|
+
else
|
|
36
|
+
raise Runtime::Error, "undefined variable: #{name}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def has?(name)
|
|
41
|
+
@scope.key?(name) or @outer&.has?(name)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
module Taoism
|
|
2
|
+
class Evaluator
|
|
3
|
+
def initialize
|
|
4
|
+
@global = Environment.new
|
|
5
|
+
@methods = {}
|
|
6
|
+
@builtins = {
|
|
7
|
+
'io.println' => proc { |arg| puts arg.to_s }
|
|
8
|
+
}
|
|
9
|
+
@in_loop = false
|
|
10
|
+
@in_function = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def evaluate(node, env = @global)
|
|
14
|
+
case node
|
|
15
|
+
when Nodes::Program
|
|
16
|
+
node.stmts.map { |s| evaluate(s, env) }.last
|
|
17
|
+
when Nodes::Block
|
|
18
|
+
inner = Environment.new(env)
|
|
19
|
+
last = nil
|
|
20
|
+
|
|
21
|
+
node.stmts.each { |s| last = evaluate(s, inner) }
|
|
22
|
+
last
|
|
23
|
+
when Nodes::ExprStmt then evaluate(node.expr, env)
|
|
24
|
+
when Nodes::IntLit then node.value
|
|
25
|
+
when Nodes::FloatLit then node.value
|
|
26
|
+
when Nodes::StringLit
|
|
27
|
+
node.value.gsub(/\\(\\|"|n|r|t|0|\((\w+)\))/) do
|
|
28
|
+
case $1
|
|
29
|
+
when '\\' then '\\'
|
|
30
|
+
when '"' then '"'
|
|
31
|
+
when 'n' then "\n"
|
|
32
|
+
when 'r' then "\r"
|
|
33
|
+
when 't' then "\t"
|
|
34
|
+
when '0' then "\0"
|
|
35
|
+
else
|
|
36
|
+
env.get($2).then { |v|
|
|
37
|
+
v.is_a?(String) ? v : Runtime.repr(v)
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
when Nodes::BoolLit then node.value
|
|
42
|
+
when Nodes::NoneLit then nil
|
|
43
|
+
when Nodes::ListLit
|
|
44
|
+
node.elements.map { |e| evaluate(e, env) }
|
|
45
|
+
when Nodes::Identifier then env.get(node.name)
|
|
46
|
+
when Nodes::Let
|
|
47
|
+
val = evaluate(node.value, env)
|
|
48
|
+
env.define(node.name, val, mutable: node.mutable)
|
|
49
|
+
val
|
|
50
|
+
when Nodes::LetPair
|
|
51
|
+
values = evaluate(node.expr, env)
|
|
52
|
+
|
|
53
|
+
unless values.is_a?(Array)
|
|
54
|
+
raise Runtime::Error, "cannot destructure #{values}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if values.length < 2
|
|
58
|
+
raise Runtime::Error, "cannot destructure #{values}, expected at least 2 elements"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
env.define(node.first, values[0], mutable: false)
|
|
62
|
+
env.define(node.second, values[1], mutable: false)
|
|
63
|
+
values
|
|
64
|
+
when Nodes::Assign
|
|
65
|
+
val = evaluate(node.value, env)
|
|
66
|
+
|
|
67
|
+
case node.target
|
|
68
|
+
when Nodes::Identifier
|
|
69
|
+
env.set(node.target.name, val)
|
|
70
|
+
when Nodes::DotExpr
|
|
71
|
+
target = evaluate(node.target.target, env)
|
|
72
|
+
|
|
73
|
+
unless target.is_a?(Runtime::Instance)
|
|
74
|
+
raise Runtime::Error, "field access on non-instance"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
unless target.fields.key?(node.target.name)
|
|
78
|
+
raise Runtime::Error, "undefined field: #{node.target.name}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
target.fields[node.target.name] = val
|
|
82
|
+
when Nodes::Index
|
|
83
|
+
target = evaluate(node.target.target, env)
|
|
84
|
+
|
|
85
|
+
unless target.is_a?(Array) || target.is_a?(String)
|
|
86
|
+
raise Runtime::Error, "index assignment on non-indexable"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
index = evaluate(node.target.index, env)
|
|
90
|
+
|
|
91
|
+
unless index.is_a?(Integer)
|
|
92
|
+
raise Runtime::Error, "index must be an integer: #{index}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if target.is_a?(String)
|
|
96
|
+
unless val.is_a?(String)
|
|
97
|
+
raise Runtime::Error, "index value must be a string"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
unless index.between?(-target.length, target.length)
|
|
101
|
+
raise Runtime::Error, "index out of range: #{index}"
|
|
102
|
+
end
|
|
103
|
+
elsif index < -target.length
|
|
104
|
+
raise Runtime::Error, "index out of range: #{index}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
target[index] = val
|
|
108
|
+
else
|
|
109
|
+
raise Runtime::Error, "invalid assignment target"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
val
|
|
113
|
+
when Nodes::Const
|
|
114
|
+
val = evaluate(node.value, env)
|
|
115
|
+
env.define(node.name, val, mutable: false)
|
|
116
|
+
val
|
|
117
|
+
when Nodes::BinaryOp
|
|
118
|
+
left = evaluate(node.left, env)
|
|
119
|
+
case node.op
|
|
120
|
+
when :and then left && evaluate(node.right, env)
|
|
121
|
+
when :or then left || evaluate(node.right, env)
|
|
122
|
+
else
|
|
123
|
+
right = evaluate(node.right, env)
|
|
124
|
+
|
|
125
|
+
if %i(+ - * / % < <= > >=).include?(node.op)
|
|
126
|
+
unless Types.numeric?(left) && Types.numeric?(right)
|
|
127
|
+
raise Runtime::Error, "cannot apply #{node.op} to #{Types.typeof(left)} and #{Types.typeof(right)}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
if %i(/ %).include?(node.op) && right == 0
|
|
132
|
+
raise Runtime::Error, "cannot divide by zero"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
left.send(node.op, right)
|
|
136
|
+
end
|
|
137
|
+
when Nodes::UnaryOp
|
|
138
|
+
operand = evaluate(node.operand, env)
|
|
139
|
+
|
|
140
|
+
if node.op == :'-'
|
|
141
|
+
unless Types.numeric?(operand)
|
|
142
|
+
raise Runtime::Error, "cannot negate #{Types.typeof(operand)}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
-operand
|
|
146
|
+
else
|
|
147
|
+
!operand
|
|
148
|
+
end
|
|
149
|
+
when Nodes::If
|
|
150
|
+
if evaluate(node.cond, env)
|
|
151
|
+
evaluate(node.then_body, env)
|
|
152
|
+
elsif node.else_body
|
|
153
|
+
evaluate(node.else_body, env)
|
|
154
|
+
else
|
|
155
|
+
end
|
|
156
|
+
when Nodes::Switch
|
|
157
|
+
val = evaluate(node.value, env)
|
|
158
|
+
result = nil
|
|
159
|
+
matched = false
|
|
160
|
+
|
|
161
|
+
node.arms.each do |arm|
|
|
162
|
+
if evaluate(arm.pattern, env) == val
|
|
163
|
+
result = evaluate(arm.value, env)
|
|
164
|
+
matched = true
|
|
165
|
+
break
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if !matched && node.else_body
|
|
170
|
+
result = evaluate(node.else_body, env)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
result
|
|
174
|
+
when Nodes::Loop
|
|
175
|
+
prev = @in_loop
|
|
176
|
+
@in_loop = true
|
|
177
|
+
|
|
178
|
+
begin
|
|
179
|
+
loop do
|
|
180
|
+
begin
|
|
181
|
+
evaluate(node.body, Environment.new(env))
|
|
182
|
+
rescue Runtime::Leave
|
|
183
|
+
break
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
ensure
|
|
187
|
+
@in_loop = prev
|
|
188
|
+
end
|
|
189
|
+
when Nodes::Leave
|
|
190
|
+
unless @in_loop
|
|
191
|
+
raise Runtime::Error, "leave outside loop"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
raise Runtime::Leave
|
|
195
|
+
when Nodes::Return
|
|
196
|
+
result = evaluate(node.value, env)
|
|
197
|
+
raise Runtime::Return.new(result)
|
|
198
|
+
when Nodes::FunDef
|
|
199
|
+
fn = Runtime::Function.new(
|
|
200
|
+
node.name, node.params, node.body, env, node.receiver
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if node.receiver
|
|
204
|
+
type_name = node.receiver.type_name
|
|
205
|
+
|
|
206
|
+
unless env.has?(type_name) && env.get(type_name).is_a?(Runtime::DataType)
|
|
207
|
+
raise Runtime::Error, "undefined type: #{type_name}"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
fn_name = "#{type_name}.#{node.name}"
|
|
211
|
+
|
|
212
|
+
if @methods[fn_name]
|
|
213
|
+
raise Runtime::Error, "already defined method: #{fn_name}"
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
@methods[fn_name] = fn
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
if node.name && node.receiver.nil?
|
|
220
|
+
env.define(node.name, fn, mutable: false)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
fn
|
|
224
|
+
when Nodes::Call
|
|
225
|
+
args = node.args.map { |a| evaluate(a, env) }
|
|
226
|
+
|
|
227
|
+
if node.callee.is_a?(Nodes::DotExpr)
|
|
228
|
+
if node.callee.target.is_a?(Nodes::Identifier)
|
|
229
|
+
key = "#{node.callee.target.name}.#{node.callee.name}"
|
|
230
|
+
if fn = @builtins[key]
|
|
231
|
+
return fn.call(*args)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
target = evaluate(node.callee.target, env)
|
|
236
|
+
|
|
237
|
+
unless target.is_a?(Runtime::Instance)
|
|
238
|
+
raise Runtime::Error, "method call on non-instance"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
key = "#{target.type}.#{node.callee.name}"
|
|
242
|
+
fn = @methods[key]
|
|
243
|
+
|
|
244
|
+
unless fn
|
|
245
|
+
raise Runtime::Error, "undefined method: #{key}"
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
if args.length != fn.params.length
|
|
249
|
+
raise Runtime::Error, arity_error(fn.params.length, args.length)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
call_env = Environment.new(fn.closure)
|
|
253
|
+
call_env.define(fn.receiver.param_name, target, mutable: true)
|
|
254
|
+
|
|
255
|
+
fn.params.each_with_index do |p, i|
|
|
256
|
+
call_env.define(p, args[i], mutable: true)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
prev = @in_function
|
|
260
|
+
@in_function = true
|
|
261
|
+
|
|
262
|
+
begin
|
|
263
|
+
evaluate(fn.body, call_env)
|
|
264
|
+
rescue Runtime::Return => ret
|
|
265
|
+
ret.value
|
|
266
|
+
ensure
|
|
267
|
+
@in_function = prev
|
|
268
|
+
end
|
|
269
|
+
else
|
|
270
|
+
callee = evaluate(node.callee, env)
|
|
271
|
+
case callee
|
|
272
|
+
when Runtime::Function
|
|
273
|
+
if args.length != callee.params.length
|
|
274
|
+
raise Runtime::Error, arity_error(callee.params.length, args.length)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
call_env = Environment.new(callee.closure)
|
|
278
|
+
callee.params.each_with_index do |p, i|
|
|
279
|
+
call_env.define(p, args[i], mutable: true)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
prev = @in_function
|
|
283
|
+
@in_function = true
|
|
284
|
+
|
|
285
|
+
begin
|
|
286
|
+
evaluate(callee.body, call_env)
|
|
287
|
+
rescue Runtime::Return => ret
|
|
288
|
+
ret.value
|
|
289
|
+
ensure
|
|
290
|
+
@in_function = prev
|
|
291
|
+
end
|
|
292
|
+
when Runtime::DataType
|
|
293
|
+
fields = {}
|
|
294
|
+
|
|
295
|
+
if args.length > callee.fields.length
|
|
296
|
+
raise Runtime::Error, arity_error("at most #{callee.fields.length}", args.length)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
callee.fields.each_with_index do |f, i|
|
|
300
|
+
fields[f.name] = args.length > i ? args[i] : evaluate(f.default, env)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
Runtime::Instance.new(callee.name, fields)
|
|
304
|
+
else
|
|
305
|
+
raise Runtime::Error, "not callable: #{callee.inspect}"
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
when Nodes::DotExpr
|
|
309
|
+
target = evaluate(node.target, env)
|
|
310
|
+
|
|
311
|
+
if target.is_a?(Runtime::Instance)
|
|
312
|
+
unless target.fields.key?(node.name)
|
|
313
|
+
raise Runtime::Error, "undefined field: #{node.name}"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
target.fields[node.name]
|
|
317
|
+
else
|
|
318
|
+
raise Runtime::Error, "field access on non-instance"
|
|
319
|
+
end
|
|
320
|
+
when Nodes::Index
|
|
321
|
+
target = evaluate(node.target, env)
|
|
322
|
+
index = evaluate(node.index, env)
|
|
323
|
+
|
|
324
|
+
unless index.is_a?(Integer)
|
|
325
|
+
raise Runtime::Error, "index must be an integer: #{index}"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
unless target.is_a?(Array) || target.is_a?(String)
|
|
329
|
+
raise Runtime::Error, "index access on non-indexable"
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
target[index]
|
|
333
|
+
when Nodes::DataDef
|
|
334
|
+
node.fields.each do |field|
|
|
335
|
+
next if field.default.nil?
|
|
336
|
+
unless literal?(field.default)
|
|
337
|
+
raise Runtime::Error, "field default must be a literal: #{field.name}"
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
value = Runtime::DataType.new(node.name, node.fields)
|
|
342
|
+
env.define(node.name, value, mutable: false)
|
|
343
|
+
value
|
|
344
|
+
when Nodes::Try
|
|
345
|
+
unless @in_function
|
|
346
|
+
raise Runtime::Error, "try outside function"
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
result = evaluate(node.call, env)
|
|
350
|
+
|
|
351
|
+
unless result.is_a?(Array) && result.length == 2
|
|
352
|
+
raise Runtime::Error, "try requires [val, err] tuple"
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
val, err = result
|
|
356
|
+
return val if err.nil?
|
|
357
|
+
raise Runtime::Return.new(err)
|
|
358
|
+
when Nodes::Package then nil
|
|
359
|
+
when Nodes::Import then nil
|
|
360
|
+
when nil then nil
|
|
361
|
+
else
|
|
362
|
+
raise Runtime::Error, "unknown node: #{node.class}"
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def arity_error(expected, got)
|
|
367
|
+
"expected #{expected} argument(s) but got #{got}"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def literal?(node)
|
|
371
|
+
case node
|
|
372
|
+
when Nodes::IntLit,
|
|
373
|
+
Nodes::FloatLit,
|
|
374
|
+
Nodes::StringLit,
|
|
375
|
+
Nodes::BoolLit,
|
|
376
|
+
Nodes::NoneLit
|
|
377
|
+
true
|
|
378
|
+
when Nodes::ListLit
|
|
379
|
+
node.elements.all? { |el| literal?(el) }
|
|
380
|
+
else
|
|
381
|
+
false
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|