Home Reference Source Test

lib/interp/registry.js

const xRegExp = require("xregexp");

const utils = require("../utils.js");

// specialForms and topEnv maps
const SpecialForms = Object.create(null);
const TopEnv = Object.create(null);

SpecialForms["if"] = {
  fun: (args, env) => {
    const ifEnv = Object.create(env);

    if (args[0].evaluate(env) === true) {
      return args[1].evaluate(ifEnv);
    } else {
      return args[2].evaluate(ifEnv);
    }
  },
  n_args: n => n === 3
};

SpecialForms["while"] = {
  fun: (args, env) => {
    const whileEnv = Object.create(env);

    while (args[0].evaluate(whileEnv) === true) {
      args[1].evaluate(whileEnv);
    }

    // Egg has no undefined so we return false when there's no meaningful result
    return false;
  },
  n_args: n => n === 2
};

SpecialForms["for"] = {
  fun: (args, env) => {
    const forEnv = Object.create(env);

    // Variable
    args[0].evaluate(forEnv);

    // Condition
    while (args[1].evaluate(forEnv) === true) {
      // Body
      args[3].evaluate(forEnv);

      // Increment
      args[2].evaluate(forEnv);
    }

    return false;
  },
  n_args: n => n === 4
};

SpecialForms["foreach"] = {
  fun: (args, env) => {
    if (args[0].type !== "word") {
      throw new SyntaxError("The first argument to foreach must be a valid word");
    }

    const localEnv = Object.create(env);

    const iterable = args[1].evaluate(localEnv);
    for (const val of iterable) {
      localEnv[args[0].name] = val;
      args[2].evaluate(localEnv);
    }

    return false;
  },
  n_args: n => n === 3
};

SpecialForms["do"] = {
  fun: (args, env) => {
    const doEnv = Object.create(env);

    let value = false;

    args.forEach(arg => {
      value = arg.evaluate(doEnv);
    });

    return value;
  },
  n_args: n => n >= 0
};

SpecialForms["def"] = SpecialForms["define"] = SpecialForms[":="] = {
  fun: (args, env) => {
    // Value to assign to the variable
    let value = args[1].evaluate(env);

    // Variable name
    let valName = args[0].name;

    env[valName] = value;
    return value;
  },
  n_args: n => n === 2
};

SpecialForms["fun"] = SpecialForms["->"] = {
  fun: (args, env) => {
    function name(expr) {
      if (expr.type !== "word") {
        throw new SyntaxError("Arg names must be words");
      }

      return expr.name;
    }

    let argNames = args.slice(0, args.length - 1).map(name);
    let body = args[args.length - 1];

    return function() {
      if (arguments.length !== argNames.length) {
        throw new SyntaxError(
          `Wrong number of arguments. Got: ${arguments.length}. Expected: ${
            argNames.length
          }`
        );
      }

      const localEnv = Object.create(env);

      for (let i = 0; i < arguments.length; i++) {
        localEnv[argNames[i]] = arguments[i];
      }

      return body.evaluate(localEnv);
    };
  },
  n_args: n => n >= 1
};

SpecialForms["set"] = SpecialForms["="] = {
  fun: (args, env) => {
    if (args[0].type !== "word") {
      throw new SyntaxError("Bad use of set");
    }

    let valName = args[0].name;

    let indices = args.slice(1, -1).map(arg => arg.evaluate(env));

    let value = args[args.length - 1].evaluate(env);

    for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) {
      // TODO: Reduce code duplication
      if ("this" in scope) {
        // TODO: use hasOwnProperty ?

        if (Object.prototype.hasOwnProperty.call(scope["this"], valName)) {
          if (indices.length === 0) {
            scope["this"][valName] = value;
          } else {
            scope["this"][valName].setelem(value, ...indices);
          }

          return value;
        }
      }

      if (Object.prototype.hasOwnProperty.call(scope, valName)) {
        if (indices.length === 0) {
          scope[valName] = value;
        } else {
          scope[valName].setelem(value, ...indices);
        }

        return value;
      }
    }

    throw new ReferenceError(`Tried setting an undefined variable: ${valName}`);
  },
  n_args: n => n >= 2
};

SpecialForms["object"] = {
  fun: (args, env) => {
    // Create a new object and a new scope
    const object = {};
    const objectEnv = Object.create(env);

    // Add the variable 'this' as a reference to the current object
    objectEnv["this"] = object;

    // Evaluate the arguments and add the methods/properties to the object
    const evArgs = args.map(arg => arg.evaluate(objectEnv));

    for (const pair of utils.chunk(evArgs, 2)) {
      const name = pair[0];
      const value = pair[1];

      object[name] = value;
    }

    return object;
  },
  n_args: n => n % 2 === 0
};

const ARITHM_OPERATORS = [
  "+",
  "-",
  "*",
  "/",
  "==",
  "!=",
  "<",
  ">",
  ">=",
  "<=",
  "&&",
  "||",
  "&",
  "|",
  "<<",
  ">>",
  ">>>"
];

ARITHM_OPERATORS.forEach(op => {
  TopEnv[op] = { fun: new Function("a, b", `return a ${op} b;`), n_args: n => n == 2 };
});

TopEnv["true"] = true;
TopEnv["false"] = false;
TopEnv["null"] = null;

["const"].forEach(key => {
  TopEnv[key] = {
    fun: word => {
      return word;
    },
    n_args: n => n === 1
  };
});

TopEnv["print"] = {
  fun: (...value) => {
    console.log(...value);
    return value;
  },
  n_args: n => n >= 1
};

TopEnv["arr"] = TopEnv["array"] = {
  fun: (...args) => {
    return args;
  },
  n_args: n => n >= 0
};

TopEnv["map"] = TopEnv["dict"] = {
  fun: (...args) => {
    return new Map(utils.chunk(args, 2));
  },
  n_args: n => n % 2 == 0
};

TopEnv["<-"] = TopEnv["[]"] = TopEnv["element"] = {
  fun: (object, ...indices) => {
    return object.sub(...indices);
  },
  n_args: n => n >= 2
};

TopEnv["length"] = {
  fun: array => {
    return array.length;
  },
  n_args: n => n === 1
};

TopEnv["RegExp"] = {
  fun: (method, ...args) => {
    return xRegExp[method](...args);
  },
  n_args: n => n >= 1
};

TopEnv["child"] = {
  fun: parent => {
    return Object.create(parent);
  },
  n_args: n => n === 1
};

module.exports = {
  SpecialForms,
  TopEnv,
  ARITHM_OPERATORS
};