2020-05-28 10:10:00 +00:00
|
|
|
<?php
|
|
|
|
namespace MadLisp;
|
|
|
|
|
|
|
|
class Tokenizer
|
|
|
|
{
|
|
|
|
public function tokenize(string $a): array
|
|
|
|
{
|
|
|
|
$tokens = [];
|
|
|
|
$current = '';
|
2020-05-31 02:56:23 +00:00
|
|
|
|
|
|
|
$isString = false;
|
|
|
|
$isComment = false;
|
|
|
|
|
|
|
|
$parens = [0, 0, 0];
|
|
|
|
$parenIndexes = ['(' => 0, ')' => 0, '[' => 1, ']' => 1, '{' => 2, '}' => 2];
|
2020-05-28 10:10:00 +00:00
|
|
|
|
|
|
|
$addCurrent = function () use (&$tokens, &$current) {
|
|
|
|
if ($current !== '') {
|
|
|
|
$tokens[] = $current;
|
|
|
|
$current = '';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for ($i = 0; $i < strlen($a); $i++) {
|
|
|
|
$c = substr($a, $i, 1);
|
|
|
|
|
2020-05-31 02:56:23 +00:00
|
|
|
if ($isString) {
|
2020-05-28 10:10:00 +00:00
|
|
|
// Inside string, add all characters
|
|
|
|
$current .= $c;
|
|
|
|
|
2020-05-31 04:34:24 +00:00
|
|
|
// Stop at first double quote
|
2020-05-28 10:10:00 +00:00
|
|
|
if ($c == '"') {
|
2020-06-16 13:15:57 +00:00
|
|
|
// If previous character is not a backslash
|
|
|
|
if (strlen($current) < 2 || substr($current, -2, 1) != "\\") {
|
|
|
|
$addCurrent();
|
|
|
|
$isString = false;
|
|
|
|
}
|
2020-05-31 02:56:23 +00:00
|
|
|
}
|
|
|
|
} elseif ($isComment) {
|
|
|
|
// Comments stop at first newline
|
|
|
|
if ($c == "\n" || $c == "\r") {
|
|
|
|
$isComment = false;
|
2020-05-28 10:10:00 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-05-31 02:56:23 +00:00
|
|
|
// Not inside string or comment
|
2020-05-28 10:10:00 +00:00
|
|
|
|
|
|
|
if ($c == '"') {
|
|
|
|
// Start of string
|
|
|
|
$addCurrent();
|
|
|
|
$current .= $c;
|
2020-05-31 02:56:23 +00:00
|
|
|
$isString = true;
|
|
|
|
} elseif ($c == ';') {
|
|
|
|
// Start of comment
|
|
|
|
$addCurrent();
|
|
|
|
$isComment = true;
|
2020-06-04 01:28:54 +00:00
|
|
|
} elseif ($c == ' ' || $c == "\t" || $c == "\n" || $c == "\r" || $c == ':') {
|
|
|
|
// Whitespace and colon are ignored
|
2020-05-28 10:10:00 +00:00
|
|
|
$addCurrent();
|
2020-05-31 02:56:23 +00:00
|
|
|
} elseif ($c == '(' || $c == '[' || $c == '{') {
|
|
|
|
// Start of collection
|
2020-05-28 10:10:00 +00:00
|
|
|
$addCurrent();
|
2020-05-31 02:56:23 +00:00
|
|
|
$tokens[] = $c;
|
|
|
|
$parens[$parenIndexes[$c]]++;
|
|
|
|
} elseif ($c == ')' || $c == ']' || $c == '}') {
|
|
|
|
// End of collection
|
|
|
|
if ($parens[$parenIndexes[$c]] == 0) {
|
|
|
|
throw new MadLispException("unexpected closing $c");
|
2020-05-28 10:10:00 +00:00
|
|
|
}
|
|
|
|
$addCurrent();
|
2020-05-31 02:56:23 +00:00
|
|
|
$tokens[] = $c;
|
|
|
|
$parens[$parenIndexes[$c]]--;
|
2020-12-05 10:38:42 +00:00
|
|
|
} elseif ($c == "'" || $c == "`" || $c == "~") {
|
2020-05-31 02:56:23 +00:00
|
|
|
// Other special characters
|
|
|
|
$addCurrent();
|
|
|
|
$tokens[] = $c;
|
2020-05-28 10:10:00 +00:00
|
|
|
} else {
|
|
|
|
// All other characters
|
|
|
|
$current .= $c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-31 02:56:23 +00:00
|
|
|
// Add last token
|
2020-05-28 10:10:00 +00:00
|
|
|
$addCurrent();
|
|
|
|
|
|
|
|
// Check for errors
|
2020-12-05 07:53:21 +00:00
|
|
|
if ($isString) {
|
|
|
|
throw new MadLispException("unterminated string");
|
|
|
|
} elseif ($parens[0] != 0) {
|
2020-05-31 02:56:23 +00:00
|
|
|
throw new MadLispException("missing closing )");
|
|
|
|
} elseif ($parens[1] != 0) {
|
|
|
|
throw new MadLispException("missing closing ]");
|
|
|
|
} elseif ($parens[2] != 0) {
|
|
|
|
throw new MadLispException("missing closing }");
|
2020-05-28 10:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $tokens;
|
|
|
|
}
|
|
|
|
}
|