]> git.kianting.info Git - anotherTypesetter/commitdiff
add map and add pdf page
authorTan Kian-ting <chenjt30@gmail.com>
Wed, 10 Apr 2024 14:56:29 +0000 (22:56 +0800)
committerTan Kian-ting <chenjt30@gmail.com>
Wed, 10 Apr 2024 14:56:29 +0000 (22:56 +0800)
README.md
package-lock.json
package.json
src/index.js
src/index.ts
text.lisp [new file with mode: 0644]
text.lisp.pdf [new file with mode: 0644]
tsconfig.json

index b77e925a8b95f8c1a17bd2f274ef0dcc00084c14..217e3faf9e54e1dd750af31b6f552bed5c90c2b3 100644 (file)
--- a/README.md
+++ b/README.md
@@ -5,7 +5,12 @@
  - [v] car - 240403
  - [v] cdr and cons
  - [v] concat string (++)
- - [ ] create pdf
+ - [v] set!
+ - [v] list ref by index
+ - [ ] dict ref by id
+ - [v] map 20240410
+ - [v] add pdf page 240410 (addPDFPages)
+ - [v] create pdf 240410
  - [ ] close pdf
  - [ ] add character
  - [ ] add path
index dcfd9a6da6cab303a17f20c7009024078d062611..09be7cca0ab481d25e53f45d51010e492c6ee92a 100644 (file)
@@ -9,6 +9,7 @@
       "version": "0.0.1",
       "license": "MIT",
       "dependencies": {
+        "pdf-lib": "^1.17.1",
         "typescript-parsec": "^0.3.4"
       },
       "devDependencies": {
         "node": ">= 8"
       }
     },
+    "node_modules/@pdf-lib/standard-fonts": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
+      "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
+      "dependencies": {
+        "pako": "^1.0.6"
+      }
+    },
+    "node_modules/@pdf-lib/upng": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
+      "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
+      "dependencies": {
+        "pako": "^1.0.10"
+      }
+    },
     "node_modules/@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
         "node": ">=8"
       }
     },
+    "node_modules/pdf-lib": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
+      "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
+      "dependencies": {
+        "@pdf-lib/standard-fonts": "^1.0.0",
+        "@pdf-lib/upng": "^1.0.1",
+        "pako": "^1.0.11",
+        "tslib": "^1.11.1"
+      }
+    },
     "node_modules/picomatch": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
         "typescript": ">=4.2.0"
       }
     },
+    "node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+    },
     "node_modules/type-check": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
         "fastq": "^1.6.0"
       }
     },
+    "@pdf-lib/standard-fonts": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
+      "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
+      "requires": {
+        "pako": "^1.0.6"
+      }
+    },
+    "@pdf-lib/upng": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
+      "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
+      "requires": {
+        "pako": "^1.0.10"
+      }
+    },
     "@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
         "p-limit": "^3.0.2"
       }
     },
+    "pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+    },
     "parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
       "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
       "dev": true
     },
+    "pdf-lib": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
+      "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
+      "requires": {
+        "@pdf-lib/standard-fonts": "^1.0.0",
+        "@pdf-lib/upng": "^1.0.1",
+        "pako": "^1.0.11",
+        "tslib": "^1.11.1"
+      }
+    },
     "picomatch": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
       "dev": true,
       "requires": {}
     },
+    "tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+    },
     "type-check": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
index e945b6465008bca2634c7fc0a7ef3450d7ca26b7..0f9f047efdd0d9e41f475f6b16fc8e2f7befb777 100644 (file)
@@ -16,6 +16,7 @@
   "author": "Tan Kian-ting",
   "license": "MIT",
   "dependencies": {
+    "pdf-lib": "^1.17.1",
     "typescript-parsec": "^0.3.4"
   },
   "devDependencies": {
index dfbc12c32295c41d7971aced00dc0eba5e23e023..4c125a11453c15b5a9cd446c68a90df635b67bc7 100644 (file)
@@ -1,7 +1,31 @@
 "use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
 Object.defineProperty(exports, "__esModule", { value: true });
+const fs = __importStar(require("fs"));
+const pdf_lib_1 = require("pdf-lib");
 const typescript_parsec_1 = require("typescript-parsec");
 const typescript_parsec_2 = require("typescript-parsec");
+/** input lisp file */
+const filename = "./text.lisp";
+let pdfDoc;
 var TokenKind;
 (function (TokenKind) {
     TokenKind[TokenKind["Id"] = 0] = "Id";
@@ -27,13 +51,14 @@ var ItemType;
     ItemType[ItemType["Bool"] = 4] = "Bool";
     ItemType[ItemType["Clos"] = 5] = "Clos";
     ItemType[ItemType["Ls"] = 6] = "Ls";
+    ItemType[ItemType["Unit"] = 7] = "Unit";
 })(ItemType || (ItemType = {}));
 const tokenizer = (0, typescript_parsec_1.buildLexer)([
-    [true, /^\d+/g, TokenKind.Int],
-    [true, /^\d+\.\d+/g, TokenKind.Flo],
+    [true, /^-?\d+/g, TokenKind.Int],
+    [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],
@@ -146,6 +171,9 @@ function astToString(ast, isInQuoted) {
         else if (ast.type === ItemType.Bool) {
             return ast.bool.toString();
         }
+        else if (ast.type === ItemType.Unit) {
+            return "#unit"; // mark for unit
+        }
         else if (ast.type === ItemType.Clos) {
             const binding = astToString(ast.vars);
             const body = astToString(ast.body);
@@ -185,7 +213,7 @@ function interpBinary(op, argsMapped) {
             };
         }
         else {
-            throw new Error("the type of add should be (int, int) or (flo, flo)");
+            throw new Error(`the type of ${op.toString()} should be (int, int) or (flo, flo)`);
         }
     }
     else {
@@ -209,7 +237,7 @@ function interpBinaryBool(op, argsMapped) {
             };
         }
         else {
-            throw new Error("the type of add should be (int, int) or (flo, flo)");
+            throw new Error(`the type of ${op.toString()} should be (int, int) or (flo, flo)`);
         }
     }
     else {
@@ -246,6 +274,13 @@ function ne(x, y) {
 function ge(x, y) {
     return x >= y;
 }
+function otherNe(x, y) {
+    return astToString(x) !== astToString(y);
+}
+function otherEq(x, y) {
+    return astToString(x) === astToString(y);
+}
+// string manipulations
 function concatString(l, r) {
     const rtn = {
         type: ItemType.Str,
@@ -253,6 +288,43 @@ function concatString(l, r) {
     };
     return rtn;
 }
+/**
+ * get string `s`'s substring from ith-char to (j-1)th-char.
+ * @param s the string
+ * @param i beginning index
+ * @param j ending index (excluded)
+ * @returns the substring
+ */
+function subString(s, i, j) {
+    const realI = i.int;
+    const realStr = s.str;
+    if (realI >= realStr.length || realI < 0) {
+        throw new Error("the 2nd argument of `listRef` should between 0..(length of string `s` - 1)");
+    }
+    else if (j === undefined) {
+        const rtn = {
+            type: ItemType.Str,
+            str: realStr.substring(realI)
+        };
+        return rtn;
+    }
+    else {
+        const realJ = j.int;
+        if (realJ >= realStr.length || realJ < 0) {
+            throw new Error("the 3rd argument of `listRef` should between 0..(length of string `s` - 1)");
+        }
+        else if (realI > realJ) {
+            throw new Error("the 2nd argument should not larger than the 3rd arg.");
+        }
+        else {
+            const rtn = {
+                type: ItemType.Str,
+                str: realStr.substring(realI, realJ),
+            };
+            return rtn;
+        }
+    }
+}
 /** list manipulation */
 function car(x) {
     const fst = x.list[0];
@@ -286,6 +358,16 @@ function cons(h, t) {
     };
     return rtnList;
 }
+function listRef(l, i) {
+    const realI = i.int;
+    if (realI >= l.list.length || realI < 0) {
+        throw new Error("the argument of `listRef` should between 0..(length of l - 1)");
+    }
+    else {
+        const rtn = l.list[realI];
+        return rtn;
+    }
+}
 function extendEnv(env, vari, isRec, data) {
     // add var
     if (!(vari in env)) {
@@ -447,10 +529,48 @@ function interp(prog, env) {
                         return interpBinaryBool(le, argsMapped);
                     }
                     else if (op.id === "=") {
-                        return interpBinaryBool(eq, argsMapped);
+                        if (argsMapped[1].type === ItemType.Flo ||
+                            argsMapped[1].type === ItemType.Int) {
+                            return interpBinaryBool(eq, argsMapped);
+                        }
+                        else {
+                            if (prog.length !== 3) {
+                                throw invalidLengthException('=', 2);
+                            }
+                            else if (!isItem(argsMapped[0])
+                                || !isItem(argsMapped[1])) {
+                                throw new Error("Either 1st or 2nd arg of '=' is not a item.");
+                            }
+                            else {
+                                return {
+                                    type: ItemType.Bool,
+                                    bool: otherEq(argsMapped[0], argsMapped[1]),
+                                };
+                            }
+                        }
                     }
                     else if (op.id === "!=") {
-                        return interpBinaryBool(ne, argsMapped);
+                        if ((argsMapped[0].type === ItemType.Flo &&
+                            argsMapped[0].type === argsMapped[1].type) ||
+                            (argsMapped[0].type === ItemType.Int) &&
+                                (argsMapped[0].type === argsMapped[1].type)) {
+                            return interpBinaryBool(ne, argsMapped);
+                        }
+                        else {
+                            if (prog.length !== 3) {
+                                throw invalidLengthException('!=', 2);
+                            }
+                            else if (!isItem(argsMapped[1])
+                                || !isItem(argsMapped[2])) {
+                                throw new Error("Either 1st or 2nd arg of '!=' is not a item.");
+                            }
+                            else {
+                                return {
+                                    type: ItemType.Bool,
+                                    bool: otherNe(argsMapped[0], argsMapped[1]),
+                                };
+                            }
+                        }
                     }
                     else if (op.id === "car") {
                         const arg = argsMapped[0];
@@ -487,7 +607,23 @@ function interp(prog, env) {
                         else {
                             return cons(arg[0], arg[1]);
                         }
-                    } // string manipulations
+                    }
+                    else if (op.id === "listRef") {
+                        const arg = argsMapped;
+                        if (prog.length !== 3) {
+                            throw invalidLengthException('listRef', 2);
+                        }
+                        else if (!arg[0].hasOwnProperty('type') || arg[0].type !== ItemType.Ls) {
+                            throw new Error("the 1st arg of 'listRef' is not a list.");
+                        }
+                        else if (!arg[1].hasOwnProperty('type') || arg[1].type !== ItemType.Int) {
+                            throw new Error("the 2nd arg of 'listRef' is not a number.");
+                        }
+                        else {
+                            return listRef(arg[0], arg[1]);
+                        }
+                    }
+                    // string manipulations
                     else if (op.id === "++") {
                         const lhs = prog[1];
                         const rhs = prog[2];
@@ -502,6 +638,63 @@ function interp(prog, env) {
                             return concatString(lhs, rhs);
                         }
                     }
+                    else if (op.id === "subString") {
+                        const str = prog[1];
+                        const i = prog[2];
+                        if (prog.length !== 3 && prog.length !== 4) {
+                            throw new Error(`the number of args for 'subString' should be 2 or 3.`);
+                        }
+                        else if (!isItem(str) || str.type != ItemType.Str) {
+                            throw new Error("the 1st item of the arg for 'subString' should be a string.");
+                        }
+                        else {
+                            if (prog.length == 3) {
+                                // str.substring(i)
+                                return subString(str, i);
+                            }
+                            else {
+                                // str.substring(i,j)
+                                return subString(str, i, prog[3]);
+                            }
+                        }
+                    }
+                    // set manipulations
+                    else if (op.id === "set!") {
+                        const vari = prog[1];
+                        const replacer = prog[2];
+                        if (prog.length !== 3) {
+                            throw invalidLengthException('set!', 2);
+                        }
+                        else if (!isItem(vari) || !isItem(replacer)
+                            || env[vari.id][0].value.type != replacer.type) {
+                            throw new Error("the type of replace and variable should be the same.");
+                        }
+                        else {
+                            env[vari.id][0].value = prog[2];
+                            return { type: ItemType.Unit };
+                        }
+                    }
+                    else if (op.id === "addPDFPage") {
+                        if (prog.length !== 2) {
+                            throw invalidLengthException('addPDFPage', 1);
+                        }
+                        else if (astToString(argsMapped[0]) !== "'()") {
+                            throw new Error("the arg of addPdfPage should be a empty string '()");
+                        }
+                        else {
+                            const page = pdfDoc.addPage();
+                            return {
+                                type: ItemType.Unit,
+                            };
+                        }
+                        const rtn = argsMapped[argsMapped.length - 1];
+                        return rtn;
+                    }
+                    // procedures returning the last called command
+                    else if (op.id === "begin") {
+                        const rtn = argsMapped[argsMapped.length - 1];
+                        return rtn;
+                    }
                     // other named function call
                     else {
                         const caller = interp(prog[0], env);
@@ -509,7 +702,7 @@ function interp(prog, env) {
                         const varArgLen = varArgs.length;
                         const argsMappedLen = argsMapped.length;
                         if (argsMappedLen !== varArgLen) {
-                            throw new Error("the number of the arguments is"
+                            throw new Error("the number of the arguments of the caller is"
                                 + " not the same of that of the input vars.");
                         }
                         else {
@@ -604,11 +797,17 @@ function evaluate(expr) {
 // evaluate("@(let (a 17) (+ a 10))@")
 // eval print loop
 const readline = require("node:readline");
+const node_process_1 = require("node:process");
 const rl = readline.createInterface({
     input: process.stdin,
     output: process.stdout,
 });
-rl.question(`What's your program?`, (prog) => {
+async function run() {
+    pdfDoc = await pdf_lib_1.PDFDocument.create();
+    const prog = fs.readFileSync(filename, { encoding: 'utf8' });
     console.log(evaluate(prog));
-    rl.close();
-});
+    const pdfBytes = await pdfDoc.save();
+    fs.writeFileSync(filename + '.pdf', pdfBytes, 'binary');
+    (0, node_process_1.exit)(0);
+}
+run();
index b2ce069875e5a371f60c4d59d64c61982c7b3ef3..cf96017e1f46356f5caadca48b19815f3c6bb7d5 100644 (file)
@@ -1,4 +1,5 @@
-import { validateHeaderName } from "http";
+import * as fs from 'fs';
+import { PDFDocument } from 'pdf-lib'
 import { Token } from "typescript-parsec";
 import {
   buildLexer,
@@ -19,6 +20,12 @@ import {
   opt,
 } from "typescript-parsec";
 
+
+
+/** input lisp file */
+const filename = "./text.lisp";
+let pdfDoc :  PDFDocument;
+
 enum TokenKind {
   Id,
   Int,
@@ -43,15 +50,21 @@ enum ItemType {
   Bool,
   Clos,
   Ls,
+  Unit,
 }
 
-type Item = ItemStr | ItemInt | ItemId | ItemFlo | ItemBool | Closure | List;
+type Item = ItemStr | ItemInt | ItemId | ItemFlo | ItemBool | ItemUnit | Closure | List;
 
 interface ItemStr {
   type: ItemType.Str;
   str: string;
 }
 
+// returned type for input or print, etc. #unit for representation
+interface ItemUnit {
+  type: ItemType.Unit;
+}
+
 interface ItemInt {
   type: ItemType.Int;
   int: number;
@@ -97,11 +110,11 @@ interface Closure{
 type AST = Item | AST[];
 
 const tokenizer = buildLexer([
-  [true, /^\d+/g, TokenKind.Int],
-  [true, /^\d+\.\d+/g, TokenKind.Flo],
+  [true, /^-?\d+/g, TokenKind.Int],
+  [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],
@@ -264,6 +277,8 @@ function astToString(ast: AST, isInQuoted? : boolean): string {
       return ast.flo.toString();
     } else if (ast.type === ItemType.Bool) {
         return ast.bool.toString();
+    }else if (ast.type === ItemType.Unit) {
+      return "#unit"; // mark for unit
     }else if (ast.type === ItemType.Clos){
         const binding = astToString(ast.vars);
         const body = astToString(ast.body);
@@ -302,7 +317,8 @@ function interpBinary(op: (a : number, b : number) => number, argsMapped: AST[])
         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 ${op.toString()} should be (int, int) or (flo, flo)`);
     }
   } else {
     throw new Error(`the number of args of ${op} should be 2, but it's ${argsMapped}`);
@@ -325,7 +341,7 @@ function interpBinaryBool(op: (a : number, b : number) => boolean, argsMapped: A
         bool: op(fst.int, snd.int) as  boolean,
       };
     } else {
-      throw new Error("the type of add should be (int, int) or (flo, flo)");
+      throw new Error(`the type of ${op.toString()} should be (int, int) or (flo, flo)`);
     }
   } else {
     throw new Error("the number of args should be 2.");
@@ -362,6 +378,16 @@ function ne(x: number, y: number): boolean {
 function ge(x: number, y: number): boolean {
   return x >= y;
 }
+
+function otherNe(x: any, y: any): boolean {
+  return astToString(x) !== astToString(y);
+}
+function otherEq(x: any, y: any): boolean {
+  return astToString(x) === astToString(y);
+}
+
+
+// string manipulations
 function concatString(l: ItemStr, r : ItemStr) : ItemStr {
   const rtn : ItemStr = {
       type: ItemType.Str,
@@ -369,6 +395,41 @@ function concatString(l: ItemStr, r : ItemStr) : ItemStr {
   }
     return rtn;
 }
+/**
+ * get string `s`'s substring from ith-char to (j-1)th-char.
+ * @param s the string
+ * @param i beginning index
+ * @param j ending index (excluded)
+ * @returns the substring
+ */
+function subString(s: ItemStr, i: ItemInt, j? : ItemInt): ItemStr {
+  const realI = i.int;
+  const realStr = s.str;
+  if (realI >= realStr.length || realI < 0){
+    throw new Error("the 2nd argument of `listRef` should between 0..(length of string `s` - 1)");
+  }
+  else if(j === undefined){
+    const rtn : ItemStr = {
+      type:ItemType.Str,
+      str:realStr.substring(realI)
+    };
+    return rtn;
+  }
+  else{
+
+  const realJ = j.int;
+  if (realJ >= realStr.length || realJ < 0){
+    throw new Error("the 3rd argument of `listRef` should between 0..(length of string `s` - 1)");
+  }else if (realI > realJ){
+    throw new Error("the 2nd argument should not larger than the 3rd arg.");
+  }else{
+    const rtn : ItemStr = {
+      type:ItemType.Str,
+      str:realStr.substring(realI,realJ),
+    };
+    return rtn;
+  }}
+}
 
 
 /** list manipulation */
@@ -404,6 +465,17 @@ function cons(h: AST, t : List) : List {
     return rtnList;
 }
 
+function listRef(l: List, i: ItemInt): AST {
+  const realI = i.int;
+  if (realI >= l.list.length || realI < 0){
+    throw new Error("the argument of `listRef` should between 0..(length of l - 1)");
+  }else{
+    const rtn = l.list[realI];
+    return rtn;
+  }
+}
+
+
 
 function extendEnv(env : Env, vari : string, isRec: boolean, data : AST) : Env{
     // add var
@@ -437,7 +509,6 @@ function isClosure(x: any): x is Closure {
     return x.hasOwnProperty('type') && x.hasOwnProperty('vars');
 }
 
-
 function interp(prog: AST, env: Env): AST {
   if (Array.isArray(prog)) {
     if (!Array.isArray(prog[0])) {
@@ -556,9 +627,42 @@ function interp(prog: AST, env: Env): AST {
         } else if (op.id === "<=") {
           return interpBinaryBool(le, argsMapped);
         } else if (op.id === "=") {
-          return interpBinaryBool(eq, argsMapped);
+          if ((argsMapped[1] as Item).type === ItemType.Flo ||
+          (argsMapped[1] as Item).type === ItemType.Int){
+            return interpBinaryBool(eq, argsMapped);
+          }else{
+            if (prog.length !== 3){
+              throw invalidLengthException('=', 2);
+            }else if(!isItem(argsMapped[0])
+              ||!isItem(argsMapped[1])){
+              throw new Error("Either 1st or 2nd arg of '=' is not a item.")
+            }else{
+              return {
+                type:ItemType.Bool,
+                bool:otherEq(argsMapped[0], argsMapped[1]),
+              };
+            }
+          }
         }  else if (op.id === "!=") {
+          if (
+            ((argsMapped[0] as Item).type === ItemType.Flo &&
+            (argsMapped[0] as Item).type ===  (argsMapped[1] as Item).type)||
+          ((argsMapped[0] as Item).type === ItemType.Int) &&
+          ((argsMapped[0] as Item).type === (argsMapped[1] as Item).type)){
             return interpBinaryBool(ne, argsMapped);
+          }else{
+            if (prog.length !== 3){
+              throw invalidLengthException('!=', 2);
+            }else if(!isItem(argsMapped[1])
+              ||!isItem(argsMapped[2])){
+              throw new Error("Either 1st or 2nd arg of '!=' is not a item.")
+            }else{
+              return {
+                type:ItemType.Bool,
+                bool:otherNe(argsMapped[0], argsMapped[1]),
+              };
+            }
+          }
         } else if (op.id === "car") {
           const arg = argsMapped[0];
           if (prog.length !== 2){
@@ -587,7 +691,23 @@ function interp(prog: AST, env: Env): AST {
           }else{
             return cons(arg[0], (arg[1] as List));
           }
-        }        // string manipulations
+        }
+        else if (op.id === "listRef"){
+          const arg = argsMapped;
+          if (prog.length !== 3){
+            throw invalidLengthException('listRef', 2);
+          }else if (!arg[0].hasOwnProperty('type') || (arg[0] as Item).type !== ItemType.Ls){
+            throw new Error("the 1st arg of 'listRef' is not a list.")
+          }else if (!arg[1].hasOwnProperty('type') || (arg[1] as Item).type !== ItemType.Int){
+            throw new Error("the 2nd arg of 'listRef' is not a number.")
+          }else{
+            return listRef(arg[0] as List, arg[1] as ItemInt);
+          }          
+        }
+
+
+        
+        // string manipulations
         else if (op.id === "++") {
         const lhs = prog[1];
         const rhs = prog[2];
@@ -599,7 +719,57 @@ function interp(prog: AST, env: Env): AST {
         }else{
           return concatString(lhs, rhs);
         }}
+        else if (op.id === "subString") {
+        const str = prog[1];
+        const i = prog[2];
+        if (prog.length !== 3 && prog.length !== 4){
+          throw new Error(`the number of args for 'subString' should be 2 or 3.`);
+        }else if (!isItem(str) ||  str.type != ItemType.Str ){
+          throw new Error("the 1st item of the arg for 'subString' should be a string.")
+        }else{
+          if (prog.length == 3){
+            // str.substring(i)
+            return subString(str, i as ItemInt);}
+          else{
+            // str.substring(i,j)
+            return subString(str, i as ItemInt, prog[3] as ItemInt);}
+          }
+        }
 
+        // set manipulations
+        else if (op.id === "set!") {
+          const vari : ItemId = prog[1] as ItemId;
+          const replacer = prog[2];
+          if (prog.length !== 3){
+            throw invalidLengthException('set!', 2);
+          }else if (!isItem(vari) || !isItem(replacer)
+          || (env[vari.id][0].value as Item).type !=  replacer.type ){
+            throw new Error("the type of replace and variable should be the same.")
+          }else{
+            env[vari.id][0].value = prog[2];
+            return {type:ItemType.Unit};
+          }
+        }
+        else if (op.id === "addPDFPage"){
+          if (prog.length !== 2){
+            throw invalidLengthException('addPDFPage', 1);
+          }else if(astToString(argsMapped[0]) !== "'()"){
+            throw new Error("the arg of addPdfPage should be a empty string '()")
+          }else{
+            const page = pdfDoc.addPage();
+            return {
+              type:ItemType.Unit,
+            }
+          }
+
+            const rtn = argsMapped[argsMapped.length-1];
+            return rtn;
+        }
+        // procedures returning the last called command
+        else if (op.id === "begin"){
+          const rtn = argsMapped[argsMapped.length-1];
+          return rtn;
+      }
         
         // other named function call
         else {
@@ -611,7 +781,7 @@ function interp(prog: AST, env: Env): AST {
           const varArgLen = varArgs.length;
           const argsMappedLen = argsMapped.length;
           if (argsMappedLen !== varArgLen){
-            throw new Error("the number of the arguments is"
+            throw new Error("the number of the arguments of the caller is"
             +" not the same of that of the input vars.");
           }else{
             let newEnv = structuredClone((caller as Closure).env);
@@ -717,13 +887,25 @@ function evaluate(expr: string): string {
 
 // eval print loop
 import readline = require("node:readline");
+import { exit } from "node:process";
 
 const rl = readline.createInterface({
   input: process.stdin,
   output: process.stdout,
 });
 
-rl.question(`What's your program?`, (prog: string) => {
+
+async function run(){
+  pdfDoc = await PDFDocument.create();
+
+
+  const prog = fs.readFileSync(filename, { encoding: 'utf8' });
+
   console.log(evaluate(prog));
-  rl.close();
-});
+
+  const pdfBytes = await pdfDoc.save();
+  fs.writeFileSync(filename+'.pdf', pdfBytes, 'binary');
+  exit(0);
+}
+
+run();
\ No newline at end of file
diff --git a/text.lisp b/text.lisp
new file mode 100644 (file)
index 0000000..637a51d
--- /dev/null
+++ b/text.lisp
@@ -0,0 +1,12 @@
+(letrec ((
+    map (lambda (f l)
+        (if (!= l '())
+            (cons (f (car l)) (map f (cdr l)))
+            '()))
+))
+(begin
+(addPDFPage '())
+(addPDFPage '())
+(map (lambda (x) (+ x 2)) '(8 9 10))
+
+))
\ No newline at end of file
diff --git a/text.lisp.pdf b/text.lisp.pdf
new file mode 100644 (file)
index 0000000..a64d82f
Binary files /dev/null and b/text.lisp.pdf differ
index 56794816baf823f6c2be68914c1cecc8af38a43e..a3a4c9987ddd373ab497126d502198ac0619b676 100644 (file)
@@ -11,7 +11,7 @@
     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 
     /* Language and Environment */
-    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+    "target": "es2017",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
     // "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */