my blog lives here now
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
5.2 KiB

6 years ago
  1. ---
  2. title: Interactive amc-prove
  3. date: September 29, 2019
  4. ---
  5. Following my [last post announcing amc-prove](/posts/2019-09-25.html), I
  6. decided I'd make it more accessible to people who wouldn't like to spend
  7. almost 10 minutes compiling Haskell code just to play with a fiddly
  8. prover.
  9. So I made a web interface for the thing: Play with it below.
  10. Text immediately following <span style='color:red'>> </span> is editable.
  11. <noscript>
  12. Sorry, this post isn't for you.
  13. </noscript>
  14. <div id='prover-container'>
  15. <div id='prover'>
  16. <pre id='prover-output'>
  17. <span style='color: red' id=prover-prompt>> </span><span id='prover-input' contenteditable='true'>(not P) + (not Q) -> not (P * Q)</span>
  18. </pre>
  19. </div>
  20. </div>
  21. <style>
  22. div#prover-container {
  23. width: 100%;
  24. height: 600px;
  25. border: 1px solid #c0c0c0;
  26. background: #d6d6d6;
  27. box-shadow: 1px 1px #c0c0c0;
  28. border-radius: 0.3em;
  29. }
  30. div#prover {
  31. width: 100%;
  32. height: 100%;
  33. overflow-y: scroll;
  34. }
  35. pre#prover-output, pre#prover-output > * {
  36. white-space: pre-wrap;
  37. }
  38. span#prover-input {
  39. border: 1px solid #c0c0c0;
  40. font-family: monospace;
  41. }
  42. </style>
  43. <script async>
  44. let input = document.getElementById('prover-input');
  45. let output = document.getElementById('prover-output');
  46. let prompt = document.getElementById('prover-prompt');
  47. let ERROR_REGEX = /^\<input\>\[(\d+):(\d+) \.\.(\d+):(\d+)\]: error$/;
  48. let NEGATIVE_REGEX = /^probably not\.$/;
  49. let prove = async (sentence) => {
  50. let response;
  51. try {
  52. response = await fetch('/prove', {
  53. method: 'POST',
  54. headers: {
  55. Host: '/prove'
  56. },
  57. body: sentence
  58. });
  59. } catch (e) {
  60. return { error: true, error_msg: ['Server responded with an error'] }
  61. }
  62. const prover_response = (await response.text()).split('\n').map(x => x.trimEnd()).filter(x => x !== '');
  63. const result_line = prover_response[0];
  64. console.log(result_line);
  65. console.log(result_line.match(ERROR_REGEX));
  66. if (response.status !== 200) {
  67. return { error: true, error_msg: ['Server responded with an error'] }
  68. } else if (result_line.match(ERROR_REGEX) !== null) {
  69. return { error: true, error_msg: prover_response.slice(1) }
  70. } else if (result_line.match(NEGATIVE_REGEX) !== null) {
  71. return { error: false, proven: false }
  72. } else {
  73. return { error: false, proven: true, proof: prover_response.slice(1) };
  74. }
  75. };
  76. const LEX_RULES = [
  77. [/^(fun(ction)?|match|not)/, 'kw'],
  78. [/^[a-z][a-z0-9]*/, 'va'],
  79. [/^[A-Z][A-Za-z0-9]*/, 'dt'],
  80. [/^[\(\)\[\]<->\+\*\-,|]/, 'va']
  81. ];
  82. let tokenise = (code) => {
  83. let tokens = []
  84. let exit = false;
  85. while (code !== '' && !exit) {
  86. let matched_this_loop = false;
  87. LEX_RULES.map(([regex, clss]) => {
  88. let had_spc = code.match(/^\s*/)[0]
  89. let match = code.trimStart().match(regex);
  90. if (match !== null) {
  91. let matched = match[0];
  92. code = code.trimStart().slice(matched.length);
  93. tokens.push({ t: had_spc + matched, c : clss });
  94. matched_this_loop = true;
  95. };
  96. });
  97. if (!matched_this_loop) {
  98. exit = true;
  99. tokens.push({ t : code, c : 'va' });
  100. }
  101. }
  102. return tokens;
  103. }
  104. let syntax_highlight = (code) => {
  105. let container = document.createElement('span');
  106. code.map(line => {
  107. let elems = tokenise(line).map(token => {
  108. let span = document.createElement('span');
  109. span.innerText = token.t;
  110. span.classList.add(token.c);
  111. container.appendChild(span);
  112. });
  113. container.appendChild(document.createElement('br'));
  114. });
  115. return container;
  116. }
  117. let history = [];
  118. let history_index = 0;
  119. input.onkeydown = async (e) => {
  120. let key = e.key || e.which;
  121. if (key == 'Enter' && !e.shiftKey) {
  122. e.preventDefault();
  123. let text = input.innerText;
  124. history_index = -1;
  125. if (text !== history[history.length - 1]) {
  126. history.push(text);
  127. }
  128. let result = await prove(text);
  129. let old_input = syntax_highlight(['> ' + text]);
  130. let out_block = document.createElement('span');
  131. if (result.error) {
  132. out_block.style = 'color: red';
  133. out_block.innerText = 'error:\n';
  134. result.error_msg.slice(0, 1).map(e => {
  135. let span = document.createElement('span');
  136. span.style = 'color: red';
  137. span.innerText = ' ' + e + '\n';
  138. out_block.appendChild(span);
  139. });
  140. } else if (result.proven) {
  141. out_block.classList.add('kw');
  142. out_block.innerText = 'yes:\n'
  143. out_block.appendChild(syntax_highlight(result.proof));
  144. } else {
  145. out_block.classList.add('kw');
  146. out_block.innerText = 'not proven\n';
  147. }
  148. input.innerText = '';
  149. output.insertBefore(old_input, prompt);
  150. output.insertBefore(out_block, prompt);
  151. input.scrollIntoView();
  152. } else if (key === 'Up' || key === 'ArrowUp') {
  153. e.preventDefault();
  154. console.log(history, history_index)
  155. if(history_index > 0) {
  156. history_index--;
  157. } else if(history_index < 0 && history.length > 0) {
  158. history_index = history.length - 1
  159. } else {
  160. return;
  161. }
  162. input.innerText = history[history_index];
  163. input.focus();
  164. } else if (key == 'Down' || key === 'ArrowDown') {
  165. e.preventDefault();
  166. if(history_index >= 0) {
  167. history_index = history_index < history.length - 1 ? history_index + 1 : -1;
  168. } else {
  169. return;
  170. }
  171. input.innerText = history[history_index] || '';
  172. }
  173. }
  174. </script>