Implemented step 2.

This commit is contained in:
didier committed 2025-05-26 10:28:11 -04:00
1 parent 97f047e10a
commit df9e91ebd5
4 files changed
+149 -51

No files matched your search

+32 -45
View File
@@ -1,12 +1,11 @@
import {readUserInputs} from "./src/shared.ts"; import {assertEquals} from "https://deno.land/std@0.224.0/assert/assert_equals.ts";
import {assertEquals} from "jsr:@std/assert";
type StringToken = { type: "string"; value: string }; export type StringToken = { type: "string"; value: string };
type NumberToken = { type: "number"; value: number }; export type NumberToken = { type: "number"; value: number };
type BooleanToken = { type: "boolean"; value: boolean }; export type BooleanToken = { type: "boolean"; value: boolean };
type SymToken = { type: "sym"; value: string }; export type SymToken = { type: "sym"; value: string };
type NilToken = { type: "nil" }; export type NilToken = { type: "nil"; value: null };
type ListToken = { export type ListToken = {
type: "list"; type: "list";
value: ( value: (
| StringToken | StringToken
@@ -15,14 +14,19 @@ type ListToken = {
| SymToken | SymToken
| NilToken | NilToken
| ListToken | ListToken
| FunctionToken
)[]; )[];
}; };
type AST =
export type FunctionToken = {type: 'function', value: (...args: any[]) => any};
export type AST =
| NilToken | NilToken
| StringToken | StringToken
| NumberToken | NumberToken
| BooleanToken | BooleanToken
| SymToken | SymToken
| FunctionToken
| ListToken; | ListToken;
function findStringEnd(s: string): number { function findStringEnd(s: string): number {
@@ -51,8 +55,8 @@ function findTokenEnd(source: string): number {
return ret !== -1 ? ret : source.length; return ret !== -1 ? ret : source.length;
} }
function isNumber(token: string): boolean{ function isNumber(token: string): boolean {
return /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/.test(token) return /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/.test(token);
} }
function findNextNoneStringToken(source: string): AST { function findNextNoneStringToken(source: string): AST {
@@ -60,7 +64,7 @@ function findNextNoneStringToken(source: string): AST {
const token = source.substring(0, tokenEnd); const token = source.substring(0, tokenEnd);
if (token === "nil") { if (token === "nil") {
return { type: "nil" }; return { type: "nil", value: null };
} }
if (token === "true" || token === "false") { if (token === "true" || token === "false") {
@@ -70,14 +74,13 @@ function findNextNoneStringToken(source: string): AST {
return { type: "number", value: JSON.parse(token) }; return { type: "number", value: JSON.parse(token) };
} }
return { type: "sym", value: token }; return { type: "sym", value: token };
} }
function findNextToken(source: string): [AST, string] { function findNextToken(source: string): [AST, string] {
if (source.startsWith("(")) { if (source.startsWith("(")) {
let x = source.substring(1).trim(); let x = source.substring(1).trim();
let token; let token;
const lst = []; const lst: AST[] = [];
while (!x.trim().startsWith(")")) { while (!x.trim().startsWith(")")) {
[token, x] = findNextToken(x); [token, x] = findNextToken(x);
lst.push(token); lst.push(token);
@@ -96,20 +99,16 @@ function findNextToken(source: string): [AST, string] {
return [findNextNoneStringToken(token), source.substring(tokenEnd).trim()]; return [findNextNoneStringToken(token), source.substring(tokenEnd).trim()];
} }
function read(source: string): AST { export function readAST(source: string): AST {
source = source.trim(); source = source.trim();
const [ret, rest] = findNextToken(source); const [ret, _] = findNextToken(source);
return ret; return ret;
} }
function eval_(ast: AST): AST { export function printAST(form: AST): string {
return ast;
}
function print(form: AST): string {
if (form.type === "list") { if (form.type === "list") {
const ret = form.value.map(print).join(" "); const ret = form.value.map(printAST).join(" ");
return `(${ret})`; return `(${ret})`;
} }
if (form.type === "sym") { if (form.type === "sym") {
@@ -121,10 +120,6 @@ function print(form: AST): string {
return JSON.stringify(form.value); return JSON.stringify(form.value);
} }
function rep(source: string): string {
return print(eval_(read(source)));
}
Deno.test("basic strings", function (): void { Deno.test("basic strings", function (): void {
const testCases: [string, string][] = [ const testCases: [string, string][] = [
['"foo"', "foo"], ['"foo"', "foo"],
@@ -133,7 +128,7 @@ Deno.test("basic strings", function (): void {
]; ];
for (const [input, exptValue] of testCases) { for (const [input, exptValue] of testCases) {
const expt: StringToken = { type: "string", value: exptValue }; const expt: StringToken = { type: "string", value: exptValue };
assertEquals(expt, read(input)); assertEquals(expt, readAST(input));
} }
}); });
@@ -145,7 +140,7 @@ Deno.test("basic symbols", function (): void {
]; ];
for (const [input, exptValue] of testCases) { for (const [input, exptValue] of testCases) {
const expt: SymToken = { type: "sym", value: exptValue }; const expt: SymToken = { type: "sym", value: exptValue };
assertEquals(expt, read(input)); assertEquals(expt, readAST(input));
} }
}); });
@@ -157,7 +152,7 @@ Deno.test("basic numbers", function (): void {
]; ];
for (const [input, exptValue] of testCases) { for (const [input, exptValue] of testCases) {
const expt: NumberToken = { type: "number", value: exptValue }; const expt: NumberToken = { type: "number", value: exptValue };
assertEquals(expt, read(input)); assertEquals(expt, readAST(input));
} }
}); });
@@ -168,7 +163,7 @@ Deno.test("basic booleans", function (): void {
]; ];
for (const [input, exptValue] of testCases) { for (const [input, exptValue] of testCases) {
const expt: BooleanToken = { type: "boolean", value: exptValue }; const expt: BooleanToken = { type: "boolean", value: exptValue };
assertEquals(expt, read(input)); assertEquals(expt, readAST(input));
} }
}); });
@@ -191,7 +186,7 @@ Deno.test("basic list", function (): void {
numberToken(3), numberToken(3),
numberToken(4), numberToken(4),
]); ]);
assertEquals(expt, read(input)); assertEquals(expt, readAST(input));
}); });
Deno.test("list of numbers", function (): void { Deno.test("list of numbers", function (): void {
@@ -201,7 +196,7 @@ Deno.test("list of numbers", function (): void {
numberToken(2), numberToken(2),
numberToken(3), numberToken(3),
]); ]);
assertEquals(expt, read(input)); assertEquals(expt, readAST(input));
}); });
Deno.test("composed list", function (): void { Deno.test("composed list", function (): void {
@@ -211,12 +206,12 @@ Deno.test("composed list", function (): void {
listToken([symToken("mul"), numberToken(4), numberToken(9)]), listToken([symToken("mul"), numberToken(4), numberToken(9)]),
listToken([symToken("inc"), numberToken(-5)]), listToken([symToken("inc"), numberToken(-5)]),
]); ]);
assertEquals(expt, read(input)); assertEquals(expt, readAST(input));
}); });
Deno.test("basic nil", function () { Deno.test("basic nil", function () {
const expt: NilToken = { type: "nil" }; const expt: NilToken = { type: "nil", value: null };
assertEquals(expt, read("nil")); assertEquals(expt, readAST("nil"));
}); });
Deno.test("step 1 tests", function (): void { Deno.test("step 1 tests", function (): void {
@@ -227,17 +222,9 @@ Deno.test("step 1 tests", function (): void {
[" abc ", "abc"], [" abc ", "abc"],
["(123 456)", "(123 456)"], ["(123 456)", "(123 456)"],
["( 123 456 789 )", "(123 456 789)"], ["( 123 456 789 )", "(123 456 789)"],
["( + 2 (* 3 4) ) ", "(+ 2 (* 3 4))"] ["( + 2 (* 3 4) ) ", "(+ 2 (* 3 4))"],
]; ];
for (const [input, expt] of cases) { for (const [input, expt] of cases) {
assertEquals(expt, print(read(input))); assertEquals(expt, printAST(readAST(input)));
} }
}); });
function main(): void {
for (const userInput of readUserInputs()) {
rep(userInput);
}
}
// main();
+2 -6
View File
@@ -1,4 +1,4 @@
import {readUserInputs} from "./src/shared.ts"; import {readUserInputs} from "./shared.ts";
function read(source: string): string { function read(source: string): string {
return source; return source;
@@ -18,12 +18,8 @@ function rep(source: string): string{
function main(): void { export function main(): void {
for(const userInput of readUserInputs()){ for(const userInput of readUserInputs()){
rep(userInput); rep(userInput);
} }
} }
main();
+25
View File
@@ -0,0 +1,25 @@
import {readUserInputs} from "./shared.ts";
import {AST, readAST, printAST} from './reader-printer.ts';
function read(source: string): AST {
return readAST(source);
}
function eval_(ast: AST): AST {
return ast;
}
function print(form: AST): string {
return printAST(form);
}
function rep(source: string): string {
return print(eval_(read(source)));
}
export function main(): void {
for (const userInput of readUserInputs()) {
rep(userInput);
}
}
+90
View File
@@ -0,0 +1,90 @@
import {assertEquals} from "https://deno.land/std@0.224.0/assert/assert_equals.ts";
import {AST, FunctionToken, printAST, readAST} from "./reader-printer.ts";
type MalEnv = Map<string, FunctionToken>;
function functionToken(fn: (...args: any[]) => any): FunctionToken {
return { type: "function", value: fn };
}
function isBoolean(v: unknown): v is boolean {
return v === true || v === false;
}
function isNumber(v: unknown): v is number {
return typeof v === "number" && isFinite(v);
}
function isString(v: unknown): v is string {
return typeof v === "string" || v instanceof String;
}
class REPL {
private readonly env: MalEnv;
constructor(env: MalEnv) {
this.env = env;
}
static mathRepl(): REPL {
const mathFunctions = new Map<string, FunctionToken>();
mathFunctions.set("+", functionToken((a, b) => a + b));
mathFunctions.set("-", functionToken((a, b) => a - b));
mathFunctions.set("*", functionToken((a, b) => a * b));
mathFunctions.set("/", functionToken((a, b) => a / b));
return new REPL(mathFunctions);
}
read(source: string): AST {
return readAST(source);
}
cast(value: unknown): AST {
if (value === true || value === false) {
return { type: "boolean", value: value };
}
if (isBoolean(value)) {
return { type: "boolean", value: value };
}
if (isString(value)) {
return { type: "string", value: value };
}
if (isNumber(value)) {
return { type: "number", value: value };
}
throw new Error(`Unknown return type from functon: ${value}`);
}
eval_(ast: AST): AST {
if (ast.type === "list") {
const fn = this.eval_(ast.value[0]) as FunctionToken;
const ret = fn.value(
...ast.value.slice(1).map((n) => this.eval_(n).value),
);
return this.cast(ret);
}
if (ast.type === "sym") {
const ret = this.env.get(ast.value);
if (ret === undefined) {
throw new Error(`Unknown reference ${ast.value}`);
}
return ret;
}
return ast;
}
print(form: AST): string {
return printAST(form);
}
rep(source: string): string {
return this.print(this.eval_(this.read(source)));
}
}
Deno.test("basic", function (): void {
const repl = REPL.mathRepl();
assertEquals(repl.rep("(+ 1 1)"), "2");
assertEquals(repl.rep("(+ 2 (* 3 4))"), "14");
});