forked from kanaka/mal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstepA_mal.sk
194 lines (180 loc) · 6.18 KB
/
stepA_mal.sk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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)")
}
}
}