From 28b63c0ca619b54b9e16332cd47aff24211d88ab Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Sat, 15 Jun 2019 17:05:46 +0200 Subject: [PATCH] mal: implement macro without metadata Support for metadata becomes optional. Support for fn? becomes optional again, reverting 5e5d4892. --- mal/core.mal | 22 +++++++----- mal/step8_macros.mal | 14 ++++---- mal/step9_try.mal | 14 ++++---- mal/stepA_mal.mal | 14 ++++---- process/guide.md | 47 ++++++++++++------------- tests/stepA_mal.mal | 82 ++++++++++++++++++++++---------------------- 6 files changed, 96 insertions(+), 97 deletions(-) diff --git a/mal/core.mal b/mal/core.mal index 60c5e97e..09241c61 100644 --- a/mal/core.mal +++ b/mal/core.mal @@ -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] diff --git a/mal/step8_macros.mal b/mal/step8_macros.mal index 80c1cf11..8b2e6525 100644 --- a/mal/step8_macros.mal +++ b/mal/step8_macros.mal @@ -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) diff --git a/mal/step9_try.mal b/mal/step9_try.mal index ad4d763a..0b93f954 100644 --- a/mal/step9_try.mal +++ b/mal/step9_try.mal @@ -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) diff --git a/mal/stepA_mal.mal b/mal/stepA_mal.mal index e053802e..189ae393 100644 --- a/mal/stepA_mal.mal +++ b/mal/stepA_mal.mal @@ -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) diff --git a/process/guide.md b/process/guide.md index 4c0f4393..9aa8a15b 100644 --- a/process/guide.md +++ b/process/guide.md @@ -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 diff --git a/tests/stepA_mal.mal b/tests/stepA_mal.mal index 8771074c..9ea1cb5f 100644 --- a/tests/stepA_mal.mal +++ b/tests/stepA_mal.mal @@ -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