this repo has no description
at trunk 213 lines 7.0 kB view raw
1<!DOCTYPE html> 2<html> 3<head> 4<meta charset="utf-8"> 5<meta name="viewport" content="width=device-width, initial-scale=1"> 6<title>Scrapscript Web REPL</title> 7<link rel="shortcut icon" type="image/x-icon" href="data:image/vnd.microsoft.icon;base64,AAABAAEAEBAQAAAAAAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAD/AAAA////AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAREBEQAAAAAQAQEAEAAAABABAQAQAAAAAREBEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIiIiIiIiIiIiIiIiIiIiIiERIiESEiIiIiESEiISIiIhEiISIhEiIiIREiESEhIiIiIiIiIiIiIiIiIiIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"> 8<link rel="preconnect" href="https://fonts.googleapis.com" /> 9<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 10<link 11 href="https://fonts.googleapis.com/css2?family=Fira+Code&display=swap" 12 rel="stylesheet" 13/> 14<link 15 href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,500;0,700&display=swap" 16 rel="stylesheet" 17/> 18<link 19 href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,wght@0,400;0,700;0,900;1,400&display=swap" 20 rel="stylesheet" 21/> 22<link rel="stylesheet" href="/style.css" /> 23</head> 24<body> 25<main> 26<div> 27<p>See <a href="https://scrapscript.org/">scrapscript.org</a> for a slightly 28out of date language reference.</p> 29<p>This REPL is completely client-side and works by running 30<a href="https://github.com/tekknolagi/scrapscript">scrapscript.py</a> in the 31browser using <a href="https://pyodide.org/">Pyodide</a>.</p> 32</div> 33<div> 34<!-- TODO(max): Add button to save to/load from disk. --> 35<button id="clear-local-storage">Clear</button> 36</div> 37<div id="output" style="height: 400px; overflow: auto;"> 38Output: 39</div> 40<div> 41<input id="input" type="text" disabled placeholder="Loading pyodide..." /><input id="submit-input" type="submit" value="Submit" /> 42</div> 43<script type="module"> 44"use strict"; 45 46function updateHistory(inp, out) { 47 const wrap = document.createElement("div"); 48 const pre_inp = document.createElement("pre"); 49 pre_inp.setAttribute("class", "language-text"); 50 const code_inp = document.createElement("code"); 51 code_inp.setAttribute("class", "language-text"); 52 code_inp.append(`>>> ${inp}`); 53 pre_inp.append(code_inp); 54 55 const pre_out = document.createElement("pre"); 56 pre_out.setAttribute("class", "result language-text"); 57 const code_out = document.createElement("code"); 58 code_out.setAttribute("class", "language-text"); 59 code_out.append(`${out}`); 60 pre_out.append(code_out); 61 62 wrap.append(pre_inp); 63 wrap.append(pre_out); 64 output.append(wrap); 65 output.scrollTop = output.scrollHeight; 66} 67 68async function sendRequest(exp) { 69 const scrap = document.scrapscript; 70 const [result, monad] = document.monad.bind(scrap.parse(scrap.tokenize(exp))); 71 return {result: scrap.pretty(result), monad, ok: true}; 72} 73 74let input = document.getElementById("input"); 75const output = document.getElementById("output"); 76const history = []; 77const clearButton = document.getElementById("clear-local-storage"); 78const submitButton = document.getElementById("submit-input"); 79 80function renderHistory() { 81 for (const [inp, out] of history) { 82 updateHistory(inp, out); 83 } 84} 85 86function loadFromLocalStorage() { 87 history.length = 0; 88 output.innerHTML = ""; 89 renderHistory(); 90} 91 92function expandInput() { 93 if (input.tagName === "TEXTAREA") { 94 return; 95 } 96 const textarea = document.createElement("textarea"); 97 textarea.setAttribute("id", "input"); 98 textarea.setAttribute("rows", "1"); 99 textarea.setAttribute("style", "height: 1.5em;"); 100 textarea.value = input.value; 101 input.replaceWith(textarea); 102 input = textarea; 103 input.focus(); 104 input.addEventListener("keyup", e => inputHandler(e)); 105} 106 107function shrinkInput() { 108 if (input.tagName === "INPUT") { 109 return; 110 } 111 const newInput = document.createElement("input"); 112 newInput.setAttribute("id", "input"); 113 newInput.setAttribute("type", "text"); 114 newInput.value = input.value; 115 input.replaceWith(newInput); 116 input = newInput; 117 input.focus(); 118 input.addEventListener("keyup", e => inputHandler(e)); 119} 120 121async function submitInput() { 122 const response = await sendRequest(input.value); 123 if (response.ok) { 124 const {result, monad} = response; 125 updateHistory(input.value, result); 126 history.push([input.value, result]); 127 input.value = ""; 128 document.monad = monad; 129 } else { 130 const msg = await response.text(); 131 updateHistory(input.value, msg); 132 input.value = ""; 133 } 134} 135 136loadFromLocalStorage(); 137input.focus(); 138async function inputHandler(e) { 139 // TODO(max): Make up/down arrow keys navigate history 140 if (e.key === "Enter") { 141 if (e.shiftKey && input.tagName === "INPUT") { 142 // Shift-Enter expands the input to a textarea and does not submit 143 // input. 144 expandInput(); 145 return; 146 } 147 if (input.tagName === "TEXTAREA") { 148 // Enter in a textarea should not submit input. 149 if (e.shiftKey) { 150 shrinkInput(); 151 } 152 return; 153 } 154 submitInput() 155 } 156} 157input.addEventListener("keyup", e => inputHandler(e)); 158clearButton.addEventListener("click", () => { 159 window.localStorage.clear(); 160 loadFromLocalStorage(); 161 input.focus(); 162}); 163submitButton.addEventListener("click", () => { 164 submitInput() 165 input.focus(); 166}); 167</script> 168<script> 169"use strict"; 170 171async function main() { 172 // const scrapscript = await fetch('/scrapscript.py'); 173 let indexURL = "https://cdn.jsdelivr.net/pyodide/v0.25.1/full/"; 174 const urlParams = new URLSearchParams(window.location.search); 175 const buildParam = urlParams.get("build"); 176 if (buildParam) { 177 if (["full", "debug", "pyc"].includes(buildParam)) { 178 indexURL = indexURL.replace( 179 "/full/", 180 "/" + urlParams.get("build") + "/", 181 ); 182 } else { 183 console.warn( 184 'Invalid URL parameter: build="' + 185 buildParam + 186 '". Using default "full".', 187 ); 188 } 189 } 190 const { loadPyodide } = await import(indexURL + "pyodide.mjs"); 191 const pyodide = await loadPyodide({ 192 stdout: (msg) => console.log(`Pyodide: ${msg}`), 193 }); 194 await pyodide.runPythonAsync(` 195 from pyodide.http import pyfetch 196 response = await pyfetch("/scrapscript.py") 197 with open("scrapscript.py", "wb") as f: 198 f.write(await response.bytes()) 199 `) 200 const scrapscript = await pyodide.pyimport("scrapscript"); 201document.monad = scrapscript.ScrapMonad(scrapscript.boot_env()); 202document.scrapscript = scrapscript; 203 input.removeAttribute("disabled"); 204 input.setAttribute("placeholder", "Enter Scrapscript code here..."); 205 input.focus(); 206} 207main(); 208</script> 209</main> 210<script data-goatcounter="https://scrapscript.goatcounter.com/count" 211 async src="//gc.zgo.at/count.js"></script> 212</body> 213</html>