From 64a7f19b8bc3a2681f0d9cf8f4b5d671710dc06e Mon Sep 17 00:00:00 2001 From: Pekka Laiho Date: Tue, 2 Jun 2020 09:51:21 +0700 Subject: [PATCH] functions for collections --- bootstrap.php | 3 +- repl.php | 2 + src/Evaller.php | 4 +- src/Lib/Collections.php | 94 +++++++++++++++++++++++++++++++++++++++++ src/Lib/Types.php | 20 ++++++++- src/Printer.php | 2 +- src/Seq.php | 8 +++- 7 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 src/Lib/Collections.php diff --git a/bootstrap.php b/bootstrap.php index 1e66f59..b0b31b7 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -17,8 +17,9 @@ function ml_get_lisp(): array $lisp->register($env); // Register libraries - (new MadLisp\Lib\Math())->register($env); + (new MadLisp\Lib\Collections())->register($env); (new MadLisp\Lib\Compare())->register($env); + (new MadLisp\Lib\Math())->register($env); (new MadLisp\Lib\Types())->register($env); return [$lisp, $env]; diff --git a/repl.php b/repl.php index 5d4e211..113030d 100644 --- a/repl.php +++ b/repl.php @@ -10,6 +10,8 @@ while (true) { $lisp->rep($input, $env); } catch (MadLisp\MadLispException $ex) { print('error: ' . $ex->getMessage()); + } catch (TypeError $ex) { + print('error: invalid argument type: ' . $ex->getMessage()); } print(PHP_EOL); diff --git a/src/Evaller.php b/src/Evaller.php index ce0e5b6..fd71c80 100644 --- a/src/Evaller.php +++ b/src/Evaller.php @@ -82,7 +82,7 @@ class Evaller } } - return function (...$args) use ($bindings, $ast, $env) { + return new UserFunc(function (...$args) use ($bindings, $ast, $env) { $newEnv = new Env($env); for ($i = 0; $i < count($bindings); $i++) { @@ -90,7 +90,7 @@ class Evaller } return $this->doEval($ast->get(2), $newEnv); - }; + }); } elseif ($ast->get(0)->getName() == 'if') { if ($ast->count() < 3 || $ast->count() > 4) { throw new MadLispException("if requires 2 or 3 arguments"); diff --git a/src/Lib/Collections.php b/src/Lib/Collections.php new file mode 100644 index 0000000..3da2304 --- /dev/null +++ b/src/Lib/Collections.php @@ -0,0 +1,94 @@ +set('hash', new CoreFunc('hash', 'Return hash which contains the arguments.', 0, -1, + fn (...$args) => Util::makeHash($args) + )); + + $env->set('list', new CoreFunc('list', 'Return list which contains the arguments.', 0, -1, + fn (...$args) => new MList($args) + )); + + $env->set('vector', new CoreFunc('vector', 'Return vector which contains the arguments.', 0, -1, + fn (...$args) => new Vector($args) + )); + + // Other + + $env->set('empty?', new CoreFunc('empty?', 'Return true if collection is empty.', 1, 1, + fn (Collection $a) => $a->count() == 0 + )); + + $env->set('len', new CoreFunc('len', 'Return the length of string or number of elements in collection.', 1, 1, + function ($a) { + if ($a instanceof Collection) { + return $a->count(); + } else { + return strlen($a); + } + } + )); + + $env->set('first', new CoreFunc('first', 'Return the first element of a sequence or null.', 1, 1, + fn (Seq $a) => $a->getData()[0] ?? null + )); + + $env->set('last', new CoreFunc('last', 'Return the last element of a sequence or null.', 1, 1, + function (Seq $a) { + if ($a->count() == 0) { + return null; + } + + return $a->getData()[$a->count() - 1]; + } + )); + + $env->set('head', new CoreFunc('head', 'Return new sequence containing all elements of argument except last.', 1, 1, + function (Seq $a) { + return $a::new(array_slice($a->getData(), 0, $a->count() - 1)); + } + )); + + $env->set('tail', new CoreFunc('tail', 'Return new sequence containing all elements of argument except first.', 1, 1, + function (Seq $a) { + return $a::new(array_slice($a->getData(), 1)); + } + )); + + $env->set('map', new CoreFunc('map', 'Apply the first argument (function) to all elements of second argument (sequence).', 2, 2, + function (Func $f, Seq $a) { + return $a::new(array_map($f->getClosure(), $a->getData())); + } + )); + + $env->set('reduce', new CoreFunc('reduce', 'Apply the first argument (function) to each element of second argument (sequence) incrementally. Optional third argument is the initial value to be used as first input for function and it defaults to null.', 2, 3, + function (Func $f, Seq $a, $initial = null) { + return array_reduce($a->getData(), $f->getClosure(), $initial); + } + )); + + $env->set('keys', new CoreFunc('keys', 'Return the keys of a hash-map as a list.', 1, 1, + fn (Hash $a) => new MList(array_keys($a->getData())) + )); + + $env->set('values', new CoreFunc('values', 'Return the values of a hash-map as a list.', 1, 1, + fn (Hash $a) => new MList(array_values($a->getData())) + )); + } +} diff --git a/src/Lib/Types.php b/src/Lib/Types.php index 1b35709..e41d601 100644 --- a/src/Lib/Types.php +++ b/src/Lib/Types.php @@ -28,7 +28,7 @@ class Types implements ILib fn ($a) => intval($a) )); - $env->set('str', new CoreFunc('str', 'Convert arguments to string and concatenate them together.', 1, -1, + $env->set('str', new CoreFunc('str', 'Convert arguments to string and concatenate them together.', 0, -1, fn (...$args) => implode('', array_map('strval', $args)) )); @@ -111,5 +111,23 @@ class Types implements ILib $env->set('str?', new CoreFunc('str?', 'Return true if argument is a string.', 1, 1, fn ($a) => is_string($a) )); + + // Helpers for numbers + + $env->set('zero?', new CoreFunc('zero?', 'Return true if argument is an integer with value 0.', 1, 1, + fn ($a) => $a === 0 + )); + + $env->set('one?', new CoreFunc('one?', 'Return true if argument is an integer with value 1.', 1, 1, + fn ($a) => $a === 1 + )); + + $env->set('even?', new CoreFunc('even?', 'Return true if argument is divisible by 2.', 1, 1, + fn ($a) => $a % 2 === 0 + )); + + $env->set('odd?', new CoreFunc('odd?', 'Return true if argument is not divisible by 2.', 1, 1, + fn ($a) => $a % 2 !== 0 + )); } } diff --git a/src/Printer.php b/src/Printer.php index 0b41022..d6ca9e9 100644 --- a/src/Printer.php +++ b/src/Printer.php @@ -28,7 +28,7 @@ class Printer } elseif ($a === false) { return 'false'; } elseif ($a === null) { - return ''; + return 'null'; } elseif (is_string($a)) { return '"' . $a . '"'; } else { diff --git a/src/Seq.php b/src/Seq.php index d79d9ed..1b4ee41 100644 --- a/src/Seq.php +++ b/src/Seq.php @@ -3,5 +3,9 @@ namespace MadLisp; abstract class Seq extends Collection { - -} \ No newline at end of file + public static function new(array $data = []): self + { + // late static binding + return new static($data); + } +}