]> git.kianting.info Git - anotherTypesetter/commitdiff
add recursion
authorTan Kian-ting <chenjt30@gmail.com>
Sat, 6 Apr 2024 08:13:44 +0000 (16:13 +0800)
committerTan Kian-ting <chenjt30@gmail.com>
Sat, 6 Apr 2024 08:13:44 +0000 (16:13 +0800)
README.md
src/index.js
src/index.ts

index 4c9b1e64d30ccaeafb27cccb22d76f9d2fbc61a9..b77e925a8b95f8c1a17bd2f274ef0dcc00084c14 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
 ## Just another interpretation of typesetting
 ## TODO
  - [v] apply - 240403
- - [v] letrec - 240403
+ - [v] letrec - 240405
  - [v] car - 240403
- - [ ] cdr and cons
- - [ ] concat string (++)
+ - [v] cdr and cons
+ - [v] concat string (++)
  - [ ] create pdf
  - [ ] close pdf
  - [ ] add character
index 919edbcc32dfc62192ac997c3f5a363929de477e..dfbc12c32295c41d7971aced00dc0eba5e23e023 100644 (file)
@@ -33,7 +33,7 @@ const tokenizer = (0, typescript_parsec_1.buildLexer)([
     [true, /^\d+\.\d+/g, TokenKind.Flo],
     [true, /^true/g, TokenKind.Bool],
     [true, /^false/g, TokenKind.Bool],
-    [true, /^([+\-*/a-zA-Z_][0-9+\-*/a-zA-Z_]*|[<>]=?|==)/g, TokenKind.Id],
+    [true, /^([+\-*/a-zA-Z_][0-9+\-*/a-zA-Z_]*|[<>]=?|!?=)/g, TokenKind.Id],
     [true, /^\"([^\"]|\\\")+\"/g, TokenKind.Str],
     [true, /^[(]/g, TokenKind.LParen],
     [true, /^[)]/g, TokenKind.RParen],
@@ -44,7 +44,7 @@ const tokenizer = (0, typescript_parsec_1.buildLexer)([
     [true, /^\\/g, TokenKind.BSlash],
     [true, /^([^+\-*/a-zA-Z_0-9\[\]()'\s\t\r\n\\]+)/g, TokenKind.Other],
 ]);
-/**
+/*
  * ## BNF
 LISP = UNIT | LISPS | CON_STR
 LISPS = "(" LISP ")" | "'" "(" LISP ")"
@@ -95,7 +95,7 @@ function applyStr(value) {
     };
 }
 function applyBool(value) {
-    if (value.text == "true") {
+    if (value.text === "true") {
         return {
             type: ItemType.Bool,
             bool: true,
@@ -112,48 +112,53 @@ function applyList(value) {
     return value;
 }
 function applyQuoted(value) {
-    let head = { type: ItemType.Id, id: "quote" };
-    let merged = [head, value];
+    const head = { type: ItemType.Id, id: "quote" };
+    const merged = [head, value];
     return merged;
 }
 function applyStrings(value) {
-    let head = [{ type: ItemType.Id, id: "%concat" }];
-    let merged = head.concat(value);
+    const head = [{ type: ItemType.Id, id: "%concat" }];
+    const merged = head.concat(value);
     return merged;
 }
 /** for convinence to omit the spaces and newlines */
-let __ = (0, typescript_parsec_2.opt)((0, typescript_parsec_2.tok)(TokenKind.SpaceNL));
+const __ = (0, typescript_parsec_2.opt)((0, typescript_parsec_2.tok)(TokenKind.SpaceNL));
 LISP.setPattern((0, typescript_parsec_2.alt)((0, typescript_parsec_2.kleft)(SINGLE, __), (0, typescript_parsec_2.kleft)(LISPS, __), (0, typescript_parsec_2.kleft)(CON_STR, __)));
 SINGLE.setPattern((0, typescript_parsec_2.alt)((0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Id), applyId), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Int), applyInt), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Flo), applyFlo), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Str), applyStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Bool), applyBool)));
 LISPS.setPattern((0, typescript_parsec_2.alt)((0, typescript_parsec_2.apply)((0, typescript_parsec_2.kmid)((0, typescript_parsec_2.seq)((0, typescript_parsec_2.str)("("), __), (0, typescript_parsec_2.rep_sc)(LISP), (0, typescript_parsec_2.str)(")")), applyList), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.str)("'"), (0, typescript_parsec_2.kmid)((0, typescript_parsec_2.seq)((0, typescript_parsec_2.str)("("), __), (0, typescript_parsec_2.rep_sc)(LISP), (0, typescript_parsec_2.str)(")"))), applyQuoted)));
 CON_STR_INNER.setPattern((0, typescript_parsec_2.alt)((0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Id), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Int), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Flo), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Str), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Other), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.SpaceNL), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.LParen)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.RParen)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.LBrack)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.RBrack)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.Apos)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.BSlash)), bSlashTokenToStr), LISPS));
 CON_STR.setPattern((0, typescript_parsec_2.apply)((0, typescript_parsec_2.kmid)((0, typescript_parsec_2.str)("["), (0, typescript_parsec_2.rep_sc)(CON_STR_INNER), (0, typescript_parsec_2.str)("]")), applyStrings));
-function astToString(ast) {
+function astToString(ast, isInQuoted) {
     if (Array.isArray(ast)) {
-        let ast2 = ast.map(astToString);
+        const ast2 = ast.map((x) => astToString(x, isInQuoted));
         return "(" + ast2.join(" ") + ")";
     }
     else {
-        if (ast.type == ItemType.Str) {
+        if (ast.type === ItemType.Str) {
             return "`" + ast.str + "`";
         }
-        else if (ast.type == ItemType.Id) {
+        else if (ast.type === ItemType.Id) {
             return ast.id;
         }
-        else if (ast.type == ItemType.Flo) {
+        else if (ast.type === ItemType.Flo) {
             return ast.flo.toString();
         }
-        else if (ast.type == ItemType.Bool) {
+        else if (ast.type === ItemType.Bool) {
             return ast.bool.toString();
         }
-        else if (ast.type == ItemType.Clos) {
-            let binding = astToString(ast.vars);
-            let body = astToString(ast.body);
+        else if (ast.type === ItemType.Clos) {
+            const binding = astToString(ast.vars);
+            const body = astToString(ast.body);
             return `<closure; binding : ${binding}, body : ${body}>`;
         }
-        else if (ast.type == ItemType.Ls) {
-            let body = astToString(ast.list);
-            return "'" + body;
+        else if (ast.type === ItemType.Ls) {
+            const body = astToString(ast.list, true);
+            if (isInQuoted) {
+                return body;
+            }
+            else {
+                return "'" + body;
+            }
         }
         else {
             return ast.int.toString();
@@ -163,36 +168,48 @@ function astToString(ast) {
 function isItem(x) {
     return !Array.isArray(x);
 }
-function interpBinary(op, argsMapped, isBool) {
-    let fst = argsMapped[0];
-    let snd = argsMapped[1];
-    if (argsMapped.length == 2 && isItem(fst) && isItem(snd)) {
-        if (fst.type == ItemType.Flo && snd.type == ItemType.Flo) {
-            if (isBool == true) {
-                return {
-                    type: ItemType.Bool,
-                    bool: op(fst.flo, snd.flo),
-                };
-            }
+function interpBinary(op, argsMapped) {
+    const fst = argsMapped[0];
+    const snd = argsMapped[1];
+    if (argsMapped.length === 2 && isItem(fst) && isItem(snd)) {
+        if (fst.type === ItemType.Flo && snd.type === ItemType.Flo) {
             return {
                 type: ItemType.Flo,
                 flo: op(fst.flo, snd.flo),
             };
         }
-        else if (fst.type == ItemType.Int && snd.type == ItemType.Int) {
-            if (isBool == true) {
-                return {
-                    type: ItemType.Bool,
-                    bool: op(fst.int, snd.int),
-                };
-            }
+        else if (fst.type === ItemType.Int && snd.type === ItemType.Int) {
             return {
                 type: ItemType.Int,
                 int: op(fst.int, snd.int),
             };
         }
         else {
-            throw new Error("the type of add should be (int, int) or (flo, flo");
+            throw new Error("the type of add should be (int, int) or (flo, flo)");
+        }
+    }
+    else {
+        throw new Error(`the number of args of ${op} should be 2, but it's ${argsMapped}`);
+    }
+}
+function interpBinaryBool(op, argsMapped) {
+    const fst = argsMapped[0];
+    const snd = argsMapped[1];
+    if (argsMapped.length === 2 && isItem(fst) && isItem(snd)) {
+        if (fst.type === ItemType.Flo && snd.type === ItemType.Flo) {
+            return {
+                type: ItemType.Bool,
+                bool: op(fst.flo, snd.flo),
+            };
+        }
+        else if (fst.type === ItemType.Int && snd.type === ItemType.Int) {
+            return {
+                type: ItemType.Bool,
+                bool: op(fst.int, snd.int),
+            };
+        }
+        else {
+            throw new Error("the type of add should be (int, int) or (flo, flo)");
         }
     }
     else {
@@ -218,19 +235,29 @@ function gt(x, y) {
     return x > y;
 }
 function eq(x, y) {
-    return x == y;
+    return x === y;
 }
 function le(x, y) {
     return x <= y;
 }
+function ne(x, y) {
+    return x !== y;
+}
 function ge(x, y) {
     return x >= y;
 }
+function concatString(l, r) {
+    const rtn = {
+        type: ItemType.Str,
+        str: l.str + r.str,
+    };
+    return rtn;
+}
 /** list manipulation */
 function car(x) {
-    let fst = x.list[0];
+    const fst = x.list[0];
     if (Array.isArray(fst)) {
-        let rtnList = {
+        const rtnList = {
             type: ItemType.Ls,
             list: fst,
         };
@@ -240,18 +267,37 @@ function car(x) {
         return fst;
     }
 }
+function cdr(x) {
+    if (x.list.length == 0) {
+        throw new Error("the argument of 'cdr' can't be a empty list.");
+    }
+    const remained = x.list.slice(1);
+    const rtnList = {
+        type: ItemType.Ls,
+        list: remained,
+    };
+    return rtnList;
+}
+function cons(h, t) {
+    const inner = [h].concat(t.list);
+    const rtnList = {
+        type: ItemType.Ls,
+        list: inner,
+    };
+    return rtnList;
+}
 function extendEnv(env, vari, isRec, data) {
     // add var
     if (!(vari in env)) {
-        env[vari] = [{ isRec: isRec, value: data }];
+        env[vari] = [{ isRec, value: data }];
         // update
     }
     else {
-        env[vari] = [{ isRec: isRec, value: data }].concat(env[vari]);
+        env[vari] = [{ isRec, value: data }].concat(env[vari]);
     }
     return env;
 }
-var emptyEnv = {};
+const emptyEnv = {};
 /**
  * @throws {Error}
  */
@@ -270,11 +316,11 @@ function isClosure(x) {
 function interp(prog, env) {
     if (Array.isArray(prog)) {
         if (!Array.isArray(prog[0])) {
-            let op = prog[0];
-            if (op.type == ItemType.Id) {
+            const op = prog[0];
+            if (op.type === ItemType.Id) {
                 // a list
-                if (op.id == "quote") {
-                    let body = prog[1];
+                if (op.id === "quote") {
+                    const body = prog[1];
                     if (!Array.isArray(body)) {
                         throw new Error("the argument of quote, aka: " + body + ", is not a list.");
                     }
@@ -285,10 +331,10 @@ function interp(prog, env) {
                         };
                     }
                 }
-                /**lambda */
-                else if (op.id == "lambda") {
-                    let vars = prog[1];
-                    if (prog.length != 3) {
+                /* lambda */
+                else if (op.id === "lambda") {
+                    const vars = prog[1];
+                    if (prog.length !== 3) {
                         throw invalidLengthException('lambda', 2);
                     }
                     else if (!isItemArray(vars)) {
@@ -297,17 +343,17 @@ function interp(prog, env) {
                     else {
                         return {
                             type: ItemType.Clos,
-                            env: env,
-                            vars: vars,
+                            env,
+                            vars,
                             body: prog[2],
                         };
                     }
                 }
                 /** let function */
-                else if (op.id == "let" || op.id == "letrec") {
-                    let bindings = prog[1];
-                    if (prog.length != 3) {
-                        if (op.id == "let") {
+                else if (op.id === "let" || op.id === "letrec") {
+                    const bindings = prog[1];
+                    if (prog.length !== 3) {
+                        if (op.id === "let") {
                             throw invalidLengthException('let', 2);
                         }
                         else {
@@ -318,12 +364,12 @@ function interp(prog, env) {
                         throw new Error("the bindings should be array");
                     }
                     else {
-                        var newEnv = structuredClone(env);
-                        for (var i = 0; i < bindings.length; i++) {
-                            let binding = bindings[i];
+                        let newEnv = structuredClone(env);
+                        for (let i = 0; i < bindings.length; i++) {
+                            const binding = bindings[i];
                             if (!Array.isArray(binding)
-                                || (binding).length != 2) {
-                                if (op.id == "let") {
+                                || binding.length !== 2) {
+                                if (op.id === "let") {
                                     throw new Error("malformed of let.");
                                 }
                                 else {
@@ -331,11 +377,11 @@ function interp(prog, env) {
                                 }
                             }
                             else {
-                                let vari = binding[0];
+                                const vari = binding[0];
                                 if (vari.hasOwnProperty("id")) {
-                                    let variName = vari.id;
-                                    let data = interp(binding[1], env);
-                                    if (op.id == "letrec") {
+                                    const variName = vari.id;
+                                    const data = interp(binding[1], env);
+                                    if (op.id === "letrec") {
                                         newEnv = extendEnv(newEnv, variName, true, data);
                                     }
                                     else {
@@ -344,24 +390,24 @@ function interp(prog, env) {
                                 }
                             }
                         }
-                        let body = prog[2];
+                        const body = prog[2];
                         return interp(body, newEnv);
                     }
                 }
                 // end of let
-                else if (op.id == "if") {
-                    if (prog.length != 4) {
+                else if (op.id === "if") {
+                    if (prog.length !== 4) {
                         throw invalidLengthException('if', 3);
                     }
                     else {
-                        let cond = interp(prog[1], env);
+                        const cond = interp(prog[1], env);
                         if (Array.isArray(cond)) {
                             throw new Error("cond can't be reduced to a constant");
                         }
-                        else if (cond.type != ItemType.Bool) {
+                        else if (cond.type !== ItemType.Bool) {
                             throw new Error("type error of cond, not a bool");
                         }
-                        else if (cond.bool == true) {
+                        else if (cond.bool === true) {
                             return interp(prog[2], env);
                             // if cond is false
                         }
@@ -371,74 +417,116 @@ function interp(prog, env) {
                     }
                 }
                 else {
-                    let argsMapped = prog.slice(1).map((x) => {
+                    const argsMapped = prog.slice(1).map((x) => {
                         return interp(x, env);
                     });
                     // binary basic operator
-                    if (op.id == "+") {
+                    if (op.id === "+") {
                         return interpBinary(add, argsMapped);
                     }
-                    else if (op.id == "-") {
+                    else if (op.id === "-") {
                         return interpBinary(sub, argsMapped);
                     }
-                    else if (op.id == "*") {
+                    else if (op.id === "*") {
                         return interpBinary(mul, argsMapped);
                     }
-                    else if (op.id == "/") {
+                    else if (op.id === "/") {
                         return interpBinary(div, argsMapped);
                         // bool calculation
                     }
-                    else if (op.id == ">") {
-                        return interpBinary(gt, argsMapped, true);
+                    else if (op.id === ">") {
+                        return interpBinaryBool(gt, argsMapped);
                     }
-                    else if (op.id == "<") {
-                        return interpBinary(lt, argsMapped, true);
+                    else if (op.id === "<") {
+                        return interpBinaryBool(lt, argsMapped);
                     }
-                    else if (op.id == ">=") {
-                        return interpBinary(ge, argsMapped, true);
+                    else if (op.id === ">=") {
+                        return interpBinaryBool(ge, argsMapped);
                     }
-                    else if (op.id == "<=") {
-                        return interpBinary(le, argsMapped, true);
+                    else if (op.id === "<=") {
+                        return interpBinaryBool(le, argsMapped);
                     }
-                    else if (op.id == "==") {
-                        return interpBinary(eq, argsMapped, true);
+                    else if (op.id === "=") {
+                        return interpBinaryBool(eq, argsMapped);
                     }
-                    else if (op.id == "car") {
-                        let arg = argsMapped[0];
-                        if (prog.length != 2) {
+                    else if (op.id === "!=") {
+                        return interpBinaryBool(ne, argsMapped);
+                    }
+                    else if (op.id === "car") {
+                        const arg = argsMapped[0];
+                        if (prog.length !== 2) {
                             throw invalidLengthException('car', 1);
                         }
-                        else if (!arg.hasOwnProperty('type') || arg.type != ItemType.Ls) {
+                        else if (!arg.hasOwnProperty('type') || arg.type !== ItemType.Ls) {
                             throw new Error("the arg of 'car' is not a list.");
                         }
                         else {
                             return car(arg);
                         }
-                        // other named function call
                     }
+                    else if (op.id === "cdr") {
+                        const arg = argsMapped[0];
+                        if (prog.length !== 2) {
+                            throw invalidLengthException('cdr', 1);
+                        }
+                        else if (!arg.hasOwnProperty('type') || arg.type !== ItemType.Ls) {
+                            throw new Error("the arg of 'cdr' is not a list.");
+                        }
+                        else {
+                            return cdr(arg);
+                        }
+                    }
+                    else if (op.id === "cons") {
+                        const arg = argsMapped;
+                        if (prog.length !== 3) {
+                            throw invalidLengthException('cdr', 2);
+                        }
+                        else if (!arg[1].hasOwnProperty('type') || arg[1].type !== ItemType.Ls) {
+                            throw new Error("the 2nd arg of 'cons' is not a list.");
+                        }
+                        else {
+                            return cons(arg[0], arg[1]);
+                        }
+                    } // string manipulations
+                    else if (op.id === "++") {
+                        const lhs = prog[1];
+                        const rhs = prog[2];
+                        if (prog.length !== 3) {
+                            throw invalidLengthException('++', 2);
+                        }
+                        else if (!isItem(lhs) || !isItem(rhs)
+                            || lhs.type != ItemType.Str || rhs.type != ItemType.Str) {
+                            throw new Error("at least one of the arg. of '++' is not a str.");
+                        }
+                        else {
+                            return concatString(lhs, rhs);
+                        }
+                    }
+                    // other named function call
                     else {
-                        let caller = interp(prog[0], env);
-                        let varArgs = caller.vars;
-                        let varArgLen = varArgs.length;
-                        let argsMappedLen = argsMapped.length;
-                        if (argsMappedLen != varArgLen) {
+                        const caller = interp(prog[0], env);
+                        const varArgs = caller.vars;
+                        const varArgLen = varArgs.length;
+                        const argsMappedLen = argsMapped.length;
+                        if (argsMappedLen !== varArgLen) {
                             throw new Error("the number of the arguments is"
                                 + " not the same of that of the input vars.");
                         }
                         else {
-                            var newEnv = structuredClone(caller.env);
-                            for (var i = 0; i < Object.keys(env).length; i++) {
-                                let currentKey = Object.keys(env)[i];
-                                let currentValue = env[currentKey];
-                                if (currentValue[0].isRec !== undefined && currentValue[0].isRec == true) {
-                                    newEnv = extendEnv(newEnv, currentKey, true, currentValue[0].value);
-                                }
-                            }
-                            var fuBody = caller.body;
-                            for (var i = 0; i < argsMapped.length; i++) {
-                                let varArg = varArgs[i];
-                                var varArgIsRec = false;
-                                if (varArg.isRec !== undefined && varArg.isRec == true) {
+                            let newEnv = structuredClone(caller.env);
+                            // for recursion function usage
+                            /*for(const key in env){
+                              const currentKey = key;
+                              const currentValue = env[currentKey];
+                              if (currentValue[0].isRec !== undefined && currentValue[0].isRec === true){
+                                newEnv = extendEnv(newEnv, currentKey, true, currentValue[0].value);
+                              }
+                            }*/
+                            const fuBody = caller.body;
+                            for (let i = 0; i < argsMapped.length; i++) {
+                                const varArg = varArgs[i];
+                                let varArgIsRec = false;
+                                if (varArg.isRec !== undefined && varArg.isRec === true) {
                                     varArgIsRec = true;
                                 }
                                 newEnv = extendEnv(newEnv, varArgs[i].id, varArgIsRec, argsMapped[i]);
@@ -455,24 +543,24 @@ function interp(prog, env) {
             // the caller which is a higher-function call
         }
         else {
-            let argsMapped = prog.slice(1).map((x) => {
+            const argsMapped = prog.slice(1).map((x) => {
                 return interp(x, env);
             });
-            let caller = interp(prog[0], env);
-            let varArgs = caller.vars;
-            let varArgLen = varArgs.length;
-            let argsMappedLen = argsMapped.length;
-            if (argsMappedLen != varArgLen) {
+            const caller = interp(prog[0], env);
+            const varArgs = caller.vars;
+            const varArgLen = varArgs.length;
+            const argsMappedLen = argsMapped.length;
+            if (argsMappedLen !== varArgLen) {
                 throw new Error("the number of the arguments is"
                     + " not the same of that of the input vars.");
             }
             else {
-                var fuBody = caller.body;
-                var newEnv = structuredClone(env);
+                const fuBody = caller.body;
+                let newEnv = structuredClone(env);
                 // for recursion function usage
-                for (var i = 0; i < argsMapped.length; i++) {
-                    var varArgIsRec = false;
-                    if (varArgs[i].isRec !== undefined && varArgs[i].isRec == true) {
+                for (let i = 0; i < argsMapped.length; i++) {
+                    let varArgIsRec = false;
+                    if (varArgs[i].isRec !== undefined && varArgs[i].isRec === true) {
                         varArgIsRec = true;
                     }
                     newEnv = extendEnv(newEnv, varArgs[i].id, varArgIsRec, argsMapped[i]);
@@ -483,24 +571,37 @@ function interp(prog, env) {
     }
     else {
         // constant
-        if (prog.type != ItemType.Id) {
+        if (prog.type !== ItemType.Id) {
             return prog;
         }
         // other variable
         else {
-            let varName = prog.id;
-            let isRecAndVal = env[varName][0];
+            const varName = prog.id;
+            const isRecAndVal = env[varName][0];
+            // for letrec's usage
+            if (isRecAndVal.isRec === true) {
+                let value = isRecAndVal.value;
+                if (isClosure(value)) {
+                    for (const key in env) {
+                        const valueOfKey = env[key][0];
+                        if (valueOfKey.isRec == true) {
+                            value.env = extendEnv(value.env, key, true, valueOfKey.value);
+                        }
+                    }
+                    return value;
+                }
+            }
             return isRecAndVal.value;
         }
     }
 }
 function evaluate(expr) {
-    let input = (0, typescript_parsec_1.expectSingleResult)((0, typescript_parsec_1.expectEOF)(LISP.parse(tokenizer.parse(expr))));
-    let interped = interp(input, emptyEnv);
+    const input = (0, typescript_parsec_1.expectSingleResult)((0, typescript_parsec_1.expectEOF)(LISP.parse(tokenizer.parse(expr))));
+    const interped = interp(input, emptyEnv);
     return astToString(interped);
 }
-//evaluate(`(main '((text 12)) [ 快狐跳懶狗\\\\\\\[\\\]\\\(\\\)(italic "fox and dog") (bold [OK])])`)
-//evaluate("@(let (a 17) (+ a 10))@")
+// evaluate(`(main '((text 12)) [ 快狐跳懶狗\\\\\\\[\\\]\\\(\\\)(italic "fox and dog") (bold [OK])])`)
+// evaluate("@(let (a 17) (+ a 10))@")
 // eval print loop
 const readline = require("node:readline");
 const rl = readline.createInterface({
index 5700e3e8dbe08f0c65b8666716ac20ebcd952246..b2ce069875e5a371f60c4d59d64c61982c7b3ef3 100644 (file)
@@ -101,7 +101,7 @@ const tokenizer = buildLexer([
   [true, /^\d+\.\d+/g, TokenKind.Flo],
   [true, /^true/g, TokenKind.Bool],
   [true, /^false/g, TokenKind.Bool],
-  [true, /^([+\-*/a-zA-Z_][0-9+\-*/a-zA-Z_]*|[<>]=?|==)/g, TokenKind.Id],
+  [true, /^([+\-*/a-zA-Z_][0-9+\-*/a-zA-Z_]*|[<>]=?|!?=)/g, TokenKind.Id],
   [true, /^\"([^\"]|\\\")+\"/g, TokenKind.Str],
   [true, /^[(]/g, TokenKind.LParen],
   [true, /^[)]/g, TokenKind.RParen],
@@ -251,9 +251,9 @@ CON_STR.setPattern(
   apply(kmid(str("["), rep_sc(CON_STR_INNER), str("]")), applyStrings)
 );
 
-function astToString(ast: AST): string {
+function astToString(ast: AST, isInQuoted? : boolean): string {
   if (Array.isArray(ast)) {
-    const ast2 = ast.map(astToString);
+    const ast2 = ast.map((x)=>astToString(x, isInQuoted));
     return "(" + ast2.join(" ") + ")";
   } else {
     if (ast.type === ItemType.Str) {
@@ -269,8 +269,12 @@ function astToString(ast: AST): string {
         const body = astToString(ast.body);
         return `<closure; binding : ${binding}, body : ${body}>`;
     }else if (ast.type === ItemType.Ls){
-      const body = astToString(ast.list);
-      return "'"+body;
+      const body = astToString(ast.list, true);
+      if (isInQuoted){
+        return body;
+      }else{
+        return "'"+body;
+      }
     }
      else {
       return ast.int.toString();
@@ -301,7 +305,7 @@ function interpBinary(op: (a : number, b : number) => number, argsMapped: AST[])
       throw new Error("the type of add should be (int, int) or (flo, flo)");
     }
   } else {
-    throw new Error("the number of args should be 2.");
+    throw new Error(`the number of args of ${op} should be 2, but it's ${argsMapped}`);
   }
 }
 
@@ -352,9 +356,20 @@ function eq(x: number, y: number): boolean {
 function le(x: number, y: number): boolean {
   return x <= y;
 }
+function ne(x: number, y: number): boolean {
+  return x !== y;
+}
 function ge(x: number, y: number): boolean {
   return x >= y;
 }
+function concatString(l: ItemStr, r : ItemStr) : ItemStr {
+  const rtn : ItemStr = {
+      type: ItemType.Str,
+      str: l.str + r.str,
+  }
+    return rtn;
+}
+
 
 /** list manipulation */
 function car(x : List) : Item {
@@ -365,10 +380,30 @@ function car(x : List) : Item {
       list: fst,
   }
   return rtnList;
-}else{
-  return fst;
+  }else{
+    return fst;
+  }
 }
+function cdr(x : List) : Item {
+  if (x.list.length == 0){
+    throw new Error("the argument of 'cdr' can't be a empty list.")
+  }
+  const remained = (x.list as AST[]).slice(1);
+  const rtnList : List = {
+      type: ItemType.Ls,
+      list: remained,
+  }
+    return rtnList;
 }
+function cons(h: AST, t : List) : List {
+  const inner = [h].concat(t.list);
+  const rtnList : List = {
+      type: ItemType.Ls,
+      list: inner,
+  }
+    return rtnList;
+}
+
 
 function extendEnv(env : Env, vari : string, isRec: boolean, data : AST) : Env{
     // add var
@@ -520,8 +555,10 @@ function interp(prog: AST, env: Env): AST {
           return interpBinaryBool(ge, argsMapped);
         } else if (op.id === "<=") {
           return interpBinaryBool(le, argsMapped);
-        } else if (op.id === "==") {
+        } else if (op.id === "=") {
           return interpBinaryBool(eq, argsMapped);
+        }  else if (op.id === "!=") {
+            return interpBinaryBool(ne, argsMapped);
         } else if (op.id === "car") {
           const arg = argsMapped[0];
           if (prog.length !== 2){
@@ -531,8 +568,41 @@ function interp(prog: AST, env: Env): AST {
           }else{
             return car((arg as List));
           }
+        }
+        else if (op.id === "cdr") {
+          const arg = argsMapped[0];
+          if (prog.length !== 2){
+            throw invalidLengthException('cdr', 1);
+          }else if (!arg.hasOwnProperty('type') || (arg as Item).type !== ItemType.Ls){
+            throw new Error("the arg of 'cdr' is not a list.")
+          }else{
+            return cdr((arg as List));
+          }
+        }else if (op.id === "cons") {
+          const arg = argsMapped;
+          if (prog.length !== 3){
+            throw invalidLengthException('cdr', 2);
+          }else if (!arg[1].hasOwnProperty('type') || (arg[1] as Item).type !== ItemType.Ls){
+            throw new Error("the 2nd arg of 'cons' is not a list.")
+          }else{
+            return cons(arg[0], (arg[1] as List));
+          }
+        }        // string manipulations
+        else if (op.id === "++") {
+        const lhs = prog[1];
+        const rhs = prog[2];
+        if (prog.length !== 3){
+          throw invalidLengthException('++', 2);
+        }else if (!isItem(lhs) || !isItem(rhs)
+        || lhs.type != ItemType.Str || rhs.type != ItemType.Str){
+          throw new Error("at least one of the arg. of '++' is not a str.")
+        }else{
+          return concatString(lhs, rhs);
+        }}
+
+        
         // other named function call
-        else {
+        else {
 
           const caller = interp(prog[0],env);
 
@@ -548,13 +618,13 @@ function interp(prog: AST, env: Env): AST {
 
 
             // for recursion function usage
-            for(const key in env){
+            /*for(const key in env){
               const currentKey = key;
               const currentValue = env[currentKey];
               if (currentValue[0].isRec !== undefined && currentValue[0].isRec === true){
                 newEnv = extendEnv(newEnv, currentKey, true, currentValue[0].value);
               }
-            }
+            }*/
             const fuBody = (caller as Closure).body;
 
             for(let i=0;i<argsMapped.length;i++){
@@ -616,6 +686,21 @@ function interp(prog: AST, env: Env): AST {
     else{
       const varName = prog.id;
       const isRecAndVal = env[varName][0];
+
+      // for letrec's usage
+      if (isRecAndVal.isRec === true){
+        let value = isRecAndVal.value;
+        if (isClosure(value)){
+          for (const key in env){
+            const valueOfKey = env[key][0];
+            if (valueOfKey.isRec == true){
+              value.env = extendEnv(value.env, key, true, valueOfKey.value);
+            }
+          }
+          
+          return value;
+        }
+      }
       return isRecAndVal.value;
     }
   }