diff --git a/Makefile b/Makefile index c42c5ca62f..27651dafac 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ DOCKERIZE = # IMPLS = ada ada.2 awk bash basic bbc-basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ - elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gnu-smalltalk \ + deno elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gnu-smalltalk \ guile haskell haxe hy io java js jq julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ plsql powershell ps python python.2 r racket rexx rpython ruby rust scala scheme skew \ @@ -201,6 +201,7 @@ crystal_STEP_TO_PROG = impls/crystal/$($(1)) cs_STEP_TO_PROG = impls/cs/$($(1)).exe d_STEP_TO_PROG = impls/d/$($(1)) dart_STEP_TO_PROG = impls/dart/$($(1)).dart +deno_STEP_TO_PROG = impls/deno/$($(1)).ts elisp_STEP_TO_PROG = impls/elisp/$($(1)).el elixir_STEP_TO_PROG = impls/elixir/lib/mix/tasks/$($(1)).ex elm_STEP_TO_PROG = impls/elm/$($(1)).js diff --git a/impls/deno/Dockerfile b/impls/deno/Dockerfile new file mode 100644 index 0000000000..e303672a95 --- /dev/null +++ b/impls/deno/Dockerfile @@ -0,0 +1,32 @@ +FROM ubuntu:18.04 +MAINTAINER Graeme Lockley + +########################################################## +# General requirements for testing or common across many +# implementations +########################################################## + +RUN apt-get -y update + +# Required for running tests +RUN apt-get -y install make python + +# Some typical implementation and test requirements +RUN apt-get -y install curl libreadline-dev libedit-dev + +RUN mkdir -p /mal && chmod 777 /mal +WORKDIR /mal + +########################################################## +# Specific implementation requirements +########################################################## + +RUN apt-get -y install unzip vim + +ENV DENO_INSTALL=/deno +RUN mkdir -p /deno \ + && chmod 777 /deno \ + && curl -fsSL https://deno.land/x/install/install.sh | sh + +ENV PATH=${DENO_INSTALL}/bin:${PATH} \ + DENO_DIR=${DENO_INSTALL}/.cache/deno diff --git a/impls/deno/core.ts b/impls/deno/core.ts new file mode 100644 index 0000000000..03fda0af27 --- /dev/null +++ b/impls/deno/core.ts @@ -0,0 +1,553 @@ +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + +const mkHashPairs = ( + items: Array, + procedureName: string, +): Array<[MalType.MalType, MalType.MalType]> => { + const args: Array<[MalType.MalType, MalType.MalType]> = []; + + if (items.length % 2 === 1) { + throw new Error( + `Invalid Argument: ${procedureName}: Odd number of arguments`, + ); + } + for (let lp = 0; lp < items.length; lp += 2) { + args.push([items[lp], items[lp + 1]]); + } + + return args; +}; + +const validateArgument = ( + o: number, + v: MalType.MalType | undefined, + tags: Array | string | undefined = undefined, +) => { + if (v === undefined) { + throw { error: "UndefinedParameter", parameter: o, tags }; + } + + if (tags !== undefined) { + if (Array.isArray(tags)) { + if (!tags.includes(v.tag)) { + throw { error: "InvalidTag", parameter: o, value: v, tags }; + } + } else if (tags !== v.tag) { + throw { error: "InvalidTag", parameter: o, value: v, tags: [tags] }; + } + } +}; + +const __ns = ( + evaluate: (ast: MalType.MalType, env: Env.Env) => MalType.MalType, +) => ({ + "+": ([a, b]: Array): MalType.MalType => { + validateArgument(0, a, "MalNumber"); + validateArgument(1, b, "MalNumber"); + + return MalType.mkNumber(asNumber(a) + asNumber(b)); + }, + + "-": ([a, b]: Array): MalType.MalType => { + validateArgument(0, a, "MalNumber"); + validateArgument(1, b, "MalNumber"); + + return MalType.mkNumber(asNumber(a) - asNumber(b)); + }, + + "*": ([a, b]: Array): MalType.MalType => { + validateArgument(0, a, "MalNumber"); + validateArgument(1, b, "MalNumber"); + + return MalType.mkNumber(asNumber(a) * asNumber(b)); + }, + + "/": ([a, b]: Array): MalType.MalType => { + validateArgument(0, a, "MalNumber"); + validateArgument(1, b, "MalNumber"); + + return MalType.mkNumber(asNumber(a) / asNumber(b)); + }, + + "=": ([a, b]: Array): MalType.MalType => + MalType.mkBoolean(MalType.equals(a, b)), + + "<": ([a, b]: Array): MalType.MalType => { + validateArgument(0, a, "MalNumber"); + validateArgument(1, b, "MalNumber"); + + return MalType.mkBoolean(asNumber(a) < asNumber(b)); + }, + + "<=": ([a, b]: Array): MalType.MalType => { + validateArgument(0, a, "MalNumber"); + validateArgument(1, b, "MalNumber"); + + return MalType.mkBoolean(asNumber(a) <= asNumber(b)); + }, + + ">": ([a, b]: Array): MalType.MalType => { + validateArgument(0, a, "MalNumber"); + validateArgument(1, b, "MalNumber"); + + return MalType.mkBoolean(asNumber(a) > asNumber(b)); + }, + + ">=": ([a, b]: Array): MalType.MalType => { + validateArgument(0, a, "MalNumber"); + validateArgument(1, b, "MalNumber"); + + return MalType.mkBoolean(asNumber(a) >= asNumber(b)); + }, + + apply: ([f, ...rest]: Array): MalType.MalType => { + validateArgument(0, f, ["MalFunction", "MalInternalFunction"]); + + const items: Array = []; + + rest.forEach((v) => { + if (v.tag === "MalList" || v.tag == "MalVector") { + v.items.forEach((i) => items.push(i)); + } else { + items.push(v); + } + }); + + if (f.tag === "MalInternalFunction") { + return f.fn(items); + } else if (f.tag === "MalFunction") { + const ast = f.body; + const env = Env.mkEnv(f.env, f.params, items); + return evaluate(ast, env); + } else { + return MalType.nil; + } + }, + + assoc: ([m, ...items]: Array): MalType.MalType => { + validateArgument(0, m, "MalHashMap"); + + return MalType.mapAssoc(asHashMap(m), mkHashPairs(items ?? [], "assoc")); + }, + + atom: ([v]: Array): MalType.MalType => + MalType.mkAtom(v ?? MalType.nil), + + "atom?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalAtom"), + + concat: (lst: Array): MalType.MalType => { + const result: Array = []; + + lst.forEach((v, i) => { + validateArgument(i, v, ["MalNil", "MalList", "MalVector"]); + if (v.tag !== "MalNil") { + asSeq(v).items.forEach((e) => result.push(e)); + } + }); + + return MalType.mkList(result); + }, + + conj: ([c, ...es]: Array): MalType.MalType => { + validateArgument(0, c, ["MalList", "MalVector"]); + + return (es === undefined) + ? c + : (c.tag === "MalList") + ? MalType.mkList([...es.reverse(), ...c.items]) + : MalType.mkVector([...asSeq(c).items, ...es]); + }, + + cons: ([a, b]: Array): MalType.MalType => { + validateArgument(0, a); + validateArgument(1, b, ["MalNil", "MalList", "MalVector"]); + + return MalType.mkList([a, ...asSeq(b).items]); + }, + + "contains?": ([m, key]: Array): MalType.MalType => { + validateArgument(0, m, "MalHashMap"); + validateArgument(1, key, ["MalKeyword", "MalString"]); + + return MalType.mapContains(asHashMap(m), key); + }, + + count: ([v]: Array): MalType.MalType => { + validateArgument(0, v, ["MalList", "MalVector", "MalNil"]); + + return (v.tag === "MalNil") + ? MalType.mkNumber(0) + : MalType.mkNumber(asSeq(v).items.length); + }, + + deref: ([v]: Array): MalType.MalType => { + validateArgument(0, v, "MalAtom"); + + return asAtom(v).value; + }, + + dissoc: ([m, ...items]: Array): MalType.MalType => { + validateArgument(0, m, "MalHashMap"); + + return MalType.mapDissoc(asHashMap(m), items); + }, + + "empty?": ([v]: Array): MalType.MalType => { + validateArgument(0, v, ["MalList", "MalVector"]); + + return MalType.mkBoolean(asSeq(v).items.length === 0); + }, + + "false?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalBoolean" && !v.value), + + first: ([v]: Array): MalType.MalType => { + validateArgument(0, v, ["MalList", "MalVector", "MalNil"]); + + return v.tag === "MalNil" ? MalType.nil : asSeq(v).items[0] ?? MalType.nil; + }, + + "fn?": ([v]: Array): MalType.MalType => + MalType.mkBoolean( + v !== undefined && + (v.tag === "MalFunction" && !v.isMacro || + v.tag === "MalInternalFunction"), + ), + + get: ([m, key]: Array): MalType.MalType => { + validateArgument(0, m, ["MalHashMap", "MalNil"]); + validateArgument(1, key, ["MalKeyword", "MalString"]); + + return m.tag === "MalNil" ? MalType.nil : MalType.mapGet(asHashMap(m), key); + }, + + "hash-map": (items: Array): MalType.MalType => + MalType.mkHashMap(mkHashPairs(items, "hash-map")), + + keys: ([m]: Array): MalType.MalType => { + validateArgument(0, m, "MalHashMap"); + + return MalType.mkList(MalType.mapKeys(asHashMap(m))); + }, + + keyword: ([v]: Array): MalType.MalType => { + validateArgument(0, v, ["MalKeyword", "MalString"]); + + return (v.tag === "MalKeyword") ? v : MalType.mkKeyword(`:${asString(v)}`); + }, + + "keyword?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalKeyword"), + + list: MalType.mkList, + + "list?": ([v]: Array): MalType.MalType => { + validateArgument(0, v); + + return MalType.mkBoolean(v.tag === "MalList"); + }, + + "macro?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalFunction" && v.isMacro), + + map: ([f, seq]: Array): MalType.MalType => { + validateArgument(0, f, ["MalFunction", "MalInternalFunction"]); + validateArgument(1, seq, ["MalList", "MalVector"]); + + const fn = f.tag === "MalFunction" + ? (args: Array): MalType.MalType => + evaluate(f.body, Env.mkEnv(f.env, f.params, args)) + : asInternalFunction(f).fn; + + const mappedItems = asSeq(seq).items.map((p) => fn([p])); + + return MalType.mkList(mappedItems); + }, + + "map?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalHashMap"), + + meta: ([v]: Array): MalType.MalType => { + if (v === undefined) { + return MalType.nil; + } else { + switch (v.tag) { + case "MalFunction": + case "MalHashMap": + case "MalInternalFunction": + case "MalVector": + case "MalList": + return v.meta ?? MalType.nil; + default: + return MalType.nil; + } + } + }, + + "nil?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalNil"), + + nth: ([l, i]: Array): MalType.MalType => { + validateArgument(0, l, ["MalList", "MalVector", "MalNil"]); + validateArgument(1, i, "MalNumber"); + + const result = asSeq(l).items[asNumber(i)]; + if (result === undefined) { + throw new Error( + `Index Out Of Range: nth: ${asNumber(i)} exceeds bounds of ${ + JSON.stringify(l) + }`, + ); + } else { + return result; + } + }, + + "number?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalNumber"), + + "pr_str": ([v]: Array): MalType.MalType => { + console.log(Printer.prStr(v, true)); + return MalType.nil; + }, + + println: (args: Array): MalType.MalType => { + console.log(args.map((v) => Printer.prStr(v, false)).join(" ")); + return MalType.nil; + }, + + "pr-str": (args: Array): MalType.MalType => + MalType.mkString(args.map((v) => Printer.prStr(v, true)).join(" ")), + + prn: (args: Array): MalType.MalType => { + console.log(args.map((v) => Printer.prStr(v, true)).join(" ")); + return MalType.nil; + }, + + "read-string": ([s]: Array): MalType.MalType => { + if (s === undefined) { + return MalType.nil; + } + + validateArgument(0, s, "MalString"); + + return Reader.readStr(asString(s)); + }, + + readline: ([prompt]: Array): MalType.MalType => { + validateArgument(0, prompt, "MalString"); + + const text = readline(`${asString(prompt)}> `); + + return text === undefined ? MalType.nil : MalType.mkString(text); + }, + + "reset!": ([a, v]: Array): MalType.MalType => { + validateArgument(0, a, "MalAtom"); + + asAtom(a).value = v ?? MalType.nil; + + return asAtom(a).value; + }, + + rest: ([v]: Array): MalType.MalType => { + validateArgument(0, v, ["MalList", "MalVector", "MalNil"]); + + return v.tag === "MalNil" + ? MalType.mkList([]) + : MalType.mkList(asSeq(v).items.slice(1)); + }, + + seq: ([s]: Array): MalType.MalType => { + validateArgument(0, s, ["MalList", "MalVector", "MalNil", "MalString"]); + + if (s.tag === "MalList") { + return s.items.length === 0 ? MalType.nil : s; + } else if (s.tag === "MalVector") { + return s.items.length === 0 ? MalType.nil : MalType.mkList(s.items); + } else if (s.tag === "MalNil") { + return s; + } else { + const sp = asString(s); + + return sp.length === 0 + ? MalType.nil + : MalType.mkList(sp.split("").map((e) => MalType.mkString(e))); + } + }, + + "sequential?": ([v]: Array): MalType.MalType => + MalType.mkBoolean( + v !== undefined && (v.tag === "MalVector" || v.tag === "MalList"), + ), + + slurp: ([s]: Array): MalType.MalType => { + validateArgument(0, s, "MalString"); + + return MalType.mkString(Deno.readTextFileSync(asString(s))); + }, + + str: (args: Array): MalType.MalType => + MalType.mkString(args.map((v) => Printer.prStr(v, false)).join("")), + + symbol: ([v]: Array): MalType.MalType => { + validateArgument(0, v, ["MalSymbol", "MalString"]); + + return (v.tag === "MalSymbol") ? v : MalType.mkSymbol(asString(v)); + }, + + "string?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalString"), + + "symbol?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalSymbol"), + + "swap!": ([a, f, ...args]: Array): MalType.MalType => { + validateArgument(0, a, "MalAtom"); + validateArgument(1, f, ["MalFunction", "MalInternalFunction"]); + + const ap = asAtom(a); + + args = [ap.value, ...(args ?? [])]; + + if (f.tag === "MalFunction") { + ap.value = evaluate(f.body, Env.mkEnv(f.env, f.params, args)); + } else if (f.tag === "MalInternalFunction") { + ap.value = f.fn(args); + } + + return ap.value; + }, + + throw: ([v]: Array): MalType.MalType => { + validateArgument(0, v); + + throw v; + }, + + "time-ms": (_: Array): MalType.MalType => + MalType.mkNumber(performance.now()), + + "true?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalBoolean" && v.value), + + vals: ([m]: Array): MalType.MalType => { + validateArgument(0, m, "MalHashMap"); + + return MalType.mkList(MalType.mapValues(asHashMap(m))); + }, + + vec: ([v]: Array): MalType.MalType => { + validateArgument(0, v, ["MalList", "MalVector"]); + + return (v.tag === "MalVector") ? v : MalType.mkVector(asSeq(v).items); + }, + + vector: MalType.mkVector, + + "vector?": ([v]: Array): MalType.MalType => + MalType.mkBoolean(v !== undefined && v.tag === "MalVector"), + + "with-meta": ([v, m]: Array): MalType.MalType => { + validateArgument(0, v); + validateArgument(1, m); + + return MalType.withMeta(v, m); + }, +}); + +export const ns = ( + evaluate: (ast: MalType.MalType, env: Env.Env) => MalType.MalType, +): Array<[string, MalType.MalType]> => { + const fs: any = __ns(evaluate); + + const mkFun = ( + name: string, + fn: (args: Array) => MalType.MalType, + ): MalType.MalInternalFunction => { + const fnp = (args: Array): MalType.MalType => { + try { + return fn(args); + } catch (e: any) { + if (e.error === "UndefinedParameter") { + throw MalType.mkList([ + MalType.mkSymbol("UndefinedParameter"), + MalType.mkSymbol(name), + MalType.mkNumber(e.parameter), + ]); + } else if (e.error === "InvalidTag") { + throw MalType.mkList([ + MalType.mkSymbol("InvalidParameter"), + MalType.mkSymbol(name), + MalType.mkNumber(e.parameter), + e.value, + MalType.mkList(e.tags.map(MalType.mkSymbol)), + ]); + } else { + throw e; + } + } + }; + + return MalType.mkInternalFunction(fnp); + }; + + return Object.keys(fs).map(( + key: string, + ) => [key, mkFun(key, fs[key])]); +}; + +const asAtom = (v: MalType.MalType): MalType.MalAtom => { + if (v.tag === "MalAtom") { + return v; + } else { + throw Error("Unable to coerce to atom"); + } +}; + +const asHashMap = (v: MalType.MalType): MalType.MalHashMap => { + if (v.tag === "MalHashMap") { + return v; + } else { + throw Error("Unable to coerce to hashmap"); + } +}; + +const asInternalFunction = ( + v: MalType.MalType, +): MalType.MalInternalFunction => { + if (v.tag === "MalInternalFunction") { + return v; + } else { + throw Error("Unable to coerce to internal function"); + } +}; + +const asNumber = (v: MalType.MalType): number => { + if (v.tag === "MalNumber") { + return v.value; + } else { + throw Error("Unable to coerce to number"); + } +}; + +const asSeq = (v: MalType.MalType): MalType.MalList | MalType.MalVector => { + if (v.tag === "MalList" || v.tag === "MalVector") { + return v; + } else { + throw Error("Unable to coerce to sequence"); + } +}; + +const asString = (v: MalType.MalType): string => { + if (v.tag === "MalString") { + return v.value; + } else { + throw Error("Unable to coerce to string"); + } +}; diff --git a/impls/deno/env.ts b/impls/deno/env.ts new file mode 100644 index 0000000000..013c2ce0ae --- /dev/null +++ b/impls/deno/env.ts @@ -0,0 +1,64 @@ +import * as MalType from "./types.ts"; + +export type Scope = Map; + +export type Env = { + outer: Env | undefined; + data: Scope; +}; + +export const mkEnv = ( + outer: Env | undefined = undefined, + binds: Array = [], + exprs: Array = [], +): Env => { + const data: Array<[string, MalType.MalType]> = []; + + for (let lp = 0; lp < binds.length; lp += 1) { + if (binds[lp].name === "&") { + if (lp !== binds.length - 2) { + throw new Error( + `Illegal Argument: the '&' symbol must be the second last parameter`, + ); + } else { + data.push([binds[lp + 1].name, MalType.mkList(exprs.slice(lp))]); + break; + } + } else { + data.push([binds[lp].name, exprs[lp]]); + } + } + + return { outer, data: new Map(data) }; +}; + +export const find = ( + name: MalType.MalSymbol, + env: Env, +): MalType.MalType | undefined => { + const result = env.data.get(name.name); + + if (result === undefined) { + return env.outer === undefined ? undefined : find(name, env.outer); + } else { + return result; + } +}; + +export const get = (name: MalType.MalSymbol, env: Env): MalType.MalType => { + const result = find(name, env); + + if (result === undefined) { + throw new Error(`'${name.name}' not found`); + } else { + return result; + } +}; + +export const set = ( + name: MalType.MalSymbol, + value: MalType.MalType, + env: Env, +): void => { + env.data.set(name.name, value); +}; diff --git a/impls/deno/printer.ts b/impls/deno/printer.ts new file mode 100644 index 0000000000..4b11567697 --- /dev/null +++ b/impls/deno/printer.ts @@ -0,0 +1,45 @@ +import { MalType, mapKeyValues } from "./types.ts"; + +export const prStr = ( + v: MalType, + printReabably: boolean = true, +): string => { + const prStrReadably = (v: MalType): string => { + switch (v.tag) { + case "MalList": + return `(${v.items.map(prStrReadably).join(" ")})`; + case "MalVector": + return `[${v.items.map(prStrReadably).join(" ")}]`; + case "MalHashMap": + return `{${ + mapKeyValues(v).map(([k, v]) => + `${prStrReadably(k)} ${prStrReadably(v)}` + ).join(" ") + }}`; + case "MalNil": + return "nil"; + case "MalString": + return printReabably + ? `"${ + v.value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll( + "\n", + "\\n", + ) + }"` + : v.value; + case "MalAtom": + return `(atom ${prStrReadably(v.value)})`; + case "MalBoolean": + case "MalNumber": + return `${v.value}`; + case "MalKeyword": + case "MalSymbol": + return `${v.name}`; + case "MalInternalFunction": + case "MalFunction": + return "#"; + } + }; + + return prStrReadably(v); +}; diff --git a/impls/deno/reader.ts b/impls/deno/reader.ts new file mode 100644 index 0000000000..3f03cff015 --- /dev/null +++ b/impls/deno/reader.ts @@ -0,0 +1,181 @@ +import { + MalNumber, + MalType, + MalVector, + mkBoolean, + mkHashMap, + mkKeyword, + mkList, + mkNumber, + mkString, + mkSymbol, + mkVector, + nil, +} from "./types.ts"; + +interface Reader { + position: number; + peek: () => string; + next: () => string; +} + +const newReader = (tokens: Array): Reader => ({ + position: 0, + + peek: function (): string { + return tokens[this.position]; + }, + + next: function (): string { + const current = this.peek(); + this.position += 1; + return current; + }, +}); + +export const readStr = (input: string): MalType => + readForm(newReader(tokenize(input))); + +const tokenize = (input: string): Array => { + const regex = + /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g; + + const tokens = []; + while (true) { + const tokenResults = regex.exec(input); + if (tokenResults === null || tokenResults[0] === "") { + break; + } else if (tokenResults[1][0] !== ";") { + tokens.push(tokenResults[1]); + } + } + + return tokens; +}; + +const readForm = (reader: Reader): MalType => { + const token = reader.peek(); + + switch (token) { + case undefined: + throw new Error("Reader Error: No input"); + case "(": + return readList(reader); + case "[": + return readVector(reader); + case "{": + return readHashMap(reader); + case "'": + return readSymbol(reader, "quote"); + case "`": + return readSymbol(reader, "quasiquote"); + case "~": + return readSymbol(reader, "unquote"); + case "~@": + return readSymbol(reader, "splice-unquote"); + case "@": + return readSymbol(reader, "deref"); + case "^": + return readMetaData(reader); + case ")": + case "]": + case "}": + throw new Error(`Syntax Error: Unexpected '${token}'`); + default: + return readAtom(reader); + } +}; + +const readList = (reader: Reader): MalType => + readCollection(reader, ")", mkList); + +const readVector = (reader: Reader): MalType => + readCollection(reader, "]", mkVector); + +const readHashMap = ( + reader: Reader, +): MalType => { + const buildMap = (items: Array): MalType => { + const args: Array<[MalType, MalType]> = []; + + if (items.length % 2 === 1) { + throw new Error(`Syntax Error: Odd number of map arguments`); + } + for (let lp = 0; lp < items.length; lp += 2) { + args.push([items[lp], items[lp + 1]]); + } + + return mkHashMap(args); + }; + + return readCollection(reader, "}", buildMap); +}; + +const readCollection = ( + reader: Reader, + close: string, + mk: (a: Array) => MalType, +): MalType => { + reader.next(); + + const items: Array = []; + + while (true) { + const token = reader.peek(); + + if (token === undefined) { + throw new Error(`Syntax Error: EOF whilst expecting '${close}'`); + } else if (token === close) { + reader.next(); + return mk(items); + } else { + items.push(readForm(reader)); + } + } +}; + +const readSymbol = (reader: Reader, name: string): MalType => { + reader.next(); + + return mkList([mkSymbol(name), readForm(reader)]); +}; + +const readMetaData = (reader: Reader): MalType => { + reader.next(); + + const v1 = readForm(reader); + const v2 = readForm(reader); + + return mkList([mkSymbol("with-meta"), v2, v1]); +}; + +const readAtom = (reader: Reader): MalType => { + const token = reader.next(); + + if (token === undefined) { + throw new Error(`Syntax Error: Unexpected EOF`); + } else if (token.match(/^-?[0-9]+$/)) { + return mkNumber(parseInt(token)); + } else if (token == "false") { + return mkBoolean(false); + } else if (token == "true") { + return mkBoolean(true); + } else if (token == "nil") { + return nil; + } else if (token[0] === '"') { + if (token.match(/^"(?:\\.|[^\\"])*"$/)) { + return mkString( + token.substr(1, token.length - 2).replace( + /\\(.)/g, + (_, c: string) => c == "n" ? "\n" : c, + ), + ); + } else { + throw new Error(`Syntax Error: EOF whilst expecting '"': ${token}`); + } + } else if (token[0] === ":") { + return mkKeyword(token); + } else { + return mkSymbol(token); + } +}; diff --git a/impls/deno/readline.ts b/impls/deno/readline.ts new file mode 100644 index 0000000000..05d4ac9213 --- /dev/null +++ b/impls/deno/readline.ts @@ -0,0 +1,28 @@ +export const readline = ( + pp: string | undefined = undefined, +): string | undefined => { + const result: Array = []; + + if (pp !== undefined) { + Deno.stdout.writeSync(new TextEncoder().encode(pp)); + } + + while (true) { + const buffer = new Uint8Array(16); + const uint8Read = Deno.stdin.readSync(buffer); + + if (uint8Read == null) { + return undefined; + } else { + const decoder = new TextDecoder(); + const text = decoder.decode(buffer).substr(0, uint8Read); + + if (text.endsWith("\n")) { + result.push(text.substr(0, text.length - 1)); + return result.join(""); + } else { + result.push(text); + } + } + } +}; diff --git a/impls/deno/run b/impls/deno/run new file mode 100755 index 0000000000..575e70f93a --- /dev/null +++ b/impls/deno/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec deno run --allow-all $(dirname $0)/${STEP:-stepA_mal}.ts "${@}" diff --git a/impls/deno/step0_repl.ts b/impls/deno/step0_repl.ts new file mode 100644 index 0000000000..bed7b84b92 --- /dev/null +++ b/impls/deno/step0_repl.ts @@ -0,0 +1,19 @@ +import { readline } from "./readline.ts"; + +const read = (str: string): any => str; + +const eval_ = (ast: any): any => ast; + +const print = (exp: any): string => exp.toString(); + +const rep = (str: string): string => print(eval_(read(str))); + +while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } + + console.log(rep(value)); +} diff --git a/impls/deno/step1_read_print.ts b/impls/deno/step1_read_print.ts new file mode 100644 index 0000000000..84a0210166 --- /dev/null +++ b/impls/deno/step1_read_print.ts @@ -0,0 +1,29 @@ +import * as Reader from "./reader.ts"; +import * as Printer from "./printer.ts"; +import { MalType } from "./types.ts"; +import { readline } from "./readline.ts"; + + +const read = (str: string): MalType => Reader.readStr(str); + +const eval_ = (ast: MalType): MalType => ast; + +const print = (exp: MalType): string => Printer.prStr(exp); + +const rep = (str: string): string => print(eval_(read(str))); + +while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value)); + } catch (e) { + console.error(e.message); + } +} diff --git a/impls/deno/step2_eval.ts b/impls/deno/step2_eval.ts new file mode 100644 index 0000000000..954348bf33 --- /dev/null +++ b/impls/deno/step2_eval.ts @@ -0,0 +1,114 @@ +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + + +const replEnv = Env.mkEnv(); + +Env.set( + MalType.mkSymbol("+"), + MalType.mkInternalFunction(([a, b]) => + MalType.mkNumber(MalType.asNumber(a) + MalType.asNumber(b)) + ), + replEnv, +); + +Env.set( + MalType.mkSymbol("-"), + MalType.mkInternalFunction(([a, b]) => + MalType.mkNumber(MalType.asNumber(a) - MalType.asNumber(b)) + ), + replEnv, +); + +Env.set( + MalType.mkSymbol("*"), + MalType.mkInternalFunction(([a, b]) => + MalType.mkNumber(MalType.asNumber(a) * MalType.asNumber(b)) + ), + replEnv, +); + +Env.set( + MalType.mkSymbol("/"), + MalType.mkInternalFunction(([a, b]) => + MalType.mkNumber(MalType.asNumber(a) / MalType.asNumber(b)) + ), + replEnv, +); + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + if (ast.tag === "MalSymbol") { + const binding = Env.find(ast, env); + + if (binding === undefined) { + throw new Error(`Unknown Symbol: ${ast.name}`); + } else { + return binding; + } + } else if (ast.tag === "MalList") { + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalVector") { + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalHashMap") { + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + } else { + return ast; + } +}; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } else { + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + return evaluate( + callerItem.body, + Env.mkEnv(callerItem.env, callerItem.params, callerArgs), + ); + } + } + } + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); + } + } else { + return evaluate_ast(ast, env); + } +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string): string => print(evaluate(read(str), replEnv)); + +type FunctionType = (a: MalType.MalType, b: MalType.MalType) => MalType.MalType; + +while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value)); + } catch (e) { + console.error(e.message); + } +} diff --git a/impls/deno/step3_env.ts b/impls/deno/step3_env.ts new file mode 100644 index 0000000000..3fb57bb94d --- /dev/null +++ b/impls/deno/step3_env.ts @@ -0,0 +1,170 @@ +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + + +const replEnv = Env.mkEnv(); + +Env.set( + MalType.mkSymbol("+"), + MalType.mkInternalFunction(([a, b]) => + MalType.mkNumber(MalType.asNumber(a) + MalType.asNumber(b)) + ), + replEnv, +); + +Env.set( + MalType.mkSymbol("-"), + MalType.mkInternalFunction(([a, b]) => + MalType.mkNumber(MalType.asNumber(a) - MalType.asNumber(b)) + ), + replEnv, +); + +Env.set( + MalType.mkSymbol("*"), + MalType.mkInternalFunction(([a, b]) => + MalType.mkNumber(MalType.asNumber(a) * MalType.asNumber(b)) + ), + replEnv, +); + +Env.set( + MalType.mkSymbol("/"), + MalType.mkInternalFunction(([a, b]) => + MalType.mkNumber(MalType.asNumber(a) / MalType.asNumber(b)) + ), + replEnv, +); + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + if (ast.tag === "MalSymbol") { + return Env.get(ast, env); + } else if (ast.tag === "MalList") { + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalVector") { + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalHashMap") { + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + } else { + return ast; + } +}; + +const isNamedSymbol = (ast: MalType.MalType, name: string): boolean => + ast.tag === "MalSymbol" && ast.name === name; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } else if (isNamedSymbol(ast.items[0], "def!")) { + return evaluateDefBang(ast, env); + } else if (isNamedSymbol(ast.items[0], "let*")) { + return evaluateLetStar(ast, env); + } else { + return evaluateFunctionInvocation(ast, env); + } + } else { + return evaluate_ast(ast, env); + } +}; + +const evaluateDefBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: def! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateLetStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const bindings = ast.items[1]; + + if (bindings.tag !== "MalList" && bindings.tag !== "MalVector") { + throw new Error( + `Invalid Argument: let* requires a list of bindings: ${ + JSON.stringify(bindings) + }`, + ); + } + + const innerEnv = Env.mkEnv(env); + for (let lp = 0; lp < bindings.items.length; lp += 2) { + const name = bindings.items[lp]; + const value = bindings.items[lp + 1] ?? MalType.nil; + + if (name.tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: let* binding requires a symbol name: ${ + JSON.stringify(name) + }`, + ); + } + + Env.set(name, evaluate(value, innerEnv), innerEnv); + } + + return evaluate(ast.items[2], innerEnv); +}; + +const evaluateFunctionInvocation = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + return evaluate( + callerItem.body, + Env.mkEnv(callerItem.env, callerItem.params, callerArgs), + ); + } + } + } + + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string): string => print(evaluate(read(str), replEnv)); + +while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value)); + } catch (e) { + console.error(e.message); + } +} diff --git a/impls/deno/step4_if_fn_do.ts b/impls/deno/step4_if_fn_do.ts new file mode 100644 index 0000000000..25a2a16ae0 --- /dev/null +++ b/impls/deno/step4_if_fn_do.ts @@ -0,0 +1,246 @@ +import * as Core from "./core.ts"; +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + if (ast.tag === "MalSymbol") { + return Env.get(ast, env); + } else if (ast.tag === "MalList") { + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalVector") { + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalHashMap") { + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + } else { + return ast; + } +}; + +const isNamedSymbol = (ast: MalType.MalType, name: string): boolean => + ast.tag === "MalSymbol" && ast.name === name; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } else if (isNamedSymbol(ast.items[0], "def!")) { + return evaluateDefBang(ast, env); + } else if (isNamedSymbol(ast.items[0], "do")) { + return evaluateDo(ast, env); + } else if (isNamedSymbol(ast.items[0], "fn*")) { + return evaluateFnStar(ast, env); + } else if (isNamedSymbol(ast.items[0], "if")) { + return evaluateIf(ast, env); + } else if (isNamedSymbol(ast.items[0], "let*")) { + return evaluateLetStar(ast, env); + } else { + return evaluateFunctionInvocation(ast, env); + } + } else { + return evaluate_ast(ast, env); + } +}; + +const evaluateDefBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: def! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDo = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const result = evaluate_ast(MalType.mkList(ast.items.slice(1)), env); + + if (result.tag !== "MalList") { + throw new Error( + `Invalid Argument: do expected a list: ${JSON.stringify(result)}`, + ); + } + if (result.items.length === 0) { + throw new Error("Invalid Argument: do expected a non-empty list"); + } + + return result.items[result.items.length - 1]; +}; + +const evaluateFnStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const [_, parameters, body] = ast.items; + + if ( + parameters == undefined || + parameters.tag !== "MalList" && parameters.tag !== "MalVector" + ) { + throw new Error("Invalid Argument: fn* expects a list of parameters"); + } + if (body === undefined) { + throw new Error("Invalid Argument: fn* expects a body"); + } + + const formalParameters: Array = []; + parameters.items.forEach((p, idx) => { + if (p.tag === "MalSymbol") { + formalParameters.push(p); + } else { + throw new Error( + `Invalid Argument: Parameter ${idx + 1} in fn* is not a symbol: ${ + JSON.stringify(p) + }`, + ); + } + }); + + const fn = (args: Array): MalType.MalType => + evaluate(body, Env.mkEnv(env, formalParameters, args)); + + return MalType.mkInternalFunction(fn); +}; + +const evaluateIf = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items.length < 3) { + throw new Error( + `Invalid Argument: if expects at least 3 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + if (ast.items.length > 4) { + throw new Error( + `Invalid Argument: if expects no more than 4 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + + const ifGuard = evaluate(ast.items[1], env); + + if ( + ifGuard.tag === "MalNil" || ifGuard.tag === "MalBoolean" && !ifGuard.value + ) { + return ast.items.length === 4 ? evaluate(ast.items[3], env) : MalType.nil; + } else { + return evaluate(ast.items[2], env); + } +}; + +const evaluateLetStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const bindings = ast.items[1]; + + if (bindings.tag !== "MalList" && bindings.tag !== "MalVector") { + throw new Error( + `Invalid Argument: let* requires a list of bindings: ${ + JSON.stringify(bindings) + }`, + ); + } + + const innerEnv = Env.mkEnv(env); + for (let lp = 0; lp < bindings.items.length; lp += 2) { + const name = bindings.items[lp]; + const value = bindings.items[lp + 1] ?? MalType.nil; + + if (name.tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: let* binding requires a symbol name: ${ + JSON.stringify(name) + }`, + ); + } + + Env.set(name, evaluate(value, innerEnv), innerEnv); + } + + return evaluate(ast.items[2], innerEnv); +}; + +const evaluateFunctionInvocation = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + return evaluate( + callerItem.body, + Env.mkEnv(callerItem.env, callerItem.params, callerArgs), + ); + } + } + } + + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string, env: Env.Env): string => + print(evaluate(read(str), env)); + +const initReplEnv = () => { + const ns = Core.ns(evaluate); + + const env = Env.mkEnv( + undefined, + ns.map(([n]) => MalType.mkSymbol(n)), + ns.map(([, t]) => t), + ); + + rep("(def! not (fn* (a) (if a false true)))", env); + + return env; +}; + +const repl = () => { + const env = initReplEnv(); + + while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value, env)); + } catch (e) { + console.error(e.message); + } + } +}; + +repl(); diff --git a/impls/deno/step5_tco.ts b/impls/deno/step5_tco.ts new file mode 100644 index 0000000000..279676482d --- /dev/null +++ b/impls/deno/step5_tco.ts @@ -0,0 +1,227 @@ +import * as Core from "./core.ts"; +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + if (ast.tag === "MalSymbol") { + return Env.get(ast, env); + } else if (ast.tag === "MalList") { + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalVector") { + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalHashMap") { + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + } else { + return ast; + } +}; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + while (true) { + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } + + if (ast.items[0].tag === "MalSymbol") { + switch (ast.items[0].name) { + case "def!": + return evaluateDefBang(ast, env); + case "do": + ast = evaluateDo(ast, env); + continue; + case "fn*": + return evaluateFnStar(ast, env); + case "if": + ast = evaluateIf(ast, env); + continue; + case "let*": { + const bindings = ast.items[1]; + + if (bindings.tag !== "MalList" && bindings.tag !== "MalVector") { + throw new Error( + `Invalid Argument: let* requires a list of bindings: ${ + JSON.stringify(bindings) + }`, + ); + } + + const innerEnv = Env.mkEnv(env); + for (let lp = 0; lp < bindings.items.length; lp += 2) { + const name = bindings.items[lp]; + const value = bindings.items[lp + 1] ?? MalType.nil; + + if (name.tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: let* binding requires a symbol name: ${ + JSON.stringify(name) + }`, + ); + } + + Env.set(name, evaluate(value, innerEnv), innerEnv); + } + + ast = ast.items[2]; + env = innerEnv; + continue; + } + } + } + + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + ast = callerItem.body; + env = Env.mkEnv(callerItem.env, callerItem.params, callerArgs); + continue; + } + } + } + + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); + } else { + return evaluate_ast(ast, env); + } + } +}; + +const evaluateDefBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: def! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDo = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + evaluate_ast(MalType.mkList(ast.items.slice(1, ast.items.length - 1)), env); + return ast.items[ast.items.length - 1]; +}; + +const evaluateFnStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const [_, parameters, body] = ast.items; + + if ( + parameters == undefined || + parameters.tag !== "MalList" && parameters.tag !== "MalVector" + ) { + throw new Error("Invalid Argument: fn* expects a list of parameters"); + } + if (body === undefined) { + throw new Error("Invalid Argument: fn* expects a body"); + } + + const formalParameters: Array = []; + parameters.items.forEach((p, idx) => { + if (p.tag === "MalSymbol") { + formalParameters.push(p); + } else { + throw new Error( + `Invalid Argument: Parameter ${idx + 1} in fn* is not a symbol: ${ + JSON.stringify(p) + }`, + ); + } + }); + + return MalType.mkFunction(body, formalParameters, env); +}; + +const evaluateIf = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items.length < 3) { + throw new Error( + `Invalid Argument: if expects at least 3 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + if (ast.items.length > 4) { + throw new Error( + `Invalid Argument: if expects no more than 4 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + + const ifGuard = evaluate(ast.items[1], env); + + if ( + ifGuard.tag === "MalNil" || ifGuard.tag === "MalBoolean" && !ifGuard.value + ) { + return ast.items.length === 4 ? ast.items[3] : MalType.nil; + } else { + return ast.items[2]; + } +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string, env: Env.Env): string => + print(evaluate(read(str), env)); + +const initReplEnv = () => { + const ns = Core.ns(evaluate); + + const env = Env.mkEnv( + undefined, + ns.map(([n]) => MalType.mkSymbol(n)), + ns.map(([, t]) => t), + ); + + rep("(def! not (fn* (a) (if a false true)))", env); + + return env; +}; + +const repl = () => { + const env = initReplEnv(); + + while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value, env)); + } catch (e) { + console.error(e.message); + } + } +}; + +repl(); diff --git a/impls/deno/step6_file.ts b/impls/deno/step6_file.ts new file mode 100644 index 0000000000..2556d4f3e6 --- /dev/null +++ b/impls/deno/step6_file.ts @@ -0,0 +1,260 @@ +import * as Core from "./core.ts"; +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + if (ast.tag === "MalSymbol") { + return Env.get(ast, env); + } else if (ast.tag === "MalList") { + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalVector") { + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + } else if (ast.tag === "MalHashMap") { + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + } else { + return ast; + } +}; + +const isNamedSymbol = (ast: MalType.MalType, name: string): boolean => + ast.tag === "MalSymbol" && ast.name === name; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + while (true) { + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } + + if (ast.items[0].tag === "MalSymbol") { + switch (ast.items[0].name) { + case "def!": + return evaluateDefBang(ast, env); + case "do": + ast = evaluateDo(ast, env); + continue; + case "fn*": + return evaluateFnStar(ast, env); + case "if": + ast = evaluateIf(ast, env); + continue; + case "let*": { + const bindings = ast.items[1]; + + if (bindings.tag !== "MalList" && bindings.tag !== "MalVector") { + throw new Error( + `Invalid Argument: let* requires a list of bindings: ${ + JSON.stringify(bindings) + }`, + ); + } + + const innerEnv = Env.mkEnv(env); + for (let lp = 0; lp < bindings.items.length; lp += 2) { + const name = bindings.items[lp]; + const value = bindings.items[lp + 1] ?? MalType.nil; + + if (name.tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: let* binding requires a symbol name: ${ + JSON.stringify(name) + }`, + ); + } + + Env.set(name, evaluate(value, innerEnv), innerEnv); + } + + ast = ast.items[2]; + env = innerEnv; + continue; + } + } + } + + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + ast = callerItem.body; + env = Env.mkEnv(callerItem.env, callerItem.params, callerArgs); + continue; + } + } + } + + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); + } else { + return evaluate_ast(ast, env); + } + } +}; + +const evaluateDefBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: def! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDo = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + evaluate_ast(MalType.mkList(ast.items.slice(1, ast.items.length - 1)), env); + return ast.items[ast.items.length - 1]; +}; + +const evaluateFnStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const [_, parameters, body] = ast.items; + + if ( + parameters == undefined || + parameters.tag !== "MalList" && parameters.tag !== "MalVector" + ) { + throw new Error("Invalid Argument: fn* expects a list of parameters"); + } + if (body === undefined) { + throw new Error("Invalid Argument: fn* expects a body"); + } + + const formalParameters: Array = []; + parameters.items.forEach((p, idx) => { + if (p.tag === "MalSymbol") { + formalParameters.push(p); + } else { + throw new Error( + `Invalid Argument: Parameter ${idx + 1} in fn* is not a symbol: ${ + JSON.stringify(p) + }`, + ); + } + }); + + return MalType.mkFunction(body, formalParameters, env); +}; + +const evaluateIf = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items.length < 3) { + throw new Error( + `Invalid Argument: if expects at least 3 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + if (ast.items.length > 4) { + throw new Error( + `Invalid Argument: if expects no more than 4 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + + const ifGuard = evaluate(ast.items[1], env); + + if ( + ifGuard.tag === "MalNil" || ifGuard.tag === "MalBoolean" && !ifGuard.value + ) { + return ast.items.length === 4 ? ast.items[3] : MalType.nil; + } else { + return ast.items[2]; + } +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string, env: Env.Env): string => + print(evaluate(read(str), env)); + +const initReplEnv = () => { + const ns = Core.ns(evaluate); + + const env = Env.mkEnv( + undefined, + ns.map(([n]) => MalType.mkSymbol(n)), + ns.map(([, t]) => t), + ); + + Env.set( + MalType.mkSymbol("eval"), + MalType.mkInternalFunction(([a]) => { + if (a === undefined) { + return MalType.nil; + } + return evaluate(a, env); + }), + env, + ); + + rep("(def! not (fn* (a) (if a false true)))", env); + rep( + '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', + env, + ); + + return env; +}; + +const repl = (env: Env.Env) => { + while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value, env)); + } catch (e) { + if (e.message !== "Reader Error: No input") { + console.error(e.message); + } + } + } +}; + +if (Deno.args.length > 0) { + const env = initReplEnv(); + + Env.set( + MalType.mkSymbol("*ARGV*"), + MalType.mkList(Deno.args.slice(1).map(MalType.mkString)), + env, + ); + + rep(`(load-file "${Deno.args[0]}")`, env); +} else { + const env = initReplEnv(); + Env.set(MalType.mkSymbol("*ARGV*"), MalType.mkList([]), env); + + repl(env); +} diff --git a/impls/deno/step7_quote.ts b/impls/deno/step7_quote.ts new file mode 100644 index 0000000000..b0d3f9c4a5 --- /dev/null +++ b/impls/deno/step7_quote.ts @@ -0,0 +1,309 @@ +import * as Core from "./core.ts"; +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + switch (ast.tag) { + case "MalSymbol": + return Env.get(ast, env); + case "MalList": + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + case "MalVector": + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + case "MalHashMap": + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + default: + return ast; + } +}; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + while (true) { + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } + + if (ast.items[0].tag === "MalSymbol") { + switch (ast.items[0].name) { + case "def!": + return evaluateDefBang(ast, env); + case "do": + ast = evaluateDo(ast, env); + continue; + case "fn*": + return evaluateFnStar(ast, env); + case "if": + ast = evaluateIf(ast, env); + continue; + case "let*": { + const bindings = ast.items[1]; + + if (bindings.tag !== "MalList" && bindings.tag !== "MalVector") { + throw new Error( + `Invalid Argument: let* requires a list of bindings: ${ + JSON.stringify(bindings) + }`, + ); + } + + const innerEnv = Env.mkEnv(env); + for (let lp = 0; lp < bindings.items.length; lp += 2) { + const name = bindings.items[lp]; + const value = bindings.items[lp + 1] ?? MalType.nil; + + if (name.tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: let* binding requires a symbol name: ${ + JSON.stringify(name) + }`, + ); + } + + Env.set(name, evaluate(value, innerEnv), innerEnv); + } + + ast = ast.items[2]; + env = innerEnv; + continue; + } + case "quasiquote": + ast = evaluateQuasiQuote(ast.items[1]); + continue; + case "quasiquoteexpand": + return evaluateQuasiQuote(ast.items[1]); + case "quote": + return ast.items[1]; + } + } + + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + ast = callerItem.body; + env = Env.mkEnv(callerItem.env, callerItem.params, callerArgs); + continue; + } + } + } + + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); + } else { + return evaluate_ast(ast, env); + } + } +}; + +const evaluateDefBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: def! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDo = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + evaluate_ast(MalType.mkList(ast.items.slice(1, ast.items.length - 1)), env); + return ast.items[ast.items.length - 1]; +}; + +const evaluateFnStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const [_, parameters, body] = ast.items; + + if ( + parameters == undefined || + parameters.tag !== "MalList" && parameters.tag !== "MalVector" + ) { + throw new Error("Invalid Argument: fn* expects a list of parameters"); + } + if (body === undefined) { + throw new Error("Invalid Argument: fn* expects a body"); + } + + const formalParameters: Array = []; + parameters.items.forEach((p, idx) => { + if (p.tag === "MalSymbol") { + formalParameters.push(p); + } else { + throw new Error( + `Invalid Argument: Parameter ${idx + 1} in fn* is not a symbol: ${ + JSON.stringify(p) + }`, + ); + } + }); + + return MalType.mkFunction(body, formalParameters, env); +}; + +const evaluateIf = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items.length < 3) { + throw new Error( + `Invalid Argument: if expects at least 3 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + if (ast.items.length > 4) { + throw new Error( + `Invalid Argument: if expects no more than 4 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + + const ifGuard = evaluate(ast.items[1], env); + + if ( + ifGuard.tag === "MalNil" || ifGuard.tag === "MalBoolean" && !ifGuard.value + ) { + return ast.items.length === 4 ? ast.items[3] : MalType.nil; + } else { + return ast.items[2]; + } +}; + +const evaluateQuasiQuote = (ast: MalType.MalType): MalType.MalType => { + const startsWith = ( + items: Array, + symbolName: string, + ): boolean => (items.length == 2 && items[0].tag === "MalSymbol" && + items[0].name === symbolName); + + const loop = ( + element: MalType.MalType, + accumulator: MalType.MalList, + ): MalType.MalList => + (element.tag == "MalList" && startsWith(element.items, "splice-unquote")) + ? MalType.mkList( + [MalType.mkSymbol("concat"), element.items[1], accumulator], + ) + : MalType.mkList( + [MalType.mkSymbol("cons"), evaluateQuasiQuote(element), accumulator], + ); + + const foldr = (xs: MalType.MalType[]): MalType.MalList => { + let acc = MalType.mkList([]); + for (let i = xs.length - 1; i >= 0; i -= 1) { + acc = loop(xs[i], acc); + } + return acc; + }; + + switch (ast.tag) { + case "MalSymbol": + return MalType.mkList([MalType.mkSymbol("quote"), ast]); + case "MalHashMap": + return MalType.mkList([MalType.mkSymbol("quote"), ast]); + case "MalList": + return (startsWith(ast.items, "unquote")) + ? ast.items[1] + : foldr(ast.items); + case "MalVector": + return MalType.mkList([MalType.mkSymbol("vec"), foldr(ast.items)]); + default: + return ast; + } +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string, env: Env.Env): string => + print(evaluate(read(str), env)); + +const initReplEnv = () => { + const ns = Core.ns(evaluate); + + const env = Env.mkEnv( + undefined, + ns.map(([n]) => MalType.mkSymbol(n)), + ns.map(([, t]) => t), + ); + + Env.set( + MalType.mkSymbol("eval"), + MalType.mkInternalFunction(([a]) => { + if (a === undefined) { + return MalType.nil; + } + return evaluate(a, env); + }), + env, + ); + + rep("(def! not (fn* (a) (if a false true)))", env); + rep( + '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', + env, + ); + + return env; +}; + +const repl = (env: Env.Env) => { + while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value, env)); + } catch (e) { + if (e.message !== "Reader Error: No input") { + console.error(e.message); + } + } + } +}; + +if (Deno.args.length > 0) { + const env = initReplEnv(); + + Env.set( + MalType.mkSymbol("*ARGV*"), + MalType.mkList(Deno.args.slice(1).map(MalType.mkString)), + env, + ); + + rep(`(load-file "${Deno.args[0]}")`, env); +} else { + const env = initReplEnv(); + Env.set(MalType.mkSymbol("*ARGV*"), MalType.mkList([]), env); + + repl(env); +} diff --git a/impls/deno/step8_macros.ts b/impls/deno/step8_macros.ts new file mode 100644 index 0000000000..082870219e --- /dev/null +++ b/impls/deno/step8_macros.ts @@ -0,0 +1,370 @@ +import * as Core from "./core.ts"; +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + switch (ast.tag) { + case "MalSymbol": + return Env.get(ast, env); + case "MalList": + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + case "MalVector": + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + case "MalHashMap": + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + default: + return ast; + } +}; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + while (true) { + ast = macroExpand(ast, env); + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } + + if (ast.items[0].tag === "MalSymbol") { + switch (ast.items[0].name) { + case "def!": + return evaluateDefBang(ast, env); + case "defmacro!": + return evaluateDefMacroBang(ast, env); + case "do": + ast = evaluateDo(ast, env); + continue; + case "fn*": + return evaluateFnStar(ast, env); + case "if": + ast = evaluateIf(ast, env); + continue; + case "let*": { + const bindings = ast.items[1]; + + if (bindings.tag !== "MalList" && bindings.tag !== "MalVector") { + throw new Error( + `Invalid Argument: let* requires a list of bindings: ${ + JSON.stringify(bindings) + }`, + ); + } + + const innerEnv = Env.mkEnv(env); + for (let lp = 0; lp < bindings.items.length; lp += 2) { + const name = bindings.items[lp]; + const value = bindings.items[lp + 1] ?? MalType.nil; + + if (name.tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: let* binding requires a symbol name: ${ + JSON.stringify(name) + }`, + ); + } + + Env.set(name, evaluate(value, innerEnv), innerEnv); + } + + ast = ast.items[2]; + env = innerEnv; + continue; + } + case "macroexpand": + return macroExpand(ast.items[1], env); + case "quasiquote": + ast = evaluateQuasiQuote(ast.items[1]); + continue; + case "quasiquoteexpand": + return evaluateQuasiQuote(ast.items[1]); + case "quote": + return ast.items[1]; + } + } + + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + ast = callerItem.body; + env = Env.mkEnv(callerItem.env, callerItem.params, callerArgs); + continue; + } + } + } + + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); + } else { + return evaluate_ast(ast, env); + } + } +}; + +const macroExpand = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + const macroFunction = ( + ast: MalType.MalType, + env: Env.Env, + ): [MalType.MalFunction, Array] | undefined => { + if ( + (ast.tag === "MalList" || ast.tag === "MalVector") && + ast.items.length > 0 && ast.items[0].tag === "MalSymbol" + ) { + const value = Env.find(ast.items[0], env); + + return value !== undefined && value.tag === "MalFunction" && value.isMacro + ? [value, ast.items.slice(1)] + : undefined; + } else { + return undefined; + } + }; + + while (true) { + const macro = macroFunction(ast, env); + if (macro === undefined) { + return ast; + } else { + ast = evaluate( + macro[0].body, + Env.mkEnv(macro[0].env, macro[0].params, macro[1]), + ); + } + } +}; + +const evaluateDefBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: def! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDefMacroBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: defmacro! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + + if (result.tag === "MalFunction") { + result.isMacro = true; + } + + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDo = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + evaluate_ast(MalType.mkList(ast.items.slice(1, ast.items.length - 1)), env); + return ast.items[ast.items.length - 1]; +}; + +const evaluateFnStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const [_, parameters, body] = ast.items; + + if ( + parameters == undefined || + parameters.tag !== "MalList" && parameters.tag !== "MalVector" + ) { + throw new Error("Invalid Argument: fn* expects a list of parameters"); + } + if (body === undefined) { + throw new Error("Invalid Argument: fn* expects a body"); + } + + const formalParameters: Array = []; + parameters.items.forEach((p, idx) => { + if (p.tag === "MalSymbol") { + formalParameters.push(p); + } else { + throw new Error( + `Invalid Argument: Parameter ${idx + 1} in fn* is not a symbol: ${ + JSON.stringify(p) + }`, + ); + } + }); + + return MalType.mkFunction(body, formalParameters, env); +}; + +const evaluateIf = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items.length < 3) { + throw new Error( + `Invalid Argument: if expects at least 3 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + if (ast.items.length > 4) { + throw new Error( + `Invalid Argument: if expects no more than 4 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + + const ifGuard = evaluate(ast.items[1], env); + + if ( + ifGuard.tag === "MalNil" || ifGuard.tag === "MalBoolean" && !ifGuard.value + ) { + return ast.items.length === 4 ? ast.items[3] : MalType.nil; + } else { + return ast.items[2]; + } +}; + +const evaluateQuasiQuote = (ast: MalType.MalType): MalType.MalType => { + const startsWith = ( + items: Array, + symbolName: string, + ): boolean => (items.length == 2 && items[0].tag === "MalSymbol" && + items[0].name === symbolName); + + const loop = ( + element: MalType.MalType, + accumulator: MalType.MalList, + ): MalType.MalList => + (element.tag == "MalList" && startsWith(element.items, "splice-unquote")) + ? MalType.mkList( + [MalType.mkSymbol("concat"), element.items[1], accumulator], + ) + : MalType.mkList( + [MalType.mkSymbol("cons"), evaluateQuasiQuote(element), accumulator], + ); + + const foldr = (xs: MalType.MalType[]): MalType.MalList => { + let acc = MalType.mkList([]); + for (let i = xs.length - 1; i >= 0; i -= 1) { + acc = loop(xs[i], acc); + } + return acc; + }; + + switch (ast.tag) { + case "MalSymbol": + return MalType.mkList([MalType.mkSymbol("quote"), ast]); + case "MalHashMap": + return MalType.mkList([MalType.mkSymbol("quote"), ast]); + case "MalList": + return (startsWith(ast.items, "unquote")) + ? ast.items[1] + : foldr(ast.items); + case "MalVector": + return MalType.mkList([MalType.mkSymbol("vec"), foldr(ast.items)]); + default: + return ast; + } +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string, env: Env.Env): string => + print(evaluate(read(str), env)); + +const initReplEnv = () => { + const ns = Core.ns(evaluate); + + const env = Env.mkEnv( + undefined, + ns.map(([n]) => MalType.mkSymbol(n)), + ns.map(([, t]) => t), + ); + + Env.set( + MalType.mkSymbol("eval"), + MalType.mkInternalFunction(([a]) => { + if (a === undefined) { + return MalType.nil; + } + return evaluate(a, env); + }), + env, + ); + + rep("(def! not (fn* (a) (if a false true)))", env); + rep( + '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', + env, + ); + rep( + "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", + env, + ); + + return env; +}; + +const repl = (env: Env.Env) => { + while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value, env)); + } catch (e) { + if (e.message !== "Reader Error: No input") { + console.error(e.message); + } + } + } +}; + +if (Deno.args.length > 0) { + const env = initReplEnv(); + + Env.set( + MalType.mkSymbol("*ARGV*"), + MalType.mkList(Deno.args.slice(1).map(MalType.mkString)), + env, + ); + + rep(`(load-file "${Deno.args[0]}")`, env); +} else { + const env = initReplEnv(); + Env.set(MalType.mkSymbol("*ARGV*"), MalType.mkList([]), env); + + repl(env); +} diff --git a/impls/deno/step9_try.ts b/impls/deno/step9_try.ts new file mode 100644 index 0000000000..e7138fcdf9 --- /dev/null +++ b/impls/deno/step9_try.ts @@ -0,0 +1,419 @@ +import * as Core from "./core.ts"; +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + switch (ast.tag) { + case "MalSymbol": + return Env.get(ast, env); + case "MalList": + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + case "MalVector": + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + case "MalHashMap": + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + default: + return ast; + } +}; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + while (true) { + // console.log(Printer.prStr(ast)); + + ast = macroExpand(ast, env); + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } + + if (ast.items[0].tag === "MalSymbol") { + switch (ast.items[0].name) { + case "def!": + return evaluateDefBang(ast, env); + case "defmacro!": + return evaluateDefMacroBang(ast, env); + case "do": + ast = evaluateDo(ast, env); + continue; + case "fn*": + return evaluateFnStar(ast, env); + case "if": + ast = evaluateIf(ast, env); + continue; + case "let*": { + const bindings = ast.items[1]; + + if (bindings.tag !== "MalList" && bindings.tag !== "MalVector") { + throw new Error( + `Invalid Argument: let* requires a list of bindings: ${ + JSON.stringify(bindings) + }`, + ); + } + + const innerEnv = Env.mkEnv(env); + for (let lp = 0; lp < bindings.items.length; lp += 2) { + const name = bindings.items[lp]; + const value = bindings.items[lp + 1] ?? MalType.nil; + + if (name.tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: let* binding requires a symbol name: ${ + JSON.stringify(name) + }`, + ); + } + + Env.set(name, evaluate(value, innerEnv), innerEnv); + } + + ast = ast.items[2]; + env = innerEnv; + continue; + } + case "macroexpand": + return macroExpand(ast.items[1], env); + case "quasiquote": + ast = evaluateQuasiQuote(ast.items[1]); + continue; + case "quasiquoteexpand": + return evaluateQuasiQuote(ast.items[1]); + case "quote": + return ast.items[1]; + case "try*": { + if (ast.items.length === 2) { + return evaluate(ast.items[1], env); + } + + if (ast.items.length === 3) { + const catchItem = ast.items[2]; + + if ( + (catchItem.tag === "MalList" || + catchItem.tag === "MalVector") && catchItem.items.length === 3 + ) { + const catchKeyword = catchItem.items[0]; + const catchSymbol = catchItem.items[1]; + const catchBody = catchItem.items[2]; + if ( + catchKeyword.tag === "MalSymbol" && + catchKeyword.name === "catch*" && + catchSymbol.tag === "MalSymbol" + ) { + try { + return evaluate(ast.items[1], env); + } catch (e) { + const ep = e.tag !== undefined && e.tag.startsWith("Mal") + ? e + : e.message !== undefined + ? MalType.mkString(e.message) + : MalType.mkString(JSON.stringify(e)); + + env = Env.mkEnv( + env, + [catchSymbol], + [ep], + ); + ast = catchBody; + continue; + } + } + } + } + throw new Error( + `Invalid Argument: try* is not in a valid form: ${ + JSON.stringify(ast) + }`, + ); + } + } + } + + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + ast = callerItem.body; + env = Env.mkEnv(callerItem.env, callerItem.params, callerArgs); + continue; + } + } + } + + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); + } else { + return evaluate_ast(ast, env); + } + } +}; + +const macroExpand = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + const macroFunction = ( + ast: MalType.MalType, + env: Env.Env, + ): [MalType.MalFunction, Array] | undefined => { + if ( + (ast.tag === "MalList" || ast.tag === "MalVector") && + ast.items.length > 0 && ast.items[0].tag === "MalSymbol" + ) { + const value = Env.find(ast.items[0], env); + + return value !== undefined && value.tag === "MalFunction" && value.isMacro + ? [value, ast.items.slice(1)] + : undefined; + } else { + return undefined; + } + }; + + while (true) { + const macro = macroFunction(ast, env); + if (macro === undefined) { + return ast; + } else { + ast = evaluate( + macro[0].body, + Env.mkEnv(macro[0].env, macro[0].params, macro[1]), + ); + } + } +}; + +const evaluateDefBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: def! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDefMacroBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: defmacro! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + + if (result.tag === "MalFunction") { + result.isMacro = true; + } + + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDo = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + evaluate_ast(MalType.mkList(ast.items.slice(1, ast.items.length - 1)), env); + return ast.items[ast.items.length - 1]; +}; + +const evaluateFnStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const [_, parameters, body] = ast.items; + + if ( + parameters == undefined || + parameters.tag !== "MalList" && parameters.tag !== "MalVector" + ) { + throw new Error("Invalid Argument: fn* expects a list of parameters"); + } + if (body === undefined) { + throw new Error("Invalid Argument: fn* expects a body"); + } + + const formalParameters: Array = []; + parameters.items.forEach((p, idx) => { + if (p.tag === "MalSymbol") { + formalParameters.push(p); + } else { + throw new Error( + `Invalid Argument: Parameter ${idx + 1} in fn* is not a symbol: ${ + JSON.stringify(p) + }`, + ); + } + }); + + return MalType.mkFunction(body, formalParameters, env); +}; + +const evaluateIf = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items.length < 3) { + throw new Error( + `Invalid Argument: if expects at least 3 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + if (ast.items.length > 4) { + throw new Error( + `Invalid Argument: if expects no more than 4 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + + const ifGuard = evaluate(ast.items[1], env); + + if ( + ifGuard.tag === "MalNil" || ifGuard.tag === "MalBoolean" && !ifGuard.value + ) { + return ast.items.length === 4 ? ast.items[3] : MalType.nil; + } else { + return ast.items[2]; + } +}; + +const evaluateQuasiQuote = (ast: MalType.MalType): MalType.MalType => { + const startsWith = ( + items: Array, + symbolName: string, + ): boolean => (items.length == 2 && items[0].tag === "MalSymbol" && + items[0].name === symbolName); + + const loop = ( + element: MalType.MalType, + accumulator: MalType.MalList, + ): MalType.MalList => + (element.tag == "MalList" && startsWith(element.items, "splice-unquote")) + ? MalType.mkList( + [MalType.mkSymbol("concat"), element.items[1], accumulator], + ) + : MalType.mkList( + [MalType.mkSymbol("cons"), evaluateQuasiQuote(element), accumulator], + ); + + const foldr = (xs: MalType.MalType[]): MalType.MalList => { + let acc = MalType.mkList([]); + for (let i = xs.length - 1; i >= 0; i -= 1) { + acc = loop(xs[i], acc); + } + return acc; + }; + + switch (ast.tag) { + case "MalSymbol": + return MalType.mkList([MalType.mkSymbol("quote"), ast]); + case "MalHashMap": + return MalType.mkList([MalType.mkSymbol("quote"), ast]); + case "MalList": + return (startsWith(ast.items, "unquote")) + ? ast.items[1] + : foldr(ast.items); + case "MalVector": + return MalType.mkList([MalType.mkSymbol("vec"), foldr(ast.items)]); + default: + return ast; + } +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string, env: Env.Env): string => + print(evaluate(read(str), env)); + +const initReplEnv = () => { + const ns = Core.ns(evaluate); + + const env = Env.mkEnv( + undefined, + ns.map(([n]) => MalType.mkSymbol(n)), + ns.map(([, t]) => t), + ); + + Env.set( + MalType.mkSymbol("eval"), + MalType.mkInternalFunction(([a]) => { + if (a === undefined) { + return MalType.nil; + } + return evaluate(a, env); + }), + env, + ); + + rep("(def! not (fn* (a) (if a false true)))", env); + rep( + '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', + env, + ); + rep( + "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", + env, + ); + + return env; +}; + +const repl = (env: Env.Env) => { + while (true) { + const value = readline("user> "); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value, env)); + } catch (e) { + if (e.message !== "Reader Error: No input") { + const message = (e.tag !== undefined) ? Printer.prStr(e) : e.message; + console.error(`Exception: ${message}`); + } + } + } +}; + +if (Deno.args.length > 0) { + const env = initReplEnv(); + + Env.set( + MalType.mkSymbol("*ARGV*"), + MalType.mkList(Deno.args.slice(1).map(MalType.mkString)), + env, + ); + + rep(`(load-file "${Deno.args[0]}")`, env); +} else { + const env = initReplEnv(); + Env.set(MalType.mkSymbol("*ARGV*"), MalType.mkList([]), env); + + repl(env); +} diff --git a/impls/deno/stepA_mal.ts b/impls/deno/stepA_mal.ts new file mode 100644 index 0000000000..f9c14e50db --- /dev/null +++ b/impls/deno/stepA_mal.ts @@ -0,0 +1,428 @@ +import * as Core from "./core.ts"; +import * as Env from "./env.ts"; +import * as MalType from "./types.ts"; +import * as Printer from "./printer.ts"; +import * as Reader from "./reader.ts"; +import { readline } from "./readline.ts"; + +const read = (str: string): MalType.MalType => Reader.readStr(str); + +const evaluate_ast = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + switch (ast.tag) { + case "MalSymbol": + return Env.get(ast, env); + case "MalList": + return MalType.mkList(ast.items.map((i) => evaluate(i, env))); + case "MalVector": + return MalType.mkVector(ast.items.map((i) => evaluate(i, env))); + case "MalHashMap": + return MalType.mkHashMap( + MalType.mapKeyValues(ast).map(([k, v]) => [k, evaluate(v, env)]), + ); + default: + return ast; + } +}; + +const evaluate = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + while (true) { + // console.log(Printer.prStr(ast)); + + ast = macroExpand(ast, env); + if (ast.tag === "MalList") { + if (ast.items.length === 0) { + return ast; + } + + if (ast.items[0].tag === "MalSymbol") { + switch (ast.items[0].name) { + case "def!": + return evaluateDefBang(ast, env); + case "defmacro!": + return evaluateDefMacroBang(ast, env); + case "do": + ast = evaluateDo(ast, env); + continue; + case "fn*": + return evaluateFnStar(ast, env); + case "if": + ast = evaluateIf(ast, env); + continue; + case "let*": { + const bindings = ast.items[1]; + + if (bindings.tag !== "MalList" && bindings.tag !== "MalVector") { + throw new Error( + `Invalid Argument: let* requires a list of bindings: ${ + JSON.stringify(bindings) + }`, + ); + } + + const innerEnv = Env.mkEnv(env); + for (let lp = 0; lp < bindings.items.length; lp += 2) { + const name = bindings.items[lp]; + const value = bindings.items[lp + 1] ?? MalType.nil; + + if (name.tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: let* binding requires a symbol name: ${ + JSON.stringify(name) + }`, + ); + } + + Env.set(name, evaluate(value, innerEnv), innerEnv); + } + + ast = ast.items[2]; + env = innerEnv; + continue; + } + case "macroexpand": + return macroExpand(ast.items[1], env); + case "quasiquote": + ast = evaluateQuasiQuote(ast.items[1]); + continue; + case "quasiquoteexpand": + return evaluateQuasiQuote(ast.items[1]); + case "quote": + return ast.items[1]; + case "try*": { + if (ast.items.length === 2) { + return evaluate(ast.items[1], env); + } + + if (ast.items.length === 3) { + const catchItem = ast.items[2]; + + if ( + (catchItem.tag === "MalList" || + catchItem.tag === "MalVector") && catchItem.items.length === 3 + ) { + const catchKeyword = catchItem.items[0]; + const catchSymbol = catchItem.items[1]; + const catchBody = catchItem.items[2]; + if ( + catchKeyword.tag === "MalSymbol" && + catchKeyword.name === "catch*" && + catchSymbol.tag === "MalSymbol" + ) { + try { + return evaluate(ast.items[1], env); + } catch (e) { + const ep = e.tag !== undefined && e.tag.startsWith("Mal") + ? e + : e.message !== undefined + ? MalType.mkString(e.message) + : MalType.mkString(JSON.stringify(e)); + + env = Env.mkEnv( + env, + [catchSymbol], + [ep], + ); + ast = catchBody; + continue; + } + } + } + } + throw new Error( + `Invalid Argument: try* is not in a valid form: ${ + JSON.stringify(ast) + }`, + ); + } + } + } + + const evalList = evaluate_ast(ast, env); + + if (evalList.tag === "MalList" || evalList.tag === "MalVector") { + const [callerItem, ...callerArgs] = evalList.items; + + if (callerItem !== undefined) { + if (callerItem.tag === "MalInternalFunction") { + return callerItem.fn(callerArgs); + } else if (callerItem.tag === "MalFunction") { + ast = callerItem.body; + env = Env.mkEnv(callerItem.env, callerItem.params, callerArgs); + continue; + } + } + } + + throw new Error(`Unable to invoke: ${JSON.stringify(evalList)}`); + } else { + return evaluate_ast(ast, env); + } + } +}; + +const macroExpand = (ast: MalType.MalType, env: Env.Env): MalType.MalType => { + const macroFunction = ( + ast: MalType.MalType, + env: Env.Env, + ): [MalType.MalFunction, Array] | undefined => { + if ( + (ast.tag === "MalList" || ast.tag === "MalVector") && + ast.items.length > 0 && ast.items[0].tag === "MalSymbol" + ) { + const value = Env.find(ast.items[0], env); + + return value !== undefined && value.tag === "MalFunction" && value.isMacro + ? [value, ast.items.slice(1)] + : undefined; + } else { + return undefined; + } + }; + + while (true) { + const macro = macroFunction(ast, env); + if (macro === undefined) { + return ast; + } else { + ast = evaluate( + macro[0].body, + Env.mkEnv(macro[0].env, macro[0].params, macro[1]), + ); + } + } +}; + +const evaluateDefBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: def! requires a symbol name: ${ast.items[1]}`, + ); + } + + const result = evaluate(ast.items[2], env); + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDefMacroBang = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items[1].tag !== "MalSymbol") { + throw new Error( + `Invalid Argument: defmacro! requires a symbol name: ${ast.items[1]}`, + ); + } + + let result = evaluate(ast.items[2], env); + + if (result.tag === "MalFunction") { + result = MalType.mkFunction( + result.body, + result.params, + result.env, + true, + result.meta, + ); + } + + Env.set(ast.items[1], result, env); + + return result; +}; + +const evaluateDo = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + evaluate_ast(MalType.mkList(ast.items.slice(1, ast.items.length - 1)), env); + return ast.items[ast.items.length - 1]; +}; + +const evaluateFnStar = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + const [_, parameters, body] = ast.items; + + if ( + parameters == undefined || + parameters.tag !== "MalList" && parameters.tag !== "MalVector" + ) { + throw new Error("Invalid Argument: fn* expects a list of parameters"); + } + if (body === undefined) { + throw new Error("Invalid Argument: fn* expects a body"); + } + + const formalParameters: Array = []; + parameters.items.forEach((p, idx) => { + if (p.tag === "MalSymbol") { + formalParameters.push(p); + } else { + throw new Error( + `Invalid Argument: Parameter ${idx + 1} in fn* is not a symbol: ${ + JSON.stringify(p) + }`, + ); + } + }); + + return MalType.mkFunction(body, formalParameters, env); +}; + +const evaluateIf = ( + ast: MalType.MalList, + env: Env.Env, +): MalType.MalType => { + if (ast.items.length < 3) { + throw new Error( + `Invalid Argument: if expects at least 3 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + if (ast.items.length > 4) { + throw new Error( + `Invalid Argument: if expects no more than 4 arguments: ${ + JSON.stringify(ast) + }`, + ); + } + + const ifGuard = evaluate(ast.items[1], env); + + if ( + ifGuard.tag === "MalNil" || ifGuard.tag === "MalBoolean" && !ifGuard.value + ) { + return ast.items.length === 4 ? ast.items[3] : MalType.nil; + } else { + return ast.items[2]; + } +}; + +const evaluateQuasiQuote = (ast: MalType.MalType): MalType.MalType => { + const startsWith = ( + items: Array, + symbolName: string, + ): boolean => (items.length == 2 && items[0].tag === "MalSymbol" && + items[0].name === symbolName); + + const loop = ( + element: MalType.MalType, + accumulator: MalType.MalList, + ): MalType.MalList => + (element.tag == "MalList" && startsWith(element.items, "splice-unquote")) + ? MalType.mkList( + [MalType.mkSymbol("concat"), element.items[1], accumulator], + ) + : MalType.mkList( + [MalType.mkSymbol("cons"), evaluateQuasiQuote(element), accumulator], + ); + + const foldr = (xs: MalType.MalType[]): MalType.MalList => { + let acc = MalType.mkList([]); + for (let i = xs.length - 1; i >= 0; i -= 1) { + acc = loop(xs[i], acc); + } + return acc; + }; + + switch (ast.tag) { + case "MalSymbol": + return MalType.mkList([MalType.mkSymbol("quote"), ast]); + case "MalHashMap": + return MalType.mkList([MalType.mkSymbol("quote"), ast]); + case "MalList": + return (startsWith(ast.items, "unquote")) + ? ast.items[1] + : foldr(ast.items); + case "MalVector": + return MalType.mkList([MalType.mkSymbol("vec"), foldr(ast.items)]); + default: + return ast; + } +}; + +const print = (exp: MalType.MalType): string => Printer.prStr(exp); + +const rep = (str: string, env: Env.Env): string => + print(evaluate(read(str), env)); + +const initReplEnv = () => { + const ns = Core.ns(evaluate); + + const env = Env.mkEnv( + undefined, + ns.map(([n]) => MalType.mkSymbol(n)), + ns.map(([, t]) => t), + ); + + Env.set( + MalType.mkSymbol("eval"), + MalType.mkInternalFunction(([a]) => { + if (a === undefined) { + return MalType.nil; + } + return evaluate(a, env); + }), + env, + ); + + rep("(def! not (fn* (a) (if a false true)))", env); + rep( + '(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))', + env, + ); + rep( + "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", + env, + ); + rep('(def! *host-language* "deno")', env); + + return env; +}; + +const repl = (env: Env.Env) => { + while (true) { + const prompt = "user> "; + const value = readline(prompt); + + if (value === undefined) { + break; + } else if (value === "") { + continue; + } + + try { + console.log(rep(value, env)); + } catch (e) { + if (e.message !== "Reader Error: No input") { + const message = (e.tag !== undefined) ? Printer.prStr(e) : e.message; + console.error(`Exception: ${message}`); + } + } + } +}; + +if (Deno.args.length > 0) { + const env = initReplEnv(); + + Env.set( + MalType.mkSymbol("*ARGV*"), + MalType.mkList(Deno.args.slice(1).map(MalType.mkString)), + env, + ); + + rep(`(load-file "${Deno.args[0]}")`, env); +} else { + const env = initReplEnv(); + Env.set(MalType.mkSymbol("*ARGV*"), MalType.mkList([]), env); + + rep(`(println (str "Mal [" *host-language* "]"))`, env); + repl(env); +} diff --git a/impls/deno/types.ts b/impls/deno/types.ts new file mode 100644 index 0000000000..dd9c5b8144 --- /dev/null +++ b/impls/deno/types.ts @@ -0,0 +1,309 @@ +import * as Env from "./env.ts"; + +export type MalType = + | MalAtom + | MalBoolean + | MalFunction + | MalHashMap + | MalInternalFunction + | MalKeyword + | MalList + | MalNil + | MalNumber + | MalString + | MalSymbol + | MalVector; + +export type MalAtom = { + tag: "MalAtom"; + value: MalType; +}; + +export const mkAtom = (value: MalType): MalAtom => ({ tag: "MalAtom", value }); + +export type MalBoolean = { + tag: "MalBoolean"; + value: boolean; +}; + +const booleanTrue: MalBoolean = { tag: "MalBoolean", value: true }; + +const booleanFalse: MalBoolean = { tag: "MalBoolean", value: false }; + +export const mkBoolean = (value: boolean): MalBoolean => + value ? booleanTrue : booleanFalse; + +export type MalFunction = { + tag: "MalFunction"; + body: MalType; + params: Array; + env: Env.Env; + isMacro: boolean; + meta?: MalType; +}; + +export const mkFunction = ( + body: MalType, + params: Array, + env: Env.Env, + isMacro: boolean = false, + meta?: MalType, +): MalFunction => ({ + tag: "MalFunction", + body, + params, + env, + isMacro, + meta, +}); + +export type MalHashMap = { + tag: "MalHashMap"; + items: Map; + meta?: MalType; +}; + +export const mkHashMap = ( + values: Array<[MalType, MalType]>, + meta?: MalType, +): MalHashMap => ({ + tag: "MalHashMap", + items: new Map(values.map(([k, v]) => [mapHashMapKey(k), v])), + meta, +}); + +export const mapAssoc = ( + malMap: MalHashMap, + values: Array<[MalType, MalType]>, +): MalHashMap => { + const result = new Map(malMap.items); + + values.forEach(([k, v]) => { + result.set(mapHashMapKey(k), v); + }); + + return { tag: "MalHashMap", items: result, meta: malMap.meta }; +}; + +export const mapDissoc = ( + malMap: MalHashMap, + values: Array, +): MalHashMap => { + const result = new Map(malMap.items); + + values.forEach((k) => { + result.delete(mapHashMapKey(k)); + }); + + return { tag: "MalHashMap", items: result, meta: malMap.meta }; +}; + +export const mapGet = (malMap: MalHashMap, key: MalType): MalType => + malMap.items.get(mapHashMapKey(key)) ?? nil; + +export const mapContains = (malMap: MalHashMap, key: MalType): MalType => + mkBoolean(malMap.items.get(mapHashMapKey(key)) !== undefined); + +export const mapKeys = (malMap: MalHashMap): Array => + [...malMap.items].map(([k, _]) => reverseMapHashMapKey(k)); + +export const mapValues = (malMap: MalHashMap): Array => + [...malMap.items].map(([_, v]) => v); + +export const mapKeyValues = (malMap: MalHashMap): Array<[MalType, MalType]> => + [...malMap.items].map(([k, v]) => [reverseMapHashMapKey(k), v]); + +const mapHashMapKey = (k: MalType): string => { + if (k.tag === "MalString") { + return `s${k.value}`; + } else if (k.tag === "MalKeyword") { + return `t${k.name}`; + } else { + throw new Error( + `Precondition Error: Unable to use ${ + JSON.stringify(k) + } as a hashmap key.`, + ); + } +}; + +const reverseMapHashMapKey = (k: string): MalString | MalKeyword => + k.startsWith("s") ? mkString(k.substr(1)) : mkKeyword(k.substr(1)); + +export type MalInternalFunction = { + tag: "MalInternalFunction"; + fn: (args: Array) => MalType; + meta?: MalType; +}; + +export const mkInternalFunction = ( + fn: (args: Array) => MalType, + meta?: MalType, +): MalInternalFunction => ({ tag: "MalInternalFunction", fn, meta }); + +export type MalKeyword = { + tag: "MalKeyword"; + name: string; +}; + +export const mkKeyword = (name: string): MalKeyword => ({ + tag: "MalKeyword", + name, +}); + +export type MalList = { + tag: "MalList"; + items: Array; + meta?: MalType; +}; + +export const mkList = (items: Array, meta?: MalType): MalList => ({ + tag: "MalList", + items, + meta, +}); + +export type MalNil = { + tag: "MalNil"; +}; + +export const nil: MalNil = ({ tag: "MalNil" }); + +export type MalNumber = { + tag: "MalNumber"; + value: number; +}; + +export const mkNumber = (value: number): MalNumber => ({ + tag: "MalNumber", + value, +}); + +export const asNumber = (v: MalType): number => { + if (v.tag === "MalNumber") { + return v.value; + } else { + throw new Error(`Precondition Error: ${JSON.stringify(v)} is not a number`); + } +}; + +export type MalString = { + tag: "MalString"; + value: string; +}; + +export const mkString = (value: string): MalString => ({ + tag: "MalString", + value, +}); + +export type MalSymbol = { + tag: "MalSymbol"; + name: string; +}; + +export const mkSymbol = (name: string): MalSymbol => ({ + tag: "MalSymbol", + name, +}); + +export type MalVector = { + tag: "MalVector"; + items: Array; + meta?: MalType; +}; + +export const mkVector = (items: Array, meta?: MalType): MalVector => ({ + tag: "MalVector", + items, + meta, +}); + +export const equals = (a: MalType, b: MalType): boolean => { + switch (a.tag) { + case "MalBoolean": + return b.tag === "MalBoolean" && a.value === b.value; + case "MalHashMap": + return b.tag === "MalHashMap" && hashMapEquals(a, b); + case "MalKeyword": + return b.tag === "MalKeyword" && a.name === b.name; + case "MalList": + case "MalVector": + return (b.tag === "MalList" || b.tag === "MalVector") && seqEquals(a, b); + case "MalNil": + return b.tag === "MalNil"; + case "MalNumber": + return b.tag === "MalNumber" && a.value === b.value; + case "MalString": + return b.tag === "MalString" && a.value === b.value; + case "MalSymbol": + return b.tag === "MalSymbol" && a.name === b.name; + default: + return false; + } +}; + +const hashMapEquals = (a: MalHashMap, b: MalHashMap): boolean => { + const as = a.items; + const bs = b.items; + + if (as.size !== bs.size) { + return false; + } + + for (let [key, value] of as) { + if (!bs.has(key)) return false; + + const bValue = bs.get(key); + + if (bValue === undefined || !equals(value, bValue)) { + return false; + } + } + + return true; +}; + +const seqEquals = ( + a: MalList | MalVector, + b: MalList | MalVector, +): boolean => { + const as = a.items; + const bs = b.items; + + if (as.length !== bs.length) { + return false; + } + + for (let loop = 0; loop < as.length; loop += 1) { + if (!equals(as[loop], bs[loop])) { + return false; + } + } + + return true; +}; + +export const withMeta = ( + v: MalType, + m: MalType, +): MalType => { + switch (v.tag) { + case "MalFunction": + return mkFunction(v.body, v.params, v.env, v.isMacro, m); + case "MalHashMap": + return { + tag: "MalHashMap", + items: new Map(v.items), + meta: m, + }; + case "MalInternalFunction": + return mkInternalFunction(v.fn, m); + case "MalList": + return mkList(v.items, m); + case "MalVector": + return mkVector(v.items, m); + default: + return v; + } +}; diff --git a/test.err b/test.err new file mode 100644 index 0000000000..91d831a2ae --- /dev/null +++ b/test.err @@ -0,0 +1,1823 @@ +error: Could not create TypeScript compiler cache location: "/deno/.cache/deno/gen" +Check the permission of the directory. +error: Could not create TypeScript compiler cache location: "/deno/.cache/deno/gen" +Check the permission of the directory. +error: Could not create TypeScript compiler cache location: "/deno/.cache/deno/gen" +Check the permission of the directory. +Check file:///mal/impls/deno/step0_repl.ts +user> abcABC123 +abcABC123 +user> hello mal world +hello mal world +user> []{}"'* ;:() +[]{}"'* ;:() +user> hello world abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 (;:() []{}"'* ;:() []{}"'* ;:() []{}"'*) +hello world abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 (;:() []{}"'* ;:() []{}"'* ;:() []{}"'*) +user> ! +! +user> & +& +user> + ++ +user> , +, +user> - +- +user> / +/ +user> < +< +user> = += +user> > +> +user> ? +? +user> @ +@ +user> ^ +^ +user> _ +_ +user> ` +` +user> ~ +~ +user> # +# +user> $ +$ +user> % +% +user> . +. +user> | +| +user> +Check file:///mal/impls/deno/step1_read_print.ts +user> 1 +1 +user> 7 +7 +user> 7 +7 +user> -123 +-123 +user> + ++ +user> abc +abc +user> abc +abc +user> abc5 +abc5 +user> abc-def +abc-def +user> - +- +user> -abc +-abc +user> ->> +->> +user> (+ 1 2) +(+ 1 2) +user> () +() +user> ( ) +() +user> (nil) +(nil) +user> ((3 4)) +((3 4)) +user> (+ 1 (+ 2 3)) +(+ 1 (+ 2 3)) +user> ( + 1 (+ 2 3 ) ) +(+ 1 (+ 2 3)) +user> (* 1 2) +(* 1 2) +user> (** 1 2) +(** 1 2) +user> (* -3 6) +(* -3 6) +user> (()()) +(() ()) +user> (1 2, 3,,,,),, +(1 2 3) +user> nil +nil +user> true +true +user> false +false +user> "abc" +"abc" +user> "abc" +"abc" +user> "abc (with parens)" +"abc (with parens)" +user> "abc\"def" +"abc\"def" +user> "" +"" +user> "\\" +"\\" +user> "\\\\\\\\\\\\\\\\\\" +"\\\\\\\\\\\\\\\\\\" +user> "&" +"&" +user> "'" +"'" +user> "(" +"(" +user> ")" +")" +user> "*" +"*" +user> "+" +"+" +user> "," +"," +user> "-" +"-" +user> "/" +"/" +user> ":" +":" +user> ";" +";" +user> "<" +"<" +user> "=" +"=" +user> ">" +">" +user> "?" +"?" +user> "@" +"@" +user> "[" +"[" +user> "]" +"]" +user> "^" +"^" +user> "_" +"_" +user> "`" +"`" +user> "{" +"{" +user> "}" +"}" +user> "~" +"~" +user> (1 2 +Syntax Error: EOF whilst expecting ')' +user> [1 2 +Syntax Error: EOF whilst expecting ']' +user> "abc +Syntax Error: EOF whilst expecting '"': "abc +user> " +Syntax Error: EOF whilst expecting '"': " +user> "\" +Syntax Error: EOF whilst expecting '"': "\" +user> "\\\\\\\\\\\\\\\\\\\" +Syntax Error: EOF whilst expecting '"': "\\\\\\\\\\\\\\\\\\\" +user> (1 "abc +Syntax Error: EOF whilst expecting '"': "abc +user> (1 "abc" +Syntax Error: EOF whilst expecting ')' +user> '1 +(quote 1) +user> '(1 2 3) +(quote (1 2 3)) +user> `1 +(quasiquote 1) +user> `(1 2 3) +(quasiquote (1 2 3)) +user> ~1 +(unquote 1) +user> ~(1 2 3) +(unquote (1 2 3)) +user> `(1 ~a 3) +(quasiquote (1 (unquote a) 3)) +user> ~@(1 2 3) +(splice-unquote (1 2 3)) +user> :kw +:kw +user> (:kw1 :kw2 :kw3) +(:kw1 :kw2 :kw3) +user> [+ 1 2] +[+ 1 2] +user> [] +[] +user> [ ] +[] +user> [[3 4]] +[[3 4]] +user> [+ 1 [+ 2 3]] +[+ 1 [+ 2 3]] +user> [ + 1 [+ 2 3 ] ] +[+ 1 [+ 2 3]] +user> ([]) +([]) +user> {} +{} +user> { } +{} +user> {"abc" 1} +{"abc" 1} +user> {"a" {"b" 2}} +{"a" {"b" 2}} +user> {"a" {"b" {"c" 3}}} +{"a" {"b" {"c" 3}}} +user> { "a" {"b" { "cde" 3 } }} +{"a" {"b" {"cde" 3}}} +user> {"a1" 1 "a2" 2 "a3" 3} +{"a1" 1 "a2" 2 "a3" 3} +user> { :a {:b { :cde 3 } }} +{:a {:b {:cde 3}}} +user> {"1" 1} +{"1" 1} +user> ({}) +({}) +user> ;; whole line comment (not an exception) +Reader Error: No input +user> 1 ; comment after expression +1 +user> 1; comment after expression +1 +user> @a +(deref a) +user> ^{"a" 1} [1 2 3] +(with-meta [1 2 3] {"a" 1}) +user> "\n" +"\n" +user> "#" +"#" +user> "$" +"$" +user> "%" +"%" +user> "." +"." +user> "\\" +"\\" +user> "|" +"|" +user> 1;! +1 +user> 1;" +1 +user> 1;# +1 +user> 1;$ +1 +user> 1;% +1 +user> 1;' +1 +user> 1;\ +1 +user> 1;\\ +1 +user> 1;\\\ +1 +user> 1;` +1 +user> 1; &()*+,-./:;<=>?@[]^_{|}~ +1 +user> "!" +"!" +user> +Check file:///mal/impls/deno/step2_eval.ts +user> (+ 1 2) +3 +user> (+ 5 (* 2 3)) +11 +user> (- (+ 5 (* 2 3)) 3) +8 +user> (/ (- (+ 5 (* 2 3)) 3) 4) +2 +user> (/ (- (+ 515 (* 87 311)) 302) 27) +1010 +user> (* -3 6) +-18 +user> (/ (- (+ 515 (* -87 311)) 296) 27) +-994 +user> (abc 1 2 3) +Unknown Symbol: abc +user> () +() +user> [1 2 (+ 1 2)] +[1 2 3] +user> {"a" (+ 7 8)} +{"a" 15} +user> {:a (+ 7 8)} +{:a 15} +user> [] +[] +user> {} +{} +user> +Check file:///mal/impls/deno/step3_env.ts +user> (+ 1 2) +3 +user> (/ (- (+ 5 (* 2 3)) 3) 4) +2 +user> (def! x 3) +3 +user> x +3 +user> (def! x 4) +4 +user> x +4 +user> (def! y (+ 1 7)) +8 +user> y +8 +user> (def! mynum 111) +111 +user> (def! MYNUM 222) +222 +user> mynum +111 +user> MYNUM +222 +user> (abc 1 2 3) +'abc' not found +user> (def! w 123) +123 +user> (def! w (abc)) +'abc' not found +user> w +123 +user> (let* (z 9) z) +9 +user> (let* (x 9) x) +9 +user> x +4 +user> (let* (z (+ 2 3)) (+ 1 z)) +6 +user> (let* (p (+ 2 3) q (+ 2 p)) (+ p q)) +12 +user> (def! y (let* (z 7) z)) +7 +user> y +7 +user> (def! a 4) +4 +user> (let* (q 9) q) +9 +user> (let* (q 9) a) +4 +user> (let* (z 2) (let* (q 9) a)) +4 +user> (let* [z 9] z) +9 +user> (let* [p (+ 2 3) q (+ 2 p)] (+ p q)) +12 +user> (let* (a 5 b 6) [3 4 a [b 7] 8]) +[3 4 5 [6 7] 8] +user> +Check file:///mal/impls/deno/step4_if_fn_do.ts +user> (list) +() +user> (list? (list)) +true +user> (empty? (list)) +true +user> (empty? (list 1)) +false +user> (list 1 2 3) +(1 2 3) +user> (count (list 1 2 3)) +3 +user> (count (list)) +0 +user> (count nil) +0 +user> (if (> (count (list 1 2 3)) 3) 89 78) +78 +user> (if (>= (count (list 1 2 3)) 3) 89 78) +89 +user> (if true 7 8) +7 +user> (if false 7 8) +8 +user> (if false 7 false) +false +user> (if true (+ 1 7) (+ 1 8)) +8 +user> (if false (+ 1 7) (+ 1 8)) +9 +user> (if nil 7 8) +8 +user> (if 0 7 8) +7 +user> (if (list) 7 8) +7 +user> (if (list 1 2 3) 7 8) +7 +user> (= (list) nil) +false +user> (if false (+ 1 7)) +nil +user> (if nil 8) +nil +user> (if nil 8 7) +7 +user> (if true (+ 1 7)) +8 +user> (= 2 1) +false +user> (= 1 1) +true +user> (= 1 2) +false +user> (= 1 (+ 1 1)) +false +user> (= 2 (+ 1 1)) +true +user> (= nil 1) +false +user> (= nil nil) +true +user> (> 2 1) +true +user> (> 1 1) +false +user> (> 1 2) +false +user> (>= 2 1) +true +user> (>= 1 1) +true +user> (>= 1 2) +false +user> (< 2 1) +false +user> (< 1 1) +false +user> (< 1 2) +true +user> (<= 2 1) +false +user> (<= 1 1) +true +user> (<= 1 2) +true +user> (= 1 1) +true +user> (= 0 0) +true +user> (= 1 0) +false +user> (= true true) +true +user> (= false false) +true +user> (= nil nil) +true +user> (= (list) (list)) +true +user> (= (list 1 2) (list 1 2)) +true +user> (= (list 1) (list)) +false +user> (= (list) (list 1)) +false +user> (= 0 (list)) +false +user> (= (list) 0) +false +user> (= (list nil) (list)) +false +user> (+ 1 2) +3 +user> ( (fn* (a b) (+ b a)) 3 4) +7 +user> ( (fn* () 4) ) +4 +user> ( (fn* (f x) (f x)) (fn* (a) (+ 1 a)) 7) +8 +user> ( ( (fn* (a) (fn* (b) (+ a b))) 5) 7) +12 +user> (def! gen-plus5 (fn* () (fn* (b) (+ 5 b)))) +# +user> (def! plus5 (gen-plus5)) +# +user> (plus5 7) +12 +user> (def! gen-plusX (fn* (x) (fn* (b) (+ x b)))) +# +user> (def! plus7 (gen-plusX 7)) +# +user> (plus7 8) +15 +user> (do (prn 101)) +101 +nil +user> (do (prn 102) 7) +102 +7 +user> (do (prn 101) (prn 102) (+ 1 2)) +101 +102 +3 +user> (do (def! a 6) 7 (+ a 8)) +14 +user> a +6 +user> (def! DO (fn* (a) 7)) +# +user> (DO 3) +7 +user> (def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) +# +user> (sumdown 1) +1 +user> (sumdown 2) +3 +user> (sumdown 6) +21 +user> (def! fib (fn* (N) (if (= N 0) 1 (if (= N 1) 1 (+ (fib (- N 1)) (fib (- N 2))))))) +# +user> (fib 1) +1 +user> (fib 2) +2 +user> (fib 4) +5 +user> (let* (cst (fn* (n) (if (= n 0) nil (cst (- n 1))))) (cst 1)) +nil +user> (let* (f (fn* (n) (if (= n 0) 0 (g (- n 1)))) g (fn* (n) (f n))) (f 2)) +0 +user> (if "" 7 8) +7 +user> (= "" "") +true +user> (= "abc" "abc") +true +user> (= "abc" "") +false +user> (= "" "abc") +false +user> (= "abc" "def") +false +user> (= "abc" "ABC") +false +user> (= (list) "") +false +user> (= "" (list)) +false +user> ( (fn* (& more) (count more)) 1 2 3) +3 +user> ( (fn* (& more) (list? more)) 1 2 3) +true +user> ( (fn* (& more) (count more)) 1) +1 +user> ( (fn* (& more) (count more)) ) +0 +user> ( (fn* (& more) (list? more)) ) +true +user> ( (fn* (a & more) (count more)) 1 2 3) +2 +user> ( (fn* (a & more) (count more)) 1) +0 +user> ( (fn* (a & more) (list? more)) 1) +true +user> (not false) +true +user> (not nil) +true +user> (not true) +false +user> (not "a") +false +user> (not 0) +false +user> "" +"" +user> "abc" +"abc" +user> "abc def" +"abc def" +user> "\"" +"\"" +user> "abc\ndef\nghi" +"abc\ndef\nghi" +user> "abc\\def\\ghi" +"abc\\def\\ghi" +user> "\\n" +"\\n" +user> (pr-str) +"" +user> (pr-str "") +"\"\"" +user> (pr-str "abc") +"\"abc\"" +user> (pr-str "abc def" "ghi jkl") +"\"abc def\" \"ghi jkl\"" +user> (pr-str "\"") +"\"\\\"\"" +user> (pr-str (list 1 2 "abc" "\"") "def") +"(1 2 \"abc\" \"\\\"\") \"def\"" +user> (pr-str "abc\ndef\nghi") +"\"abc\\ndef\\nghi\"" +user> (pr-str "abc\\def\\ghi") +"\"abc\\\\def\\\\ghi\"" +user> (pr-str (list)) +"()" +user> (str) +"" +user> (str "") +"" +user> (str "abc") +"abc" +user> (str "\"") +"\"" +user> (str 1 "abc" 3) +"1abc3" +user> (str "abc def" "ghi jkl") +"abc defghi jkl" +user> (str "abc\ndef\nghi") +"abc\ndef\nghi" +user> (str "abc\\def\\ghi") +"abc\\def\\ghi" +user> (str (list 1 2 "abc" "\"") "def") +"(1 2 abc \")def" +user> (str (list)) +"()" +user> (prn) + +nil +user> (prn "") +"" +nil +user> (prn "abc") +"abc" +nil +user> (prn "abc def" "ghi jkl") +"abc def" "ghi jkl" +nil +user> (prn "\"") +"\"" +nil +user> (prn "abc\ndef\nghi") +"abc\ndef\nghi" +nil +user> (prn "abc\\def\\ghi") +"abc\\def\\ghi" +nil +user> nil +nil +user> (prn (list 1 2 "abc" "\"") "def") +(1 2 "abc" "\"") "def" +nil +user> (println) + +nil +user> (println "") + +nil +user> (println "abc") +abc +nil +user> (println "abc def" "ghi jkl") +abc def ghi jkl +nil +user> (println "\"") +" +nil +user> (println "abc\ndef\nghi") +abc +def +ghi +nil +user> (println "abc\\def\\ghi") +abc\def\ghi +nil +user> (println (list 1 2 "abc" "\"") "def") +(1 2 abc ") def +nil +user> (= :abc :abc) +true +user> (= :abc :def) +false +user> (= :abc ":abc") +false +user> (= (list :abc) (list :abc)) +true +user> (if [] 7 8) +7 +user> (pr-str [1 2 "abc" "\""] "def") +"[1 2 \"abc\" \"\\\"\"] \"def\"" +user> (pr-str []) +"[]" +user> (str [1 2 "abc" "\""] "def") +"[1 2 abc \"]def" +user> (str []) +"[]" +user> (count [1 2 3]) +3 +user> (empty? [1 2 3]) +false +user> (empty? []) +true +user> (list? [4 5 6]) +false +user> (= [] (list)) +true +user> (= [7 8] [7 8]) +true +user> (= [:abc] [:abc]) +true +user> (= (list 1 2) [1 2]) +true +user> (= (list 1) []) +false +user> (= [] [1]) +false +user> (= 0 []) +false +user> (= [] 0) +false +user> (= [] "") +false +user> (= "" []) +false +user> ( (fn* [] 4) ) +4 +user> ( (fn* [f x] (f x)) (fn* [a] (+ 1 a)) 7) +8 +user> (= [(list)] (list [])) +true +user> (= [1 2 (list 3 4 [5 6])] (list 1 2 [3 4 (list 5 6)])) +true +user> +Check file:///mal/impls/deno/step5_tco.ts +user> (def! sum2 (fn* (n acc) (if (= n 0) acc (sum2 (- n 1) (+ n acc))))) +# +user> (sum2 10 0) +55 +user> (def! res2 nil) +nil +user> (def! res2 (sum2 10000 0)) +50005000 +user> res2 +50005000 +user> (def! foo (fn* (n) (if (= n 0) 0 (bar (- n 1))))) +# +user> (def! bar (fn* (n) (if (= n 0) 0 (foo (- n 1))))) +# +user> (foo 10000) +0 +user> +Check file:///mal/impls/deno/step6_file.ts +user> (do (do 1 2)) +2 +user> (read-string "(1 2 (3 4) nil)") +(1 2 (3 4) nil) +user> (= nil (read-string "nil")) +true +user> (read-string "(+ 2 3)") +(+ 2 3) +user> (read-string "\"\n\"") +"\n" +user> (read-string "7 ;; comment") +7 +user> (read-string ";; comment") +user> (eval (read-string "(+ 2 3)")) +5 +user> (slurp "../tests/test.txt") +"A line of text\n" +user> (slurp "../tests/test.txt") +"A line of text\n" +user> (load-file "../tests/inc.mal") +nil +user> (inc1 7) +8 +user> (inc2 7) +9 +user> (inc3 9) +12 +user> (def! inc3 (fn* (a) (+ 3 a))) +# +user> (def! a (atom 2)) +(atom 2) +user> (atom? a) +true +user> (atom? 1) +false +user> (deref a) +2 +user> (reset! a 3) +3 +user> (deref a) +3 +user> (swap! a inc3) +6 +user> (deref a) +6 +user> (swap! a (fn* (a) a)) +6 +user> (swap! a (fn* (a) (* 2 a))) +12 +user> (swap! a (fn* (a b) (* a b)) 10) +120 +user> (swap! a + 3) +123 +user> (def! inc-it (fn* (a) (+ 1 a))) +# +user> (def! atm (atom 7)) +(atom 7) +user> (def! f (fn* () (swap! atm inc-it))) +# +user> (f) +8 +user> (f) +9 +user> (def! g (let* (atm (atom 0)) (fn* () (deref atm)))) +# +user> (def! atm (atom 1)) +(atom 1) +user> (g) +0 +user> (load-file "../tests/computations.mal") +nil +user> (sumdown 2) +3 +user> (fib 2) +1 +user> (def! atm (atom 9)) +(atom 9) +user> @atm +9 +user> (def! g (fn* [] 78)) +# +user> (g) +78 +user> (def! g (fn* [a] (+ a 78))) +# +user> (g 3) +81 +user> (list? *ARGV*) +true +user> *ARGV* +() +user> (let* (b 12) (do (eval (read-string "(def! aa 7)")) aa )) +7 +user> (load-file "../tests/incB.mal") +nil +user> (inc4 7) +11 +user> (inc5 7) +12 +user> (load-file "../tests/incC.mal") +nil +user> mymap +{"a" 1} +user> (def! a 1) +1 +user> (let* (a 2) (eval (read-string "a"))) +1 +user> (read-string "1;!") +1 +user> (read-string "1;\"") +1 +user> (read-string "1;#") +1 +user> (read-string "1;$") +1 +user> (read-string "1;%") +1 +user> (read-string "1;'") +1 +user> (read-string "1;\\") +1 +user> (read-string "1;\\\\") +1 +user> (read-string "1;\\\\\\") +1 +user> (read-string "1;`") +1 +user> (read-string "1; &()*+,-./:;<=>?@[]^_{|}~") +1 +user> +Check file:///mal/impls/deno/step7_quote.ts +user> (cons 1 (list)) +(1) +user> (cons 1 (list 2)) +(1 2) +user> (cons 1 (list 2 3)) +(1 2 3) +user> (cons (list 1) (list 2 3)) +((1) 2 3) +user> (def! a (list 2 3)) +(2 3) +user> (cons 1 a) +(1 2 3) +user> a +(2 3) +user> (concat) +() +user> (concat (list 1 2)) +(1 2) +user> (concat (list 1 2) (list 3 4)) +(1 2 3 4) +user> (concat (list 1 2) (list 3 4) (list 5 6)) +(1 2 3 4 5 6) +user> (concat (concat)) +() +user> (concat (list) (list)) +() +user> (def! a (list 1 2)) +(1 2) +user> (def! b (list 3 4)) +(3 4) +user> (concat a b (list 5 6)) +(1 2 3 4 5 6) +user> a +(1 2) +user> b +(3 4) +user> (quote 7) +7 +user> (quote (1 2 3)) +(1 2 3) +user> (quote (1 2 (3 4))) +(1 2 (3 4)) +user> (quasiquote nil) +nil +user> (quasiquote 7) +7 +user> (quasiquote a) +a +user> (quasiquote {"a" b}) +{"a" b} +user> (quasiquote ()) +() +user> (quasiquote (1 2 3)) +(1 2 3) +user> (quasiquote (a)) +(a) +user> (quasiquote (1 2 (3 4))) +(1 2 (3 4)) +user> (quasiquote (nil)) +(nil) +user> (quasiquote (1 ())) +(1 ()) +user> (quasiquote (() 1)) +(() 1) +user> (quasiquote (1 () 2)) +(1 () 2) +user> (quasiquote (())) +(()) +user> (quasiquote (unquote 7)) +7 +user> (def! a 8) +8 +user> (quasiquote a) +a +user> (quasiquote (unquote a)) +8 +user> (quasiquote (1 a 3)) +(1 a 3) +user> (quasiquote (1 (unquote a) 3)) +(1 8 3) +user> (def! b (quote (1 "b" "d"))) +(1 "b" "d") +user> (quasiquote (1 b 3)) +(1 b 3) +user> (quasiquote (1 (unquote b) 3)) +(1 (1 "b" "d") 3) +user> (quasiquote ((unquote 1) (unquote 2))) +(1 2) +user> (let* (x 0) (quasiquote (unquote x))) +0 +user> (def! c (quote (1 "b" "d"))) +(1 "b" "d") +user> (quasiquote (1 c 3)) +(1 c 3) +user> (quasiquote (1 (splice-unquote c) 3)) +(1 1 "b" "d" 3) +user> (quasiquote (1 (splice-unquote c))) +(1 1 "b" "d") +user> (quasiquote ((splice-unquote c) 2)) +(1 "b" "d" 2) +user> (quasiquote ((splice-unquote c) (splice-unquote c))) +(1 "b" "d" 1 "b" "d") +user> (= (quote abc) (quote abc)) +true +user> (= (quote abc) (quote abcd)) +false +user> (= (quote abc) "abc") +false +user> (= "abc" (quote abc)) +false +user> (= "abc" (str (quote abc))) +true +user> (= (quote abc) nil) +false +user> (= nil (quote abc)) +false +user> '7 +7 +user> '(1 2 3) +(1 2 3) +user> '(1 2 (3 4)) +(1 2 (3 4)) +user> (cons 1 []) +(1) +user> (cons [1] [2 3]) +([1] 2 3) +user> (cons 1 [2 3]) +(1 2 3) +user> (concat [1 2] (list 3 4) [5 6]) +(1 2 3 4 5 6) +user> (concat [1 2]) +(1 2) +user> `7 +7 +user> `(1 2 3) +(1 2 3) +user> `(1 2 (3 4)) +(1 2 (3 4)) +user> `(nil) +(nil) +user> `~7 +7 +user> (def! a 8) +8 +user> `(1 ~a 3) +(1 8 3) +user> (def! b '(1 "b" "d")) +(1 "b" "d") +user> `(1 b 3) +(1 b 3) +user> `(1 ~b 3) +(1 (1 "b" "d") 3) +user> (def! c '(1 "b" "d")) +(1 "b" "d") +user> `(1 c 3) +(1 c 3) +user> `(1 ~@c 3) +(1 1 "b" "d" 3) +user> (vec (list)) +[] +user> (vec (list 1)) +[1] +user> (vec (list 1 2)) +[1 2] +user> (vec []) +[] +user> (vec [1 2]) +[1 2] +user> (def! a (list 1 2)) +(1 2) +user> (vec a) +[1 2] +user> a +(1 2) +user> ((fn* (q) (quasiquote ((unquote q) (quote (unquote q))))) (quote (fn* (q) (quasiquote ((unquote q) (quote (unquote q))))))) +((fn* (q) (quasiquote ((unquote q) (quote (unquote q))))) (quote (fn* (q) (quasiquote ((unquote q) (quote (unquote q))))))) +user> (quasiquote []) +[] +user> (quasiquote [[]]) +[[]] +user> (quasiquote [()]) +[()] +user> (quasiquote ([])) +([]) +user> (def! a 8) +8 +user> `[1 a 3] +[1 a 3] +user> (quasiquote [a [] b [c] d [e f] g]) +[a [] b [c] d [e f] g] +user> `[~a] +[8] +user> `[(~a)] +[(8)] +user> `([~a]) +([8]) +user> `[a ~a a] +[a 8 a] +user> `([a ~a a]) +([a 8 a]) +user> `[(a ~a a)] +[(a 8 a)] +user> (def! c '(1 "b" "d")) +(1 "b" "d") +user> `[~@c] +[1 "b" "d"] +user> `[(~@c)] +[(1 "b" "d")] +user> `([~@c]) +([1 "b" "d"]) +user> `[1 ~@c 3] +[1 1 "b" "d" 3] +user> `([1 ~@c 3]) +([1 1 "b" "d" 3]) +user> `[(1 ~@c 3)] +[(1 1 "b" "d" 3)] +user> `(0 unquote) +(0 unquote) +user> `(0 splice-unquote) +(0 splice-unquote) +user> `[unquote 0] +[unquote 0] +user> `[splice-unquote 0] +[splice-unquote 0] +user> (quasiquoteexpand nil) +nil +user> (quasiquoteexpand 7) +7 +user> (quasiquoteexpand a) +(quote a) +user> (quasiquoteexpand {"a" b}) +(quote {"a" b}) +user> (quasiquoteexpand ()) +() +user> (quasiquoteexpand (1 2 3)) +(cons 1 (cons 2 (cons 3 ()))) +user> (quasiquoteexpand (a)) +(cons (quote a) ()) +user> (quasiquoteexpand (1 2 (3 4))) +(cons 1 (cons 2 (cons (cons 3 (cons 4 ())) ()))) +user> (quasiquoteexpand (nil)) +(cons nil ()) +user> (quasiquoteexpand (1 ())) +(cons 1 (cons () ())) +user> (quasiquoteexpand (() 1)) +(cons () (cons 1 ())) +user> (quasiquoteexpand (1 () 2)) +(cons 1 (cons () (cons 2 ()))) +user> (quasiquoteexpand (())) +(cons () ()) +user> (quasiquoteexpand (f () g (h) i (j k) l)) +(cons (quote f) (cons () (cons (quote g) (cons (cons (quote h) ()) (cons (quote i) (cons (cons (quote j) (cons (quote k) ())) (cons (quote l) ()))))))) +user> (quasiquoteexpand (unquote 7)) +7 +user> (quasiquoteexpand a) +(quote a) +user> (quasiquoteexpand (unquote a)) +a +user> (quasiquoteexpand (1 a 3)) +(cons 1 (cons (quote a) (cons 3 ()))) +user> (quasiquoteexpand (1 (unquote a) 3)) +(cons 1 (cons a (cons 3 ()))) +user> (quasiquoteexpand (1 b 3)) +(cons 1 (cons (quote b) (cons 3 ()))) +user> (quasiquoteexpand (1 (unquote b) 3)) +(cons 1 (cons b (cons 3 ()))) +user> (quasiquoteexpand ((unquote 1) (unquote 2))) +(cons 1 (cons 2 ())) +user> (quasiquoteexpand (a (splice-unquote (b c)) d)) +(cons (quote a) (concat (b c) (cons (quote d) ()))) +user> (quasiquoteexpand (1 c 3)) +(cons 1 (cons (quote c) (cons 3 ()))) +user> (quasiquoteexpand (1 (splice-unquote c) 3)) +(cons 1 (concat c (cons 3 ()))) +user> (quasiquoteexpand (1 (splice-unquote c))) +(cons 1 (concat c ())) +user> (quasiquoteexpand ((splice-unquote c) 2)) +(concat c (cons 2 ())) +user> (quasiquoteexpand ((splice-unquote c) (splice-unquote c))) +(concat c (concat c ())) +user> (quasiquoteexpand []) +(vec ()) +user> (quasiquoteexpand [[]]) +(vec (cons (vec ()) ())) +user> (quasiquoteexpand [()]) +(vec (cons () ())) +user> (quasiquoteexpand ([])) +(cons (vec ()) ()) +user> (quasiquoteexpand [1 a 3]) +(vec (cons 1 (cons (quote a) (cons 3 ())))) +user> (quasiquoteexpand [a [] b [c] d [e f] g]) +(vec (cons (quote a) (cons (vec ()) (cons (quote b) (cons (vec (cons (quote c) ())) (cons (quote d) (cons (vec (cons (quote e) (cons (quote f) ()))) (cons (quote g) ())))))))) +user> +Check file:///mal/impls/deno/step8_macros.ts +user> (defmacro! one (fn* () 1)) +# +user> (one) +1 +user> (defmacro! two (fn* () 2)) +# +user> (two) +2 +user> (defmacro! unless (fn* (pred a b) `(if ~pred ~b ~a))) +# +user> (unless false 7 8) +7 +user> (unless true 7 8) +8 +user> (defmacro! unless2 (fn* (pred a b) (list 'if (list 'not pred) a b))) +# +user> (unless2 false 7 8) +7 +user> (unless2 true 7 8) +8 +user> (macroexpand (one)) +1 +user> (macroexpand (unless PRED A B)) +(if PRED B A) +user> (macroexpand (unless2 PRED A B)) +(if (not PRED) A B) +user> (macroexpand (unless2 2 3 4)) +(if (not 2) 3 4) +user> (defmacro! identity (fn* (x) x)) +# +user> (let* (a 123) (macroexpand (identity a))) +a +user> (let* (a 123) (identity a)) +123 +user> () +() +user> `(1) +(1) +user> (not (= 1 1)) +false +user> (not (= 1 2)) +true +user> (nth (list 1) 0) +1 +user> (nth (list 1 2) 1) +2 +user> (nth (list 1 2 nil) 2) +nil +user> (def! x "x") +"x" +user> (def! x (nth (list 1 2) 2)) +Index Out Of Range: nth: 2 exceeds bounds of {"tag":"MalList","items":[{"tag":"MalNumber","value":1},{"tag":"MalNumber","value":2}]} +user> x +"x" +user> (first (list)) +nil +user> (first (list 6)) +6 +user> (first (list 7 8 9)) +7 +user> (rest (list)) +() +user> (rest (list 6)) +() +user> (rest (list 7 8 9)) +(8 9) +user> (macroexpand (cond)) +nil +user> (cond) +nil +user> (macroexpand (cond X Y)) +(if X Y (cond)) +user> (cond true 7) +7 +user> (cond false 7) +nil +user> (macroexpand (cond X Y Z T)) +(if X Y (cond Z T)) +user> (cond true 7 true 8) +7 +user> (cond false 7 true 8) +8 +user> (cond false 7 false 8 "else" 9) +9 +user> (cond false 7 (= 2 2) 8 "else" 9) +8 +user> (cond false 7 false 8 false 9) +nil +user> (let* (x (cond false "no" true "yes")) x) +"yes" +user> (nth [1] 0) +1 +user> (nth [1 2] 1) +2 +user> (nth [1 2 nil] 2) +nil +user> (def! x "x") +"x" +user> (def! x (nth [1 2] 2)) +Index Out Of Range: nth: 2 exceeds bounds of {"tag":"MalVector","items":[{"tag":"MalNumber","value":1},{"tag":"MalNumber","value":2}]} +user> x +"x" +user> (first []) +nil +user> (first nil) +nil +user> (first [10]) +10 +user> (first [10 11 12]) +10 +user> (rest []) +() +user> (rest nil) +() +user> (rest [10]) +() +user> (rest [10 11 12]) +(11 12) +user> (rest (cons 10 [11 12])) +(11 12) +user> (let* [x (cond false "no" true "yes")] x) +"yes" +user> (def! x 2) +2 +user> (defmacro! a (fn* [] x)) +# +user> (a) +2 +user> (let* (x 3) (a)) +2 +user> +Check file:///mal/impls/deno/step9_try.ts +user> (throw "err1") +Exception: "err1" +user> (try* 123 (catch* e 456)) +123 +user> (try* abc (catch* exc (prn "exc is:" exc))) +"exc is:" "'abc' not found" +nil +user> (try* (abc 1 2) (catch* exc (prn "exc is:" exc))) +"exc is:" "'abc' not found" +nil +user> (try* (nth () 1) (catch* exc (prn "exc is:" exc))) +"exc is:" "Index Out Of Range: nth: 1 exceeds bounds of {\"tag\":\"MalList\",\"items\":[]}" +nil +user> (try* (throw "my exception") (catch* exc (do (prn "exc:" exc) 7))) +"exc:" "my exception" +7 +user> (try* (do (try* "t1" (catch* e "c1")) (throw "e1")) (catch* e "c2")) +"c2" +user> (try* (try* (throw "e1") (catch* e (throw "e2"))) (catch* e "c2")) +"c2" +user> (try* (map throw (list "my err")) (catch* exc exc)) +"my err" +user> (symbol? 'abc) +true +user> (symbol? "abc") +false +user> (nil? nil) +true +user> (nil? true) +false +user> (true? true) +true +user> (true? false) +false +user> (true? true?) +false +user> (false? false) +true +user> (false? true) +false +user> (apply + (list 2 3)) +5 +user> (apply + 4 (list 5)) +9 +user> (apply prn (list 1 2 "3" (list))) +1 2 "3" () +nil +user> (apply prn 1 2 (list "3" (list))) +1 2 "3" () +nil +user> (apply list (list)) +() +user> (apply symbol? (list (quote two))) +true +user> (apply (fn* (a b) (+ a b)) (list 2 3)) +5 +user> (apply (fn* (a b) (+ a b)) 4 (list 5)) +9 +user> (def! nums (list 1 2 3)) +(1 2 3) +user> (def! double (fn* (a) (* 2 a))) +# +user> (double 3) +6 +user> (map double nums) +(2 4 6) +user> (map (fn* (x) (symbol? x)) (list 1 (quote two) "three")) +(false true false) +user> (throw {:msg "err2"}) +Exception: {:msg "err2"} +user> (symbol? :abc) +false +user> (symbol? 'abc) +true +user> (symbol? "abc") +false +user> (symbol? (symbol "abc")) +true +user> (keyword? :abc) +true +user> (keyword? 'abc) +false +user> (keyword? "abc") +false +user> (keyword? "") +false +user> (keyword? (keyword "abc")) +true +user> (symbol "abc") +abc +user> (keyword "abc") +:abc +user> (sequential? (list 1 2 3)) +true +user> (sequential? [15]) +true +user> (sequential? sequential?) +false +user> (sequential? nil) +false +user> (sequential? "abc") +false +user> (apply + 4 [5]) +9 +user> (apply prn 1 2 ["3" 4]) +1 2 "3" 4 +nil +user> (apply list []) +() +user> (apply (fn* (a b) (+ a b)) [2 3]) +5 +user> (apply (fn* (a b) (+ a b)) 4 [5]) +9 +user> (map (fn* (a) (* 2 a)) [1 2 3]) +(2 4 6) +user> (map (fn* [& args] (list? args)) [1 2]) +(true true) +user> (vector? [10 11]) +true +user> (vector? '(12 13)) +false +user> (vector 3 4 5) +[3 4 5] +user> (map? {}) +true +user> (map? '()) +false +user> (map? []) +false +user> (map? 'abc) +false +user> (map? :abc) +false +user> (hash-map "a" 1) +{"a" 1} +user> {"a" 1} +{"a" 1} +user> (assoc {} "a" 1) +{"a" 1} +user> (get (assoc (assoc {"a" 1 } "b" 2) "c" 3) "a") +1 +user> (def! hm1 (hash-map)) +{} +user> (map? hm1) +true +user> (map? 1) +false +user> (map? "abc") +false +user> (get nil "a") +nil +user> (get hm1 "a") +nil +user> (contains? hm1 "a") +false +user> (def! hm2 (assoc hm1 "a" 1)) +{"a" 1} +user> (get hm1 "a") +nil +user> (contains? hm1 "a") +false +user> (get hm2 "a") +1 +user> (contains? hm2 "a") +true +user> (keys hm1) +() +user> (keys hm2) +("a") +user> (keys {"1" 1}) +("1") +user> (vals hm1) +() +user> (vals hm2) +(1) +user> (count (keys (assoc hm2 "b" 2 "c" 3))) +3 +user> (get {:abc 123} :abc) +123 +user> (contains? {:abc 123} :abc) +true +user> (contains? {:abcd 123} :abc) +false +user> (assoc {} :bcd 234) +{:bcd 234} +user> (keyword? (nth (keys {:abc 123 :def 456}) 0)) +true +user> (keyword? (nth (vals {"a" :abc "b" :def}) 0)) +true +user> (def! hm4 (assoc {:a 1 :b 2} :a 3 :c 1)) +{:a 3 :b 2 :c 1} +user> (get hm4 :a) +3 +user> (get hm4 :b) +2 +user> (get hm4 :c) +1 +user> (contains? {:abc nil} :abc) +true +user> (assoc {} :bcd nil) +{:bcd nil} +user> (str "A" {:abc "val"} "Z") +"A{:abc val}Z" +user> (str true "." false "." nil "." :keyw "." 'symb) +"true.false.nil.:keyw.symb" +user> (pr-str "A" {:abc "val"} "Z") +"\"A\" {:abc \"val\"} \"Z\"" +user> (pr-str true "." false "." nil "." :keyw "." 'symb) +"true \".\" false \".\" nil \".\" :keyw \".\" symb" +user> (def! s (str {:abc "val1" :def "val2"})) +"{:abc val1 :def val2}" +user> (cond (= s "{:abc val1 :def val2}") true (= s "{:def val2 :abc val1}") true) +true +user> (def! p (pr-str {:abc "val1" :def "val2"})) +"{:abc \"val1\" :def \"val2\"}" +user> (cond (= p "{:abc \"val1\" :def \"val2\"}") true (= p "{:def \"val2\" :abc \"val1\"}") true) +true +user> (apply (fn* (& more) (list? more)) [1 2 3]) +true +user> (apply (fn* (& more) (list? more)) []) +true +user> (apply (fn* (a & more) (list? more)) [1]) +true +user> (try* xyz) +Exception: 'xyz' not found +user> (try* (throw (list 1 2 3)) (catch* exc (do (prn "err:" exc) 7))) +"err:" (1 2 3) +7 +user> (def! hm3 (assoc hm2 "b" 2)) +{"a" 1 "b" 2} +user> (count (keys hm3)) +2 +user> (count (vals hm3)) +2 +user> (dissoc hm3 "a") +{"b" 2} +user> (dissoc hm3 "a" "b") +{} +user> (dissoc hm3 "a" "b" "c") +{} +user> (count (keys hm3)) +2 +user> (dissoc {:cde 345 :fgh 456} :cde) +{:fgh 456} +user> (dissoc {:cde nil :fgh 456} :cde) +{:fgh 456} +user> (= {} {}) +true +user> (= {:a 11 :b 22} (hash-map :b 22 :a 11)) +true +user> (= {:a 11 :b [22 33]} (hash-map :b [22 33] :a 11)) +true +user> (= {:a 11 :b {:c 33}} (hash-map :b {:c 33} :a 11)) +true +user> (= {:a 11 :b 22} (hash-map :b 23 :a 11)) +false +user> (= {:a 11 :b 22} (hash-map :a 11)) +false +user> (= {:a [11 22]} {:a (list 11 22)}) +true +user> (= {:a 11 :b 22} (list :a 11 :b 22)) +false +user> (= {} []) +false +user> (= [] {}) +false +user> (keyword :abc) +:abc +user> (keyword? (first (keys {":abc" 123 ":def" 456}))) +false +user> +Check file:///mal/impls/deno/stepA_mal.ts +Mal [deno] +user> (readline "mal-user> ") +mal-user> > "hello" +"\"hello\"" +user> (= "something bogus" *host-language*) +false +user> (def! e (atom {"+" +})) +(atom {"+" #}) +user> (swap! e assoc "-" -) +{"+" # "-" #} +user> ( (get @e "+") 7 8) +15 +user> ( (get @e "-") 11 8) +3 +user> (swap! e assoc "foo" (list)) +{"+" # "-" # "foo" ()} +user> (get @e "foo") +() +user> (swap! e assoc "bar" '(1 2 3)) +{"+" # "-" # "foo" () "bar" (1 2 3)} +user> (get @e "bar") +(1 2 3) +user> (do (list time-ms string? number? seq conj meta with-meta fn?) nil) +nil +user> (meta (fn* (a) a)) +nil +user> (meta (with-meta (fn* (a) a) {"b" 1})) +{"b" 1} +user> (meta (with-meta (fn* (a) a) "abc")) +"abc" +user> (def! l-wm (with-meta (fn* (a) a) {"b" 2})) +# +user> (meta l-wm) +{"b" 2} +user> (meta (with-meta l-wm {"new_meta" 123})) +{"new_meta" 123} +user> (meta l-wm) +{"b" 2} +user> (def! f-wm (with-meta (fn* [a] (+ 1 a)) {"abc" 1})) +# +user> (meta f-wm) +{"abc" 1} +user> (meta (with-meta f-wm {"new_meta" 123})) +{"new_meta" 123} +user> (meta f-wm) +{"abc" 1} +user> (def! f-wm2 ^{"abc" 1} (fn* [a] (+ 1 a))) +# +user> (meta f-wm2) +{"abc" 1} +user> (meta +) +nil +user> (def! gen-plusX (fn* (x) (with-meta (fn* (b) (+ x b)) {"meta" 1}))) +# +user> (def! plus7 (gen-plusX 7)) +# +user> (def! plus8 (gen-plusX 8)) +# +user> (plus7 8) +15 +user> (meta plus7) +{"meta" 1} +user> (meta plus8) +{"meta" 1} +user> (meta (with-meta plus7 {"meta" 2})) +{"meta" 2} +user> (meta plus8) +{"meta" 1} +user> (string? "") +true +user> (string? 'abc) +false +user> (string? "abc") +true +user> (string? :abc) +false +user> (string? (keyword "abc")) +false +user> (string? 234) +false +user> (string? nil) +false +user> (number? 123) +true +user> (number? -1) +true +user> (number? nil) +false +user> (number? false) +false +user> (number? "123") +false +user> (def! add1 (fn* (x) (+ x 1))) +# +user> (fn? +) +true +user> (fn? add1) +true +user> (fn? cond) +false +user> (fn? "+") +false +user> (fn? :+) +false +user> (fn? ^{"ismacro" true} (fn* () 0)) +true +user> (macro? cond) +true +user> (macro? +) +false +user> (macro? add1) +false +user> (macro? "+") +false +user> (macro? :+) +false +user> (macro? {}) +false +user> (conj (list) 1) +(1) +user> (conj (list 1) 2) +(2 1) +user> (conj (list 2 3) 4) +(4 2 3) +user> (conj (list 2 3) 4 5 6) +(6 5 4 2 3) +user> (conj (list 1) (list 2 3)) +((2 3) 1) +user> (conj [] 1) +[1] +user> (conj [1] 2) +[1 2] +user> (conj [2 3] 4) +[2 3 4] +user> (conj [2 3] 4 5 6) +[2 3 4 5 6] +user> (conj [1] [2 3]) +[1 [2 3]] +user> (seq "abc") +("a" "b" "c") +user> (apply str (seq "this is a test")) +"this is a test" +user> (seq '(2 3 4)) +(2 3 4) +user> (seq [2 3 4]) +(2 3 4) +user> (seq "") +nil +user> (seq '()) +nil +user> (seq []) +nil +user> (seq nil) +nil +user> (meta [1 2 3]) +nil +user> (with-meta [1 2 3] {"a" 1}) +[1 2 3] +user> (meta (with-meta [1 2 3] {"a" 1})) +{"a" 1} +user> (vector? (with-meta [1 2 3] {"a" 1})) +true +user> (meta (with-meta [1 2 3] "abc")) +"abc" +user> (with-meta [] "abc") +[] +user> (meta (with-meta (list 1 2 3) {"a" 1})) +{"a" 1} +user> (list? (with-meta (list 1 2 3) {"a" 1})) +true +user> (with-meta (list) {"a" 1}) +() +user> (empty? (with-meta (list) {"a" 1})) +true +user> (meta (with-meta {"abc" 123} {"a" 1})) +{"a" 1} +user> (map? (with-meta {"abc" 123} {"a" 1})) +true +user> (with-meta {} {"a" 1}) +{} +user> (def! l-wm (with-meta [4 5 6] {"b" 2})) +[4 5 6] +user> (meta l-wm) +{"b" 2} +user> (meta (with-meta l-wm {"new_meta" 123})) +{"new_meta" 123} +user> (meta l-wm) +{"b" 2} +user> (meta +) +nil +user> (def! f-wm3 ^{"def" 2} +) +# +user> (meta f-wm3) +{"def" 2} +user> (meta +) +nil +user> (load-file "../tests/computations.mal") +nil +user> (def! start-time (time-ms)) +2663.663896 +user> (= start-time 0) +false +user> (sumdown 10) ; Waste some time +55 +user> (> (time-ms) start-time) +true +user> (def! f (fn* [x] (number? x))) +# +user> (defmacro! m f) +# +user> (f (+ 1 1)) +true +user> (m (+ 1 1)) +false +user>