From 15735fcae5670e17c9db3a45407fde15dc199e0b Mon Sep 17 00:00:00 2001 From: Tan Kian-ting Date: Sat, 6 Apr 2024 16:13:44 +0800 Subject: [PATCH] add recursion --- README.md | 6 +- src/index.js | 361 ++++++++++++++++++++++++++++++++------------------- src/index.ts | 109 ++++++++++++++-- 3 files changed, 331 insertions(+), 145 deletions(-) diff --git a/README.md b/README.md index 4c9b1e6..b77e925 100644 --- 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 diff --git a/src/index.js b/src/index.js index 919edbc..dfbc12c 100644 --- a/src/index.js +++ b/src/index.js @@ -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 ``; } - 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({ diff --git a/src/index.ts b/src/index.ts index 5700e3e..b2ce069 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 ``; }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