<!doctype html>
<html>
<head>
<title>CodeMirror: Lambda Calculus mode</title>
<meta charset="utf-8"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.1/codemirror.min.css">
<link rel="stylesheet" href="./theme/codewars.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro">
<link rel="stylesheet" href="https://unpkg.com/awsm.css@3.0.7/dist/awsm_theme_black.min.css">

<style>
.CodeMirror { border-top: 1px solid black; border-bottom: 1px solid black; }
[v-cloak] { display: none !important; }
</style>
</head>

<body class="dark">

<header class="!mb-0">
  <h1>
    <a href="https://github.com/codewars/lambda-calculus" title="Lambda Calculus">
      <img alt="Lambda Calculus" src="https://raw.githubusercontent.com/codewars/lambda-calculus/main/logo/logo-white.svg" width="64" height="64">
    </a>
    <p class="mt-4 text-2xl text-center">Lambda Calculus mode for CodeMirror</p>
  </h1>
  <p class="text-center"><code>text/x-lambdacalc</code></p>
  <p class="text-center"><a href="https://github.com/codewars/codemirror-lambda-calculus">@codewars/codemirror-lambda-calculus</a></p>
</header>

<main class="mt-6" v-scope>

<form>
<textarea id="code">
# Ignored arguments
false = \ _a b . b
true = \ a _b . a
not = \ b . b false true
const = true

# Multiple definition
true = not false

# Invalid whitespace (tabs)
whitespace	=	()

# Bare lambda
(\ f x . f (x x))

# Bare term
const f x

# Symbols
< a b c >  => < a a a >

# Unbound
some-func = \ local . true non-existent local

# Out of scope args
other-func = \ x . const (\ scoped-arg . x ()) scoped-arg x

# Debug mode on
#debug

# Invalid names - Debug
%value = ()

# Bare lambda - Debug
(\ f x . f (x x))

# Bare term - Debug
const f x

# Symbols - Debug
< a b c >  => < a a a >

# Unbound - Debug
some-func = \ local . true non-existent local

# Out of scope args - Debug
other-func = \ x . const (\ scoped-arg . x ()) scoped-arg x

# Debug mode off
#debug

# More code
zero = false
succ = \ n f x . f (n f x)
is-z = \ n . n (const false) true
add = \ a b f x . a f (b f x)
mul = \ a b f . a (b f)
three = succ (succ (succ zero))
mt = mul three
nine = mul three three

pair = \ a b c . c a b
fst = \ c . c true
snd = \ c . c false

nil = pair false ()
null = \ l . not (fst l)
head = \ l . fst (snd l)
tail = \ l . snd (snd l)
cons = \ x xs . pair true (pair x xs)
replicate = \ n v . n (cons v) nil
repeat = \ v . cons v (repeat v)
foldr = \ f b as . null as b (f (head as) (foldr f b (tail as)))
pred = \ n f x . foldr (const f) x (tail (replicate n ())) # Bit janky lol
map = \ f xs . null xs nil (cons (f (head xs)) (map f (tail xs)))
sum = foldr add zero
drop = \ n . n tail
take = \ n xs . is-z n nil (cons (head xs) (take (pred n) (tail xs)))
col = \ n xs . head (drop n xs)
colS = \ n xs . cons (col n xs) (cons (col (succ n) xs) (cons (col (succ (succ n)) xs) (nil)))
row = \ n xs . map (col n) xs
row-s = \ n xs . cons (row n xs) (cons (row (succ n) xs) (cons (row (succ (succ n)) xs) (nil)))
chunk = \ a b xs . row-s a (colS b xs)
append = \ as bs . null as bs (cons (head as) (append (tail as) bs))
concat = foldr append nil
eq? = \ a b . is-z a (is-z b) (is-z b false (eq? (pred a) (pred b)))
all = foldr (\ a b . a b false) true
allf = \ f xs . all (map f xs)
</textarea>

</form>

<div class="mx-auto mt-8 w-96 grid grid-flow-row grid-cols-4 grid-rows-2 gap-1">
  <input type="text"
    v-model="evaluation"
    @keyup.enter="evaluate()"
    class="col-span-3"
  />
  <button @click="evaluate()" class="col-span-1">Evaluate</button>

  <code v-cloak class="px-2 py-1 mt-1 col-span-4 bg-white/10" v-show="result">{{ result }}</code>
  <code v-cloak class="px-2 py-1 mt-1 col-span-4 bg-red-800/60" v-show="error">{{ error }}</code>
</div>

<div class="mx-auto mt-4 w-80">
  <fieldset>
    <legend>Options</legend>
    <div class="grid grid-cols-2 grid-rows-3 gap-1">
      <label class="!m-0" for="purity"><code>purity</code></label>
      <select id="purity" v-model="purity">
        <option v-for="opt in purityOptions" :value="opt" :selected="opt === purity">{{ opt }}</option>
      </select>

      <label class="!m-0" for="num-encoding"><code>numEncoding</code></label>
      <select id="num-encoding" v-model="numEncoding">
        <option v-for="opt in numEncodingOptions" :value="opt" :selected="opt === numEncoding">{{ opt }}</option>
      </select>

      <label class="!m-0" for="verbosity"><code>verbosity</code></label>
      <select id="verbosity" v-model="verbosity">
        <option v-for="opt in verbosityOptions" :value="opt" :selected="opt === verbosity">{{ opt }}</option>
      </select>
    </div>
  </fieldset>
</div>
</main>

<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.1/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.1/addon/edit/matchbrackets.min.js"></script>
<script type="module">
  import "virtual:windi.css";

  import { createApp } from "https://unpkg.com/petite-vue@0.4.1?module";
  import * as LC from "https://unpkg.com/@codewars/lambda-calculus";
  import { defineMode } from "./lambdacalc.js";
  defineMode(CodeMirror);
  const editor = CodeMirror.fromTextArea(document.getElementById("code"), {
    mode: "lambdacalc",
    lineNumbers: true,
    matchBrackets: true,
    theme: "codewars",
    specialChars: /\\/,
    specialCharPlaceholder: () => {
      const elem = document.createElement("span");
      elem.setAttribute("cm-text", "\\");
      elem.innerHTML = "λ";
      return elem;
    }
  });
  editor.setSize(500, 400);

  const toLambda = (s) => s.replace(/\\/g, "λ");
  createApp({
    purity: "Let",
    purityOptions: ["Let", "LetRec", "PureLC"],
    verbosity: "Calm",
    verbosityOptions: ["Calm", "Concise", "Loquacious", "Verbose"],
    numEncoding: "Church",
    numEncodingOptions: ["None", "Church", "Scott", "BinaryScott"],

    evaluation: "not false",
    result: "",
    error: "",
    evaluate() {
      this.result = "";
      this.error = "";

      const code = editor.getValue();
      const val = this.evaluation;
      const compile = LC.compileWith({
        purity: this.purity,
        verbosity: this.verbosity,
        numEncoding: this.numEncoding,
      });
      try {
        const { result } = compile(`${code}\n\nresult = ${val}`);
        this.result = toLambda(result.term + "");
      } catch (e) {
        this.error = e.message || e.name;
      }
    },
  }).mount();
</script>

</body>
</html>