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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6257a4a47d084d1a3bdaab39c08ab216ddd729fad2f508da85a79775043539bf
4
- data.tar.gz: 4d156fb2970893a73fe4898c316dc33d96785e9cb48ee7ddf1dfa343521afff0
3
+ metadata.gz: e3888b31accff3d78029eaba044b83ef112863b85b2b4c6ee84865f3af918330
4
+ data.tar.gz: ea0fbbb5539fbda01d5f2c40eb8f2a1067e451c6a7cc9c0b16e47c3b981080d6
5
5
  SHA512:
6
- metadata.gz: 8f4fc03e11db86e3c85fab8fae14509036d1579bd166b97cfa582dc2673fcb8fb0941d75da3278884b9dc431188a1877f3b385ebac91fe711ed820357b13f323
7
- data.tar.gz: 9f3d220093d56d8423c9e8f34263b4ee949931cdcd6c7253c857d8c4568e3605e9e052d1a6181e57a215f6b251aa26f3e12b38f86e08cf66e494904bb691e283
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 'tao'
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,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'taoism'
3
+ require 'taoism/console'
4
+
5
+ Taoism::Console.new.run
@@ -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