var cnst = (value) => (_x, _y, _z) => value;
var variable = (name) => (x, y, z) => (name === "x" ? x : name === "y" ? y : z);

var one = cnst(1);
var two = cnst(2);
var three = cnst(3);

var binaryOp = (op) => (f, g) => (x, y, z) => op(f(x, y, z), g(x, y, z));
var ternaryOp = (op) => (f, g, h) => (x, y, z) =>
  op(f(x, y, z), g(x, y, z), h(x, y, z));
var quaternaryOp = (op) => (f, g, h, k) => (x, y, z) =>
  op(f(x, y, z), g(x, y, z), h(x, y, z), k(x, y, z));
var quinaryOp = (op) => (f, g, h, i, j) => (x, y, z) =>
  op(f(x, y, z), g(x, y, z), h(x, y, z), i(x, y, z), j(x, y, z));

var add = binaryOp((a, b) => a + b);
var subtract = binaryOp((a, b) => a - b);
var multiply = binaryOp((a, b) => a * b);
var divide = binaryOp((a, b) => a / b);
var negate = (f) => (x, y, z) => -f(x, y, z);

var clamp = ternaryOp((v, mn, mx) => Math.min(Math.max(v, mn), mx));
var wrap = ternaryOp(
  (v, mn, mx) => mn + ((((v - mn) % (mx - mn)) + (mx - mn)) % (mx - mn)),
);

var softClamp = quaternaryOp(
  (v, mn, mx, lambda) =>
    mn + (mx - mn) / (1 + Math.exp(lambda * ((mx + mn) / 2 - v))),
);

var argMin3 = ternaryOp((a, b, c) => [a, b, c].indexOf(Math.min(a, b, c)));
var argMax3 = ternaryOp((a, b, c) => [a, b, c].indexOf(Math.max(a, b, c)));

var argMin5 = quinaryOp((a, b, c, d, e) =>
  [a, b, c, d, e].indexOf(Math.min(a, b, c, d, e)),
);
var argMax5 = quinaryOp((a, b, c, d, e) =>
  [a, b, c, d, e].indexOf(Math.max(a, b, c, d, e)),
);

var tokenize = (expression) => {
  var tokens = [];
  var i = 0;
  while (i < expression.length) {
    while (i < expression.length && expression[i] === " ") i++;
    var start = i;
    while (i < expression.length && expression[i] !== " ") i++;
    if (i > start) tokens.push(expression.slice(start, i));
  }
  return tokens;
};

var unaryOp = (op) => (f) => (x, y, z) => op(f(x, y, z));

var arcTan = unaryOp(Math.atan);
var arcTan2 = binaryOp(Math.atan2);
var sin = unaryOp(Math.sin);
var cos = unaryOp(Math.cos);

var ARITIES = {
  "+": 2,
  "-": 2,
  "*": 2,
  "/": 2,
  negate: 1,
  clamp: 3,
  wrap: 3,
  softClamp: 4,
  argMin3: 3,
  argMax3: 3,
  argMin5: 5,
  argMax5: 5,
  atan: 1,
  arcTan: 1,
  atan2: 2,
  arcTan2: 2,
  sin: 1,
  cos: 1,
};

var OPERATIONS = {
  "+": add,
  "-": subtract,
  "*": multiply,
  "/": divide,
  negate: negate,
  clamp: clamp,
  wrap: wrap,
  softClamp: softClamp,
  argMin3: argMin3,
  argMax3: argMax3,
  argMin5: argMin5,
  argMax5: argMax5,
  atan: arcTan,
  arcTan: arcTan,
  atan2: arcTan2,
  arcTan2: arcTan2,
  sin: sin,
  cos: cos,
};

var NAMED_CONSTS = { one: one, two: two, three: three };

var parse = (expression) => {
  var tokens = tokenize(expression);
  var stack = [];
  for (var token of tokens) {
    if (token in OPERATIONS) {
      var arity = ARITIES[token];
      var args = stack.splice(-arity);
      stack.push(OPERATIONS[token](...args));
    } else if (token in NAMED_CONSTS) {
      stack.push(NAMED_CONSTS[token]);
    } else if (token === "x" || token === "y" || token === "z") {
      stack.push(variable(token));
    } else {
      stack.push(cnst(parseFloat(token)));
    }
  }
  return stack[0];
};
