1
1
mirror of https://github.com/kanaka/mal.git synced 2024-10-27 14:52:16 +03:00
mal/impls/fennel/stepA_mal.fnl
Nicolas Boulenguez 033892777a Merge eval-ast and macro expansion into EVAL, add DEBUG-EVAL
See issue #587.
* Merge eval-ast and eval into a single conditional.
* Expand macros during the apply phase, removing lots of duplicate
  tests, and increasing the overall consistency by allowing the macro
  to be computed instead of referenced by name (`((defmacro! cond
  (...)))` is currently illegal for example).
* Print "EVAL: $ast" at the top of EVAL if DEBUG-EVAL exists in the
  MAL environment.
* Remove macroexpand and quasiquoteexpand special forms.
* Use pattern-matching style in process/step*.txt.

Unresolved issues:
c.2: unable to reproduce with gcc 11.12.0.
elm: the directory is unchanged.
groovy: sometimes fail, but not on each rebuild.
nasm: fails some new soft tests, but the issue is unreproducible when
  running the interpreter manually.
objpascal: unreproducible with fpc 3.2.2.
ocaml: unreproducible with 4.11.1.
perl6: unreproducible with rakudo 2021.09.

Unrelated changes:
Reduce diff betweens steps.
Prevent defmacro! from mutating functions: c forth logo miniMAL vb.
dart: fix recent errors and warnings
ocaml: remove metadata from symbols.

Improve the logo implementation.
Encapsulate all representation in types.lg and env.lg, unwrap numbers.
Replace some manual iterations with logo control structures.
Reduce the diff between steps.
Use native iteration in env_get and env_map
Rewrite the reader with less temporary strings.
Reduce the number of temporary lists (for example, reverse iteration
with butlast requires O(n^2) allocations).
It seems possible to remove a few exceptions: GC settings
(Dockerfile), NO_SELF_HOSTING (IMPLS.yml) and step5_EXCLUDES
(Makefile.impls) .
2024-08-05 11:40:49 -05:00

279 lines
11 KiB
Fennel

(local printer (require :printer))
(local reader (require :reader))
(local t (require :types))
(local e (require :env))
(local core (require :core))
(local u (require :utils))
(local repl_env
(let [env (e.make-env)]
(each [name func (pairs core)]
(e.env-set env
(t.make-symbol name)
func))
env))
(fn READ
[code-str]
(reader.read_str code-str))
(fn starts-with
[ast name]
(when (and (t.list?* ast)
(not (t.empty?* ast)))
(let [head-ast (. (t.get-value ast) 1)]
(and (t.symbol?* head-ast)
(= name (t.get-value head-ast))))))
(var quasiquote* nil)
(fn qq-iter
[ast]
(if (t.empty?* ast)
(t.make-list [])
(let [ast-value (t.get-value ast)
elt (. ast-value 1)
acc (qq-iter (t.make-list (u.slice ast-value 2 -1)))]
(if (starts-with elt "splice-unquote")
(t.make-list [(t.make-symbol "concat")
(. (t.get-value elt) 2)
acc])
(t.make-list [(t.make-symbol "cons")
(quasiquote* elt)
acc])))))
(set quasiquote*
(fn [ast]
(if (starts-with ast "unquote")
(. (t.get-value ast) 2)
;;
(t.list?* ast)
(qq-iter ast)
;;
(t.vector?* ast)
(t.make-list [(t.make-symbol "vec") (qq-iter ast)])
;;
(or (t.symbol?* ast)
(t.hash-map?* ast))
(t.make-list [(t.make-symbol "quote") ast])
;;
ast)))
(fn EVAL
[ast-param env-param]
(var ast ast-param)
(var env env-param)
(var result nil)
(while (not result)
(let [dbgeval (e.env-get env "DEBUG-EVAL")]
(when (and dbgeval
(not (t.nil?* dbgeval))
(not (t.false?* dbgeval)))
(print (.. "EVAL: " (printer.pr_str ast true)))))
(if (t.symbol?* ast)
(let [key (t.get-value ast)]
(set result (or (e.env-get env key)
(u.throw* (t.make-string (.. "'" key
"' not found"))))))
;;
(t.vector?* ast)
(set result (t.make-vector (u.map (fn [x] (EVAL x env))
(t.get-value ast))))
;;
(t.hash-map?* ast)
(set result (t.make-hash-map (u.map (fn [x] (EVAL x env))
(t.get-value ast))))
;;
(or (not (t.list?* ast)) (t.empty?* ast))
(set result ast)
;;
(let [ast-elts (t.get-value ast)
head-name (t.get-value (. ast-elts 1))]
;; XXX: want to check for symbol, but...
(if (= "def!" head-name)
(let [def-name (. ast-elts 2)
def-val (EVAL (. ast-elts 3) env)]
(e.env-set env
def-name def-val)
(set result def-val))
;;
(= "defmacro!" head-name)
(let [def-name (. ast-elts 2)
def-val (EVAL (. ast-elts 3) env)
macro-ast (t.macrofy def-val)]
(e.env-set env
def-name macro-ast)
(set result macro-ast))
;;
(= "let*" head-name)
(let [new-env (e.make-env env)
bindings (t.get-value (. ast-elts 2))
stop (/ (length bindings) 2)]
(for [idx 1 stop]
(let [b-name
(. bindings (- (* 2 idx) 1))
b-val
(EVAL (. bindings (* 2 idx)) new-env)]
(e.env-set new-env
b-name b-val)))
;; tco
(set ast (. ast-elts 3))
(set env new-env))
;;
(= "quote" head-name)
;; tco
(set result (. ast-elts 2))
;;
(= "quasiquote" head-name)
;; tco
(set ast (quasiquote* (. ast-elts 2)))
;;
(= "try*" head-name)
(set result
(let [(ok? res)
(pcall EVAL (. ast-elts 2) env)]
(if (not ok?)
(let [maybe-catch-ast (. ast-elts 3)]
(if (not maybe-catch-ast)
(u.throw* res)
(if (not (starts-with maybe-catch-ast
"catch*"))
(u.throw*
(t.make-string
"Expected catch* form"))
(let [catch-asts
(t.get-value
maybe-catch-ast)]
(if (< (length catch-asts) 2)
(u.throw*
(t.make-string
(.. "catch* requires at "
"least 2 "
"arguments")))
(let [catch-sym-ast
(. catch-asts 2)
catch-body-ast
(. catch-asts 3)]
(EVAL catch-body-ast
(e.make-env
env
[catch-sym-ast]
[res]))))))))
res)))
;;
(= "do" head-name)
(let [most-forms (u.slice ast-elts 2 -2) ;; XXX
last-body-form (u.last ast-elts)
res-ast (u.map (fn [x] (EVAL x env)) most-forms)]
;; tco
(set ast last-body-form))
;;
(= "if" head-name)
(let [cond-res (EVAL (. ast-elts 2) env)]
(if (or (t.nil?* cond-res)
(t.false?* cond-res))
(let [else-ast (. ast-elts 4)]
(if (not else-ast)
;; tco
(set result t.mal-nil)
(set ast else-ast)))
;; tco
(set ast (. ast-elts 3))))
;;
(= "fn*" head-name)
(let [params (t.get-value (. ast-elts 2))
body (. ast-elts 3)]
;; tco
(set result
(t.make-fn
(fn [args]
(EVAL body
(e.make-env env params args)))
body params env false nil)))
;;
(let [f (EVAL (. ast-elts 1) env)
ast-rest (u.slice ast-elts 2 -1)]
(if (t.macro?* f)
(set ast ((t.get-value f) ast-rest))
(let [args (u.map (fn [x] (EVAL x env)) ast-rest)
body (t.get-ast f)] ;; tco
(if body
(do
(set ast body)
(set env
(e.make-env (t.get-env f)
(t.get-params f)
args)))
(set result
((t.get-value f) args))))))))))
result)
(fn PRINT
[ast]
(printer.pr_str ast true))
(fn rep
[code-str]
(PRINT (EVAL (READ code-str) repl_env)))
(rep "(def! not (fn* (a) (if a false true)))")
(e.env-set repl_env
(t.make-symbol "eval")
(t.make-fn
(fn [asts]
(when (< (length asts) 1)
(u.throw*
(t.make-string "eval takes 1 argument")))
(EVAL (u.first asts) repl_env))))
(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)))))))"))
(e.env-set repl_env
(t.make-symbol "*host-language*")
(t.make-string "fennel"))
(e.env-set repl_env
(t.make-symbol "*ARGV*")
(t.make-list (u.map t.make-string (u.slice arg 2 -1))))
(fn handle-error
[err]
(if (t.nil?* err)
(print)
(= "string" (type err))
(print err)
(print (.. "Error: " (PRINT err)))))
(if (<= 1 (length arg))
(xpcall (fn []
(rep (.. "(load-file \"" (. arg 1) "\")"))) ;; XXX: escaping?
handle-error)
(do
(rep "(println (str \"Mal [\" *host-language* \"]\"))")
(var done false)
(while (not done)
(io.write "user> ")
(io.flush)
(let [input (io.read)]
(if (not input)
(set done true)
(xpcall (fn []
(print (rep input)))
handle-error))))))