this repo has no description
at trunk 223 lines 7.3 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 compiler = document.compiler; 70 try { 71 const tokens = compiler.tokenize(exp); 72 const ast = compiler.parse(tokens); 73 return {result: compiler.compile_to_string(ast, false), ok: true}; 74 } catch (e) { 75 return {text: () => e.toString(), ok: false}; 76 } 77} 78 79let input = document.getElementById("input"); 80const output = document.getElementById("output"); 81const history = []; 82const clearButton = document.getElementById("clear-local-storage"); 83const submitButton = document.getElementById("submit-input"); 84 85function renderHistory() { 86 for (const [inp, out] of history) { 87 updateHistory(inp, out); 88 } 89} 90 91function loadFromLocalStorage() { 92 history.length = 0; 93 output.innerHTML = ""; 94 renderHistory(); 95} 96 97function expandInput() { 98 if (input.tagName === "TEXTAREA") { 99 return; 100 } 101 const textarea = document.createElement("textarea"); 102 textarea.setAttribute("id", "input"); 103 textarea.setAttribute("rows", "1"); 104 textarea.setAttribute("style", "height: 1.5em;"); 105 textarea.value = input.value; 106 input.replaceWith(textarea); 107 input = textarea; 108 input.focus(); 109 input.addEventListener("keyup", e => inputHandler(e)); 110} 111 112function shrinkInput() { 113 if (input.tagName === "INPUT") { 114 return; 115 } 116 const newInput = document.createElement("input"); 117 newInput.setAttribute("id", "input"); 118 newInput.setAttribute("type", "text"); 119 newInput.value = input.value; 120 input.replaceWith(newInput); 121 input = newInput; 122 input.focus(); 123 input.addEventListener("keyup", e => inputHandler(e)); 124} 125 126async function submitInput() { 127 const response = await sendRequest(input.value); 128 if (response.ok) { 129 const {result} = response; 130 updateHistory(input.value, result); 131 history.push([input.value, result]); 132 input.value = ""; 133 } else { 134 const msg = await response.text(); 135 updateHistory(input.value, msg); 136 input.value = ""; 137 } 138} 139 140loadFromLocalStorage(); 141input.focus(); 142async function inputHandler(e) { 143 // TODO(max): Make up/down arrow keys navigate history 144 if (e.key === "Enter") { 145 if (e.shiftKey && input.tagName === "INPUT") { 146 // Shift-Enter expands the input to a textarea and does not submit 147 // input. 148 expandInput(); 149 return; 150 } 151 if (input.tagName === "TEXTAREA") { 152 // Enter in a textarea should not submit input. 153 if (e.shiftKey) { 154 shrinkInput(); 155 } 156 return; 157 } 158 submitInput() 159 } 160} 161input.addEventListener("keyup", e => inputHandler(e)); 162clearButton.addEventListener("click", () => { 163 window.localStorage.clear(); 164 loadFromLocalStorage(); 165 input.focus(); 166}); 167submitButton.addEventListener("click", () => { 168 submitInput() 169 input.focus(); 170}); 171</script> 172<script> 173"use strict"; 174 175async function main() { 176 // const scrapscript = await fetch('/scrapscript.py'); 177 let indexURL = "https://cdn.jsdelivr.net/pyodide/v0.25.1/full/"; 178 const urlParams = new URLSearchParams(window.location.search); 179 const buildParam = urlParams.get("build"); 180 if (buildParam) { 181 if (["full", "debug", "pyc"].includes(buildParam)) { 182 indexURL = indexURL.replace( 183 "/full/", 184 "/" + urlParams.get("build") + "/", 185 ); 186 } else { 187 console.warn( 188 'Invalid URL parameter: build="' + 189 buildParam + 190 '". Using default "full".', 191 ); 192 } 193 } 194 const { loadPyodide } = await import(indexURL + "pyodide.mjs"); 195 const pyodide = await loadPyodide({ 196 stdout: (msg) => console.log(`Pyodide: ${msg}`), 197 }); 198 await pyodide.runPythonAsync(` 199 from pyodide.http import pyfetch 200 response = await pyfetch("/scrapscript.py") 201 with open("scrapscript.py", "wb") as f: 202 f.write(await response.bytes()) 203 response = await pyfetch("/compiler.py") 204 with open("compiler.py", "wb") as f: 205 f.write(await response.bytes()) 206 response = await pyfetch("/runtime.c") 207 with open("runtime.c", "wb") as f: 208 f.write(b"// ...runtime goes here...") 209 `) 210 const scrapscript = await pyodide.pyimport("scrapscript"); 211 const compiler = await pyodide.pyimport("compiler"); 212document.compiler = compiler; 213 input.removeAttribute("disabled"); 214 input.setAttribute("placeholder", "Enter Scrapscript code here..."); 215 input.focus(); 216} 217main(); 218</script> 219</main> 220<script data-goatcounter="https://scrapscript.goatcounter.com/count" 221 async src="//gc.zgo.at/count.js"></script> 222</body> 223</html>