mirror of
https://github.com/kanaka/mal.git
synced 2024-11-13 11:23:59 +03:00
034e82adc5
See http://skew-lang.org/ for details on the Skew language. Currently Mal only compiles to Javascript, as there are some issues with the C# backend for Skew (https://github.com/evanw/skew/issues/19). Tested with Skew 0.7.42.
195 lines
6.2 KiB
Plaintext
195 lines
6.2 KiB
Plaintext
def READ(str string) MalVal {
|
|
return read_str(str)
|
|
}
|
|
|
|
def isPair(a MalVal) bool {
|
|
return a is MalSequential && !(a as MalSequential).isEmpty
|
|
}
|
|
|
|
def quasiquote(ast MalVal) MalVal {
|
|
if !isPair(ast) {
|
|
return MalList.new([MalSymbol.new("quote"), ast])
|
|
}
|
|
const astSeq = ast as MalSequential
|
|
const a0 = astSeq[0]
|
|
if a0.isSymbol("unquote") {
|
|
return astSeq[1]
|
|
}
|
|
if isPair(a0) {
|
|
const a0Seq = a0 as MalSequential
|
|
if a0Seq[0].isSymbol("splice-unquote") {
|
|
return MalList.new([MalSymbol.new("concat"), a0Seq[1], quasiquote(astSeq.rest)])
|
|
}
|
|
}
|
|
return MalList.new([MalSymbol.new("cons"), quasiquote(a0), quasiquote(astSeq.rest)])
|
|
}
|
|
|
|
def isMacro(ast MalVal, env Env) bool {
|
|
if !(ast is MalList) { return false }
|
|
const astList = ast as MalList
|
|
if astList.isEmpty { return false }
|
|
const a0 = astList[0]
|
|
if !(a0 is MalSymbol) { return false }
|
|
const a0Sym = a0 as MalSymbol
|
|
if env.find(a0Sym) == null { return false }
|
|
const f = env.get(a0Sym)
|
|
if !(f is MalFunc) { return false }
|
|
return (f as MalFunc).isMacro
|
|
}
|
|
|
|
def macroexpand(ast MalVal, env Env) MalVal {
|
|
while isMacro(ast, env) {
|
|
const astList = ast as MalList
|
|
const mac = env.get(astList[0] as MalSymbol) as MalFunc
|
|
ast = mac.call((astList.rest as MalSequential).val)
|
|
}
|
|
return ast
|
|
}
|
|
|
|
def eval_ast(ast MalVal, env Env) MalVal {
|
|
if ast is MalSymbol {
|
|
return env.get(ast as MalSymbol)
|
|
} else if ast is MalList {
|
|
return MalList.new((ast as MalList).val.map<MalVal>(e => EVAL(e, env)))
|
|
} else if ast is MalVector {
|
|
return MalVector.new((ast as MalVector).val.map<MalVal>(e => EVAL(e, env)))
|
|
} else if ast is MalHashMap {
|
|
var result List<MalVal> = []
|
|
(ast as MalHashMap).val.each((k string, v MalVal) => {
|
|
result.append(EVAL(MalVal.fromHashKey(k), env))
|
|
result.append(EVAL(v, env))
|
|
})
|
|
return MalHashMap.fromList(result)
|
|
} else {
|
|
return ast
|
|
}
|
|
}
|
|
|
|
def EVAL(ast MalVal, env Env) MalVal {
|
|
while true {
|
|
if !(ast is MalList) { return eval_ast(ast, env) }
|
|
ast = macroexpand(ast, env)
|
|
if !(ast is MalList) { return eval_ast(ast, env) }
|
|
const astList = ast as MalList
|
|
if astList.isEmpty { return ast }
|
|
const a0sym = astList[0] as MalSymbol
|
|
if a0sym.val == "def!" {
|
|
return env.set(astList[1] as MalSymbol, EVAL(astList[2], env))
|
|
} else if a0sym.val == "let*" {
|
|
var letenv = Env.new(env)
|
|
const assigns = astList[1] as MalSequential
|
|
for i = 0; i < assigns.count; i += 2 {
|
|
letenv.set(assigns[i] as MalSymbol, EVAL(assigns[i + 1], letenv))
|
|
}
|
|
ast = astList[2]
|
|
env = letenv
|
|
continue # TCO
|
|
} else if a0sym.val == "quote" {
|
|
return astList[1]
|
|
} else if a0sym.val == "quasiquote" {
|
|
ast = quasiquote(astList[1])
|
|
continue # TCO
|
|
} else if a0sym.val == "defmacro!" {
|
|
var macro = EVAL(astList[2], env) as MalFunc
|
|
macro.setAsMacro
|
|
return env.set(astList[1] as MalSymbol, macro)
|
|
} else if a0sym.val == "macroexpand" {
|
|
return macroexpand(astList[1], env)
|
|
} else if a0sym.val == "try*" {
|
|
var exc MalVal
|
|
try {
|
|
return EVAL(astList[1], env)
|
|
}
|
|
catch e MalUserError { exc = e.data }
|
|
catch e MalError { exc = MalString.new(e.message) }
|
|
catch e Error { exc = MalString.new(e.message) }
|
|
const catchClause = astList[2] as MalList
|
|
var catchEnv = Env.new(env, [catchClause[1] as MalSymbol], [exc])
|
|
return EVAL(catchClause[2], catchEnv)
|
|
} else if a0sym.val == "do" {
|
|
const parts = astList.val.slice(1)
|
|
eval_ast(MalList.new(parts.slice(0, parts.count - 1)), env)
|
|
ast = parts[parts.count - 1]
|
|
continue # TCO
|
|
} else if a0sym.val == "if" {
|
|
const condRes = EVAL(astList[1], env)
|
|
if condRes is MalNil || condRes is MalFalse {
|
|
ast = astList.count > 3 ? astList[3] : gNil
|
|
} else {
|
|
ast = astList[2]
|
|
}
|
|
continue # TCO
|
|
} else if a0sym.val == "fn*" {
|
|
const argsNames = astList[1] as MalSequential
|
|
return MalFunc.new(astList[2], argsNames, env, (args List<MalVal>) => EVAL(astList[2], Env.new(env, argsNames.val, args)))
|
|
} else {
|
|
const evaledList = eval_ast(ast, env) as MalList
|
|
const fn = evaledList[0]
|
|
const callArgs = evaledList.val.slice(1)
|
|
if fn is MalNativeFunc {
|
|
return (fn as MalNativeFunc).call(callArgs)
|
|
} else if fn is MalFunc {
|
|
const f = fn as MalFunc
|
|
ast = f.ast
|
|
env = Env.new(f.env, f.params.val, callArgs)
|
|
continue # TCO
|
|
} else {
|
|
throw MalError.new("Expected function as head of list")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def PRINT(exp MalVal) string {
|
|
return exp?.print(true)
|
|
}
|
|
|
|
var repl_env = Env.new(null)
|
|
|
|
def RE(str string) MalVal {
|
|
return EVAL(READ(str), repl_env)
|
|
}
|
|
|
|
def REP(str string) string {
|
|
return PRINT(RE(str))
|
|
}
|
|
|
|
@entry
|
|
def main {
|
|
# core.sk: defined using Skew
|
|
ns.each((name, func) => repl_env.set(MalSymbol.new(name), MalNativeFunc.new(func)))
|
|
repl_env.set(MalSymbol.new("*ARGV*"), MalList.new(argv.isEmpty ? [] : argv.slice(1).map<MalVal>(e => MalString.new(e))))
|
|
|
|
# core.mal: defined using the language itself
|
|
RE("(def! *host-language* \"skew\")")
|
|
RE("(def! not (fn* (a) (if a false true)))")
|
|
RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
|
|
RE("(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)))))))")
|
|
RE("(def! *gensym-counter* (atom 0))")
|
|
RE("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))")
|
|
RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))")
|
|
|
|
if argv.count > 0 {
|
|
RE("(load-file \"" + argv[0] + "\")")
|
|
return
|
|
}
|
|
|
|
RE("(println (str \"Mal [\" *host-language* \"]\"))")
|
|
var line string
|
|
while (line = readLine("user> ")) != null {
|
|
if line == "" { continue }
|
|
try {
|
|
printLn(REP(line))
|
|
}
|
|
catch e MalUserError {
|
|
printLn("Error: \(e.data.print(false))")
|
|
}
|
|
catch e MalError {
|
|
printLn("Error: \(e.message)")
|
|
}
|
|
catch e Error {
|
|
printLn("Error: \(e.message)")
|
|
}
|
|
}
|
|
}
|