Finished step 1
This commit is contained in:
1 parent
5b5832e475
commit
97f047e10a
4 files changed
+261
-9
No files matched your search
@@ -0,0 +1,243 @@
|
||||
import {readUserInputs} from "./src/shared.ts";
|
||||
import {assertEquals} from "jsr:@std/assert";
|
||||
|
||||
type StringToken = { type: "string"; value: string };
|
||||
type NumberToken = { type: "number"; value: number };
|
||||
type BooleanToken = { type: "boolean"; value: boolean };
|
||||
type SymToken = { type: "sym"; value: string };
|
||||
type NilToken = { type: "nil" };
|
||||
type ListToken = {
|
||||
type: "list";
|
||||
value: (
|
||||
| StringToken
|
||||
| NumberToken
|
||||
| BooleanToken
|
||||
| SymToken
|
||||
| NilToken
|
||||
| ListToken
|
||||
)[];
|
||||
};
|
||||
type AST =
|
||||
| NilToken
|
||||
| StringToken
|
||||
| NumberToken
|
||||
| BooleanToken
|
||||
| SymToken
|
||||
| ListToken;
|
||||
|
||||
function findStringEnd(s: string): number {
|
||||
let idx = 1;
|
||||
let previousChar = "";
|
||||
for (const c of Array.from(s.substring(1))) {
|
||||
if ('"' === c && previousChar !== "\\") {
|
||||
return idx;
|
||||
}
|
||||
previousChar = c;
|
||||
idx++;
|
||||
}
|
||||
throw new Error(`Syntax error. The string ${s} never ends.`);
|
||||
}
|
||||
|
||||
function findNextString(source: string): [string, string] {
|
||||
const stringEnd = findStringEnd(source);
|
||||
const ret = JSON.parse(source.substring(0, stringEnd + 1));
|
||||
return [ret, source.substring(stringEnd + 1).trim()];
|
||||
}
|
||||
|
||||
function findTokenEnd(source: string): number {
|
||||
// const match = source.match(/(\s|\))/gi);
|
||||
const re = /(\s|\))/gi;
|
||||
const ret = source.search(re);
|
||||
return ret !== -1 ? ret : source.length;
|
||||
}
|
||||
|
||||
function isNumber(token: string): boolean{
|
||||
return /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/.test(token)
|
||||
}
|
||||
|
||||
function findNextNoneStringToken(source: string): AST {
|
||||
const tokenEnd = findTokenEnd(source);
|
||||
const token = source.substring(0, tokenEnd);
|
||||
|
||||
if (token === "nil") {
|
||||
return { type: "nil" };
|
||||
}
|
||||
|
||||
if (token === "true" || token === "false") {
|
||||
return { type: "boolean", value: token === "true" };
|
||||
}
|
||||
if (isNumber(token)) {
|
||||
return { type: "number", value: JSON.parse(token) };
|
||||
}
|
||||
return { type: "sym", value: token };
|
||||
|
||||
}
|
||||
|
||||
function findNextToken(source: string): [AST, string] {
|
||||
if (source.startsWith("(")) {
|
||||
let x = source.substring(1).trim();
|
||||
let token;
|
||||
const lst = [];
|
||||
while (!x.trim().startsWith(")")) {
|
||||
[token, x] = findNextToken(x);
|
||||
lst.push(token);
|
||||
x = x.trim();
|
||||
}
|
||||
return [{ type: "list", value: lst }, x.trim().substring(1)];
|
||||
}
|
||||
|
||||
if (source.startsWith('"')) {
|
||||
const [ret, rest] = findNextString(source);
|
||||
return [{ type: "string", value: ret }, rest];
|
||||
}
|
||||
|
||||
const tokenEnd = findTokenEnd(source);
|
||||
const token = source.substring(0, tokenEnd);
|
||||
return [findNextNoneStringToken(token), source.substring(tokenEnd).trim()];
|
||||
}
|
||||
|
||||
function read(source: string): AST {
|
||||
source = source.trim();
|
||||
const [ret, rest] = findNextToken(source);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function eval_(ast: AST): AST {
|
||||
return ast;
|
||||
}
|
||||
|
||||
function print(form: AST): string {
|
||||
if (form.type === "list") {
|
||||
const ret = form.value.map(print).join(" ");
|
||||
return `(${ret})`;
|
||||
}
|
||||
if (form.type === "sym") {
|
||||
return form.value;
|
||||
}
|
||||
if (form.type === "nil") {
|
||||
return "nil";
|
||||
}
|
||||
return JSON.stringify(form.value);
|
||||
}
|
||||
|
||||
function rep(source: string): string {
|
||||
return print(eval_(read(source)));
|
||||
}
|
||||
|
||||
Deno.test("basic strings", function (): void {
|
||||
const testCases: [string, string][] = [
|
||||
['"foo"', "foo"],
|
||||
[' "foo" ', "foo"],
|
||||
['"bonjour \\"poline\\""', 'bonjour "poline"'],
|
||||
];
|
||||
for (const [input, exptValue] of testCases) {
|
||||
const expt: StringToken = { type: "string", value: exptValue };
|
||||
assertEquals(expt, read(input));
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("basic symbols", function (): void {
|
||||
const testCases: [string, string][] = [
|
||||
["foo", "foo"],
|
||||
["trueFalse", "trueFalse"],
|
||||
["nil?", "nil?"],
|
||||
];
|
||||
for (const [input, exptValue] of testCases) {
|
||||
const expt: SymToken = { type: "sym", value: exptValue };
|
||||
assertEquals(expt, read(input));
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("basic numbers", function (): void {
|
||||
const testCases: [string, number][] = [
|
||||
["567", 567],
|
||||
["-34", -34],
|
||||
["-34.5", -34.5],
|
||||
];
|
||||
for (const [input, exptValue] of testCases) {
|
||||
const expt: NumberToken = { type: "number", value: exptValue };
|
||||
assertEquals(expt, read(input));
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("basic booleans", function (): void {
|
||||
const testCases: [string, boolean][] = [
|
||||
["true", true],
|
||||
["false", false],
|
||||
];
|
||||
for (const [input, exptValue] of testCases) {
|
||||
const expt: BooleanToken = { type: "boolean", value: exptValue };
|
||||
assertEquals(expt, read(input));
|
||||
}
|
||||
});
|
||||
|
||||
function numberToken(value: number): NumberToken {
|
||||
return { type: "number", value };
|
||||
}
|
||||
|
||||
function symToken(value: string): SymToken {
|
||||
return { type: "sym", value };
|
||||
}
|
||||
|
||||
function listToken(value: (NumberToken | SymToken | ListToken)[]): ListToken {
|
||||
return { type: "list", value: value };
|
||||
}
|
||||
|
||||
Deno.test("basic list", function (): void {
|
||||
const input = "(add 3 4)";
|
||||
const expt = listToken([
|
||||
symToken("add"),
|
||||
numberToken(3),
|
||||
numberToken(4),
|
||||
]);
|
||||
assertEquals(expt, read(input));
|
||||
});
|
||||
|
||||
Deno.test("list of numbers", function (): void {
|
||||
const input = "( 1 2 3)";
|
||||
const expt = listToken([
|
||||
numberToken(1),
|
||||
numberToken(2),
|
||||
numberToken(3),
|
||||
]);
|
||||
assertEquals(expt, read(input));
|
||||
});
|
||||
|
||||
Deno.test("composed list", function (): void {
|
||||
const input = "(add (mul 4 9) (inc -5))";
|
||||
const expt = listToken([
|
||||
symToken("add"),
|
||||
listToken([symToken("mul"), numberToken(4), numberToken(9)]),
|
||||
listToken([symToken("inc"), numberToken(-5)]),
|
||||
]);
|
||||
assertEquals(expt, read(input));
|
||||
});
|
||||
|
||||
Deno.test("basic nil", function () {
|
||||
const expt: NilToken = { type: "nil" };
|
||||
assertEquals(expt, read("nil"));
|
||||
});
|
||||
|
||||
Deno.test("step 1 tests", function (): void {
|
||||
const cases: [string, string][] = [
|
||||
["123", "123"],
|
||||
[" 123 ", "123"],
|
||||
["abc", "abc"],
|
||||
[" abc ", "abc"],
|
||||
["(123 456)", "(123 456)"],
|
||||
["( 123 456 789 )", "(123 456 789)"],
|
||||
["( + 2 (* 3 4) ) ", "(+ 2 (* 3 4))"]
|
||||
];
|
||||
for (const [input, expt] of cases) {
|
||||
assertEquals(expt, print(read(input)));
|
||||
}
|
||||
});
|
||||
|
||||
function main(): void {
|
||||
for (const userInput of readUserInputs()) {
|
||||
rep(userInput);
|
||||
}
|
||||
}
|
||||
|
||||
// main();
|
||||
Reference in new issue
Block a user