start tail call optimization

This commit is contained in:
Pekka Laiho 2020-06-04 21:26:00 +07:00
parent 291a7d6521
commit cb1a03e9bd

View File

@ -3,180 +3,202 @@ namespace MadLisp;
class Evaller class Evaller
{ {
private $p = null;
public function eval($ast, Env $env) public function eval($ast, Env $env)
{ {
// Not list or empty list // Debug
if (!($ast instanceof MList)) { if ($this->p == null) {
return $this->evalAst($ast, $env); $this->p = new Printer();
} elseif ($ast->count() == 0) {
return $ast;
} }
print("eval: ");
$this->p->print($ast);
print("\n");
// Handle special forms while (true) {
if ($ast->get(0) instanceof Symbol) {
if ($ast->get(0)->getName() == 'and') {
if ($ast->count() == 1) {
return true;
}
for ($i = 1; $i < $ast->count(); $i++) { // Not list or empty list
$value = $this->eval($ast->get($i), $env); if (!($ast instanceof MList)) {
if ($value == false) { return $this->evalAst($ast, $env);
return $value; } elseif ($ast->count() == 0) {
return $ast;
}
// Handle special forms
if ($ast->get(0) instanceof Symbol) {
if ($ast->get(0)->getName() == 'and') {
if ($ast->count() == 1) {
return true;
} }
}
return $value; for ($i = 1; $i < $ast->count() - 1; $i++) {
} elseif ($ast->get(0)->getName() == 'case') { $value = $this->eval($ast->get($i), $env);
if ($ast->count() < 2) { if ($value == false) {
throw new MadLispException("case requires at least 1 argument"); return $value;
} }
for ($i = 1; $i < $ast->count() - 1; $i += 2) {
$test = $this->eval($ast->get($i), $env);
if ($test == true) {
return $this->eval($ast->get($i + 1), $env);
} }
}
// Last value, no test $ast = $ast->get($ast->count() - 1);
if ($ast->count() % 2 == 0) { continue; // tco
return $this->eval($ast->get($ast->count() - 1), $env); } elseif ($ast->get(0)->getName() == 'case') {
} else { if ($ast->count() < 2) {
return null; throw new MadLispException("case requires at least 1 argument");
} }
} elseif ($ast->get(0)->getName() == 'def') {
if ($ast->count() != 3) {
throw new MadLispException("def requires exactly 2 arguments");
}
if (!($ast->get(1) instanceof Symbol)) { for ($i = 1; $i < $ast->count() - 1; $i += 2) {
throw new MadLispException("first argument to def is not symbol"); $test = $this->eval($ast->get($i), $env);
} if ($test == true) {
return $this->eval($ast->get($i + 1), $env);
}
}
$value = $this->eval($ast->get(2), $env); // Last value, no test
return $env->set($ast->get(1)->getName(), $value); if ($ast->count() % 2 == 0) {
} elseif ($ast->get(0)->getName() == 'do') { return $this->eval($ast->get($ast->count() - 1), $env);
if ($ast->count() < 2) { } else {
throw new MadLispException("do requires at least 1 argument"); return null;
} }
} elseif ($ast->get(0)->getName() == 'def') {
if ($ast->count() != 3) {
throw new MadLispException("def requires exactly 2 arguments");
}
for ($i = 1; $i < $ast->count(); $i++) {
$value = $this->eval($ast->get($i), $env);
}
return $value;
} elseif ($ast->get(0)->getName() == 'env') {
if ($ast->count() >= 2) {
if (!($ast->get(1) instanceof Symbol)) { if (!($ast->get(1) instanceof Symbol)) {
throw new MadLispException("first argument to env is not symbol"); throw new MadLispException("first argument to def is not symbol");
} }
return $env->get($ast->get(1)->getName()); $value = $this->eval($ast->get(2), $env);
} else { return $env->set($ast->get(1)->getName(), $value);
return $env; } elseif ($ast->get(0)->getName() == 'do') {
} if ($ast->count() < 2) {
} elseif ($ast->get(0)->getName() == 'fn') { throw new MadLispException("do requires at least 1 argument");
if ($ast->count() != 3) { }
throw new MadLispException("fn requires exactly 2 arguments");
} for ($i = 1; $i < $ast->count() - 1; $i++) {
$this->eval($ast->get($i), $env);
if (!($ast->get(1) instanceof MList)) { }
throw new MadLispException("first argument to fn is not list");
} $ast = $ast->get($ast->count() - 1);
continue; // tco
$bindings = $ast->get(1)->getData(); } elseif ($ast->get(0)->getName() == 'env') {
foreach ($bindings as $bind) { if ($ast->count() >= 2) {
if (!($bind instanceof Symbol)) { if (!($ast->get(1) instanceof Symbol)) {
throw new MadLispException("binding key for fn is not symbol"); throw new MadLispException("first argument to env is not symbol");
}
return $env->get($ast->get(1)->getName());
} else {
return $env;
}
} elseif ($ast->get(0)->getName() == 'fn') {
if ($ast->count() != 3) {
throw new MadLispException("fn requires exactly 2 arguments");
}
if (!($ast->get(1) instanceof MList)) {
throw new MadLispException("first argument to fn is not list");
}
$bindings = $ast->get(1)->getData();
foreach ($bindings as $bind) {
if (!($bind instanceof Symbol)) {
throw new MadLispException("binding key for fn is not symbol");
}
}
return new UserFunc(function (...$args) use ($bindings, $ast, $env) {
$newEnv = new Env($env);
for ($i = 0; $i < count($bindings); $i++) {
$newEnv->set($bindings[$i]->getName(), $args[$i] ?? null);
}
return $this->eval($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");
}
$result = $this->eval($ast->get(1), $env);
if ($result == true) {
echo "if tco\n";
$ast = $ast->get(2);
continue;
} elseif ($ast->count() == 4) {
echo "if tco\n";
$ast = $ast->get(3);
continue;
} else {
return null;
}
} elseif ($ast->get(0)->getName() == 'let') {
if ($ast->count() != 3) {
throw new MadLispException("let requires exactly 2 arguments");
}
if (!($ast->get(1) instanceof MList)) {
throw new MadLispException("first argument to let is not list");
}
$bindings = $ast->get(1)->getData();
if (count($bindings) % 2 == 1) {
throw new MadLispException("uneven number of bindings for let");
} }
}
return new UserFunc(function (...$args) use ($bindings, $ast, $env) {
$newEnv = new Env($env); $newEnv = new Env($env);
for ($i = 0; $i < count($bindings); $i++) { for ($i = 0; $i < count($bindings) - 1; $i += 2) {
$newEnv->set($bindings[$i]->getName(), $args[$i] ?? null); $key = $bindings[$i];
if (!($key instanceof Symbol)) {
throw new MadLispException("binding key for let is not symbol");
}
$val = $this->eval($bindings[$i + 1], $newEnv);
$newEnv->set($key->getName(), $val);
} }
return $this->eval($ast->get(2), $newEnv); $ast = $ast->get(2);
}); $env = $newEnv;
} elseif ($ast->get(0)->getName() == 'if') { continue; // tco
if ($ast->count() < 3 || $ast->count() > 4) { } elseif ($ast->get(0)->getName() == 'or') {
throw new MadLispException("if requires 2 or 3 arguments"); if ($ast->count() == 1) {
} return false;
$result = $this->eval($ast->get(1), $env);
if ($result == true) {
return $this->eval($ast->get(2), $env);
} elseif ($ast->count() == 4) {
return $this->eval($ast->get(3), $env);
} else {
return null;
}
} elseif ($ast->get(0)->getName() == 'let') {
if ($ast->count() != 3) {
throw new MadLispException("let requires exactly 2 arguments");
}
if (!($ast->get(1) instanceof MList)) {
throw new MadLispException("first argument to let is not list");
}
$bindings = $ast->get(1)->getData();
if (count($bindings) % 2 == 1) {
throw new MadLispException("uneven number of bindings for let");
}
$newEnv = new Env($env);
for ($i = 0; $i < count($bindings) - 1; $i += 2) {
$key = $bindings[$i];
if (!($key instanceof Symbol)) {
throw new MadLispException("binding key for let is not symbol");
} }
$val = $this->eval($bindings[$i + 1], $newEnv); for ($i = 1; $i < $ast->count() - 1; $i++) {
$newEnv->set($key->getName(), $val); $value = $this->eval($ast->get($i), $env);
} if ($value == true) {
return $value;
return $this->eval($ast->get(2), $newEnv); }
} elseif ($ast->get(0)->getName() == 'or') {
if ($ast->count() == 1) {
return false;
}
for ($i = 1; $i < $ast->count(); $i++) {
$value = $this->eval($ast->get($i), $env);
if ($value == true) {
return $value;
} }
}
return $value; $ast = $ast->get($ast->count() - 1);
} elseif ($ast->get(0)->getName() == 'quote') { continue; // tco
if ($ast->count() != 2) { } elseif ($ast->get(0)->getName() == 'quote') {
throw new MadLispException("quote requires exactly 1 argument"); if ($ast->count() != 2) {
} throw new MadLispException("quote requires exactly 1 argument");
}
return $ast->get(1); return $ast->get(1);
}
} }
// Get new evaluated list
$ast = $this->evalAst($ast, $env);
// Call first argument as function
$func = $ast->get(0);
if (!($func instanceof Func)) {
throw new MadLispException("eval: first item of list is not function");
}
$args = array_slice($ast->getData(), 1);
return $func->call($args);
} }
// Get new evaluated list
$ast = $this->evalAst($ast, $env);
// Call first argument as function
$func = $ast->get(0);
if (!($func instanceof Func)) {
throw new MadLispException("eval: first item of list is not function");
}
$args = array_slice($ast->getData(), 1);
return $func->call($args);
} }
private function evalAst($ast, Env $env) private function evalAst($ast, Env $env)