not finished

This commit is contained in:
Pekka Laiho 2020-05-27 16:27:47 +07:00
parent 2b73c245b2
commit 20f8aeab51
4 changed files with 180 additions and 31 deletions

35
classes.php Normal file
View File

@ -0,0 +1,35 @@
<?php
class MadLispException extends Exception {}
class Env
{
private array $data = [];
private ?Env $parent;
public function __construct(?Env $parent = null)
{
$this->parent = $parent;
}
public function has(string $key)
{
return array_key_exists($key, $this->data);
}
public function get(string $key)
{
if ($this->has($key)) {
return $this->data[$key];
} elseif ($this->parent) {
return $this->parent->get($key);
}
throw new MadLispException("symbol $key not defined");
}
public function set(string $key, $value): void
{
$this->data[$key] = $value;
}
}

17
lib.php Normal file
View File

@ -0,0 +1,17 @@
<?php
require_once('classes.php');
function ml_get_env(): Env
{
$env = new Env();
// 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);
return $env;
}

150
lisp.php
View File

@ -1,13 +1,17 @@
<?php <?php
class MadLispException extends Exception {} require_once('classes.php');
// types
define("SYMBOL", "SYMBOL"); define("SYMBOL", "SYMBOL");
define("STRING", "STRING"); define("STRING", "STRING");
define("NUMBER", "NUMBER"); define("NUMBER", "NUMBER");
define("START", "START"); define("START", "START");
define("END", "END"); define("END", "END");
// special sign for symbols
define("MAGIC", "§");
function ml_tokenize(string $a): array function ml_tokenize(string $a): array
{ {
$tokens = []; $tokens = [];
@ -15,10 +19,21 @@ function ml_tokenize(string $a): array
$string = false; $string = false;
$parens = 0; $parens = 0;
$addCurrent = function($addEmpty = false) use (&$string, &$tokens, &$current) { $addCurrent = function ($string = false) use (&$tokens, &$current) {
if ($current !== '' || $addEmpty) { if ($current !== '' || $string) {
$type = $string ? STRING : (is_numeric($current) ? NUMBER : SYMBOL); if ($string) {
$tokens[] = [$type, $current]; $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];
}
$current = ''; $current = '';
} }
}; };
@ -63,11 +78,7 @@ function ml_tokenize(string $a): array
$addCurrent(); $addCurrent();
// Check for errors // Check for errors
if (empty($tokens)) { if ($parens != 0) {
throw new MadLispException("no input");
} elseif ($tokens[0][0] !== START) {
throw new MadLispException("missing opening parenthesis");
} elseif ($parens != 0) {
throw new MadLispException("missing closing parenthesis"); throw new MadLispException("missing closing parenthesis");
} elseif ($string) { } elseif ($string) {
throw new MadLispException("unterminated string"); throw new MadLispException("unterminated string");
@ -76,44 +87,129 @@ function ml_tokenize(string $a): array
return $tokens; return $tokens;
} }
function ml_parse_expressions($tokens, &$index) function ml_read_form(array $tokens, int &$index)
{
$a = $tokens[$index];
if ($a[0] == START) {
return ml_read_list($tokens, $index);
} else {
return ml_read_atom($tokens, $index);
}
}
function ml_read_list(array $tokens, int &$index): array
{ {
$result = []; $result = [];
for (; $index < count($tokens); ) { // start tag
if ($token[0] == START) { $index++;
while ($tokens[$index][0] != END) {
$result[] = ml_read_form($tokens, $index);
} }
if ($token[0] == END) {
break; // end tag
$index++;
return $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]);
} else {
return floatval($a[1]);
}
} else {
return $a[0];
}
}
function ml_parse(array $tokens): array
{
$result = [];
$index = 0;
while ($index < count($tokens)) {
$result[] = ml_read_form($tokens, $index);
} }
return $result; return $result;
} }
function ml_read($a) function ml_is_symbol($a)
{ {
$tokens = ml_tokenize($a); return substr($a, 0, strlen(MAGIC)) === MAGIC;
}
$index = 0; function ml_strip_symbol($a)
$expressions = ml_parse_expressions($tokens, $index); {
return substr($a, strlen(MAGIC));
}
function ml_read(string $code): array
{
$tokens = ml_tokenize($code);
$expressions = ml_parse($tokens);
return $expressions; return $expressions;
} }
function ml_eval($a) function ml_eval($expr, Env $env)
{ {
if (is_array($expr)) {
// Evaluate list items
$expr = array_map(fn ($a) => ml_eval($a, $env), $expr);
// 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);
}
} elseif (ml_is_symbol($expr)) {
return $env->get(ml_strip_symbol($expr));
}
return $expr;
}
function ml_print($a): string
{
if ($a instanceof Closure) {
return '<function>';
} elseif (is_array($a)) {
return '(' . implode(' ', array_map('ml_print', $a)) . ')';
} 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 {
return $a; return $a;
} }
function ml_print($a)
{
// print(implode('*', $a));
print_r($a);
} }
function ml_rep($a) function ml_rep(string $input, Env $env): string
{ {
ml_print(ml_eval(ml_read($a))); $expressions = ml_read($input);
$results = array_map(fn ($expr) => ml_eval($expr, $env), $expressions);
return implode(" ", array_map('ml_print', $results));
} }

View File

@ -1,14 +1,15 @@
<?php <?php
require('lisp.php'); require_once('lib.php');
require_once('lisp.php');
$env = ml_get_env();
while (true) { while (true) {
$input = readline('> '); $input = readline('> ');
print('% ');
try { try {
ml_rep($input); print(ml_rep($input, $env));
} catch (MadLispException $ex) { } catch (MadLispException $ex) {
print('error: ' . $ex->getMessage()); print('error: ' . $ex->getMessage());
} }