this repo has no description
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>