(function (exports) { const library = { print: (x) => { console.log(x); return x; }, display: (x) => { console.log(x); return x; }, concat: (...items) => { return items.reduce((acc, item) => { return `${acc}${item}`; }, ""); }, // math '+': (...args) => { return args.reduce((sum, val) => sum + val); }, '-': (...args) => { // Subtracts values. return args.reduce((sum, val) => sum - val); }, '*': (...args) => { // Multiplies values. return args.reduce((sum, val) => sum * val); }, '/': (...args) => { // Divides values. return args.reduce((sum, val) => sum / val); }, '%': (a, b) => { // Returns the modulo of a and b. return a % b; }, clamp: (val, min, max) => { // Clamps a value between min and max. return Math.min(max, Math.max(min, val)); }, step: (val, step) => { return Math.round(val / step) * step; }, min: Math.min, max: Math.max, ceil: Math.ceil, floor: Math.floor, // round down to the nearest integer. sin: Math.sin, cos: Math.cos, log: Math.log, // calculates on the base of e. pow: (a, b) => { // calculates a^b. return Math.pow(a, b); }, sqrt: Math.sqrt, // calculate the square root. sq: (a) => { // calculate the square. return a * a; }, PI: Math.PI, TAU: Math.PI * 2, random: (...args) => { if (args.length >= 2) { // (random start end) return args[0] + Math.random() * (args[1] - args[0]); } else if (args.length === 1) { // (random max) return Math.random() * args[0]; } return Math.random(); }, // logic '>': (a, b) => { // Returns true if a is greater than b, else false. return a > b; }, '<': (a, b) => { // Returns true if a is less than b, else false. return a < b; }, '=': (a, b) => { // Returns true if a is equal to b, else false. return a === b; }, // consider eq? instead // what about an not equal? and: (a, b, ...rest) => { // Returns true if all conditions are true. const args = [a, b].concat(rest); for (let i = 0; i < args.length; i++) { if (!args[i]) { return args[i]; } } return args[args.length - 1]; }, or: (a, b, ...rest) => { // Returns true if at least one condition is true. const args = [a, b].concat(rest); for (let i = 0; i < args.length; i++) { if (args[i]) { return args[i]; } } return args[args.length - 1]; }, // arrays map: async (fn, arr) => { let res = []; for (let i = 0; i < arr.length; i++) { const arg = arr[i]; res.push(await fn(arr[i], i)); } return res; }, filter: (fn, arr) => { const list = Array.from(arr); return Promise.all(list.map((element, index) => fn(element, index, list))) .then((result) => { return list.filter((_, index) => { return result[index]; }); }); }, reduce: async (fn, arr, acc) => { const length = arr.length; let result = acc === undefined ? subject[0] : acc; for (let i = acc === undefined ? 1 : 0; i < length; i++) { result = await fn(result, arr[i], i, arr); } return result; }, len: (item) => { // returns the length of a list. return item.length; }, first: (arr) => { // returns the first item of a list. return arr[0]; }, car: (arr) => { // returns the first item of a list. return arr[0]; }, last: (arr) => { // returns the last return arr[arr.length - 1]; }, rest: ([_, ...arr]) => { return arr; }, cdr: ([_, ...arr]) => { return arr; }, range: (start, end, step = 1) => { const arr = []; if (step > 0) { for (let i = start; i <= end; i += step) { arr.push(i); } } else { for (let i = start; i >= end; i += step) { arr.push(i); } } return arr; }, // objects get: (item, key) => { // gets an object's parameter with name. return item[key]; }, set: (item, ...args) => { // sets an object's parameter with name as value. for (let i = 0; i < args.length; i += 2) { const key = args[i]; const val = args[i + 1]; item[key] = val; } return item; }, of: (h, ...keys) => { // gets object parameters with names. return keys.reduce((acc, key) => { return acc[key]; }, h); }, keys: (item) => { // returns a list of the object's keys return Object.keys(item); }, values: (item) => { // returns a list of the object's values return Object.values(item); }, time: (rate = 1) => { // returns timestamp in milliseconds. return (Date.now() * rate); }, js: () => { // Javascript interop. return window; // note, this only works in the browser }, test: (name, a, b) => { if (`${a}` !== `${b}`) { console.warn("failed " + name, a, b); } else { console.log("passed " + name, a); } return a === b; }, assert: (condition, message) => { if (!condition) { throw new Error(message || 'Assertion failed'); } }, benchmark: async (fn) => { // logs time taken to execute a function. const start = Date.now(); const result = await fn(); console.log(`time taken: ${Date.now() - start}ms`); return result; }, }; const TYPES = { identifier: 0, number: 1, string: 2, bool: 3, }; const Context = function (scope, parent) { this.scope = scope; this.parent = parent; this.get = function (identifier) { if (identifier in this.scope) { return this.scope[identifier]; } else if (this.parent !== undefined) { return this.parent.get(identifier); } }; }; const special = { let: function (input, context) { const letContext = input[1].reduce(function (acc, x) { acc.scope[x[0].value] = interpret(x[1], context); return acc; }, new Context({}, context)); return interpret(input[2], letContext); }, def: function (input, context) { const identifier = input[1].value; const value = input[2].type === TYPES.string && input[3] ? input[3] : input[2]; context.scope[identifier] = interpret(value, context); return value; }, defn: function (input, context) { const fnName = input[1].value; const fnParams = input[2].type === TYPES.string && input[3] ? input[3] : input[2]; const fnBody = input[2].type === TYPES.string && input[4] ? input[4] : input[3]; context.scope[fnName] = async function () { const lambdaArguments = arguments; const lambdaScope = fnParams.reduce(function (acc, x, i) { acc[x.value] = lambdaArguments[i]; return acc; }, {}); return interpret(fnBody, new Context(lambdaScope, context)); }; }, lambda: function (input, context) { return async function () { const lambdaArguments = arguments; const lambdaScope = input[1].reduce(function (acc, x, i) { acc[x.value] = lambdaArguments[i]; return acc; }, {}); return interpret(input[2], new Context(lambdaScope, context)); }; }, if: async function (input, context) { if (await interpret(input[1], context)) { return interpret(input[2], context); } return input[3] ? interpret(input[3], context) : []; }, __fn: function (input, context) { return async function () { const lambdaArguments = arguments; const keys = [ ...new Set( input.slice(2).flat(100).filter((i) => i.type === TYPES.identifier && i.value[0] === "%" ).map((x) => x.value).sort(), ), ]; const lambdaScope = keys.reduce(function (acc, x, i) { acc[x] = lambdaArguments[i]; return acc; }, {}); return interpret(input.slice(1), new Context(lambdaScope, context)); }; }, __obj: async function (input, context) { const obj = {}; for (let i = 1; i < input.length; i += 2) { obj[await interpret(input[i], context)] = await interpret( input[i + 1], context, ); } return obj; }, }; const interpretList = function (input, context) { if (input.length > 0 && input[0].value in special) { return special[input[0].value](input, context); } else { var list = input.map(function (x) { return interpret(x, context); }); if (list[0] instanceof Function) { return list[0].apply(undefined, list.slice(1)); } else { return list; } } }; const interpret = function (input, context) { if (context === undefined) { return interpret(input, new Context(library)); } else if (input instanceof Array) { return interpretList(input, context); } else if (input.type === "identifier") { return context.get(input.value); } else if (input.type === "number" || input.type === "string") { return input.value; } }; let categorize = function (input) { if (!isNaN(parseFloat(input))) { return { type: "number", value: parseFloat(input), }; } else if (input[0] === '"' && input.slice(-1) === '"') { return { type: "string", value: input.slice(1, -1), }; } else { return { type: "identifier", value: input, }; } }; let parenthesize = function (input, list) { if (list === undefined) { return parenthesize(input, []); } else { let token = input.shift(); if (token === undefined) { return list.pop(); } else if (token === "(") { list.push(parenthesize(input, [])); return parenthesize(input, list); } else if (token === ")") { return list; } else { return parenthesize(input, list.concat(categorize(token))); } } }; let tokenize = function (input) { return input.split('"') .map(function (x, i) { if (i % 2 === 0) { // not in string return x.replace(/\(/g, " ( ") .replace(/\)/g, " ) "); } else { // in string return x.replace(/ /g, "!whitespace!"); } }) .join('"') .trim() .split(/\s+/) .map(function (x) { return x.replace(/!whitespace!/g, " "); }); }; let parse = function (input) { return parenthesize(tokenize(input)); }; let run = function (input) { return interpret(parse(input)) }; exports.lisp = { parse: parse, interpret: interpret, run: run, }; })(typeof exports === "undefined" ? this : exports);