Optimization: keep macro names in cache to avoid unnecessary macro lookups

This commit is contained in:
Pekka Laiho 2020-12-06 17:32:29 +07:00
parent 9459c07529
commit 351647944d

View File

@ -3,6 +3,10 @@ namespace MadLisp;
class Evaller class Evaller
{ {
// Keep cache of macro names so we can skip
// macro expansion when possible.
protected static array $macros = [];
protected Tokenizer $tokenizer; protected Tokenizer $tokenizer;
protected Reader $reader; protected Reader $reader;
protected Printer $printer; protected Printer $printer;
@ -21,50 +25,52 @@ class Evaller
public function eval($ast, Env $env, int $depth = 1) public function eval($ast, Env $env, int $depth = 1)
{ {
// Loop for tail call optimization // Loop for tail call optimization
$loops = 0; $isTco = false;
while (true) { while (true) {
// Return fast for optimization // Return here after macro expansion
// Check two times: before and after macro expansion $expandMacros = true;
for ($check = 0; $check <= 1; $check++) { beginning:
// Return fast for optimization if not list
if (!($ast instanceof MList)) { if (!($ast instanceof MList)) {
if ($ast instanceof Symbol || $ast instanceof Collection) { if ($ast instanceof Symbol || $ast instanceof Collection) {
return $this->evalAst($ast, $env, $depth); return $this->evalAst($ast, $env, $depth);
} else { } else {
// This is not evaluated so we can just return it. // This is not evaluated so we can just return it
// and save one extra call to evalAst.
return $ast; return $ast;
} }
} }
// Perform macro expansion on first iteration
if ($check == 0) {
$ast = $this->macroexpand($ast, $env);
}
}
$astData = $ast->getData(); $astData = $ast->getData();
$astLength = count($astData); $astLength = count($astData);
// Empty list, return // Empty list, return we can also return
if ($astLength == 0) { if ($astLength == 0) {
return $ast; return $ast;
} }
// Show debug output here // Show debug output here (before macro expansion)
if ($this->debug) { if ($this->debug && $expandMacros) {
if ($loops++ == 0) { printf("%s %2d : ", $isTco ? ' tco' : 'eval', $depth);
printf("eval %2d : ", $depth);
} else {
printf(" tco %2d : ", $depth);
}
$this->printer->print($ast); $this->printer->print($ast);
print("\n"); print("\n");
$isTco = true;
} }
// Handle special forms // Handle special forms
if ($astData[0] instanceof Symbol) { if ($astData[0] instanceof Symbol) {
$symbolName = $astData[0]->getName(); $symbolName = $astData[0]->getName();
// Handle macro expansion and go back to beginning to check
// again if ast is still something we need to evaluate or not.
if ($expandMacros && array_key_exists($symbolName, self::$macros)) {
$ast = $this->macroexpand($ast, $env);
$expandMacros = false;
goto beginning;
}
if ($symbolName == 'and') { if ($symbolName == 'and') {
if ($astLength == 1) { if ($astLength == 1) {
return true; return true;
@ -117,6 +123,12 @@ class Evaller
} }
$value = $this->eval($astData[2], $env, $depth + 1); $value = $this->eval($astData[2], $env, $depth + 1);
// Save macros in cache
if ($value instanceof Func && $value->isMacro()) {
self::$macros[$name] = $value;
}
return $env->set($name, $value); return $env->set($name, $value);
} elseif ($symbolName == 'do') { } elseif ($symbolName == 'do') {
if ($astLength == 1) { if ($astLength == 1) {