1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-19 17:47:53 +03:00
mal/impls/wren/stepA_mal.wren
Joel Martin 8a19f60386 Move implementations into impls/ dir
- Reorder README to have implementation list after "learning tool"
  bullet.

- This also moves tests/ and libs/ into impls. It would be preferrable
  to have these directories at the top level.  However, this causes
  difficulties with the wasm implementations which need pre-open
  directories and have trouble with paths starting with "../../". So
  in lieu of that, symlink those directories to the top-level.

- Move the run_argv_test.sh script into the tests directory for
  general hygiene.
2020-02-10 23:50:16 -06:00

189 lines
5.9 KiB
Plaintext

import "os" for Process
import "./env" for Env
import "./readline" for Readline
import "./reader" for MalReader
import "./printer" for Printer
import "./types" for MalSymbol, MalSequential, MalList, MalVector, MalMap, MalNativeFn, MalFn, MalException
import "./core" for Core
class Mal {
static read(str) {
return MalReader.read_str(str)
}
static isPair(x) { x is MalSequential && !x.isEmpty }
static quasiquote(ast) {
if (!isPair(ast)) {
return MalList.new([MalSymbol.new("quote"), ast])
} else if (ast[0] is MalSymbol && ast[0].value == "unquote") {
return ast[1]
} else if (isPair(ast[0]) && ast[0][0] is MalSymbol && ast[0][0].value == "splice-unquote") {
return MalList.new([MalSymbol.new("concat"), ast[0][1], quasiquote(ast.rest)])
} else {
return MalList.new([MalSymbol.new("cons"), quasiquote(ast[0]), quasiquote(ast.rest)])
}
}
static isMacro(ast, env) {
return (ast is MalList &&
!ast.isEmpty &&
ast[0] is MalSymbol &&
env.find(ast[0].value) &&
env.get(ast[0].value) is MalFn &&
env.get(ast[0].value).isMacro)
}
static macroexpand(ast, env) {
while (isMacro(ast, env)) {
var macro = env.get(ast[0].value)
ast = macro.call(ast.elements[1..-1])
}
return ast
}
static eval_ast(ast, env) {
if (ast is MalSymbol) {
return env.get(ast.value)
} else if (ast is MalList) {
return MalList.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalVector) {
return MalVector.new(ast.elements.map { |e| eval(e, env) }.toList)
} else if (ast is MalMap) {
var m = {}
for (e in ast.data) {
m[e.key] = eval(e.value, env)
}
return MalMap.new(m)
} else {
return ast
}
}
static eval(ast, env) {
while (true) {
var tco = false
if (!(ast is MalList)) return eval_ast(ast, env)
ast = macroexpand(ast, env)
if (!(ast is MalList)) return eval_ast(ast, env)
if (ast.isEmpty) return ast
if (ast[0] is MalSymbol) {
if (ast[0].value == "def!") {
return env.set(ast[1].value, eval(ast[2], env))
} else if (ast[0].value == "let*") {
var letEnv = Env.new(env)
var i = 0
while (i < ast[1].count) {
letEnv.set(ast[1][i].value, eval(ast[1][i + 1], letEnv))
i = i + 2
}
ast = ast[2]
env = letEnv
tco = true
} else if (ast[0].value == "quote") {
return ast[1]
} else if (ast[0].value == "quasiquote") {
ast = quasiquote(ast[1])
tco = true
} else if (ast[0].value == "defmacro!") {
return env.set(ast[1].value, eval(ast[2], env).makeMacro())
} else if (ast[0].value == "macroexpand") {
return macroexpand(ast[1], env)
} else if (ast[0].value == "try*") {
if (ast.count > 2 && ast[2][0] is MalSymbol && ast[2][0].value == "catch*") {
var fiber = Fiber.new { eval(ast[1], env) }
var result = fiber.try()
var error = fiber.error
if (!error) return result
if (error == "___MalException___") {
error = MalException.value
MalException.set(null)
}
return eval(ast[2][2], Env.new(env, [ast[2][1]], [error]))
} else {
return eval(ast[1], env)
}
} else if (ast[0].value == "do") {
for (i in 1...(ast.count - 1)) {
eval(ast[i], env)
}
ast = ast[-1]
tco = true
} else if (ast[0].value == "if") {
var condval = eval(ast[1], env)
if (condval) {
ast = ast[2]
} else {
if (ast.count <= 3) return null
ast = ast[3]
}
tco = true
} else if (ast[0].value == "fn*") {
return MalFn.new(ast[2], ast[1].elements, env,
Fn.new { |a| eval(ast[2], Env.new(env, ast[1].elements, a)) })
}
}
if (!tco) {
var evaled_ast = eval_ast(ast, env)
var f = evaled_ast[0]
if (f is MalNativeFn) {
return f.call(evaled_ast[1..-1])
} else if (f is MalFn) {
ast = f.ast
env = Env.new(f.env, f.params, evaled_ast[1..-1])
tco = true
} else {
Fiber.abort("unknown function type")
}
}
}
}
static print(ast) {
return Printer.pr_str(ast)
}
static rep(str) {
return print(eval(read(str), __repl_env))
}
static main() {
__repl_env = Env.new()
// core.wren: defined in wren
for (e in Core.ns) { __repl_env.set(e.key, e.value) }
__repl_env.set("eval", MalNativeFn.new { |a| eval(a[0], __repl_env) })
__repl_env.set("*ARGV*", MalList.new(Process.arguments.count > 0 ? Process.arguments[1..-1] : []))
// core.mal: defined using the language itself
rep("(def! *host-language* \"wren\")")
rep("(def! not (fn* (a) (if a false true)))")
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
if (Process.arguments.count > 0) {
rep("(load-file \"%(Process.arguments[0])\")")
return
}
rep("(println (str \"Mal [\" *host-language* \"]\"))")
while (true) {
var line = Readline.readLine("user> ")
if (line == null) break
if (line != "") {
var fiber = Fiber.new { System.print(rep(line)) }
fiber.try()
var error = fiber.error
if (error) {
if (error == "___MalException___") {
error = Printer.pr_str(MalException.value, false)
MalException.set(null)
}
System.print("Error: %(error)")
}
}
}
System.print()
}
}
Mal.main()