diff --git a/.idea/deno.xml b/.idea/deno.xml
new file mode 100644
index 0000000..2e4b145
--- /dev/null
+++ b/.idea/deno.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/shared.ts b/src/shared.ts
new file mode 100644
index 0000000..cf8bd92
--- /dev/null
+++ b/src/shared.ts
@@ -0,0 +1,10 @@
+
+export function* readUserInputs(): Generator{
+ while(true){
+ const u = prompt("user> ")
+ if(u === null){
+ return
+ }
+ yield u
+ }
+}
\ No newline at end of file
diff --git a/step0_repl.ts b/step0_repl.ts
index e7b346c..b998ad6 100644
--- a/step0_repl.ts
+++ b/step0_repl.ts
@@ -1,3 +1,5 @@
+import {readUserInputs} from "./src/shared.ts";
+
function read(source: string): string {
return source;
}
@@ -14,15 +16,6 @@ function rep(source: string): string{
return print(eval_(read( source)))
}
-function* readUserInputs(): Generator{
- while(true){
- const u = prompt("user> ")
- if(u === null){
- return
- }
- yield u
- }
-}
function main(): void {
diff --git a/step1_read_print.ts b/step1_read_print.ts
new file mode 100644
index 0000000..6c58fb0
--- /dev/null
+++ b/step1_read_print.ts
@@ -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();