|
|
- ---
- 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>
|