This commit is contained in:
Pekka Laiho 2020-05-27 18:54:39 +07:00
parent 20f8aeab51
commit 766b162bc4
3 changed files with 194 additions and 110 deletions

View File

@ -2,21 +2,69 @@
class MadLispException extends Exception {}
class Env
abstract class MLCollection
{
private array $data = [];
private ?Env $parent;
protected array $data = [];
public function __construct(?Env $parent = null)
public function __construct(array $data = [])
{
$this->parent = $parent;
$this->data = $data;
}
public function has(string $key)
public function count(): int
{
return count($this->data);
}
public function has(string $key): bool
{
return array_key_exists($key, $this->data);
}
public function getData(): array
{
return $this->data;
}
}
class MLList extends MLCollection
{
public function get(int $index)
{
if ($this->has($index)) {
return $this->data[$index];
}
throw new MadLispException("list does not contain index $index");
}
}
class MLHash extends MLCollection
{
public function get(string $key)
{
if ($this->has($key)) {
return $this->data[$key];
}
throw new MadLispException("hash does not contain key $key");
}
public function set(string $key, $value): void
{
$this->data[$key] = $value;
}
}
class MLEnv extends MLHash
{
protected ?MLEnv $parent;
public function __construct(?MLEnv $parent = null)
{
$this->parent = $parent;
}
public function get(string $key)
{
if ($this->has($key)) {
@ -27,9 +75,19 @@ class Env
throw new MadLispException("symbol $key not defined");
}
}
public function set(string $key, $value): void
class MLSymbol
{
protected string $name;
public function __construct(string $name)
{
$this->data[$key] = $value;
$this->name = $name;
}
public function name(): string
{
return $this->name;
}
}

60
lib.php
View File

@ -2,16 +2,62 @@
require_once('classes.php');
function ml_get_env(): Env
function ml_get_env(): MLEnv
{
$env = new Env();
$env = new MLEnv();
// basic arithmetic
$env->set('+', fn (...$args) => array_sum($args));
$env->set('-', fn ($a, $b) => $a - $b);
$env->set('*', fn ($a, $b) => $a * $b);
$env->set('/', fn ($a, $b) => $a / $b);
$env->set('%', fn ($a, $b) => $a % $b);
$env->set('+', function (...$args) {
return array_sum($args);
});
$env->set('-', function (...$args) {
$result = $args[0] ?? null;
for ($i = 1; $i < count($args); $i++) {
$result -= $args[$i];
}
return $result;
});
$env->set('*', function (...$args) {
$result = $args[0] ?? null;
for ($i = 1; $i < count($args); $i++) {
$result *= $args[$i];
}
return $result;
});
$env->set('/', function (...$args) {
$result = $args[0] ?? null;
for ($i = 1; $i < count($args); $i++) {
$result /= $args[$i];
}
return $result;
});
$env->set('%', function (...$args) {
$result = $args[0] ?? null;
for ($i = 1; $i < count($args); $i++) {
$result %= $args[$i];
}
return $result;
});
// comparison
$env->set('=', fn ($a, $b) => $a == $b);
$env->set('<', fn ($a, $b) => $a < $b);
$env->set('>', fn ($a, $b) => $a > $b);
$env->set('<=', fn ($a, $b) => $a <= $b);
$env->set('>=', fn ($a, $b) => $a >= $b);
$env->set('!=', fn ($a, $b) => $a != $b);
// types
$env->set('type?', function ($a) {
return gettype($a);
});
return $env;
}

170
lisp.php
View File

@ -2,16 +2,6 @@
require_once('classes.php');
// types
define("SYMBOL", "SYMBOL");
define("STRING", "STRING");
define("NUMBER", "NUMBER");
define("START", "START");
define("END", "END");
// special sign for symbols
define("MAGIC", "§");
function ml_tokenize(string $a): array
{
$tokens = [];
@ -19,21 +9,9 @@ function ml_tokenize(string $a): array
$string = false;
$parens = 0;
$addCurrent = function ($string = false) use (&$tokens, &$current) {
if ($current !== '' || $string) {
if ($string) {
$tokens[] = [STRING, $current];
} elseif ($current == 'true') {
$tokens[] = [true];
} elseif ($current == 'false') {
$tokens[] = [false];
} elseif ($current == 'null') {
$tokens[] = [null];
} elseif (is_numeric($current)) {
$tokens[] = [NUMBER, $current];
} else {
$tokens[] = [SYMBOL, $current];
}
$addCurrent = function () use (&$tokens, &$current) {
if ($current !== '') {
$tokens[] = $current;
$current = '';
}
};
@ -41,36 +19,43 @@ function ml_tokenize(string $a): array
for ($i = 0; $i < strlen($a); $i++) {
$c = substr($a, $i, 1);
if ($c == '"') {
if ($string) {
// End of string
$addCurrent(true);
$string = false;
} else {
// Start of string
$string = true;
}
} elseif ($c == ' ' || $c == "\t" || $c == "\n" || $c == "\r") {
if ($string) {
// Include whitespace only inside strings
$current .= $c;
} else {
$addCurrent();
}
} elseif ($c == '(') {
$addCurrent();
$tokens[] = [START];
$parens++;
} elseif ($c == ')') {
if ($parens == 0) {
throw new MadLispException("unexpected closing parenthesis");
}
$addCurrent();
$tokens[] = [END];
$parens--;
} else {
// All other characters are included normally
if ($string) {
// Inside string, add all characters
$current .= $c;
// Stop at "
if ($c == '"') {
$addCurrent();
$string = false;
}
} else {
// Not inside string
if ($c == '"') {
// Start of string
$addCurrent();
$current .= $c;
$string = true;
} elseif ($c == ' ' || $c == "\t" || $c == "\n" || $c == "\r") {
// Whitespace is ignored
$addCurrent();
} elseif ($c == '(') {
// Start of list
$addCurrent();
$tokens[] = '(';
$parens++;
} elseif ($c == ')') {
// End of list
if ($parens == 0) {
throw new MadLispException("unexpected closing parenthesis");
}
$addCurrent();
$tokens[] = ')';
$parens--;
} else {
// All other characters
$current .= $c;
}
}
}
@ -89,48 +74,51 @@ function ml_tokenize(string $a): array
function ml_read_form(array $tokens, int &$index)
{
$a = $tokens[$index];
if ($a[0] == START) {
if ($tokens[$index] == '(') {
return ml_read_list($tokens, $index);
} else {
return ml_read_atom($tokens, $index);
}
}
function ml_read_list(array $tokens, int &$index): array
function ml_read_list(array $tokens, int &$index): MLList
{
$result = [];
// start tag
$index++;
while ($tokens[$index][0] != END) {
while ($tokens[$index] != ')') {
$result[] = ml_read_form($tokens, $index);
}
// end tag
$index++;
return $result;
return new MLList($result);
}
function ml_read_atom(array $tokens, int &$index)
{
$a = $tokens[$index++];
if ($a[0] == STRING) {
return $a[1];
} elseif ($a[0] == SYMBOL) {
return MAGIC . $a[1];
} elseif ($a[0] == NUMBER) {
if (filter_var($a[1], FILTER_VALIDATE_INT) !== false) {
return intval($a[1]);
if ($a === 'true') {
return true;
} elseif ($a === 'false') {
return false;
} elseif ($a === 'null') {
return null;
} elseif (substr($a, 0, 1) === '"') {
// string
return substr($a, 1, strlen($a) - 2);
} elseif (is_numeric($a)) {
if (filter_var($a, FILTER_VALIDATE_INT) !== false) {
return intval($a);
} else {
return floatval($a[1]);
return floatval($a);
}
} else {
return $a[0];
return new MLSymbol($a);
}
}
@ -146,16 +134,6 @@ function ml_parse(array $tokens): array
return $result;
}
function ml_is_symbol($a)
{
return substr($a, 0, strlen(MAGIC)) === MAGIC;
}
function ml_strip_symbol($a)
{
return substr($a, strlen(MAGIC));
}
function ml_read(string $code): array
{
$tokens = ml_tokenize($code);
@ -165,20 +143,22 @@ function ml_read(string $code): array
return $expressions;
}
function ml_eval($expr, Env $env)
function ml_eval($expr, MLEnv $env)
{
if (is_array($expr)) {
// Evaluate list items
$expr = array_map(fn ($a) => ml_eval($a, $env), $expr);
if ($expr instanceof MLList && $expr->count() > 0) {
// Evaluate list contents
$results = array_map(fn ($a) => ml_eval($a, $env), $expr->getData());
// If the first item is a function, call it
$fn = $expr[0] ?? null;
if ($fn && $fn instanceof Closure) {
$args = array_slice($expr, 1);
return $fn(...$args);
if ($results[0] instanceof Closure) {
// If the first item is a function, call it
$args = array_slice($results, 1);
return ($results[0])(...$args);
} else {
// Otherwise return new list with evaluated contents
return new MLList($results);
}
} elseif (ml_is_symbol($expr)) {
return $env->get(ml_strip_symbol($expr));
} elseif ($expr instanceof MLSymbol) {
return $env->get($expr->name());
}
return $expr;
@ -188,16 +168,16 @@ function ml_print($a): string
{
if ($a instanceof Closure) {
return '<function>';
} elseif (is_array($a)) {
return '(' . implode(' ', array_map('ml_print', $a)) . ')';
} elseif ($a instanceof MLList) {
return '(' . implode(' ', array_map('ml_print', $a->getData())) . ')';
} elseif ($a instanceof MLSymbol) {
return $a->name();
} elseif ($a === true) {
return 'true';
} elseif ($a === false) {
return 'false';
} elseif ($a === null) {
return 'null';
} elseif (ml_is_symbol($a)) {
return ml_strip_symbol($a);
} elseif (is_string($a)) {
return '"' . $a . '"';
} else {
@ -205,7 +185,7 @@ function ml_print($a): string
}
}
function ml_rep(string $input, Env $env): string
function ml_rep(string $input, MLEnv $env): string
{
$expressions = ml_read($input);