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