From 80faff86f5e58273cf94cac2b0110118de75bae9 Mon Sep 17 00:00:00 2001 From: Pekka Laiho Date: Thu, 28 May 2020 17:10:00 +0700 Subject: [PATCH] split code to multiple classes --- bootstrap.php | 10 ++ repl.php | 2 +- src/Evaller.php | 79 ++++++++++++++ src/Lisp.php | 267 +++------------------------------------------- src/Printer.php | 57 ++++++++++ src/Reader.php | 67 ++++++++++++ src/Tokenizer.php | 75 +++++++++++++ 7 files changed, 303 insertions(+), 254 deletions(-) create mode 100644 src/Evaller.php create mode 100644 src/Printer.php create mode 100644 src/Reader.php create mode 100644 src/Tokenizer.php diff --git a/bootstrap.php b/bootstrap.php index 229c1af..4ce26a4 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -10,3 +10,13 @@ function ml_get_env(): MadLisp\Env return $env; } + +function ml_get_lisp(): MadLisp\Lisp +{ + $tokenizer = new MadLisp\Tokenizer(); + $reader = new MadLisp\Reader(); + $eval = new MadLisp\Evaller(); + $printer = new MadLisp\Printer(); + + return new MadLisp\Lisp($tokenizer, $reader, $eval, $printer); +} diff --git a/repl.php b/repl.php index 8f33df7..191265e 100644 --- a/repl.php +++ b/repl.php @@ -2,7 +2,7 @@ require('bootstrap.php'); $env = ml_get_env(); -$lisp = new MadLisp\Lisp(); +$lisp = ml_get_lisp(); while (true) { $input = readline('> '); diff --git a/src/Evaller.php b/src/Evaller.php new file mode 100644 index 0000000..8c51ea2 --- /dev/null +++ b/src/Evaller.php @@ -0,0 +1,79 @@ +doEval($expr, $env); + } + + return $results; + } + + public function doEval($expr, Env $env) + { + if ($expr instanceof MList && $expr->count() > 0) { + $first = $expr->get(0); + + if ($first instanceof Symbol) { + // Special built-in features + if ($first->getName() == 'env') { + return $env; + } elseif ($first->getName() == 'quote') { + if ($expr->count() != 2) { + throw new MadLispException("quote requires exactly 1 argument"); + } + + return $expr->get(1); + } elseif ($first->getName() == 'if') { + if ($expr->count() != 4) { + throw new MadLispException("if requires exactly 3 arguments"); + } + + // Eval condition + $result = $this->doEval($expr->get(1), $env); + + // Eval true or false branch and return it + if ($result == true) { + return $this->doEval($expr->get(2), $env); + } else { + return $this->doEval($expr->get(3), $env); + } + } + + // Normal symbol, fetch from env + $first = $env->get($first->getName()); + } + + if (!($first instanceof Closure)) { + throw new MadLispException("first argument of list is not function"); + } + + $args = array_slice($expr->getData(), 1); + + // Evaluate args + $args = array_map(fn ($a) => $this->doEval($a, $env), $args); + + // Call func and return result + return $first(...$args); + } elseif ($expr instanceof Hash) { + // Hash: return new hash with all items evaluated + $items = []; + foreach ($expr->getData() as $key => $val) { + $items[$key] = $this->doEval($val, $env); + } + return new Hash($items); + } elseif ($expr instanceof Symbol) { + return $env->get($expr->getName()); + } + + // Return the expression unchanged + return $expr; + } +} diff --git a/src/Lisp.php b/src/Lisp.php index ee51467..91c7bca 100644 --- a/src/Lisp.php +++ b/src/Lisp.php @@ -1,268 +1,29 @@ readForm($tokens, $index); - } - - return $result; - } - - public function read(string $code): array - { - $tokens = $this->tokenize($code); - - $expressions = $this->parse($tokens); - - return $expressions; - } - - public function eval($expr, Env $env) - { - if ($expr instanceof MList && $expr->count() > 0) { - $first = $expr->get(0); - - if ($first instanceof Symbol) { - // Special built-in features - if ($first->getName() == 'env') { - return $env; - } elseif ($first->getName() == 'quote') { - if ($expr->count() != 2) { - throw new MadLispException("quote requires exactly 1 argument"); - } - - return $expr->get(1); - } elseif ($first->getName() == 'if') { - if ($expr->count() != 4) { - throw new MadLispException("if requires exactly 3 arguments"); - } - - // Eval condition - $result = $this->eval($expr->get(1), $env); - - // Eval true or false branch and return it - if ($result == true) { - return $this->eval($expr->get(2), $env); - } else { - return $this->eval($expr->get(3), $env); - } - } - - // Normal symbol, fetch from env - $first = $env->get($first->getName()); - } - - if (!($first instanceof Closure)) { - throw new MadLispException("first argument of list is not function"); - } - - $args = array_slice($expr->getData(), 1); - - // Evaluate args - $args = array_map(fn ($a) => $this->eval($a, $env), $args); - - // Call func and return result - return $first(...$args); - } elseif ($expr instanceof Hash) { - // Hash: return new hash with all items evaluated - $items = []; - foreach ($expr->getData() as $key => $val) { - $items[$key] = $this->eval($val, $env); - } - return new Hash($items); - } elseif ($expr instanceof Symbol) { - return $env->get($expr->getName()); - } - - // Return the expression unchanged - return $expr; - } - - public function print(array $items): void - { - for ($i = 0; $i < count($items); $i++) { - if ($i > 0) { - print(' '); - } - $this->doPrint($items[$i]); - } + $this->tokenizer = $tokenizer; + $this->reader = $reader; + $this->eval = $eval; + $this->printer = $printer; } public function rep(string $input, Env $env): void { - $expressions = $this->read($input); + $tokens = $this->tokenizer->tokenize($input); - $results = array_map(fn ($expr) => $this->eval($expr, $env), $expressions); + $expressions = $this->reader->read($tokens); - $this->print($results); - } + $results = $this->eval->eval($expressions, $env); - private function doPrint($a): void - { - if ($a instanceof Closure) { - print(''); - } elseif ($a instanceof MList) { - print('('); - for ($i = 0; $i < $a->count(); $i++) { - if ($i > 0) { - print(' '); - } - $this->doPrint($a->get($i)); - } - print(')'); - } elseif ($a instanceof Hash) { - print('{'); - $keys = array_keys($a->getData()); - for ($i = 0; $i < count($keys); $i++) { - if ($i > 0) { - print(' '); - } - $this->doPrint($keys[$i]); - print(':'); - $this->doPrint($a->get($keys[$i])); - } - print('}'); - } elseif ($a instanceof Symbol) { - print($a->getName()); - } elseif ($a === true) { - print('true'); - } elseif ($a === false) { - print('false'); - } elseif ($a === null) { - print('null'); - } elseif (is_string($a)) { - print('"' . $a . '"'); - } else { - print($a); - } - } - - private function readForm(array $tokens, int &$index) - { - if ($tokens[$index] == '(') { - return $this->readList($tokens, $index); - } else { - return $this->readAtom($tokens, $index); - } - } - - private function readList(array $tokens, int &$index): MList - { - $result = []; - - // start tag - $index++; - - while ($tokens[$index] != ')') { - $result[] = $this->readForm($tokens, $index); - } - - // end tag - $index++; - - return new MList($result); - } - - private function readAtom(array $tokens, int &$index) - { - $a = $tokens[$index++]; - - if ($a === 'true') { - return true; - } elseif ($a === 'false') { - return false; - } elseif ($a === 'null') { - return null; - } elseif (substr($a, 0, 1) === '"') { - // string - return substr($a, 1, strlen($a) - 2); - } elseif (is_numeric($a)) { - if (filter_var($a, FILTER_VALIDATE_INT) !== false) { - return intval($a); - } else { - return floatval($a); - } - } else { - return new Symbol($a); - } + $this->printer->print($results); } } diff --git a/src/Printer.php b/src/Printer.php new file mode 100644 index 0000000..f3d4d0d --- /dev/null +++ b/src/Printer.php @@ -0,0 +1,57 @@ + 0) { + print(' '); + } + $this->doPrint($items[$i]); + } + } + + private function doPrint($a): void + { + if ($a instanceof Closure) { + print(''); + } elseif ($a instanceof MList) { + print('('); + for ($i = 0; $i < $a->count(); $i++) { + if ($i > 0) { + print(' '); + } + $this->doPrint($a->get($i)); + } + print(')'); + } elseif ($a instanceof Hash) { + print('{'); + $keys = array_keys($a->getData()); + for ($i = 0; $i < count($keys); $i++) { + if ($i > 0) { + print(' '); + } + $this->doPrint($keys[$i]); + print(':'); + $this->doPrint($a->get($keys[$i])); + } + print('}'); + } elseif ($a instanceof Symbol) { + print($a->getName()); + } elseif ($a === true) { + print('true'); + } elseif ($a === false) { + print('false'); + } elseif ($a === null) { + print('null'); + } elseif (is_string($a)) { + print('"' . $a . '"'); + } else { + print($a); + } + } +} diff --git a/src/Reader.php b/src/Reader.php new file mode 100644 index 0000000..98ea869 --- /dev/null +++ b/src/Reader.php @@ -0,0 +1,67 @@ +readForm($tokens, $index); + } + + return $result; + } + + private function readForm(array $tokens, int &$index) + { + if ($tokens[$index] == '(') { + return $this->readList($tokens, $index); + } else { + return $this->readAtom($tokens, $index); + } + } + + private function readList(array $tokens, int &$index): MList + { + $result = []; + + // start tag + $index++; + + while ($tokens[$index] != ')') { + $result[] = $this->readForm($tokens, $index); + } + + // end tag + $index++; + + return new MList($result); + } + + private function readAtom(array $tokens, int &$index) + { + $a = $tokens[$index++]; + + if ($a === 'true') { + return true; + } elseif ($a === 'false') { + return false; + } elseif ($a === 'null') { + return null; + } elseif (substr($a, 0, 1) === '"') { + // string + return substr($a, 1, strlen($a) - 2); + } elseif (is_numeric($a)) { + if (filter_var($a, FILTER_VALIDATE_INT) !== false) { + return intval($a); + } else { + return floatval($a); + } + } else { + return new Symbol($a); + } + } +} diff --git a/src/Tokenizer.php b/src/Tokenizer.php new file mode 100644 index 0000000..1e48127 --- /dev/null +++ b/src/Tokenizer.php @@ -0,0 +1,75 @@ +