const intToName = (num) => {
  let out = "";
  for (let n = num + 1; n > 0; n--) {
    out += String.fromCharCode(97 + (--n % 26));
    n = Math.floor(n / 26);
  }
  return out;
};

// ------------
// CONSTRUCTORS
// ------------

const abstraction = (name) => (body) => ({ constructor: "abstraction", name, body });

const application = (left) => (right) => ({ constructor: "application", left, right });

const symbol = (name) => ({ constructor: "symbol", name });

const definition = (name) => ({ constructor: "definition", name });

const show = (term) => {
  switch (term.constructor) {
    case "abstraction":
      return `λ${term.name}.${show(term.body)}`;
    case "application":
      return `(${show(term.left)} ${show(term.right)})`;
    case "symbol":
      return `${term.name}`;
  }
};

// ---------
// REDUCTION
// ---------

// This is a very inefficient reduction method using normal order.
// It uses de Bruijn indices to bypass alpha conversion.
// It first reduces the outer redex (WHNF), then recursively reduces
// the nested terms.

// increment de Bruijn indices that reach out of the current environment
const increment = (i, t) => {
  switch (t.constructor) {
    case "symbol":
      return symbol(i <= t.name ? t.name + 1 : t.name);
    case "application":
      return application(increment(i, t.left))(increment(i, t.right));
    case "abstraction":
      return abstraction(null)(increment(i + 1, t.body));
  }
};

// substitute de Bruijn index in term with other term
const substitute = (i, t, s) => {
  switch (t.constructor) {
    case "symbol":
      return i === t.name ? s : symbol(t.name > i ? t.name - 1 : t.name);
    case "application":
      return application(substitute(i, t.left, s))(substitute(i, t.right, s));
    case "abstraction":
      return abstraction(null)(substitute(i + 1, t.body, increment(0, s)));
  }
};

// weak-head normal form (substitute until no outer redex)
const whnf = (t) => {
  if (t.constructor === "application") {
    const _left = whnf(t.left);
    return _left.constructor === "abstraction"
      ? whnf(substitute(0, _left.body, t.right))
      : application(_left)(t.right);
  }
  return t;
};

// reduce to normal form
const nf = (t) => {
  const w = whnf(t);
  switch (w.constructor) {
    case "abstraction":
      return abstraction(null)(nf(w.body));
    case "application":
      return application(nf(w.left))(nf(w.right));
  }
  return w;
};

// convert from/to de Bruijn indices
// we do this to bypass alpha conversion (potential problems with shadowed variables)
const toggleDeBruijn = (t, bruijn) => {
  const go = (env) => (t) => {
    switch (t.constructor) {
      case "application":
        return application(go(env)(t.left))(go(env)(t.right));
      case "abstraction":
        if (!bruijn) return abstraction(null)(go([t.name, ...env])(t.body));
        const name = intToName(env.length);
        return abstraction(name)(go([name, ...env])(t.body));
      case "symbol":
        if (!bruijn) return symbol(env.indexOf(t.name));
        return symbol(env[t.name]);
      default:
        throw Error("unexpected " + t.constructor);
    }
  };
  return go([])(t);
};

const reduce = (term) => {
  return toggleDeBruijn(nf(toggleDeBruijn(term, false)), true);
};

// -------
// PARSING
// -------

const consume = (str) => (predicate) => {
  let out = "";
  while (str && predicate(str[0])) {
    out += str[0];
    str = str.slice(1);
  }
  return [out, str.trim()];
};

const isSymbol = (x) => x >= "a" && x <= "z";

const isDefinition = (x) => (x >= "A" && x <= "Z") || (x >= "0" && x <= "9");

const parseTerm = (program) => {
  const go = (str) => {
    // skip spaces
    str = str.trim();

    const head = str[0];
    const tail = str.slice(1).trim();

    // abstraction start
    if ("\\λ".includes(head)) {
      const [name, tail1] = consume(tail)(isSymbol);
      const tail2 = tail1.slice(1).trim(); // skip .
      const [body, tail3] = go(tail2);
      return [abstraction(name)(body), tail3];
    }

    // application start
    if (head === "(") {
      const [left, tail1] = go(tail);
      const [right, tail2] = go(tail1);
      return [application(left)(right), tail2.trim().slice(1)];
    }

    // application end - already consumed above
    if (head === ")") {
      throw Error("unexpected " + head);
    }

    // symbol / variable (lowercase letters)
    if (isSymbol(head)) {
      const [sym, tail1] = consume(str)(isSymbol);
      return [symbol(sym), tail1];
    }

    // definition (uppercase letters)
    if (isDefinition(head)) {
      const [name, tail1] = consume(str)(isDefinition);
      return [definition(name), tail1];
    }

    throw Error("unexpected " + head);
  };

  const [term, tail] = go(program);
  if (tail != "") throw Error("unexpected " + tail);
  return term;
};

const parse = (program) => {
  const definitions = {};

  const substituteDefinition = (t) => {
    switch (t.constructor) {
      case "application":
        return application(substituteDefinition(t.left))(
          substituteDefinition(t.right),
        );
      case "abstraction":
        return abstraction(t.name)(substituteDefinition(t.body));
      case "symbol":
        return t;
      case "definition":
        if (t.name in definitions) return definitions[t.name];
        else throw Error("invalid definition " + t.name);
      default:
        throw Error("unexpected " + t.constructor);
    }
  };

  program
    .trim()
    .split("\n")
    .filter((line) => !(line.startsWith("//") || line.trim() === ""))
    .forEach((line) => {
      const [definition, term] = line.split("=");
      definitions[definition.trim()] = substituteDefinition(
        parseTerm(term.trim()),
      );
    });
  if (!("MAIN" in definitions)) throw Error("no 'MAIN' definition");
  return definitions["MAIN"];
};

// ---
// CLI
// ---

const data = require("fs").readFileSync("/dev/stdin");
console.log(show(reduce(parse(data + ""))));