diff --git a/README.md b/README.md index b6a259c..216ddb4 100644 --- a/README.md +++ b/README.md @@ -214,17 +214,20 @@ The `quasiquote` form described above is essential for declaring macros. Interna ## Reflection -You can use the `meta` function to retrieve the arguments, body or full code of user-defined functions: +You can use the `meta` special form to retrieve the arguments, body, code or full definition of user-defined functions: ```text > (defn add (a b) (+ a b)) + > (meta add "args") (a b) > (meta add "body") (+ a b) > (meta add "code") (fn (a b) (+ a b)) +> (meta add "def") +(defn add (a b) (+ a b)) ``` This allows for some fun tricks. For example, we can retrieve the body of a function and evaluate it as part of another function: @@ -257,6 +260,7 @@ let | yes | `(let (a (+ 1 2)) a)` | `3` | Create a new local environment using load | no | `(load "file.mad")` | | Read and evaluate a file. The contents are implicitly wrapped in a `do` expression. macro | yes | | | See the section Macros. macroexpand | yes | | | See the section Macros. +meta | no | | | See the sections Environments and Reflection. or | yes | `(or false 0 1)` | `1` | Return the first value that evaluates to true, or the last value. quote | yes | | | See the section Quoting. quasiquote | yes | | | See the section Quoting. @@ -274,7 +278,6 @@ doc | yes | `(doc +)` | `"Return the sum of all arguments."` | Show the docume error | yes | `(error "invalid value")` | `error: invalid value` | Throw an exception with message as argument. exit | no | `(exit 1)` | | Terminate the script with given exit code using [exit](https://www.php.net/manual/en/function.exit.php). loop | yes | `(loop (fn (a) (do (print a) (coinflip))) "hello ")` | `hello hello hello false` | Call the given function repeatedly in a loop until it returns false. -meta | no | `(meta (env) "name")` | `"root/user"` | Read meta information of an entity. print | no | `(print "hello world")` | `"hello world"null` | Print expression on the screen. Print returns null (which is shown due to the extra print in repl). Give optional second argument as `true` to show strings in readable format. read | no | `(read "(+ 1 2 3)")` | `(+ 1 2 3)` | Read a string as code and return the expression. sleep | no | `(sleep 2000)` | `null` | Sleep for the given period given in milliseconds using [usleep](https://www.php.net/manual/en/function.usleep). diff --git a/src/Evaller.php b/src/Evaller.php index a4f110a..7cc9ad9 100644 --- a/src/Evaller.php +++ b/src/Evaller.php @@ -295,6 +295,45 @@ class Evaller } return $this->macroexpand($astData[1], $env); + } elseif (!$this->safemode && $symbolName == 'meta') { + if ($astLength != 3) { + throw new MadLispException("meta requires exactly 2 arguments"); + } elseif (!is_string($astData[2])) { + throw new MadLispException("third argument to meta is not string"); + } + + $obj = $this->eval($astData[1], $env, $depth + 1); + $attribute = $astData[2]; + + if ($obj instanceof Env) { + if ($attribute == 'name') { + return $obj->getFullName(); + } elseif ($attribute == 'parent') { + return $obj->getParent(); + } else { + throw new MadLispException('unknown attribute for meta'); + } + } elseif ($obj instanceof UserFunc) { + if ($attribute == 'args') { + return $obj->getBindings(); + } elseif ($attribute == 'body') { + return $obj->getAst(); + } elseif ($attribute == 'code') { + $name = $obj->isMacro() ? 'macro' : 'fn'; + return new MList([new Symbol($name), $obj->getBindings(), $obj->getAst()]); + } elseif ($attribute == 'def') { + if ($astData[1] instanceof Symbol) { + $def = $obj->isMacro() ? 'defmacro' : 'defn'; + return new MList([new Symbol($def), $astData[1], $obj->getBindings(), $obj->getAst()]); + } else { + throw new MadLispException('no name for def in meta'); + } + } else { + throw new MadLispException('unknown attribute for meta'); + } + } else { + throw new MadLispException('unknown entity for meta'); + } } elseif ($symbolName == 'or') { if ($astLength == 1) { return false; diff --git a/src/Lib/Core.php b/src/Lib/Core.php index 9bc04df..e8ce042 100644 --- a/src/Lib/Core.php +++ b/src/Lib/Core.php @@ -85,35 +85,6 @@ class Core implements ILib } )); - if (!$this->safemode) { - $env->set('meta', new CoreFunc('meta', 'Read meta information of an entity.', 2, 2, - function ($obj, $attribute) { - if ($obj instanceof Env) { - if ($attribute == 'name') { - return $obj->getFullName(); - } elseif ($attribute == 'parent') { - return $obj->getParent(); - } else { - throw new MadLispException('unknown attribute for meta'); - } - } elseif ($obj instanceof UserFunc) { - if ($attribute == 'args') { - return $obj->getBindings(); - } elseif ($attribute == 'body') { - return $obj->getAst(); - } elseif ($attribute == 'code') { - $name = $obj->isMacro() ? 'macro' : 'fn'; - return new MList([new Symbol($name), $obj->getBindings(), $obj->getAst()]); - } else { - throw new MadLispException('unknown attribute for meta'); - } - } else { - throw new MadLispException('unknown entity for meta'); - } - } - )); - } - if (!$this->safemode) { $env->set('print', new CoreFunc('print', 'Print argument. Give second argument as true to show strings in readable format.', 1, 2, function ($a, bool $readable = false) {