mirror of
https://github.com/peklaiho/madlisp.git
synced 2024-11-26 07:04:27 +00:00
cleanup
This commit is contained in:
parent
20f8aeab51
commit
766b162bc4
74
classes.php
74
classes.php
@ -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
60
lib.php
@ -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
170
lisp.php
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user