diff --git a/bootstrap.php b/bootstrap.php index b0869df..bb92f1f 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -5,13 +5,13 @@ function ml_get_lisp(): array { $tokenizer = new MadLisp\Tokenizer(); $reader = new MadLisp\Reader(); - $eval = new MadLisp\Evaller(); + $eval = new MadLisp\Evaller($tokenizer, $reader); $printer = new MadLisp\Printer(); $lisp = new MadLisp\Lisp($tokenizer, $reader, $eval, $printer); // Environment - $env = new MadLisp\Env(); + $env = new MadLisp\Env('root'); // Register core functions $lisp->register($env); @@ -25,8 +25,5 @@ function ml_get_lisp(): array (new MadLisp\Lib\Time())->register($env); (new MadLisp\Lib\Types())->register($env); - // Functions defined in lisp itself - $lisp->re('(def loadf (fn (f) (if (file? f) (eval (read (str "(do " (fread f) ")"))) (error (str "file " f " does not exist")))))', $env); - return [$lisp, $env]; } diff --git a/repl.php b/repl.php index 4a16b6c..42f6d9a 100644 --- a/repl.php +++ b/repl.php @@ -4,7 +4,7 @@ require('bootstrap.php'); list($lisp, $rootEnv) = ml_get_lisp(); // Create new env for user definitions -$userEnv = new MadLisp\Env($rootEnv); +$userEnv = new MadLisp\Env('repl', $rootEnv); while (true) { $input = readline('> '); diff --git a/src/Env.php b/src/Env.php index 63aff5f..3638578 100644 --- a/src/Env.php +++ b/src/Env.php @@ -3,13 +3,24 @@ namespace MadLisp; class Env extends Hash { + protected string $name; protected ?Env $parent; - public function __construct(?Env $parent = null) + public function __construct(string $name, ?Env $parent = null) { + $this->name = $name; $this->parent = $parent; } + public function getFullName(): string + { + if ($this->parent) { + return $this->parent->getFullName() . '/' . $this->name; + } + + return $this->name; + } + public function get(string $key) { if ($this->has($key)) { diff --git a/src/Evaller.php b/src/Evaller.php index d40acb7..aaf3f6f 100644 --- a/src/Evaller.php +++ b/src/Evaller.php @@ -3,6 +3,15 @@ namespace MadLisp; class Evaller { + protected Tokenizer $tokenizer; + protected Reader $reader; + + public function __construct(Tokenizer $tokenizer, Reader $reader) + { + $this->tokenizer = $tokenizer; + $this->reader = $reader; + } + public function eval($ast, Env $env) { while (true) { @@ -82,6 +91,13 @@ class Evaller } else { return $env; } + } elseif ($ast->get(0)->getName() == 'eval') { + if ($ast->count() == 1) { + return null; + } + + $ast = $this->eval($ast->get(1), $env); + continue; // tco } elseif ($ast->get(0)->getName() == 'fn') { if ($ast->count() != 3) { throw new MadLispException("fn requires exactly 2 arguments"); @@ -99,7 +115,7 @@ class Evaller } $closure = function (...$args) use ($bindings, $ast, $env) { - $newEnv = new Env($env); + $newEnv = new Env('closure', $env); for ($i = 0; $i < count($bindings); $i++) { $newEnv->set($bindings[$i]->getName(), $args[$i] ?? null); @@ -140,7 +156,7 @@ class Evaller throw new MadLispException("uneven number of bindings for let"); } - $newEnv = new Env($env); + $newEnv = new Env('let', $env); for ($i = 0; $i < count($bindings) - 1; $i += 2) { $key = $bindings[$i]; @@ -156,6 +172,31 @@ class Evaller $ast = $ast->get(2); $env = $newEnv; continue; // tco + } elseif ($ast->get(0)->getName() == 'load') { + // Load is here because we want to load into + // current $env which is hard otherwise. + + if ($ast->count() != 2) { + throw new MadLispException("load requires exactly 1 argument"); + } + + $filename = $ast->get(1); + + if (!is_string($filename)) { + throw new MadLispException("first argument to load is not string"); + } elseif (!is_readable($filename)) { + throw new MadLispException("unable to read file $filename"); + } + + $input = @file_get_contents($filename); + + // Wrap input in a do to process multiple expressions + $input = "(do $input)"; + + $expr = $this->reader->read($this->tokenizer->tokenize($input)); + + $ast = $this->eval($expr, $env); + continue; // tco } elseif ($ast->get(0)->getName() == 'or') { if ($ast->count() == 1) { return false; diff --git a/src/Lisp.php b/src/Lisp.php index bae56a4..163b27f 100644 --- a/src/Lisp.php +++ b/src/Lisp.php @@ -16,18 +16,13 @@ class Lisp $this->printer = $printer; } - public function re(string $input, Env $env) + public function rep(string $input, Env $env): void { $tokens = $this->tokenizer->tokenize($input); $expr = $this->reader->read($tokens); - return $this->eval->eval($expr, $env); - } - - public function rep(string $input, Env $env): void - { - $result = $this->re($input, $env); + $result = $this->eval->eval($expr, $env); $this->printer->print($result); } @@ -48,10 +43,6 @@ class Lisp fn (string $a) => $this->reader->read($this->tokenizer->tokenize($a)) )); - $env->set('eval', new CoreFunc('eval', 'Evaluate argument.', 1, 1, - fn ($a) => $this->eval->eval($a, $env) - )); - $env->set('print', new CoreFunc('print', 'Print argument.', 1, 1, function ($a) { $this->printer->print($a); diff --git a/src/UserFunc.php b/src/UserFunc.php index 42448eb..2780e74 100644 --- a/src/UserFunc.php +++ b/src/UserFunc.php @@ -25,7 +25,7 @@ class UserFunc extends Func public function getEnv(array $args) { - $newEnv = new Env($this->tempEnv); + $newEnv = new Env('apply', $this->tempEnv); for ($i = 0; $i < count($this->bindings); $i++) { $newEnv->set($this->bindings[$i]->getName(), $args[$i] ?? null);