1
1
mirror of https://github.com/kanaka/mal.git synced 2024-08-16 17:20:23 +03:00

mal: implement macro without metadata

Support for metadata becomes optional.
Support for fn? becomes optional again, reverting 5e5d4892.
This commit is contained in:
Nicolas Boulenguez 2019-06-15 17:05:46 +02:00
parent aac5cf7bf5
commit 28b63c0ca6
6 changed files with 96 additions and 97 deletions

View File

@ -1,13 +1,17 @@
(def! _fn? (fn* [x]
(if (fn? x)
(not (get (meta x) "ismacro"))
false)))
(def! _macro_magic
:_I_tell_ya_this_is_a_MAL_macro_mark_my_words)
(def! _macro_wrap (fn* [f]
[_macro_magic f]))
(def! _macro_unwrap (fn* [x]
(if (vector? x)
(if (= (first x) _macro_magic)
(nth x 1)))))
(def! _macro? (fn* [x]
(if (fn? x)
(if (get (meta x) "ismacro")
true
false)
(if (_macro_unwrap x)
true
false)))
(def! core_ns
@ -22,7 +26,7 @@
['symbol? symbol?]
['keyword keyword]
['keyword? keyword?]
['fn? _fn?]
['fn? fn?]
['macro? _macro?]
['pr-str pr-str]

View File

@ -29,12 +29,13 @@
(let* [a0 (first ast)]
(if (symbol? a0)
(if (env-find env a0)
(_macro? (env-get env a0))))))))
(_macro_unwrap (env-get env a0))))))))
(def! MACROEXPAND (fn* [ast env]
(if (is-macro-call ast env)
(MACROEXPAND (apply (env-get env (first ast)) (rest ast)) env)
ast)))
(let* [m (is-macro-call ast env)]
(if m
(MACROEXPAND (apply m (rest ast)) env)
ast))))
(def! eval-ast (fn* [ast env]
;; (do (prn "eval-ast" ast "/" (keys env)) )
@ -84,10 +85,7 @@
(EVAL (QUASIQUOTE (nth ast 1)) env)
(= 'defmacro! a0)
(let* [f (EVAL (nth ast 2) env)
m (meta f)
mac (with-meta f (assoc (if m m {}) "ismacro" true))]
(env-set env (nth ast 1) mac))
(env-set env (nth ast 1) (_macro_wrap (EVAL (nth ast 2) env)))
(= 'macroexpand a0)
(MACROEXPAND (nth ast 1) env)

View File

@ -29,12 +29,13 @@
(let* [a0 (first ast)]
(if (symbol? a0)
(if (env-find env a0)
(_macro? (env-get env a0))))))))
(_macro_unwrap (env-get env a0))))))))
(def! MACROEXPAND (fn* [ast env]
(if (is-macro-call ast env)
(MACROEXPAND (apply (env-get env (first ast)) (rest ast)) env)
ast)))
(let* [m (is-macro-call ast env)]
(if m
(MACROEXPAND (apply m (rest ast)) env)
ast))))
(def! eval-ast (fn* [ast env]
;; (do (prn "eval-ast" ast "/" (keys env)) )
@ -84,10 +85,7 @@
(EVAL (QUASIQUOTE (nth ast 1)) env)
(= 'defmacro! a0)
(let* [f (EVAL (nth ast 2) env)
m (meta f)
mac (with-meta f (assoc (if m m {}) "ismacro" true))]
(env-set env (nth ast 1) mac))
(env-set env (nth ast 1) (_macro_wrap (EVAL (nth ast 2) env)))
(= 'macroexpand a0)
(MACROEXPAND (nth ast 1) env)

View File

@ -29,12 +29,13 @@
(let* [a0 (first ast)]
(if (symbol? a0)
(if (env-find env a0)
(_macro? (env-get env a0))))))))
(_macro_unwrap (env-get env a0))))))))
(def! MACROEXPAND (fn* [ast env]
(if (is-macro-call ast env)
(MACROEXPAND (apply (env-get env (first ast)) (rest ast)) env)
ast)))
(let* [m (is-macro-call ast env)]
(if m
(MACROEXPAND (apply m (rest ast)) env)
ast))))
(def! eval-ast (fn* [ast env]
;; (do (prn "eval-ast" ast "/" (keys env)) )
@ -84,10 +85,7 @@
(EVAL (QUASIQUOTE (nth ast 1)) env)
(= 'defmacro! a0)
(let* [f (EVAL (nth ast 2) env)
m (meta f)
mac (with-meta f (assoc (if m m {}) "ismacro" true))]
(env-set env (nth ast 1) mac))
(env-set env (nth ast 1) (_macro_wrap (EVAL (nth ast 2) env)))
(= 'macroexpand a0)
(MACROEXPAND (nth ast 1) env)

View File

@ -1521,27 +1521,6 @@ diff -urp ../process/step9_try.txt ../process/stepA_mal.txt
entered by the user is returned as a string. If the user sends an
end-of-file (usually Ctrl-D), then nil is returned.
* Add meta-data support to mal functions by adding a new metadata
attribute on mal functions that refers to another mal value/type
(nil by default). Add the following metadata related core functions:
* `meta`: this takes a single mal function argument and returns the
value of the metadata attribute.
* `with-meta`: this function takes two arguments. The first argument
is a mal function and the second argument is another mal
value/type to set as metadata. A copy of the mal function is
returned that has its `meta` attribute set to the second argument.
Note that it is important that the environment and macro attribute
of mal function are retained when it is copied.
* Add a reader-macro that expands the token "^" to
return a new list that contains the symbol "with-meta" and the
result of reading the next next form (2nd argument) (`read_form`) and the
next form (1st argument) in that order
(metadata comes first with the ^ macro and the function second).
* If you implemented as `defmacro!` to mutate an existing function
without copying it, you can now use the function copying mechanism
used for metadata to make functions immutable even in the
defmacro! case...
* Add a new "\*host-language\*" (symbol) entry to your REPL
environment. The value of this entry should be a mal string
containing the name of the current implementation.
@ -1552,6 +1531,7 @@ diff -urp ../process/step9_try.txt ../process/stepA_mal.txt
"(println (str \"Mal [\" \*host-language\* \"]\"))".
* Ensure that the REPL environment contains definitions for `time-ms`,
`meta`, `with-meta`, `fn?`
`string?`, `number?`, `seq`, and `conj`. It doesn't really matter
what they do at this stage: they just need to be defined. Making
them functions that raise a "not implemented" exception would be
@ -1610,8 +1590,29 @@ implementation.
#### Optional additions
* Add metadata support to other composite data types (lists, vectors
and hash-maps), and to native functions.
* Add meta-data support to composite data types (lists, vectors
and hash-maps), and to functions (native or not), by adding a new
metadata attribute that refers to another mal value/type
(nil by default). Add the following metadata related core functions
(and remove any stub versions):
* `meta`: this takes a single mal function argument and returns the
value of the metadata attribute.
* `with-meta`: this function takes two arguments. The first argument
is a mal function and the second argument is another mal
value/type to set as metadata. A copy of the mal function is
returned that has its `meta` attribute set to the second argument.
Note that it is important that the environment and macro attribute
of mal function are retained when it is copied.
* Add a reader-macro that expands the token "^" to
return a new list that contains the symbol "with-meta" and the
result of reading the next next form (2nd argument) (`read_form`) and the
next form (1st argument) in that order
(metadata comes first with the ^ macro and the function second).
* If you implemented as `defmacro!` to mutate an existing function
without copying it, you can now use the function copying mechanism
used for metadata to make functions immutable even in the
defmacro! case...
* Add the following new core functions (and remove any stub versions):
* `time-ms`: takes no arguments and returns the number of
milliseconds since epoch (00:00:00 UTC January 1, 1970), or, if

View File

@ -24,6 +24,33 @@
;; ------- (Needed for self-hosting) -------
;;
;;
;; Testing hash-map evaluation and atoms (i.e. an env)
(def! e (atom {"+" +}))
(swap! e assoc "-" -)
( (get @e "+") 7 8)
;=>15
( (get @e "-") 11 8)
;=>3
(swap! e assoc "foo" (list))
(get @e "foo")
;=>()
(swap! e assoc "bar" '(1 2 3))
(get @e "bar")
;=>(1 2 3)
;; Testing for presence of optional functions
(do (list time-ms string? number? seq conj meta with-meta fn?) nil)
;=>nil
;; ------------------------------------------------------------------
;>>> soft=True
;>>> optional=True
;;
;; ------- Optional Functionality --------------
;; ------- (Not needed for self-hosting) -------
;; Testing metadata on functions
;;
@ -64,20 +91,6 @@
(meta +)
;=>nil
;; Testing fn? function
(fn? +)
;=>true
(fn? (fn* () 0))
;=>true
(fn? cond)
;=>false
(fn? "+")
;=>false
(fn? :+)
;=>false
(fn? ^{"ismacro" true} (fn* () 0))
;=>true
;;
;; Make sure closures and metadata co-exist
(def! gen-plusX (fn* (x) (with-meta (fn* (b) (+ x b)) {"meta" 1})))
@ -94,33 +107,6 @@
(meta plus8)
;=>{"meta" 1}
;;
;; Testing hash-map evaluation and atoms (i.e. an env)
(def! e (atom {"+" +}))
(swap! e assoc "-" -)
( (get @e "+") 7 8)
;=>15
( (get @e "-") 11 8)
;=>3
(swap! e assoc "foo" (list))
(get @e "foo")
;=>()
(swap! e assoc "bar" '(1 2 3))
(get @e "bar")
;=>(1 2 3)
;; Testing for presence of optional functions
(do (list time-ms string? number? seq conj) nil)
;=>nil
;; ------------------------------------------------------------------
;>>> soft=True
;>>> optional=True
;;
;; ------- Optional Functionality --------------
;; ------- (Not needed for self-hosting) -------
;;
;; Testing string? function
(string? "")
@ -152,6 +138,20 @@
(def! add1 (fn* (x) (+ x 1)))
;; Testing fn? function
(fn? +)
;=>true
(fn? add1)
;=>true
(fn? cond)
;=>false
(fn? "+")
;=>false
(fn? :+)
;=>false
(fn? ^{"ismacro" true} (fn* () 0))
;=>true
;; Testing macro? function
(macro? cond)
;=>true