---
title: Interactive amc-prove
date: September 29, 2019
---

Following my [last post announcing amc-prove](/posts/2019-09-25.html), I
decided I'd make it more accessible to people who wouldn't like to spend
almost 10 minutes compiling Haskell code just to play with a fiddly
prover.

So I made a web interface for the thing: Play with it below.

Text immediately following <span style='color:red'>> </span> is editable.

<noscript>
Sorry, this post isn't for you.
</noscript>

<div id='prover-container'>
<div id='prover'>
<pre id='prover-output'>
<span style='color: red' id=prover-prompt>> </span><span id='prover-input' contenteditable='true'>(not P) + (not Q) -> not (P * Q)</span>
</pre>
</div>
</div>

<style>
div#prover-container {
  width: 100%;
  height: 600px;
  border: 1px solid #c0c0c0;
  background: #d6d6d6;
  box-shadow: 1px 1px #c0c0c0;
  border-radius: 0.3em;
}

div#prover {
  width: 100%;
  height: 100%;
  overflow-y: scroll;
}
pre#prover-output, pre#prover-output > * {
  white-space: pre-wrap;
}
span#prover-input {
  border: 1px solid #c0c0c0;
  font-family: monospace;
}
</style>

<script async>
let input = document.getElementById('prover-input');
let output = document.getElementById('prover-output');
let prompt = document.getElementById('prover-prompt');

let ERROR_REGEX = /^\<input\>\[(\d+):(\d+) \.\.(\d+):(\d+)\]: error$/;
let NEGATIVE_REGEX = /^probably not\.$/;


let prove = async (sentence) => {
  let response;
  try {
    response = await fetch('/prove', {
        method: 'POST',
        headers: {
        Host: '/prove'
      },
      body: sentence
    });
  } catch (e) {
    return { error: true, error_msg: ['Server responded with an error'] }
  }
  const prover_response = (await response.text()).split('\n').map(x => x.trimEnd()).filter(x => x !== '');
  const result_line = prover_response[0];

  console.log(result_line);
  console.log(result_line.match(ERROR_REGEX));

  if (response.status !== 200) {
    return { error: true, error_msg: ['Server responded with an error'] }
  } else if (result_line.match(ERROR_REGEX) !== null) {
    return { error: true, error_msg: prover_response.slice(1) }
  } else if (result_line.match(NEGATIVE_REGEX) !== null) {
    return { error: false, proven: false }
  } else {
    return { error: false, proven: true, proof: prover_response.slice(1) };
  }
};

const LEX_RULES = [
  [/^(fun(ction)?|match|not)/, 'kw'],
  [/^[a-z][a-z0-9]*/, 'va'],
  [/^[A-Z][A-Za-z0-9]*/, 'dt'],
  [/^[\(\)\[\]<->\+\*\-,|]/, 'va']
 ];

let tokenise = (code) => {
  let tokens = []
  let exit = false;
  while (code !== '' && !exit) {
    let matched_this_loop = false;

    LEX_RULES.map(([regex, clss]) => {
      let had_spc = code.match(/^\s*/)[0]
      let match = code.trimStart().match(regex);

      if (match !== null) {
        let matched = match[0];
        code = code.trimStart().slice(matched.length);
        tokens.push({ t: had_spc + matched, c : clss });
        matched_this_loop = true;
      };
    });

    if (!matched_this_loop) {
      exit = true;
      tokens.push({ t : code, c : 'va' });
    }
  }
  return tokens;
}

let syntax_highlight = (code) => {
  let container = document.createElement('span');
  code.map(line => {
    let elems = tokenise(line).map(token => {
      let span = document.createElement('span');
      span.innerText = token.t;
      span.classList.add(token.c);
      container.appendChild(span);
    });
    container.appendChild(document.createElement('br'));
  });
  return container;
}

let history = [];
let history_index = 0;

input.onkeydown = async (e) => {
  let key = e.key || e.which;
  if (key == 'Enter' && !e.shiftKey) {
    e.preventDefault();
    let text = input.innerText;

    history_index = -1;
    if (text !== history[history.length - 1]) {
      history.push(text);
    }

    let result = await prove(text);

    let old_input = syntax_highlight(['> ' + text]);
    let out_block = document.createElement('span');

    if (result.error) {
      out_block.style = 'color: red';
      out_block.innerText = 'error:\n';
      result.error_msg.slice(0, 1).map(e => {
        let span = document.createElement('span');
        span.style = 'color: red';
        span.innerText = '  ' + e + '\n';
        out_block.appendChild(span);
      });
    } else if (result.proven) {
      out_block.classList.add('kw');
      out_block.innerText = 'yes:\n'
      out_block.appendChild(syntax_highlight(result.proof));
    } else {
      out_block.classList.add('kw');
      out_block.innerText = 'not proven\n';
    }
    input.innerText = '';

    output.insertBefore(old_input, prompt);
    output.insertBefore(out_block, prompt);
    input.scrollIntoView();
  } else if (key === 'Up' || key === 'ArrowUp') {
    e.preventDefault();
    console.log(history, history_index)
    if(history_index > 0) {
      history_index--;
    } else if(history_index < 0 && history.length > 0) {
      history_index = history.length - 1
    } else {
      return;
    }
    input.innerText = history[history_index];
    input.focus();
  } else if (key == 'Down' || key === 'ArrowDown') {
    e.preventDefault();
    if(history_index >= 0) {
      history_index = history_index < history.length - 1 ? history_index + 1 : -1;
    } else {
      return;
    }

    input.innerText = history[history_index] || '';
  }
}

</script>
