madlisp/src/Evaller.php

222 lines
8.1 KiB
PHP
Raw Normal View History

2020-05-28 10:10:00 +00:00
<?php
namespace MadLisp;
class Evaller
{
2020-06-04 02:10:48 +00:00
public function eval($ast, Env $env)
2020-05-28 12:41:41 +00:00
{
2020-06-04 14:26:00 +00:00
while (true) {
2020-06-04 11:00:30 +00:00
2020-06-04 14:26:00 +00:00
// Not list or empty list
if (!($ast instanceof MList)) {
return $this->evalAst($ast, $env);
} elseif ($ast->count() == 0) {
return $ast;
}
2020-06-04 11:26:26 +00:00
2020-06-04 14:26:00 +00:00
// Handle special forms
if ($ast->get(0) instanceof Symbol) {
if ($ast->get(0)->getName() == 'and') {
if ($ast->count() == 1) {
return true;
2020-06-04 11:26:26 +00:00
}
2020-06-04 14:26:00 +00:00
for ($i = 1; $i < $ast->count() - 1; $i++) {
$value = $this->eval($ast->get($i), $env);
if ($value == false) {
return $value;
}
}
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
$ast = $ast->get($ast->count() - 1);
continue; // tco
} elseif ($ast->get(0)->getName() == 'case') {
if ($ast->count() < 2) {
throw new MadLispException("case requires at least 1 argument");
}
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
for ($i = 1; $i < $ast->count() - 1; $i += 2) {
$test = $this->eval($ast->get($i), $env);
if ($test == true) {
2020-06-04 14:39:26 +00:00
$ast = $ast->get($i + 1);
continue 2; // tco
2020-06-04 14:26:00 +00:00
}
}
2020-05-30 12:19:37 +00:00
2020-06-04 14:26:00 +00:00
// Last value, no test
if ($ast->count() % 2 == 0) {
2020-06-04 14:39:26 +00:00
$ast = $ast->get($ast->count() - 1);
continue; // tco
2020-06-04 14:26:00 +00:00
} else {
return null;
}
} elseif ($ast->get(0)->getName() == 'def') {
if ($ast->count() != 3) {
throw new MadLispException("def requires exactly 2 arguments");
}
2020-05-30 12:19:37 +00:00
2020-06-04 10:36:01 +00:00
if (!($ast->get(1) instanceof Symbol)) {
2020-06-04 14:26:00 +00:00
throw new MadLispException("first argument to def is not symbol");
2020-06-04 10:36:01 +00:00
}
2020-06-04 14:26:00 +00:00
$value = $this->eval($ast->get(2), $env);
return $env->set($ast->get(1)->getName(), $value);
} elseif ($ast->get(0)->getName() == 'do') {
2020-06-06 02:39:12 +00:00
if ($ast->count() == 1) {
return null;
2020-06-04 14:26:00 +00:00
}
2020-05-31 09:35:03 +00:00
2020-06-04 14:26:00 +00:00
for ($i = 1; $i < $ast->count() - 1; $i++) {
$this->eval($ast->get($i), $env);
}
2020-05-31 09:35:03 +00:00
2020-06-04 14:26:00 +00:00
$ast = $ast->get($ast->count() - 1);
continue; // tco
} elseif ($ast->get(0)->getName() == 'env') {
if ($ast->count() >= 2) {
if (!($ast->get(1) instanceof 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");
2020-05-31 09:35:03 +00:00
}
2020-06-04 14:26:00 +00:00
if (!($ast->get(1) instanceof MList)) {
throw new MadLispException("first argument to fn is not list");
}
2020-05-31 09:35:03 +00:00
2020-06-04 14:26:00 +00:00
$bindings = $ast->get(1)->getData();
foreach ($bindings as $bind) {
if (!($bind instanceof Symbol)) {
throw new MadLispException("binding key for fn is not symbol");
}
2020-05-31 09:35:03 +00:00
}
$closure = function (...$args) use ($bindings, $ast, $env) {
2020-06-04 14:26:00 +00:00
$newEnv = new Env($env);
2020-05-30 12:02:41 +00:00
2020-06-04 14:26:00 +00:00
for ($i = 0; $i < count($bindings); $i++) {
$newEnv->set($bindings[$i]->getName(), $args[$i] ?? null);
}
2020-05-30 12:02:41 +00:00
2020-06-04 14:26:00 +00:00
return $this->eval($ast->get(2), $newEnv);
};
return new UserFunc($closure, $ast->get(2), $env, $bindings);
2020-06-04 14:26:00 +00:00
} elseif ($ast->get(0)->getName() == 'if') {
if ($ast->count() < 3 || $ast->count() > 4) {
throw new MadLispException("if requires 2 or 3 arguments");
}
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
$result = $this->eval($ast->get(1), $env);
if ($result == true) {
$ast = $ast->get(2);
continue;
} elseif ($ast->count() == 4) {
$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");
}
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
if (!($ast->get(1) instanceof MList)) {
throw new MadLispException("first argument to let is not list");
}
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
$bindings = $ast->get(1)->getData();
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
if (count($bindings) % 2 == 1) {
throw new MadLispException("uneven number of bindings for let");
}
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
$newEnv = new Env($env);
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
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);
$newEnv->set($key->getName(), $val);
2020-05-30 11:47:54 +00:00
}
2020-06-04 14:26:00 +00:00
$ast = $ast->get(2);
$env = $newEnv;
continue; // tco
} elseif ($ast->get(0)->getName() == 'or') {
if ($ast->count() == 1) {
return false;
}
2020-05-30 11:47:54 +00:00
2020-06-04 14:26:00 +00:00
for ($i = 1; $i < $ast->count() - 1; $i++) {
$value = $this->eval($ast->get($i), $env);
if ($value == true) {
return $value;
}
}
2020-06-04 11:00:30 +00:00
2020-06-04 14:26:00 +00:00
$ast = $ast->get($ast->count() - 1);
continue; // tco
} elseif ($ast->get(0)->getName() == 'quote') {
if ($ast->count() != 2) {
throw new MadLispException("quote requires exactly 1 argument");
2020-06-04 11:00:30 +00:00
}
2020-06-04 14:26:00 +00:00
return $ast->get(1);
2020-05-28 13:30:37 +00:00
}
}
2020-06-04 14:26:00 +00:00
// Get new evaluated list
$ast = $this->evalAst($ast, $env);
2020-05-28 10:10:00 +00:00
// First item is function, rest are arguments
2020-06-04 14:26:00 +00:00
$func = $ast->get(0);
$args = array_slice($ast->getData(), 1);
if ($func instanceof CoreFunc) {
return $func->call($args);
} elseif ($func instanceof UserFunc) {
$ast = $func->getAst();
$env = $func->getEnv($args);
} else {
2020-06-04 14:26:00 +00:00
throw new MadLispException("eval: first item of list is not function");
}
}
2020-05-28 10:10:00 +00:00
}
2020-06-04 02:10:48 +00:00
private function evalAst($ast, Env $env)
{
if ($ast instanceof Symbol) {
// Lookup symbol from env
return $env->get($ast->getName());
2020-06-05 08:55:38 +00:00
} elseif ($ast instanceof Seq) {
$results = [];
foreach ($ast->getData() as $val) {
$results[] = $this->eval($val, $env);
}
return $ast::new($results);
2020-06-04 02:10:48 +00:00
} elseif ($ast instanceof Hash) {
$results = [];
foreach ($ast->getData() as $key => $val) {
$results[$key] = $this->eval($val, $env);
}
return new Hash($results);
}
return $ast;
}
2020-05-28 10:10:00 +00:00
}