Skip to content

Commit 3dfb721

Browse files
committed
chapter A: self-hosting (wip)
1 parent 0d51b60 commit 3dfb721

File tree

9 files changed

+221
-69
lines changed

9 files changed

+221
-69
lines changed

ast.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,18 @@ type ExprFn struct { // if it weren't for TCO, just the above `ExprFunc` would s
4545
env *Env
4646
isMacro bool
4747
isVariadic bool
48+
nameMaybe string
4849
}
4950

5051
func (me *ExprFn) envWith(args []Expr) (*Env, error) {
5152
num_args_min, num_args_max := len(me.params), len(me.params)
5253
if me.isVariadic {
5354
num_args_min, num_args_max = len(me.params)-1, -1
5455
}
55-
if err := checkArgsCount(num_args_min, num_args_max, args); err != nil {
56+
if err := checkArgsCount(num_args_min, num_args_max, strings.TrimSpace("function "+me.nameMaybe), args); err != nil {
57+
if me.nameMaybe == "" && fakeFuncNamesForDebugging {
58+
panic("WHOIS")
59+
}
5660
return nil, err
5761
}
5862
if me.isVariadic {
@@ -96,7 +100,7 @@ func exprStrOrKeyword(s string) Expr {
96100
}
97101

98102
func compare(args []Expr) (int, error) {
99-
if err := checkArgsCount(2, 2, args); err != nil {
103+
if err := checkArgsCount(2, 2, "comparer", args); err != nil {
100104
return 0, err
101105
}
102106
switch it := args[0].(type) {
@@ -120,7 +124,7 @@ func isListOrVec(seq Expr) bool {
120124
func isListStartingWithIdent(maybeList Expr, ident ExprIdent, mustHaveLen int) (list []Expr, doesListStartWithIdent bool, err error) {
121125
if list, _ = maybeList.(ExprList); len(list) > 0 {
122126
if maybe_ident, _ := list[0].(ExprIdent); maybe_ident == ident {
123-
doesListStartWithIdent, err = true, checkArgsCount(mustHaveLen, mustHaveLen, list)
127+
doesListStartWithIdent, err = true, checkArgsCount(mustHaveLen, mustHaveLen, "form `"+string(ident)+"`", list)
124128
}
125129
}
126130
return

interp.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import (
44
"fmt"
55
)
66

7-
const disableTcoFuncs = false // caution: if `true`, cannot def `macro`s; this bool is just for quick temporary via-REPL trouble-shootings to see if TCO got somehow broken (or to enable call tracing for trouble-shooting)
7+
const disableTcoFuncs = true // caution: if `true`, cannot def `macro`s; this bool is just for quick temporary via-REPL trouble-shootings to see if TCO got somehow broken (or to enable call tracing for trouble-shooting)
88
const disableTracing = true || !disableTcoFuncs
9+
const fakeFuncNamesForDebugging = false // costly but can aid the occasional trouble-shooting
910

1011
// to confirm TCO still works, uncomment the 2 commented lines in `evalAndApply` below that are referring to `id`.
1112
// another way: run `(sum2 10000000 0)` with TCO disabled (stack overflow) and then re-enabled (no stack overflow), where `sum2` is in github.com/kanaka/mal/blob/master/impls/tests/step5_tco.mal
@@ -31,6 +32,10 @@ func evalAndApply(env *Env, expr Expr) (Expr, error) {
3132
}
3233
} else {
3334
trace(true, func() string { return str(true, it) })
35+
maybe_ident, _ := it[0].(ExprIdent)
36+
if fakeFuncNamesForDebugging && maybe_ident == "" {
37+
maybe_ident = ExprIdent(str(true, it))
38+
}
3439
expr, err = evalExpr(env, it)
3540
if err != nil {
3641
return nil, err
@@ -46,6 +51,9 @@ func evalAndApply(env *Env, expr Expr) (Expr, error) {
4651
trace(false, func() string { return fmt.Sprintf("RET<<<%s", str(true, expr)) })
4752
env = nil
4853
case *ExprFn:
54+
if (fn.nameMaybe == "") && (maybe_ident != "") {
55+
fn.nameMaybe = "`" + string(maybe_ident) + "`"
56+
}
4957
expr = fn.body
5058
env, err = fn.envWith(args)
5159
if err != nil {

main.go

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func main() {
2121
addOsArgsToEnv()
2222

2323
if malCompat {
24-
makeCompatibleWithMAL()
24+
ensureMALCompatibility()
2525
}
2626

2727
// check if we are to run the REPL or run a specified source file
@@ -58,16 +58,6 @@ func readAndEval(str string) (Expr, error) {
5858
return evalAndApply(&envMain, expr)
5959
}
6060

61-
func addOsArgsToEnv() {
62-
if len(os.Args) > 1 {
63-
args := make(ExprList, 0, len(os.Args)-2)
64-
for _, arg := range os.Args[2:] {
65-
args = append(args, ExprStr(arg))
66-
}
67-
envMain.set("osArgs", args)
68-
}
69-
}
70-
7161
const srcMiniStdlibNonMacros = `
7262
7363
@@ -78,12 +68,12 @@ const srcMiniStdlibNonMacros = `
7868
(def nth at)
7969
8070
(def first
81-
(fn (list)
82-
(at list 0)))
71+
(fn (lst)
72+
(at lst 0)))
8373
8474
(def rest
85-
(fn (list)
86-
(at list 1 -1)))
75+
(fn (lst)
76+
(at lst 1 -1)))
8777
8878
(def loadFile
8979
(fn (srcFilePath)
@@ -93,10 +83,10 @@ const srcMiniStdlibNonMacros = `
9383
(eval expr)))
9484
9585
(def map
96-
(fn (func list)
97-
(if (isEmpty list)
86+
(fn (func lst)
87+
(if (isEmpty lst)
9888
()
99-
(cons (func (first list)) (map func (rest list))))))
89+
(cons (func (first lst)) (map func (rest lst))))))
10090
10191
`
10292

mal/core.mal

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(def! _macro? (fn* [x]
2+
(if (map? x)
3+
(contains? x :__MAL_MACRO__)
4+
false)))
5+
6+
(def! core_ns '[* + - / < <= = > >= apply assoc atom atom? concat conj
7+
cons contains? count deref dissoc empty? false? first fn? get
8+
hash-map keys keyword keyword? list list? map map? meta nil? nil true false
9+
nth number? pr-str println prn read-string readline reset! rest seq
10+
sequential? slurp str string? swap! symbol symbol? throw time-ms
11+
true? vals vec vector vector? with-meta])

mal/step4_if_fn_do.mal

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
(load-file "../mal/env.mal")
2+
(load-file "../mal/core.mal")
3+
4+
;; EVAL extends this stack trace when propagating exceptions. If the
5+
;; exception reaches the REPL loop, the full trace is printed.
6+
(def! trace (atom ""))
7+
8+
;; read
9+
(def! READ read-string)
10+
11+
12+
;; eval
13+
(def! eval-ast (fn* [ast env]
14+
;; (do (prn "eval-ast" ast "/" (keys @env)) )
15+
(cond
16+
(symbol? ast) (env-get env ast)
17+
18+
(list? ast) (map (fn* [exp] (EVAL exp env)) ast)
19+
20+
(vector? ast) (vec (map (fn* [exp] (EVAL exp env)) ast))
21+
22+
(map? ast) (apply hash-map
23+
(apply concat
24+
(map (fn* [k] [k (EVAL (get ast k) env)])
25+
(keys ast))))
26+
27+
"else" ast)))
28+
29+
(def! LET (fn* [env binds form]
30+
(if (empty? binds)
31+
(EVAL form env)
32+
(do
33+
(env-set env (first binds) (EVAL (nth binds 1) env))
34+
(LET env (rest (rest binds)) form)))))
35+
36+
(def! EVAL (fn* [ast env]
37+
;; (do (prn "EVAL" ast "/" (keys @env)) )
38+
(try*
39+
(if (not (list? ast))
40+
(eval-ast ast env)
41+
42+
;; apply list
43+
(let* [a0 (first ast)]
44+
(cond
45+
(empty? ast)
46+
ast
47+
48+
(= 'def! a0)
49+
(env-set env (nth ast 1) (EVAL (nth ast 2) env))
50+
51+
(= 'let* a0)
52+
(LET (new-env env) (nth ast 1) (nth ast 2))
53+
54+
(= 'do a0)
55+
(nth (eval-ast (rest ast) env) (- (count ast) 2))
56+
57+
(= 'if a0)
58+
(if (EVAL (nth ast 1) env)
59+
(EVAL (nth ast 2) env)
60+
(if (> (count ast) 3)
61+
(EVAL (nth ast 3) env)))
62+
63+
(= 'fn* a0)
64+
(fn* [& args] (EVAL (nth ast 2) (new-env env (nth ast 1) args)))
65+
66+
"else"
67+
(let* [el (eval-ast ast env)]
68+
(apply (first el) (rest el))))))
69+
70+
(catch* exc
71+
(do
72+
(swap! trace str "\n in mal EVAL: " ast)
73+
(throw exc))))))
74+
75+
;; print
76+
(def! PRINT pr-str)
77+
78+
;; repl
79+
(def! repl-env (new-env))
80+
(def! rep (fn* [strng]
81+
(PRINT (EVAL (READ strng) repl-env))))
82+
83+
;; core.mal: defined directly using mal
84+
(map (fn* [sym] (env-set repl-env sym (eval sym))) core_ns)
85+
86+
;; core.mal: defined using the new language itself
87+
(rep "(def! not (fn* [a] (if a false true)))")
88+
89+
;; repl loop
90+
(def! repl-loop (fn* [line]
91+
(if line
92+
(do
93+
(if (not (= "" line))
94+
(try*
95+
(println (rep line))
96+
(catch* exc
97+
(do
98+
(println "Uncaught exception:" exc @trace)
99+
(reset! trace "")))))
100+
(repl-loop (readline "mal-user> "))))))
101+
102+
;; main
103+
(repl-loop "")

mal_compat.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import (
66
"strings"
77
)
88

9-
var malCompat = (os.Getenv("MAL_COMPAT") != "")
9+
var (
10+
malCompat = (os.Getenv("MAL_COMPAT") != "")
11+
malMeta = map[Expr]Expr{}
12+
)
1013

11-
func makeCompatibleWithMAL() {
14+
func ensureMALCompatibility() {
1215
// simple aliases: special-forms
1316
for mals, ours := range map[ExprIdent]ExprIdent{
1417
"def!": "def",
@@ -43,6 +46,7 @@ func makeCompatibleWithMAL() {
4346
"contains?": "hashmapHas",
4447
"keys": "hashmapKeys",
4548
"vals": "hashmapVals",
49+
"symbol": "ident",
4650
} {
4751
it := envMain.Map[ours]
4852
if envMain.Map[mals] = it; it == nil {
@@ -62,6 +66,20 @@ func makeCompatibleWithMAL() {
6266
}
6367
return ExprStr(buf.String()), nil
6468
}),
69+
"with-meta": ExprFunc(func(args []Expr) (Expr, error) {
70+
if len(args) > 1 {
71+
malMeta[args[0]] = args[1]
72+
}
73+
return args[0], nil
74+
}),
75+
"meta": ExprFunc(func(args []Expr) (Expr, error) {
76+
if len(args) > 0 {
77+
if meta := malMeta[args[0]]; meta != nil {
78+
return meta, nil
79+
}
80+
}
81+
return exprNil, nil
82+
}),
6583
} {
6684
envMain.Map[name] = expr
6785
}
@@ -94,7 +112,7 @@ func makeCompatibleWithMAL() {
94112
}),
95113

96114
"defmacro!": SpecialForm(func(env *Env, args []Expr) (*Env, Expr, error) {
97-
if err := checkArgsCount(2, 2, args); err != nil {
115+
if err := checkArgsCount(2, 2, "`defmacro!`", args); err != nil {
98116
return nil, nil, err
99117
}
100118
if list, is, _ := isListStartingWithIdent(args[1], "fn*", -1); is {

special_forms.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func stdSet(env *Env, args []Expr) (*Env, Expr, error) {
4242
return defOrSet(false, env, args)
4343
}
4444
func defOrSet(isDef bool, env *Env, args []Expr) (*Env, Expr, error) {
45-
if err := checkArgsCount(2, 2, args); err != nil {
45+
if err := checkArgsCount(2, 2, "`def` and `set`", args); err != nil {
4646
return nil, nil, err
4747
}
4848
name, err := checkIs[ExprIdent](args[0])
@@ -82,7 +82,7 @@ func defOrSet(isDef bool, env *Env, args []Expr) (*Env, Expr, error) {
8282
}
8383

8484
func stdDo(env *Env, args []Expr) (tailEnv *Env, expr Expr, err error) {
85-
if err = checkArgsCount(1, -1, args); err != nil {
85+
if err = checkArgsCount(1, -1, "`do`", args); err != nil {
8686
return
8787
}
8888
for _, arg := range args[:len(args)-1] {
@@ -95,7 +95,7 @@ func stdDo(env *Env, args []Expr) (tailEnv *Env, expr Expr, err error) {
9595
}
9696

9797
func stdLet(env *Env, args []Expr) (*Env, Expr, error) {
98-
if err := checkArgsCount(2, -1, args); err != nil {
98+
if err := checkArgsCount(2, -1, "`let`", args); err != nil {
9999
return nil, nil, err
100100
}
101101
bindings, err := checkIsSeq(args[0])
@@ -108,7 +108,7 @@ func stdLet(env *Env, args []Expr) (*Env, Expr, error) {
108108
if err != nil {
109109
return nil, nil, err
110110
}
111-
if err = checkArgsCount(2, 2, pair); err != nil {
111+
if err = checkArgsCount(2, 2, "`let`", pair); err != nil {
112112
return nil, nil, err
113113
}
114114
name, err := checkIs[ExprIdent](pair[0])
@@ -128,7 +128,7 @@ func stdLet(env *Env, args []Expr) (*Env, Expr, error) {
128128
}
129129

130130
func stdIf(env *Env, args []Expr) (*Env, Expr, error) {
131-
if err := checkArgsCount(2, 3, args); err != nil {
131+
if err := checkArgsCount(2, 3, "`if`", args); err != nil {
132132
return nil, nil, err
133133
}
134134
if len(args) < 3 {
@@ -146,7 +146,7 @@ func stdIf(env *Env, args []Expr) (*Env, Expr, error) {
146146
}
147147

148148
func stdFn(env *Env, args []Expr) (*Env, Expr, error) {
149-
if err := checkArgsCount(2, -1, args); err != nil {
149+
if err := checkArgsCount(2, -1, "`fn`", args); err != nil {
150150
return nil, nil, err
151151
}
152152

@@ -178,7 +178,7 @@ func stdFn(env *Env, args []Expr) (*Env, Expr, error) {
178178
}
179179

180180
func stdMacroExpand(env *Env, args []Expr) (*Env, Expr, error) {
181-
if err := checkArgsCount(1, 1, args); err != nil {
181+
if err := checkArgsCount(1, 1, "`macroExpand`", args); err != nil {
182182
return nil, nil, err
183183
}
184184
list, err := checkIs[ExprList](args[0])
@@ -194,14 +194,14 @@ func stdMacroExpand(env *Env, args []Expr) (*Env, Expr, error) {
194194
}
195195

196196
func stdQuote(_ *Env, args []Expr) (*Env, Expr, error) {
197-
if err := checkArgsCount(1, 1, args); err != nil {
197+
if err := checkArgsCount(1, 1, "`quote`", args); err != nil {
198198
return nil, nil, err
199199
}
200200
return nil, args[0], nil
201201
}
202202

203203
func stdQuasiQuote(env *Env, args []Expr) (*Env, Expr, error) {
204-
if err := checkArgsCount(1, 1, args); err != nil {
204+
if err := checkArgsCount(1, 1, "`quasiQuote`", args); err != nil {
205205
return nil, nil, err
206206
}
207207
_, is_vec := args[0].(ExprVec)
@@ -259,7 +259,7 @@ func stdQuasiQuote(env *Env, args []Expr) (*Env, Expr, error) {
259259
}
260260

261261
func stdTryCatch(env *Env, args []Expr) (*Env, Expr, error) {
262-
if err := checkArgsCount(1, 2, args); err != nil {
262+
if err := checkArgsCount(1, 2, "`try`", args); err != nil {
263263
return nil, nil, err
264264
}
265265
if len(args) == 1 {

0 commit comments

Comments
 (0)