about summary refs log tree commit diff stats
path: root/lisp
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2023-03-03 10:21:04 -0500
committerelioat <elioat@tilde.institute>2023-03-03 10:21:04 -0500
commit2dd6313dfc5ca90da8a235d6bc170f6a62d6ba3c (patch)
tree4edaf0f57e439f33256b18d670fc6c54aeb81a30 /lisp
parent22798d2fde16c47e11fd4df32433f21d71872f7d (diff)
downloadtour-2dd6313dfc5ca90da8a235d6bc170f6a62d6ba3c.tar.gz
very tiny lisp
Diffstat (limited to 'lisp')
-rw-r--r--lisp/js/lisp.js424
-rw-r--r--lisp/js/repl.js11
2 files changed, 435 insertions, 0 deletions
diff --git a/lisp/js/lisp.js b/lisp/js/lisp.js
new file mode 100644
index 0000000..8f7d3c1
--- /dev/null
+++ b/lisp/js/lisp.js
@@ -0,0 +1,424 @@
+// lifted from https://github.com/maryrosecook/littlelisp
+// extended by eli
+
+(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
+    add: (...args) => {
+      return args.reduce((sum, val) => sum + val);
+    },
+
+    sub: (...args) => { // Subtracts values.
+      return args.reduce((sum, val) => sum - val);
+    },
+
+    mul: (...args) => { // Multiplies values.
+      return args.reduce((sum, val) => sum * val);
+    },
+
+    div: (...args) => { // Divides values.
+      return args.reduce((sum, val) => sum / val);
+    },
+
+    mod: (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,
+    TWO_PI: 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
+    gt: (a, b) => { // Returns true if a is greater than b, else false.
+      return a > b;
+    },
+
+    lt: (a, b) => { // Returns true if a is less than b, else false.
+      return a < b;
+    },
+
+    eq: (a, b) => { // Returns true if a is equal to b, else false.
+      return a === b;
+    },
+
+    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;
+    },
+
+    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));
+  };
+
+  exports.lisp = {
+    parse: parse,
+    interpret: interpret,
+  };
+})(typeof exports === "undefined" ? this : exports);
diff --git a/lisp/js/repl.js b/lisp/js/repl.js
new file mode 100644
index 0000000..6ea6929
--- /dev/null
+++ b/lisp/js/repl.js
@@ -0,0 +1,11 @@
+// lifted from https://github.com/maryrosecook/littlelisp
+var repl = require("repl");
+var lisp = require("./lisp").lisp;
+
+repl.start({
+  prompt: "* ",
+  eval: function (cmd, context, filename, callback) {
+    var ret = lisp.interpret(lisp.parse(cmd));
+    callback(null, ret);
+  },
+});