From 69b3778975bb3f7fba7292982c387fa9bbc0794c Mon Sep 17 00:00:00 2001 From: Pekka Laiho Date: Sun, 6 Dec 2020 09:32:45 +0700 Subject: [PATCH] add built-in macros, add macros to readme --- README.md | 44 ++++++++++++++++++++++++++++++++++++-------- src/Lisp.php | 5 +++++ src/LispFactory.php | 12 +++++++++--- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4b9f7dd..2456da7 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,24 @@ Internally hash maps are just regular associative PHP arrays wrapped in a class. Symbols are words which do not match any other type and are separated by whitespace. They can contain special characters. Examples of symbols are `a`, `name` or `+`. +## Environments + +Environments are hash-maps which store key-value pairs and use symbols as keys. Symbols are evaluated by looking up the corresponding value from the current environment. If the key is not defined in current environment the lookup proceeds to the parent environment and so forth. The initial environment is called `root` and contains all the built-in functions listed here. Then another environment called `user` is created for anything the user wants to define. The `let` and `fn` special forms create new local environments. Note that `def` always uses the current environment, so anything defined with `def` is not visible in the parent environment. + +You can get the name of an environment using the `meta` function: + +``` +> (meta (env) "name") +"root/user" +``` + +You can also retrieve the parent environment: + +``` +> (meta (env) "parent") +{} +``` + ## Quoting Use the `quote` special form to skip evaluation: @@ -152,24 +170,34 @@ You can use the single-quote (`'`), backtick and tilde (`~`) characters as short All special forms related to quoting require exactly one argument. -## Environments +## Macros -Environments are hash-maps which store key-value pairs and use symbols as keys. Symbols are evaluated by looking up the corresponding value from the current environment. If the key is not defined in current environment the lookup proceeds to the parent environment and so forth. The initial environment is called `root` and contains all the built-in functions listed here. Then another environment called `user` is created for anything the user wants to define. The `let` and `fn` special forms create new local environments. Note that `def` always uses the current environment, so anything defined with `def` is not visible in the parent environment. +The language has support for Lisp-style macros. Macros are like preprocessor directives and allow the manipulation of the language syntax before evaluation. -You can get the name of an environment using the `meta` function: +There are two built-in macros: `defn` which is a shortcut for the form `(def ... (fn ...))` and `defmacro` which is a shortcut for the form `(def ... (macro ...))`. + +We can use the special form `macroexpand` to test macro expansion without actually evaluating the resulting code. To illustrate how macros work, lets use `defn` as an example, and then view the expanded form using `macroexpand`: ``` -> (meta (env) "name") -"root/user" +> (def defn (macro (name args body) (quasiquote (def (unquote name) (fn (unquote args) (unquote body)))))) + +> (macroexpand (defn add (a b) (+ a b))) +(def add (fn (a b) (+ a b))) ``` -You can also retrieve the parent environment: +For another example, lets combine `if` and `not` into a macro named `unless`, this time using a shorter syntax: ``` -> (meta (env) "parent") -{} +> (defmacro unless (pred a b) `(if (not ~pred) ~a ~b)) + +> (macroexpand (unless false "is false" "not false")) +(if (not false) "is false" "not false") +> (unless false "is false" "not false") +"is false" ``` +The `quasiquote` form described above is essential for declaring macros. + ## Special forms Name | Safe-mode | Example | Example result | Description diff --git a/src/Lisp.php b/src/Lisp.php index 1263d99..7b70cbd 100644 --- a/src/Lisp.php +++ b/src/Lisp.php @@ -34,4 +34,9 @@ class Lisp $this->printer->print($result, $printReadable); } + + public function setEnv(Env $env): void + { + $this->env = $env; + } } diff --git a/src/LispFactory.php b/src/LispFactory.php index d566251..c1435f6 100644 --- a/src/LispFactory.php +++ b/src/LispFactory.php @@ -33,9 +33,15 @@ class LispFactory (new Lib\IO())->register($env); } - // User environment - $env = new Env('user', $env); + $lisp = new Lisp($tokenizer, $reader, $eval, $printer, $env); - return new Lisp($tokenizer, $reader, $eval, $printer, $env); + // Add some built-in macros + $lisp->readEval('(def defn (macro (name args body) (quasiquote (def (unquote name) (fn (unquote args) (unquote body))))))'); + $lisp->readEval('(def defmacro (macro (name args body) (quasiquote (def (unquote name) (macro (unquote args) (unquote body))))))'); + + // Separate environment for user-defined stuff + $lisp->setEnv(new Env('user', $env)); + + return $lisp; } }