add map and add pdf page
This commit is contained in:
parent
15735fcae5
commit
3359c79098
8 changed files with 502 additions and 28 deletions
|
@ -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
|
||||
|
|
75
package-lock.json
generated
75
package-lock.json
generated
|
@ -9,6 +9,7 @@
|
|||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pdf-lib": "^1.17.1",
|
||||
"typescript-parsec": "^0.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -157,6 +158,22 @@
|
|||
"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",
|
||||
|
@ -1306,6 +1323,11 @@
|
|||
"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",
|
||||
|
@ -1358,6 +1380,17 @@
|
|||
"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",
|
||||
|
@ -1586,6 +1619,11 @@
|
|||
"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",
|
||||
|
@ -1789,6 +1827,22 @@
|
|||
"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",
|
||||
|
@ -2638,6 +2692,11 @@
|
|||
"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",
|
||||
|
@ -2675,6 +2734,17 @@
|
|||
"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",
|
||||
|
@ -2815,6 +2885,11 @@
|
|||
"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",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"author": "Tan Kian-ting",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pdf-lib": "^1.17.1",
|
||||
"typescript-parsec": "^0.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
223
src/index.js
223
src/index.js
|
@ -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();
|
||||
|
|
210
src/index.ts
210
src/index.ts
|
@ -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();
|
12
text.lisp
Normal file
12
text.lisp
Normal file
|
@ -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))
|
||||
|
||||
))
|
BIN
text.lisp.pdf
Normal file
BIN
text.lisp.pdf
Normal file
Binary file not shown.
|
@ -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. */
|
||||
|
|
Loading…
Reference in a new issue