Optimization: read astData to memory instead of calling get() many times in eval

This commit is contained in:
Pekka Laiho 2020-12-03 17:27:26 +07:00
parent 3c9885c7f9
commit 60726f0923

View File

@ -40,15 +40,17 @@ class Evaller
return $this->evalAst($ast, $env); return $this->evalAst($ast, $env);
} }
$astData = $ast->getData();
$astLength = count($astData);
// Empty list // Empty list
$astLength = $ast->count();
if ($astLength == 0) { if ($astLength == 0) {
return $ast; return $ast;
} }
// Handle special forms // Handle special forms
if ($ast->get(0) instanceof Symbol) { if ($astData[0] instanceof Symbol) {
$symbolName = $ast->get(0)->getName(); $symbolName = $astData[0]->getName();
if ($symbolName == 'and') { if ($symbolName == 'and') {
if ($astLength == 1) { if ($astLength == 1) {
@ -56,13 +58,13 @@ class Evaller
} }
for ($i = 1; $i < $astLength - 1; $i++) { for ($i = 1; $i < $astLength - 1; $i++) {
$value = $this->eval($ast->get($i), $env); $value = $this->eval($astData[$i], $env);
if ($value == false) { if ($value == false) {
return $value; return $value;
} }
} }
$ast = $ast->get($astLength - 1); $ast = $astData[$astLength - 1];
continue; // tco continue; // tco
} elseif ($symbolName == 'case') { } elseif ($symbolName == 'case') {
if ($astLength < 2) { if ($astLength < 2) {
@ -70,16 +72,16 @@ class Evaller
} }
for ($i = 1; $i < $astLength - 1; $i += 2) { for ($i = 1; $i < $astLength - 1; $i += 2) {
$test = $this->eval($ast->get($i), $env); $test = $this->eval($astData[$i], $env);
if ($test == true) { if ($test == true) {
$ast = $ast->get($i + 1); $ast = $astData[$i + 1];
continue 2; // tco continue 2; // tco
} }
} }
// Last value, no test // Last value, no test
if ($astLength % 2 == 0) { if ($astLength % 2 == 0) {
$ast = $ast->get($astLength - 1); $ast = $astData[$astLength - 1];
continue; // tco continue; // tco
} else { } else {
return null; return null;
@ -89,11 +91,11 @@ class Evaller
throw new MadLispException("def requires exactly 2 arguments"); throw new MadLispException("def requires exactly 2 arguments");
} }
if (!($ast->get(1) instanceof Symbol)) { if (!($astData[1] instanceof Symbol)) {
throw new MadLispException("first argument to def is not symbol"); throw new MadLispException("first argument to def is not symbol");
} }
$name = $ast->get(1)->getName(); $name = $astData[1]->getName();
// Do not allow reserved symbols to be defined // Do not allow reserved symbols to be defined
$reservedSymbols = ['__FILE__', '__DIR__']; $reservedSymbols = ['__FILE__', '__DIR__'];
@ -101,7 +103,7 @@ class Evaller
throw new MadLispException("def reserved symbol $name"); throw new MadLispException("def reserved symbol $name");
} }
$value = $this->eval($ast->get(2), $env); $value = $this->eval($astData[2], $env);
return $env->set($name, $value); return $env->set($name, $value);
} elseif ($symbolName == 'do') { } elseif ($symbolName == 'do') {
if ($astLength == 1) { if ($astLength == 1) {
@ -109,18 +111,18 @@ class Evaller
} }
for ($i = 1; $i < $astLength - 1; $i++) { for ($i = 1; $i < $astLength - 1; $i++) {
$this->eval($ast->get($i), $env); $this->eval($astData[$i], $env);
} }
$ast = $ast->get($astLength - 1); $ast = $astData[$astLength - 1];
continue; // tco continue; // tco
} elseif ($symbolName == 'env') { } elseif ($symbolName == 'env') {
if ($astLength >= 2) { if ($astLength >= 2) {
if (!($ast->get(1) instanceof Symbol)) { if (!($astData[1] instanceof Symbol)) {
throw new MadLispException("first argument to env is not symbol"); throw new MadLispException("first argument to env is not symbol");
} }
return $env->get($ast->get(1)->getName()); return $env->get($astData[1]->getName());
} else { } else {
return $env; return $env;
} }
@ -129,18 +131,18 @@ class Evaller
return null; return null;
} }
$ast = $this->eval($ast->get(1), $env); $ast = $this->eval($astData[1], $env);
continue; // tco continue; // tco
} elseif ($symbolName == 'fn') { } elseif ($symbolName == 'fn') {
if ($astLength != 3) { if ($astLength != 3) {
throw new MadLispException("fn requires exactly 2 arguments"); throw new MadLispException("fn requires exactly 2 arguments");
} }
if (!($ast->get(1) instanceof MList)) { if (!($astData[1] instanceof MList)) {
throw new MadLispException("first argument to fn is not list"); throw new MadLispException("first argument to fn is not list");
} }
$bindings = $ast->get(1)->getData(); $bindings = $astData[1]->getData();
foreach ($bindings as $bind) { foreach ($bindings as $bind) {
if (!($bind instanceof Symbol)) { if (!($bind instanceof Symbol)) {
throw new MadLispException("binding key for fn is not symbol"); throw new MadLispException("binding key for fn is not symbol");
@ -154,22 +156,22 @@ class Evaller
$newEnv->set($bindings[$i]->getName(), $args[$i] ?? null); $newEnv->set($bindings[$i]->getName(), $args[$i] ?? null);
} }
return $this->eval($ast->get(2), $newEnv); return $this->eval($astData[2], $newEnv);
}; };
return new UserFunc($closure, $ast->get(2), $env, $bindings); return new UserFunc($closure, $astData[2], $env, $bindings);
} elseif ($symbolName == 'if') { } elseif ($symbolName == 'if') {
if ($astLength < 3 || $astLength > 4) { if ($astLength < 3 || $astLength > 4) {
throw new MadLispException("if requires 2 or 3 arguments"); throw new MadLispException("if requires 2 or 3 arguments");
} }
$result = $this->eval($ast->get(1), $env); $result = $this->eval($astData[1], $env);
if ($result == true) { if ($result == true) {
$ast = $ast->get(2); $ast = $astData[2];
continue; continue;
} elseif ($astLength == 4) { } elseif ($astLength == 4) {
$ast = $ast->get(3); $ast = $astData[3];
continue; continue;
} else { } else {
return null; return null;
@ -179,11 +181,11 @@ class Evaller
throw new MadLispException("let requires exactly 2 arguments"); throw new MadLispException("let requires exactly 2 arguments");
} }
if (!($ast->get(1) instanceof MList)) { if (!($astData[1] instanceof MList)) {
throw new MadLispException("first argument to let is not list"); throw new MadLispException("first argument to let is not list");
} }
$bindings = $ast->get(1)->getData(); $bindings = $astData[1]->getData();
if (count($bindings) % 2 == 1) { if (count($bindings) % 2 == 1) {
throw new MadLispException("uneven number of bindings for let"); throw new MadLispException("uneven number of bindings for let");
@ -202,7 +204,7 @@ class Evaller
$newEnv->set($key->getName(), $val); $newEnv->set($key->getName(), $val);
} }
$ast = $ast->get(2); $ast = $astData[2];
$env = $newEnv; $env = $newEnv;
continue; // tco continue; // tco
} elseif ($symbolName == 'load') { } elseif ($symbolName == 'load') {
@ -214,7 +216,7 @@ class Evaller
} }
// We have to evaluate the argument, it could be a function // We have to evaluate the argument, it could be a function
$filename = $this->eval($ast->get(1), $env); $filename = $this->eval($astData[1], $env);
if (!is_string($filename)) { if (!is_string($filename)) {
throw new MadLispException("first argument to load is not string"); throw new MadLispException("first argument to load is not string");
@ -256,29 +258,30 @@ class Evaller
} }
for ($i = 1; $i < $astLength - 1; $i++) { for ($i = 1; $i < $astLength - 1; $i++) {
$value = $this->eval($ast->get($i), $env); $value = $this->eval($astData[$i], $env);
if ($value == true) { if ($value == true) {
return $value; return $value;
} }
} }
$ast = $ast->get($astLength - 1); $ast = $astData[$astLength - 1];
continue; // tco continue; // tco
} elseif ($symbolName == 'quote') { } elseif ($symbolName == 'quote') {
if ($astLength != 2) { if ($astLength != 2) {
throw new MadLispException("quote requires exactly 1 argument"); throw new MadLispException("quote requires exactly 1 argument");
} }
return $ast->get(1); return $astData[1];
} }
} }
// Get new evaluated list // Get new evaluated list
$ast = $this->evalAst($ast, $env); $ast = $this->evalAst($ast, $env);
$astData = $ast->getData();
// First item is function, rest are arguments // First item is function, rest are arguments
$func = $ast->get(0); $func = $astData[0];
$args = array_slice($ast->getData(), 1); $args = array_slice($astData, 1);
if ($func instanceof CoreFunc) { if ($func instanceof CoreFunc) {
return $func->call($args); return $func->call($args);