add support for variable arguments using &

This commit is contained in:
Pekka Laiho 2020-12-19 17:45:18 +07:00
parent ed2738c91a
commit 16808c7ff9
4 changed files with 80 additions and 7 deletions

View File

@ -241,9 +241,7 @@ class Evaller
$closure = function (...$args) use ($bindings, $env, $astData, $depth) { $closure = function (...$args) use ($bindings, $env, $astData, $depth) {
$newEnv = new Env('closure', $env); $newEnv = new Env('closure', $env);
for ($i = 0; $i < count($bindings); $i++) { Util::bindArguments($newEnv, $bindings, $args);
$newEnv->set($bindings[$i]->getName(), $args[$i] ?? null);
}
return $this->eval($astData[2], $newEnv, $depth + 1); return $this->eval($astData[2], $newEnv, $depth + 1);
}; };

View File

@ -38,10 +38,7 @@ class UserFunc extends Func
{ {
$newEnv = new Env('apply', $this->tempEnv); $newEnv = new Env('apply', $this->tempEnv);
$bindings = $this->bindings->getData(); Util::bindArguments($newEnv, $this->bindings->getData(), $args);
for ($i = 0; $i < count($bindings); $i++) {
$newEnv->set($bindings[$i]->getName(), $args[$i] ?? null);
}
return $newEnv; return $newEnv;
} }

View File

@ -9,6 +9,22 @@ namespace MadLisp;
class Util class Util
{ {
public static function bindArguments(Env $env, array $bindings, array $args): void
{
for ($i = 0; $i < count($bindings); $i++) {
if ($bindings[$i]->getName() == '&') {
if ($i < count($bindings) - 1) {
$env->set($bindings[$i + 1]->getName(), new Vector(array_slice($args, $i)));
return;
} else {
throw new MadLispException('no binding after &');
}
} else {
$env->set($bindings[$i]->getName(), $args[$i] ?? null);
}
}
}
public static function makeHash(array $args): Hash public static function makeHash(array $args): Hash
{ {
if (count($args) % 2 == 1) { if (count($args) % 2 == 1) {

View File

@ -7,12 +7,74 @@
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use MadLisp\Env;
use MadLisp\Hash; use MadLisp\Hash;
use MadLisp\MadLispException; use MadLisp\MadLispException;
use MadLisp\Symbol;
use MadLisp\Util; use MadLisp\Util;
use MadLisp\Vector;
class UtilTest extends TestCase class UtilTest extends TestCase
{ {
public function testBindArguments()
{
$env = new Env('env');
Util::bindArguments($env, [
new Symbol('a'),
new Symbol('b')
], [
1,
2
]);
$this->assertSame(['a' => 1, 'b' => 2], $env->getData());
}
public function testBindArgumentsVariable()
{
$env = new Env('env');
Util::bindArguments($env, [
new Symbol('a'),
new Symbol('b'),
new Symbol('&'),
new Symbol('c')
], [
1,
2,
3,
4
]);
$data = $env->getData();
$this->assertCount(3, $data);
$this->assertSame(1, $data['a']);
$this->assertSame(2, $data['b']);
$vec = $data['c'];
$this->assertInstanceOf(Vector::class, $vec);
$this->assertSame([3, 4], $vec->getData());
}
public function testBindArgumentsVariableInvalid()
{
$this->expectException(MadLispException::class);
$this->expectExceptionMessage('no binding after &');
$env = new Env('env');
Util::bindArguments($env, [
new Symbol('a'),
new Symbol('b'),
new Symbol('&')
], [
1,
2,
3,
4
]);
}
public function testMakeHash() public function testMakeHash()
{ {
$hash = Util::makeHash(['a', 1, 'b', 2]); $hash = Util::makeHash(['a', 1, 'b', 2]);