add built-in macros, add macros to readme

This commit is contained in:
Pekka Laiho 2020-12-06 09:32:45 +07:00
parent 496e1c2c2e
commit 69b3778975
3 changed files with 50 additions and 11 deletions

View File

@ -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 `+`. 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 ## Quoting
Use the `quote` special form to skip evaluation: 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. 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") > (def defn (macro (name args body) (quasiquote (def (unquote name) (fn (unquote args) (unquote body))))))
"root/user" <macro>
> (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))
{} <macro>
> (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 ## Special forms
Name | Safe-mode | Example | Example result | Description Name | Safe-mode | Example | Example result | Description

View File

@ -34,4 +34,9 @@ class Lisp
$this->printer->print($result, $printReadable); $this->printer->print($result, $printReadable);
} }
public function setEnv(Env $env): void
{
$this->env = $env;
}
} }

View File

@ -33,9 +33,15 @@ class LispFactory
(new Lib\IO())->register($env); (new Lib\IO())->register($env);
} }
// User environment $lisp = new Lisp($tokenizer, $reader, $eval, $printer, $env);
$env = new Env('user', $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;
} }
} }