Implemented step 2.
This commit is contained in:
1 parent
97f047e10a
commit
df9e91ebd5
4 files changed
+148
-50
No files matched your search
@@ -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 {
|
||||||
@@ -52,7 +56,7 @@ function findTokenEnd(source: string): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
@@ -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();
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
});
|
||||||
Reference in new issue
Block a user