diff options
Diffstat (limited to 'html/playground')
-rw-r--r-- | html/playground/APL386.ttf | bin | 0 -> 203668 bytes | |||
-rw-r--r-- | html/playground/counter.html | 262 | ||||
-rw-r--r-- | html/playground/index.html | 248 | ||||
-rw-r--r-- | html/playground/little-regex.html | 728 | ||||
-rw-r--r-- | html/playground/regex.html | 477 | ||||
-rw-r--r-- | html/playground/scheme.html | 533 |
6 files changed, 2248 insertions, 0 deletions
diff --git a/html/playground/APL386.ttf b/html/playground/APL386.ttf new file mode 100644 index 0000000..5e3a338 --- /dev/null +++ b/html/playground/APL386.ttf Binary files differdiff --git a/html/playground/counter.html b/html/playground/counter.html new file mode 100644 index 0000000..1b16f54 --- /dev/null +++ b/html/playground/counter.html @@ -0,0 +1,262 @@ +<html lang="en"><head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>JavaScript Playground</title> + <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles."> + <style> + body { + display: flex; + flex-direction: column; + align-items: center; + background-color: #ddd; + padding: 10px; + height: 100vh; + margin: 0; + } + + textarea { + width: 100%; + height: 64%; + font-family: monospace; + background-color: #FFFEEC; + border: 2px solid #000; + scrollbar-width: none; + font-size: 1rem; + margin-bottom: 10px; + padding: 10px; + box-sizing: border-box; + resize: none; + border-bottom: 12px solid teal; + -webkit-user-modify: read-write-plaintext-only; + } + + textarea::-webkit-scrollbar { + display: none; + } + + textarea::selection { + background-color: #EFECA7; + } + + textarea:focus { + outline: none; + } + + #console { + width: 100%; + height: 22%; + background-color: #000; + color: #0fc; + font-family: monospace; + font-size: 1rem; + overflow-y: auto; + padding: 10px; + box-sizing: border-box; + white-space: pre-wrap; + } + + .button-container { + width: 100%; + display: flex; + justify-content: flex-end; + margin-bottom: 10px; + } + + button { + padding: 10px 20px; + font-size: 1rem; + margin-left: 10px; + cursor: pointer; + border: none; + transition: background-color 0.3s ease; + } + + button:hover, button:focus { + outline: none; + } + + button.run { + background-color: teal; + color: #FFFFFF; + } + </style> +</head> +<body> + + <div class="playground" id="playground"> + <div class="seesaw" id="seesaw"></div> + <div class="slide" id="slide"></div> + </div> + + <div class="button-container"> + <button onclick="clearEverything()">Clear</button> + <button onclick="downloadCodeAndEditor()">Download</button> + <button onclick="shareCode()">Share</button> + <button onclick="evaluateCode()" class="run">Run Code</button> + </div> + <textarea id="codeInput">mount(playground => { + const d = document.createElement('div'); + const p = document.createElement('p'); + const b = document.createElement('button'); + const b2 = document.createElement('button'); + + d.style = 'border: 1px solid black; padding: 2em'; + p.textContent = 'Count: 0'; + p.style = 'text-align: center; font-weight: bold'; + b.textContent = 'Click Me'; + b2.textContent = 'Reset'; + b2.style = 'background-color: red; color: white'; + d.append(p,b,b2); + + let count = 0; + b.onclick = () => { count++; p.innerHTML = 'Count: ' + count; } + b2.onclick = () => { count = 0; p.innerHTML = 'Count: ' + count; } + + playground.appendChild(d); +}); +</textarea> + <div id="console"></div> + + <script> + function evaluateCode() { + const code = document.getElementById('codeInput').value; + const consoleDiv = document.getElementById('console'); + consoleDiv.innerHTML = ''; + + // Custom console.log function to output to the console div + const originalConsoleLog = console.log; + console.log = function(...args) { + args.forEach(arg => { + const output = document.createElement('div'); + output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg; + consoleDiv.appendChild(output); + }); + originalConsoleLog.apply(console, args); + }; + + try { + eval(code); + } catch (error) { + const errorOutput = document.createElement('div'); + errorOutput.textContent = error; + errorOutput.style.color = 'red'; + consoleDiv.appendChild(errorOutput); + } + + // Restore browser's console.log + console.log = originalConsoleLog; + } + + function downloadCodeAndEditor() { + const codeInput = document.getElementById('codeInput').value; + const htmlContent = document.documentElement.outerHTML.replace( + /<textarea id="codeInput"[^>]*>.*<\/textarea>/, + `<textarea id="codeInput">${codeInput}</textarea>` + ); + + const blob = new Blob([htmlContent], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'code_editor.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + function shareCode() { + const code = document.getElementById('codeInput').value; + const encodedCode = btoa(encodeURIComponent(code)); + window.location.hash = encodedCode; + window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href); + } + + function clearEverything() { + if (!confirm('Are you sure you want to reset the playground?')) { + return; + } else { + window.location.hash = ''; + window.location.reload(); + } + } + + function loadCodeFromHash() { + const hash = window.location.hash.substring(1); + if (hash) { + try { + const decodedCode = decodeURIComponent(atob(hash)); + document.getElementById('codeInput').value = decodedCode; + } catch (error) { + console.error('Failed to decode the URL hash:', error); + } + } + } + + function help() { + const helpText = ` + Welcome to the JavaScript Playground! Here are some tips to get you started: + + 1. Enter your JavaScript code in the textarea. + 2. Click the "Run Code" button to execute your code. + 3. The console output will be displayed below the textarea. + 4. Click the "Clear" button to reset the playground. + 5. Click the "Download" button to save your code and editor as an HTML file. + 6. Click the "Share" button to generate a URL to share your code with others. + 7. You can also press "Cmd + Enter" to run your code. + 8. There's an empty div above the buttons with the id "playground" + 9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()" + 10. You can use the "clear()" function to clear the content's of the console. + + Go nuts! Share scrappy fiddles! + `; + console.log(helpText); + } + + function clear() { + document.getElementById('console').innerHTML = ''; + } + + function mountHelp() { + console.log(` + The mount function is used to mount stuff to the playground div. + It takes a function as an argument, which in turn receives the playground div as an argument. + Before mounting, it clears the playground div. + Here's an example of how to use the mount function: + + mount(playground => { + const h1 = document.createElement('h1'); + h1.textContent = 'Hell is empty and all the devils are here.'; + playground.appendChild(h1); + }); + + This will add an h1 element to the playground div. + `); + } + + function mount(mountFunction) { + const playground = document.getElementById('playground'); + if (!playground) { + console.error("Couldn't find a div with the id 'playground'! You may need to reload the page."); + return; + } + + if (playground.innerHTML.trim() !== "") { + playground.innerHTML = ""; + } + mountFunction(playground); + } + + + document.getElementById('codeInput').addEventListener('keydown', function(event) { + if (event.metaKey && event.key === 'Enter') { + event.preventDefault(); + evaluateCode(); + } + }); + + window.onload = loadCodeFromHash; + </script> + + +</body></html> \ No newline at end of file diff --git a/html/playground/index.html b/html/playground/index.html new file mode 100644 index 0000000..a236d2f --- /dev/null +++ b/html/playground/index.html @@ -0,0 +1,248 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>JavaScript Playground</title> + <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles."> + <style> + html, body { height: 100%; margin: 0; padding: 0; } + body { display: flex; flex-direction: column; background-color: #ddd; } + #app { display: flex; flex-direction: column; padding: 10px; height: 100%; box-sizing: border-box; } + .button-container { width: 100%; display: flex; justify-content: flex-end; margin-bottom: 10px; flex-shrink: 0; } + button { padding: 10px 20px; font-size: 1rem; margin-left: 10px; cursor: pointer; border: none; transition: background-color 0.3s ease; } + button:hover, button:focus { outline: none; } + button:disabled { background-color: #999; cursor: not-allowed; } + button.run { background-color: teal; color: #FFFFFF; } + textarea { width: 100%; height: 64%; font-family: monospace; background-color: #FFFEEC; border: 2px solid #000; scrollbar-width: none; font-size: 1rem; margin-bottom: 10px; padding: 10px; box-sizing: border-box; resize: none; border-bottom: 12px solid teal; -webkit-user-modify: read-write-plaintext-only; flex-shrink: 0; } + textarea::-webkit-scrollbar { display: none; } + textarea::selection { background-color: #EFECA7; } + textarea:focus { outline: none; } + #console { width: 100%; height: 22%; background-color: #000; color: #0fc; font-family: monospace; font-size: 1rem; overflow-y: auto; padding: 10px; box-sizing: border-box; white-space: pre-wrap; flex-grow: 1; } + #console .log-item { margin-bottom: 5px; border-bottom: 1px solid #333; } + #console details { cursor: pointer; } + #console summary { outline: none; } + </style> +</head> +<body> + <div id="app"></div> + + <iframe id="sandbox" style="display: none;" title="JavaScript Sandbox"></iframe> + + <script> + /** + * ---------------------------------------------------------------- + * Functional Utilities + * ---------------------------------------------------------------- + */ + + const pipe = (...fns) => (initialValue) => fns.reduce((acc, fn) => fn(acc), initialValue); + const compose = (...fns) => (initialValue) => fns.reduceRight((acc, fn) => fn(acc), initialValue); + + /** + * ---------------------------------------------------------------- + * The Elm Architecture (Model, View, Update) Implementation + * ---------------------------------------------------------------- + */ + + const init = { + code: ` + const add = (a, b) => a + b; + const multiply = (a, b) => a * b; + + const addAndMultiply = pipe(add, multiply); + + console.log(addAndMultiply(2, 3)); +`, + consoleOutput: [], + sandboxReady: false, + }; + + const view = (model) => ` + <div class="button-container"> + <button onclick="dispatch({ type: 'CLEAR' })">Clear</button> + <button onclick="dispatch({ type: 'DOWNLOAD' })">Download</button> + <button onclick="dispatch({ type: 'SHARE' })">Share</button> + <button onclick="dispatch({ type: 'RUN_CODE' })" class="run" ${!model.sandboxReady ? 'disabled title="Sandbox is loading..."' : ''}>Run Code</button> + </div> + <textarea id="codeInput" oninput="dispatch({ type: 'CODE_CHANGED', payload: this.value })" onkeydown="handleKeyDown(event)">${model.code}</textarea> + <div id="console"> + ${model.consoleOutput.map(log => { + if (typeof log === 'object' && log !== null) { + return `<div class="log-item"><details><summary>${Object.prototype.toString.call(log)}</summary><pre>${JSON.stringify(log, null, 2)}</pre></details></div>`; + } + return `<div class="log-item">${log}</div>`; + }).join('')} + </div> + `; + + const update = (msg, model) => { + switch (msg.type) { + case 'CODE_CHANGED': + return { ...model, code: msg.payload }; + + case 'RUN_CODE': + return model; + + case 'CONSOLE_LOG': + return { ...model, consoleOutput: [...model.consoleOutput, ...msg.payload] }; + + case 'CLEAR': + if (confirm('Are you sure you want to reset the playground?')) { + window.location.hash = ''; + window.location.reload(); + } + return model; + + case 'CLEAR_CONSOLE': + return { ...model, consoleOutput: [] }; + + case 'SHARE': + case 'DOWNLOAD': + return model; + + case 'SANDBOX_READY': + return { ...model, sandboxReady: true }; + + default: + return model; + } + }; + + /** + * ---------------------------------------------------------------- + * Application Core & Side-Effect Handling + * ---------------------------------------------------------------- + */ + + let model = null; + const appContainer = document.getElementById('app'); + const sandbox = document.getElementById('sandbox'); + + const dispatch = (msg) => { + const newModel = update(msg, model); + handleSideEffects(msg, newModel); + model = newModel; + render(model); + }; + + const handleSideEffects = (msg, model) => { + if (msg.type === 'RUN_CODE') { + if (!model.sandboxReady) return; + dispatch({ type: 'CLEAR_CONSOLE' }); + sandbox.contentWindow.postMessage({ code: model.code }, '*'); + } else if (msg.type === 'SHARE') { + const encodedCode = btoa(encodeURIComponent(model.code)); + window.location.hash = encodedCode; + window.prompt("Copy the URL to share.", window.location.href); + } else if (msg.type === 'DOWNLOAD') { + const htmlContent = document.documentElement.outerHTML.replace( + /<textarea id="codeInput"[^>]*>.*<\/textarea>/, + `<textarea id="codeInput">${model.code}</textarea>` + ); + const blob = new Blob([htmlContent], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'playground.html'; + a.click(); + URL.revokeObjectURL(url); + } + }; + + const render = (model) => { + const focusedElementId = document.activeElement?.id; + const selectionStart = document.activeElement?.selectionStart; + const selectionEnd = document.activeElement?.selectionEnd; + + appContainer.innerHTML = view(model); + + if (focusedElementId === 'codeInput') { + const focusedElement = document.getElementById(focusedElementId); + if (focusedElement) { + focusedElement.focus(); + if (typeof selectionStart === 'number') { + focusedElement.selectionStart = selectionStart; + focusedElement.selectionEnd = selectionEnd; + } + } + } + }; + + const handleKeyDown = (event) => { + if (event.key === 'Tab') { + event.preventDefault(); + const start = event.target.selectionStart; + const end = event.target.selectionEnd; + event.target.value = event.target.value.substring(0, start) + " " + event.target.value.substring(end); + dispatch({ type: 'CODE_CHANGED', payload: event.target.value }); + event.target.selectionStart = event.target.selectionEnd = start + 2; + } + if (event.metaKey && event.key === 'Enter') { + event.preventDefault(); + dispatch({ type: 'RUN_CODE' }); + } + }; + + /** + * ---------------------------------------------------------------- + * Sandbox Initialization + * ---------------------------------------------------------------- + */ + + const sandboxSrc = ` + <script> + const pipe = ${pipe.toString()}; + const compose = ${compose.toString()}; + + const originalConsoleLog = console.log; + console.log = (...args) => { + parent.postMessage({ type: 'CONSOLE_LOG', payload: args }, '*'); + }; + + window.addEventListener('message', (event) => { + try { + eval(event.data.code); + } catch (e) { + console.log(e.toString()); + } + }); + <\/script> + `; + + window.addEventListener('message', (event) => { + if (event.source === sandbox.contentWindow && event.data.type === 'CONSOLE_LOG') { + dispatch(event.data); + } + }); + + /** + * ---------------------------------------------------------------- + * Application Entry Point + * ---------------------------------------------------------------- + */ + + const hash = window.location.hash.substring(1); + if (hash) { + try { + const decodedCode = decodeURIComponent(atob(hash)); + model = { ...init, code: decodedCode }; + } catch (error) { + console.error('Failed to decode the URL hash:', error); + model = init; + } + } else { + model = init; + } + + render(model); + + sandbox.onload = () => { + dispatch({ type: 'SANDBOX_READY' }); + }; + + sandbox.srcdoc = sandboxSrc; + + </script> +</body> +</html> \ No newline at end of file diff --git a/html/playground/little-regex.html b/html/playground/little-regex.html new file mode 100644 index 0000000..c09139f --- /dev/null +++ b/html/playground/little-regex.html @@ -0,0 +1,728 @@ +<html lang="en"><head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>JavaScript Playground</title> + <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles."> + <style> + body { + display: flex; + flex-direction: column; + align-items: center; + background-color: #ddd; + padding: 10px; + height: 100vh; + margin: 0; + } + + textarea { + width: 100%; + height: 64%; + font-family: monospace; + background-color: #FFFEEC; + border: 2px solid #000; + scrollbar-width: none; + font-size: 1rem; + margin-bottom: 10px; + padding: 10px; + box-sizing: border-box; + resize: none; + border-bottom: 12px solid teal; + -webkit-user-modify: read-write-plaintext-only; + } + + textarea::-webkit-scrollbar { + display: none; + } + + textarea::selection { + background-color: #EFECA7; + } + + textarea:focus { + outline: none; + } + + #console { + width: 100%; + height: 22%; + background-color: #000; + color: #0fc; + font-family: monospace; + font-size: 1rem; + overflow-y: auto; + padding: 10px; + box-sizing: border-box; + white-space: pre-wrap; + } + + .button-container { + width: 100%; + display: flex; + justify-content: flex-end; + margin-bottom: 10px; + } + + button { + padding: 10px 20px; + font-size: 1rem; + margin-left: 10px; + cursor: pointer; + border: none; + transition: background-color 0.3s ease; + } + + button:hover, button:focus { + outline: none; + } + + button.run { + background-color: teal; + color: #FFFFFF; + } + </style> +</head> +<body> + + <div class="playground" id="playground"> + <div class="seesaw" id="seesaw"></div> + <div class="slide" id="slide"></div> + </div> + + <div class="button-container"> + <button onclick="clearEverything()">Clear</button> + <button onclick="downloadCodeAndEditor()">Download</button> + <button onclick="shareCode()">Share</button> + <button onclick="evaluateCode()" class="run">Run Code</button> + </div> + <textarea id="codeInput">// Inspired by <https://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html> +const matchHere = (pattern, text) => { + if (pattern.length === 0) return true; + + // If pattern ends with $, match end of string + if (pattern[0] === ' + <div id="console"></div> + + <script> + function evaluateCode() { + const code = document.getElementById('codeInput').value; + const consoleDiv = document.getElementById('console'); + consoleDiv.innerHTML = ''; + + // Custom console.log function to output to the console div + const originalConsoleLog = console.log; + console.log = function(...args) { + args.forEach(arg => { + const output = document.createElement('div'); + output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg; + consoleDiv.appendChild(output); + }); + originalConsoleLog.apply(console, args); + }; + + try { + eval(code); + } catch (error) { + const errorOutput = document.createElement('div'); + errorOutput.textContent = error; + errorOutput.style.color = 'red'; + consoleDiv.appendChild(errorOutput); + } + + // Restore browser's console.log + console.log = originalConsoleLog; + } + + function downloadCodeAndEditor() { + const codeInput = document.getElementById('codeInput').value; + const htmlContent = document.documentElement.outerHTML.replace( + /<textarea id="codeInput"[^>]*>.*<\/textarea>/, + `<textarea id="codeInput">${codeInput}</textarea>` + ); + + const blob = new Blob([htmlContent], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'code_editor.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + function shareCode() { + const code = document.getElementById('codeInput').value; + const encodedCode = btoa(encodeURIComponent(code)); + window.location.hash = encodedCode; + window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href); + } + + function clearEverything() { + if (!confirm('Are you sure you want to reset the playground?')) { + return; + } else { + window.location.hash = ''; + window.location.reload(); + } + } + + function loadCodeFromHash() { + const hash = window.location.hash.substring(1); + if (hash) { + try { + const decodedCode = decodeURIComponent(atob(hash)); + document.getElementById('codeInput').value = decodedCode; + } catch (error) { + console.error('Failed to decode the URL hash:', error); + } + } + } + + function help() { + const helpText = ` + Welcome to the JavaScript Playground! Here are some tips to get you started: + + 1. Enter your JavaScript code in the textarea. + 2. Click the "Run Code" button to execute your code. + 3. The console output will be displayed below the textarea. + 4. Click the "Clear" button to reset the playground. + 5. Click the "Download" button to save your code and editor as an HTML file. + 6. Click the "Share" button to generate a URL to share your code with others. + 7. You can also press "Cmd + Enter" to run your code. + 8. There's an empty div above the buttons with the id "playground" + 9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()" + 10. You can use the "clear()" function to clear the content's of the console. + + Go nuts! Share scrappy fiddles! + `; + console.log(helpText); + } + + function clear() { + document.getElementById('console').innerHTML = ''; + } + + function mountHelp() { + console.log(` + The mount function is used to mount stuff to the playground div. + It takes a function as an argument, which in turn receives the playground div as an argument. + Before mounting, it clears the playground div. + Here's an example of how to use the mount function: + + mount(playground => { + const h1 = document.createElement('h1'); + h1.textContent = 'Hell is empty and all the devils are here.'; + playground.appendChild(h1); + }); + + This will add an h1 element to the playground div. + `); + } + + function mount(mountFunction) { + const playground = document.getElementById('playground'); + if (!playground) { + console.error("Couldn't find a div with the id 'playground'! You may need to reload the page."); + return; + } + + if (playground.innerHTML.trim() !== "") { + playground.innerHTML = ""; + } + mountFunction(playground); + } + + + document.getElementById('codeInput').addEventListener('keydown', function(event) { + if (event.metaKey && event.key === 'Enter') { + event.preventDefault(); + evaluateCode(); + } + }); + + window.onload = loadCodeFromHash; + </script> + + +</body></html> && pattern.length === 1) return text.length === 0; + + // If next char is '*', match zero or more occurrences of prev char + if (pattern[1] === '*') return matchStar(pattern[0], pattern.slice(2), text); + + // Match . or literal character + if (text.length !== 0 && (pattern[0] === '.' || pattern[0] === text[0])) { + return matchHere(pattern.slice(1), text.slice(1)); + } + + return false; +}; + +const matchStar = (prevChar, pattern, text) => { + // Try matching zero occurrences first + if (matchHere(pattern, text)) return true; + + // Then, match one or more occurrences of prevChar + while (text.length > 0 && (text[0] === prevChar || prevChar === '.')) { + text = text.slice(1); + if (matchHere(pattern, text)) return true; + } + + return false; +}; + +const match = (pattern, text) => { + // Handle ^ anchor at the beginning + if (pattern[0] === '^') { + return matchHere(pattern.slice(1), text); + } + + // Otherwise, check the entire string + for (let i = 0; i <= text.length; i++) { + if (matchHere(pattern, text.slice(i))) return true; + } + + return false; +}; + +const assertEqual = (actual, expected, testName) => + console.log(actual === expected ? `Passed: ${testName}` : `Failed: ${testName}. Expected ${expected} but got ${actual}`); + +assertEqual(match("ab*c", "ac"), true, "Star match 'ab*c' vs 'ac' (zero occurrences)"); +assertEqual(match("ab*c", "abbbc"), true, "Star match 'ab*c' vs 'abbbc' (multiple occurrences)"); +assertEqual(match("^ab.*c$", "abc"), true, "Complex match '^ab.*c + <div id="console"></div> + + <script> + function evaluateCode() { + const code = document.getElementById('codeInput').value; + const consoleDiv = document.getElementById('console'); + consoleDiv.innerHTML = ''; + + // Custom console.log function to output to the console div + const originalConsoleLog = console.log; + console.log = function(...args) { + args.forEach(arg => { + const output = document.createElement('div'); + output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg; + consoleDiv.appendChild(output); + }); + originalConsoleLog.apply(console, args); + }; + + try { + eval(code); + } catch (error) { + const errorOutput = document.createElement('div'); + errorOutput.textContent = error; + errorOutput.style.color = 'red'; + consoleDiv.appendChild(errorOutput); + } + + // Restore browser's console.log + console.log = originalConsoleLog; + } + + function downloadCodeAndEditor() { + const codeInput = document.getElementById('codeInput').value; + const htmlContent = document.documentElement.outerHTML.replace( + /<textarea id="codeInput"[^>]*>.*<\/textarea>/, + `<textarea id="codeInput">${codeInput}</textarea>` + ); + + const blob = new Blob([htmlContent], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'code_editor.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + function shareCode() { + const code = document.getElementById('codeInput').value; + const encodedCode = btoa(encodeURIComponent(code)); + window.location.hash = encodedCode; + window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href); + } + + function clearEverything() { + if (!confirm('Are you sure you want to reset the playground?')) { + return; + } else { + window.location.hash = ''; + window.location.reload(); + } + } + + function loadCodeFromHash() { + const hash = window.location.hash.substring(1); + if (hash) { + try { + const decodedCode = decodeURIComponent(atob(hash)); + document.getElementById('codeInput').value = decodedCode; + } catch (error) { + console.error('Failed to decode the URL hash:', error); + } + } + } + + function help() { + const helpText = ` + Welcome to the JavaScript Playground! Here are some tips to get you started: + + 1. Enter your JavaScript code in the textarea. + 2. Click the "Run Code" button to execute your code. + 3. The console output will be displayed below the textarea. + 4. Click the "Clear" button to reset the playground. + 5. Click the "Download" button to save your code and editor as an HTML file. + 6. Click the "Share" button to generate a URL to share your code with others. + 7. You can also press "Cmd + Enter" to run your code. + 8. There's an empty div above the buttons with the id "playground" + 9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()" + 10. You can use the "clear()" function to clear the content's of the console. + + Go nuts! Share scrappy fiddles! + `; + console.log(helpText); + } + + function clear() { + document.getElementById('console').innerHTML = ''; + } + + function mountHelp() { + console.log(` + The mount function is used to mount stuff to the playground div. + It takes a function as an argument, which in turn receives the playground div as an argument. + Before mounting, it clears the playground div. + Here's an example of how to use the mount function: + + mount(playground => { + const h1 = document.createElement('h1'); + h1.textContent = 'Hell is empty and all the devils are here.'; + playground.appendChild(h1); + }); + + This will add an h1 element to the playground div. + `); + } + + function mount(mountFunction) { + const playground = document.getElementById('playground'); + if (!playground) { + console.error("Couldn't find a div with the id 'playground'! You may need to reload the page."); + return; + } + + if (playground.innerHTML.trim() !== "") { + playground.innerHTML = ""; + } + mountFunction(playground); + } + + + document.getElementById('codeInput').addEventListener('keydown', function(event) { + if (event.metaKey && event.key === 'Enter') { + event.preventDefault(); + evaluateCode(); + } + }); + + window.onload = loadCodeFromHash; + </script> + + +</body></html> vs 'abc'"); +assertEqual(match("^ab.*c$", "abcd"), false, "Complex mismatch '^ab.*c + <div id="console"></div> + + <script> + function evaluateCode() { + const code = document.getElementById('codeInput').value; + const consoleDiv = document.getElementById('console'); + consoleDiv.innerHTML = ''; + + // Custom console.log function to output to the console div + const originalConsoleLog = console.log; + console.log = function(...args) { + args.forEach(arg => { + const output = document.createElement('div'); + output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg; + consoleDiv.appendChild(output); + }); + originalConsoleLog.apply(console, args); + }; + + try { + eval(code); + } catch (error) { + const errorOutput = document.createElement('div'); + errorOutput.textContent = error; + errorOutput.style.color = 'red'; + consoleDiv.appendChild(errorOutput); + } + + // Restore browser's console.log + console.log = originalConsoleLog; + } + + function downloadCodeAndEditor() { + const codeInput = document.getElementById('codeInput').value; + const htmlContent = document.documentElement.outerHTML.replace( + /<textarea id="codeInput"[^>]*>.*<\/textarea>/, + `<textarea id="codeInput">${codeInput}</textarea>` + ); + + const blob = new Blob([htmlContent], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'code_editor.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + function shareCode() { + const code = document.getElementById('codeInput').value; + const encodedCode = btoa(encodeURIComponent(code)); + window.location.hash = encodedCode; + window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href); + } + + function clearEverything() { + if (!confirm('Are you sure you want to reset the playground?')) { + return; + } else { + window.location.hash = ''; + window.location.reload(); + } + } + + function loadCodeFromHash() { + const hash = window.location.hash.substring(1); + if (hash) { + try { + const decodedCode = decodeURIComponent(atob(hash)); + document.getElementById('codeInput').value = decodedCode; + } catch (error) { + console.error('Failed to decode the URL hash:', error); + } + } + } + + function help() { + const helpText = ` + Welcome to the JavaScript Playground! Here are some tips to get you started: + + 1. Enter your JavaScript code in the textarea. + 2. Click the "Run Code" button to execute your code. + 3. The console output will be displayed below the textarea. + 4. Click the "Clear" button to reset the playground. + 5. Click the "Download" button to save your code and editor as an HTML file. + 6. Click the "Share" button to generate a URL to share your code with others. + 7. You can also press "Cmd + Enter" to run your code. + 8. There's an empty div above the buttons with the id "playground" + 9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()" + 10. You can use the "clear()" function to clear the content's of the console. + + Go nuts! Share scrappy fiddles! + `; + console.log(helpText); + } + + function clear() { + document.getElementById('console').innerHTML = ''; + } + + function mountHelp() { + console.log(` + The mount function is used to mount stuff to the playground div. + It takes a function as an argument, which in turn receives the playground div as an argument. + Before mounting, it clears the playground div. + Here's an example of how to use the mount function: + + mount(playground => { + const h1 = document.createElement('h1'); + h1.textContent = 'Hell is empty and all the devils are here.'; + playground.appendChild(h1); + }); + + This will add an h1 element to the playground div. + `); + } + + function mount(mountFunction) { + const playground = document.getElementById('playground'); + if (!playground) { + console.error("Couldn't find a div with the id 'playground'! You may need to reload the page."); + return; + } + + if (playground.innerHTML.trim() !== "") { + playground.innerHTML = ""; + } + mountFunction(playground); + } + + + document.getElementById('codeInput').addEventListener('keydown', function(event) { + if (event.metaKey && event.key === 'Enter') { + event.preventDefault(); + evaluateCode(); + } + }); + + window.onload = loadCodeFromHash; + </script> + + +</body></html> vs 'abcd'");</textarea> + <div id="console"></div> + + <script> + function evaluateCode() { + const code = document.getElementById('codeInput').value; + const consoleDiv = document.getElementById('console'); + consoleDiv.innerHTML = ''; + + // Custom console.log function to output to the console div + const originalConsoleLog = console.log; + console.log = function(...args) { + args.forEach(arg => { + const output = document.createElement('div'); + output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg; + consoleDiv.appendChild(output); + }); + originalConsoleLog.apply(console, args); + }; + + try { + eval(code); + } catch (error) { + const errorOutput = document.createElement('div'); + errorOutput.textContent = error; + errorOutput.style.color = 'red'; + consoleDiv.appendChild(errorOutput); + } + + // Restore browser's console.log + console.log = originalConsoleLog; + } + + function downloadCodeAndEditor() { + const codeInput = document.getElementById('codeInput').value; + const htmlContent = document.documentElement.outerHTML.replace( + /<textarea id="codeInput"[^>]*>.*<\/textarea>/, + `<textarea id="codeInput">${codeInput}</textarea>` + ); + + const blob = new Blob([htmlContent], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'code_editor.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + function shareCode() { + const code = document.getElementById('codeInput').value; + const encodedCode = btoa(encodeURIComponent(code)); + window.location.hash = encodedCode; + window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href); + } + + function clearEverything() { + if (!confirm('Are you sure you want to reset the playground?')) { + return; + } else { + window.location.hash = ''; + window.location.reload(); + } + } + + function loadCodeFromHash() { + const hash = window.location.hash.substring(1); + if (hash) { + try { + const decodedCode = decodeURIComponent(atob(hash)); + document.getElementById('codeInput').value = decodedCode; + } catch (error) { + console.error('Failed to decode the URL hash:', error); + } + } + } + + function help() { + const helpText = ` + Welcome to the JavaScript Playground! Here are some tips to get you started: + + 1. Enter your JavaScript code in the textarea. + 2. Click the "Run Code" button to execute your code. + 3. The console output will be displayed below the textarea. + 4. Click the "Clear" button to reset the playground. + 5. Click the "Download" button to save your code and editor as an HTML file. + 6. Click the "Share" button to generate a URL to share your code with others. + 7. You can also press "Cmd + Enter" to run your code. + 8. There's an empty div above the buttons with the id "playground" + 9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()" + 10. You can use the "clear()" function to clear the content's of the console. + + Go nuts! Share scrappy fiddles! + `; + console.log(helpText); + } + + function clear() { + document.getElementById('console').innerHTML = ''; + } + + function mountHelp() { + console.log(` + The mount function is used to mount stuff to the playground div. + It takes a function as an argument, which in turn receives the playground div as an argument. + Before mounting, it clears the playground div. + Here's an example of how to use the mount function: + + mount(playground => { + const h1 = document.createElement('h1'); + h1.textContent = 'Hell is empty and all the devils are here.'; + playground.appendChild(h1); + }); + + This will add an h1 element to the playground div. + `); + } + + function mount(mountFunction) { + const playground = document.getElementById('playground'); + if (!playground) { + console.error("Couldn't find a div with the id 'playground'! You may need to reload the page."); + return; + } + + if (playground.innerHTML.trim() !== "") { + playground.innerHTML = ""; + } + mountFunction(playground); + } + + + document.getElementById('codeInput').addEventListener('keydown', function(event) { + if (event.metaKey && event.key === 'Enter') { + event.preventDefault(); + evaluateCode(); + } + }); + + window.onload = loadCodeFromHash; + </script> + + +</body></html> \ No newline at end of file diff --git a/html/playground/regex.html b/html/playground/regex.html new file mode 100644 index 0000000..41f50e9 --- /dev/null +++ b/html/playground/regex.html @@ -0,0 +1,477 @@ +<html lang="en"><head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>JavaScript Playground</title> + <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles."> + <style> + body { + display: flex; + flex-direction: column; + align-items: center; + background-color: #ddd; + padding: 10px; + height: 100vh; + margin: 0; + } + + textarea { + width: 100%; + height: 64%; + font-family: monospace; + background-color: #FFFEEC; + border: 2px solid #000; + scrollbar-width: none; + font-size: 1rem; + margin-bottom: 10px; + padding: 10px; + box-sizing: border-box; + resize: none; + border-bottom: 12px solid teal; + -webkit-user-modify: read-write-plaintext-only; + } + + textarea::-webkit-scrollbar { + display: none; + } + + textarea::selection { + background-color: #EFECA7; + } + + textarea:focus { + outline: none; + } + + #console { + width: 100%; + height: 22%; + background-color: #000; + color: #0fc; + font-family: monospace; + font-size: 1rem; + overflow-y: auto; + padding: 10px; + box-sizing: border-box; + white-space: pre-wrap; + } + + .button-container { + width: 100%; + display: flex; + justify-content: flex-end; + margin-bottom: 10px; + } + + button { + padding: 10px 20px; + font-size: 1rem; + margin-left: 10px; + cursor: pointer; + border: none; + transition: background-color 0.3s ease; + } + + button:hover, button:focus { + outline: none; + } + + button.run { + background-color: teal; + color: #FFFFFF; + } + </style> +</head> +<body> + + <div class="playground" id="playground"> + <div class="seesaw" id="seesaw"></div> + <div class="slide" id="slide"></div> + </div> + + <div class="button-container"> + <button onclick="clearEverything()">Clear</button> + <button onclick="downloadCodeAndEditor()">Download</button> + <button onclick="shareCode()">Share</button> + <button onclick="evaluateCode()" class="run">Run Code</button> + </div> + <textarea id="codeInput">const tokenize = (pattern) => { + const tokens = []; + let i = 0; + + while (i < pattern.length) { + const char = pattern[i]; + + if (char === '.' || char === '*' || char === '(' || char === ')' || char === '|') { + tokens.push({ + type: char, + value: char + }); + } else if (char === '\\') { // Handle escaped characters + i++; + tokens.push({ + type: 'literal', + value: pattern[i] + }); + } else if (char === '[') { // Handle character classes + let charClass = ''; + i++; + while (pattern[i] !== ']' && i < pattern.length) { + charClass += pattern[i]; + i++; + } + tokens.push({ + type: 'charClass', + value: charClass + }); + } else { + tokens.push({ + type: 'literal', + value: char + }); + } + i++; + } + + return tokens; +}; + + + + +const parse = (tokens) => { + let i = 0; + + const parseSequenceExpression = () => { + const node = { + type: 'sequence', + elements: [] + }; + + while (i < tokens.length) { + const token = tokens[i]; + + if (token.type === 'literal') { + node.elements.push({ + type: 'literal', + value: token.value + }); + } else if (token.type === '*') { + const lastElement = node.elements.pop(); + node.elements.push({ + type: 'star', + element: lastElement + }); + } else if (token.type === '.') { + node.elements.push({ + type: 'dot' + }); + } else if (token.type === 'charClass') { + node.elements.push({ + type: 'charClass', + value: token.value + }); + } else if (token.type === '|') { + i++; + const right = parseSequenceExpression(); + return { + type: 'alternation', + left: node, + right + }; + } else if (token.type === '(') { + i++; + node.elements.push(parseSequenceExpression()); + } else if (token.type === ')') { + break; // End of a grouping + } + + i++; + } + + return node; + }; + + return parseSequenceExpression(); +}; + + + +const evaluateMatch = (node) => (input) => { + if (node.type === 'literal') { + return input[0] === node.value ? input.slice(1) : null; + } + if (node.type === 'dot') { + return input.length > 0 ? input.slice(1) : null; + } + if (node.type === 'star') { + let remainder = input; + while (remainder !== null) { + const next = evaluateMatch(node.element)(remainder); + if (next === null) { + break; + } + remainder = next; + } + return remainder; + } + if (node.type === 'charClass') { + return node.value.includes(input[0]) ? input.slice(1) : null; + } + if (node.type === 'alternation') { + const remainderLeft = evaluateMatch(node.left)(input); + const remainderRight = evaluateMatch(node.right)(input); + return remainderLeft !== null ? remainderLeft : remainderRight; + } + if (node.type === 'sequence') { + let remainder = input; + for (const element of node.elements) { + remainder = evaluateMatch(element)(remainder); + if (remainder === null) { + return null; + } + } + return remainder; + } +}; + + + + +const assertEqual = (expected, actual, message) => { + if (expected !== actual) { + console.error(`FAIL: ${message}`); + } else { + console.log(`PASS: ${message}`); + } +}; + + + +const runTests = () => { + const tests = [{ + pattern: "a.b*c", + input: "abbbc", + expected: true + }, + { + pattern: "a.b*c", + input: "abc", + expected: true + }, + { + pattern: "a.b*c", + input: "ac", + expected: true + }, + { + pattern: "a.b*c", + input: "abbc", + expected: true + }, + { + pattern: "a.b*c", + input: "axbc", + expected: false + }, + + // Character class tests + { + pattern: "[abc]x", + input: "bx", + expected: true + }, + { + pattern: "[abc]x", + input: "dx", + expected: false + }, + + // Grouping and alternation tests + { + pattern: "(a|b)c", + input: "ac", + expected: true + }, + { + pattern: "(a|b)c", + input: "bc", + expected: true + }, + { + pattern: "(a|b)c", + input: "cc", + expected: false + }, + + // Escaped characters tests + { + pattern: "a\\.b", + input: "a.b", + expected: true + }, + { + pattern: "a\\.b", + input: "a-b", + expected: false + }, + { + pattern: "a\\*b", + input: "a*b", + expected: true + } + ]; + + tests.forEach(({ pattern, input, expected }, index) => { + const tokens = tokenize(pattern); + const ast = parse(tokens); + const result = evaluateMatch(ast)(input) !== null; + assertEqual(expected, result, `Test ${index + 1}`); + }); +}; + +runTests();</textarea> + <div id="console"><div>PASS: Test 1</div><div>PASS: Test 2</div><div>PASS: Test 4</div><div>PASS: Test 6</div><div>PASS: Test 7</div><div>PASS: Test 8</div><div>PASS: Test 9</div><div>PASS: Test 10</div><div>PASS: Test 11</div><div>PASS: Test 12</div><div>PASS: Test 13</div></div> + + <script> + function evaluateCode() { + const code = document.getElementById('codeInput').value; + const consoleDiv = document.getElementById('console'); + consoleDiv.innerHTML = ''; + + // Custom console.log function to output to the console div + const originalConsoleLog = console.log; + console.log = function(...args) { + args.forEach(arg => { + const output = document.createElement('div'); + output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg; + consoleDiv.appendChild(output); + }); + originalConsoleLog.apply(console, args); + }; + + try { + eval(code); + } catch (error) { + const errorOutput = document.createElement('div'); + errorOutput.textContent = error; + errorOutput.style.color = 'red'; + consoleDiv.appendChild(errorOutput); + } + + // Restore browser's console.log + console.log = originalConsoleLog; + } + + function downloadCodeAndEditor() { + const codeInput = document.getElementById('codeInput').value; + const htmlContent = document.documentElement.outerHTML.replace( + /<textarea id="codeInput"[^>]*>.*<\/textarea>/, + `<textarea id="codeInput">${codeInput}</textarea>` + ); + + const blob = new Blob([htmlContent], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'code_editor.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + function shareCode() { + const code = document.getElementById('codeInput').value; + const encodedCode = btoa(encodeURIComponent(code)); + window.location.hash = encodedCode; + window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href); + } + + function clearEverything() { + if (!confirm('Are you sure you want to reset the playground?')) { + return; + } else { + window.location.hash = ''; + window.location.reload(); + } + } + + function loadCodeFromHash() { + const hash = window.location.hash.substring(1); + if (hash) { + try { + const decodedCode = decodeURIComponent(atob(hash)); + document.getElementById('codeInput').value = decodedCode; + } catch (error) { + console.error('Failed to decode the URL hash:', error); + } + } + } + + function help() { + const helpText = ` + Welcome to the JavaScript Playground! Here are some tips to get you started: + + 1. Enter your JavaScript code in the textarea. + 2. Click the "Run Code" button to execute your code. + 3. The console output will be displayed below the textarea. + 4. Click the "Clear" button to reset the playground. + 5. Click the "Download" button to save your code and editor as an HTML file. + 6. Click the "Share" button to generate a URL to share your code with others. + 7. You can also press "Cmd + Enter" to run your code. + 8. There's an empty div above the buttons with the id "playground" + 9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()" + 10. You can use the "clear()" function to clear the content's of the console. + + Go nuts! Share scrappy fiddles! + `; + console.log(helpText); + } + + function clear() { + document.getElementById('console').innerHTML = ''; + } + + function mountHelp() { + console.log(` + The mount function is used to mount stuff to the playground div. + It takes a function as an argument, which in turn receives the playground div as an argument. + Before mounting, it clears the playground div. + Here's an example of how to use the mount function: + + mount(playground => { + const h1 = document.createElement('h1'); + h1.textContent = 'Hell is empty and all the devils are here.'; + playground.appendChild(h1); + }); + + This will add an h1 element to the playground div. + `); + } + + function mount(mountFunction) { + const playground = document.getElementById('playground'); + if (!playground) { + console.error("Couldn't find a div with the id 'playground'! You may need to reload the page."); + return; + } + + if (playground.innerHTML.trim() !== "") { + playground.innerHTML = ""; + } + mountFunction(playground); + } + + + document.getElementById('codeInput').addEventListener('keydown', function(event) { + if (event.metaKey && event.key === 'Enter') { + event.preventDefault(); + evaluateCode(); + } + }); + + window.onload = loadCodeFromHash; + </script> + + +</body></html> \ No newline at end of file diff --git a/html/playground/scheme.html b/html/playground/scheme.html new file mode 100644 index 0000000..b8ecd6f --- /dev/null +++ b/html/playground/scheme.html @@ -0,0 +1,533 @@ +<html lang="en"><head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>JavaScript Playground</title> + <meta name="description" content="A JavaScript jungle-gym for doing experiments and sharing scrappy fiddles."> + <style> + body { + display: flex; + flex-direction: column; + align-items: center; + background-color: #ddd; + padding: 10px; + height: 100vh; + margin: 0; + } + + textarea { + width: 100%; + height: 64%; + font-family: monospace; + background-color: #FFFEEC; + border: 2px solid #000; + scrollbar-width: none; + font-size: 1rem; + margin-bottom: 10px; + padding: 10px; + box-sizing: border-box; + resize: none; + border-bottom: 12px solid teal; + -webkit-user-modify: read-write-plaintext-only; + } + + textarea::-webkit-scrollbar { + display: none; + } + + textarea::selection { + background-color: #EFECA7; + } + + textarea:focus { + outline: none; + } + + #console { + width: 100%; + height: 22%; + background-color: #000; + color: #0fc; + font-family: monospace; + font-size: 1rem; + overflow-y: auto; + padding: 10px; + box-sizing: border-box; + white-space: pre-wrap; + } + + .button-container { + width: 100%; + display: flex; + justify-content: flex-end; + margin-bottom: 10px; + } + + button { + padding: 10px 20px; + font-size: 1rem; + margin-left: 10px; + cursor: pointer; + border: none; + transition: background-color 0.3s ease; + } + + button:hover, button:focus { + outline: none; + } + + button.run { + background-color: teal; + color: #FFFFFF; + } + </style> +</head> +<body> + + <div class="playground" id="playground"> + <div class="seesaw" id="seesaw"></div> + <div class="slide" id="slide"></div> + </div> + + <div class="button-container"> + <button onclick="clearEverything()">Clear</button> + <button onclick="downloadCodeAndEditor()">Download</button> + <button onclick="shareCode()">Share</button> + <button onclick="evaluateCode()" class="run">Run Code</button> + </div> + <textarea id="codeInput">function tokenizeScheme(input) { + const tokens = []; + let current = 0; + + const isWhitespace = (char) => /\s/.test(char); + const isDigit = (char) => /[0-9]/.test(char); + const isParen = (char) => char === '(' || char === ')'; + // Symbols can include letters, numbers, and some punctuation like - _ ! ? + const isSymbolChar = (char) => /[a-zA-Z0-9\+\-\*\/\=\?\!\_]/.test(char); + + while (current < input.length) { + let char = input[current]; + + if (isWhitespace(char)) { + current++; + continue; + } + + if (isParen(char)) { + tokens.push({ type: 'paren', value: char }); + current++; + continue; + } + + if (isDigit(char) || (char === '-' && isDigit(input[current + 1]))) { + let number = ''; + while (isDigit(char) || char === '-') { + number += char; + char = input[++current]; + } + tokens.push({ type: 'number', value: number }); + continue; + } + + // Handle symbols, including letters, numbers, punctuation + if (isSymbolChar(char)) { + let symbol = ''; + while (isSymbolChar(char)) { + symbol += char; + char = input[++current]; + } + tokens.push({ type: 'symbol', value: symbol }); + continue; + } + + throw new Error(`Unexpected character: ${char}`); + } + + return tokens; +} + + +function parseScheme(tokens) { + let current = 0; + + function walk() { + let token = tokens[current]; + + if (token.type === 'number') { + current++; + return { type: 'NumberLiteral', value: Number(token.value) }; + } + + if (token.type === 'symbol') { + current++; + return { type: 'Symbol', value: token.value }; + } + + if (token.type === 'paren' && token.value === '(') { + current++; + const node = { type: 'List', value: [] }; + + while (!(tokens[current].type === 'paren' && tokens[current].value === ')')) { + node.value.push(walk()); + } + + current++; // Skip closing ')' + return node; + } + + throw new Error(`Unexpected token: ${token.type}`); + } + + return walk(); +} + +const globalEnv = { + '+': (...args) => args.reduce((acc, curr) => acc + curr), + '-': (...args) => args.reduce((acc, curr) => acc - curr), + '*': (...args) => args.reduce((acc, curr) => acc * curr), + '/': (a, b) => a / b, // Only two arguments for division + 'eq?': (...args) => args.every((val, i, arr) => val === arr[0]), + 'car': (list) => { + if (list.type !== 'List' || list.value.length === 0) { + throw new Error('car expects a non-empty list'); + } + return list.value[0]; + }, + 'cdr': (list) => { + if (list.type !== 'List' || list.value.length === 0) { + throw new Error('cdr expects a non-empty list'); + } + return { type: 'List', value: list.value.slice(1) }; + }, + 'cons': (a, b) => { + if (b.type !== 'List') { + throw new Error('cons expects second argument to be a list'); + } + return { type: 'List', value: [a].concat(b.value) }; + }, + 'null?': (list) => list.type === 'List' && list.value.length === 0, + 'zero?': (n) => n === 0, + 'atom?': (x) => typeof x !== 'object' || x === null, + 'number?': (x) => typeof x === 'number', + 'add1': (n) => n + 1, + 'sub1': (n) => n - 1, + 'quote': (x) => x, // Simply return the quoted expression + 'and': (...args) => args.every(Boolean), + 'or': (...args) => args.some(Boolean), + 'true': true, + 'false': false +}; + + + + +function evaluate(node, env = globalEnv) { + if (node.type === 'NumberLiteral') { + return node.value; + } + + if (node.type === 'Symbol') { + if (env[node.value] !== undefined) { + return env[node.value]; + } + throw new Error(`Undefined symbol: ${node.value}`); + } + + if (node.type === 'List') { + const [first, ...rest] = node.value; + + // Is the first element a symbol, like an operator or function name? + if (first.type === 'Symbol') { + const operator = first.value; + + // Special case for define + if (operator === 'define') { + const [symbol, expr] = rest; + env[symbol.value] = evaluate(expr, env); + return; + } + + // Special case for lambda + if (operator === 'lambda') { + const [params, body] = rest; + + // Create a closure to return + return function (...args) { + const lambdaEnv = { ...env }; + + // Bind each argument to the corresponding parameter... + params.value.forEach((param, i) => { + lambdaEnv[param.value] = args[i]; + }); + + // ...and then evaluate the body with the environment + return evaluate(body, lambdaEnv); + }; + } + + // Special case for if + if (operator === 'if') { + const [test, consequent, alternate] = rest; + const condition = evaluate(test, env); + return condition ? evaluate(consequent, env) : evaluate(alternate, env); + } + + // Special case for quote + if (operator === 'quote') { + return rest[0]; // Return the quoted expression without evaluating it + } + + // Special case for cond + if (operator === 'cond') { + for (let clause of rest) { + const [test, expr] = clause.value; + if (evaluate(test, env)) { + return evaluate(expr, env); + } + } + return null; // No matching condition + } + + // Special case for letrec (recursive let) + if (operator === 'letrec') { + const [bindings, body] = rest; + const letEnv = { ...env }; + + // Loop through bindings and evaluate each + bindings.value.forEach(binding => { + const [name, expr] = binding.value; + letEnv[name.value] = evaluate(expr, letEnv); + }); + + return evaluate(body, letEnv); + } + } + + // Evaluate the first element + const func = evaluate(first, env); + + if (typeof func !== 'function') { + throw new Error(`Expected a function but got: ${func}`); + } + + const args = rest.map(arg => evaluate(arg, env)); + return func(...args); + } + + throw new Error(`Unexpected node type: ${node.type}`); +} + + + +function evalScheme(input) { + const tokens = tokenizeScheme(input); + const ast = parseScheme(tokens); + return evaluate(ast); +} + + + + + + + +function mountRepl(playground) { + // Create a REPL container + const replContainer = document.createElement('div'); + replContainer.style.display = 'flex'; + replContainer.style.flexDirection = 'column'; + replContainer.style.width = '100%'; + + // Create an input field for the Scheme expressions + const input = document.createElement('textarea'); + input.placeholder = "Scheme here..."; + input.style.width = '100%'; + input.style.height = '100px'; + input.style.marginBottom = '10px'; + input.style.fontFamily = 'monospace'; + + // Create a button to evaluate the expression + const evalButton = document.createElement('button'); + evalButton.textContent = 'Evaluate'; + + // Create a container to display the results + const output = document.createElement('pre'); + output.style.width = '100%'; + output.style.height = '200px'; + output.style.overflowY = 'auto'; + output.style.backgroundColor = '#f0f0f0'; + output.style.padding = '10px'; + output.style.fontFamily = 'monospace'; + + // Add the input, button, and output to the REPL container + replContainer.appendChild(input); + replContainer.appendChild(evalButton); + replContainer.appendChild(output); + + // Add the REPL container to the playground div + playground.appendChild(replContainer); + + evalButton.addEventListener('click', () => { + const expression = input.value.trim(); + if (expression) { + try { + // Evaluate the expression + const result = evalScheme(expression); + // Append the result to the output area + output.textContent += `> ${expression}\n${result}\n\n`; + } catch (error) { + // Error if the expression is invalid + output.textContent += `> ${expression}\nError: ${error.message}\n\n`; + } + } + // Clear input after evaluation + input.value = ''; + }); +} + + +mount(mountRepl);</textarea> + <div id="console"></div> + + <script> + function evaluateCode() { + const code = document.getElementById('codeInput').value; + const consoleDiv = document.getElementById('console'); + consoleDiv.innerHTML = ''; + + // Custom console.log function to output to the console div + const originalConsoleLog = console.log; + console.log = function(...args) { + args.forEach(arg => { + const output = document.createElement('div'); + output.textContent = typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg; + consoleDiv.appendChild(output); + }); + originalConsoleLog.apply(console, args); + }; + + try { + eval(code); + } catch (error) { + const errorOutput = document.createElement('div'); + errorOutput.textContent = error; + errorOutput.style.color = 'red'; + consoleDiv.appendChild(errorOutput); + } + + // Restore browser's console.log + console.log = originalConsoleLog; + } + + function downloadCodeAndEditor() { + const codeInput = document.getElementById('codeInput').value; + const htmlContent = document.documentElement.outerHTML.replace( + /<textarea id="codeInput"[^>]*>.*<\/textarea>/, + `<textarea id="codeInput">${codeInput}</textarea>` + ); + + const blob = new Blob([htmlContent], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'code_editor.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + function shareCode() { + const code = document.getElementById('codeInput').value; + const encodedCode = btoa(encodeURIComponent(code)); + window.location.hash = encodedCode; + window.prompt("Copy the URL to share.\nBe warned! Very long URLs don't share wicked well, sometimes.", window.location.href); + } + + function clearEverything() { + if (!confirm('Are you sure you want to reset the playground?')) { + return; + } else { + window.location.hash = ''; + window.location.reload(); + } + } + + function loadCodeFromHash() { + const hash = window.location.hash.substring(1); + if (hash) { + try { + const decodedCode = decodeURIComponent(atob(hash)); + document.getElementById('codeInput').value = decodedCode; + } catch (error) { + console.error('Failed to decode the URL hash:', error); + } + } + } + + function help() { + const helpText = ` + Welcome to the JavaScript Playground! Here are some tips to get you started: + + 1. Enter your JavaScript code in the textarea. + 2. Click the "Run Code" button to execute your code. + 3. The console output will be displayed below the textarea. + 4. Click the "Clear" button to reset the playground. + 5. Click the "Download" button to save your code and editor as an HTML file. + 6. Click the "Share" button to generate a URL to share your code with others. + 7. You can also press "Cmd + Enter" to run your code. + 8. There's an empty div above the buttons with the id "playground" + 9. You can mount stuff to it using the "mount" function, for more info run "mountHelp()" + 10. You can use the "clear()" function to clear the content's of the console. + + Go nuts! Share scrappy fiddles! + `; + console.log(helpText); + } + + function clear() { + document.getElementById('console').innerHTML = ''; + } + + function mountHelp() { + console.log(` + The mount function is used to mount stuff to the playground div. + It takes a function as an argument, which in turn receives the playground div as an argument. + Before mounting, it clears the playground div. + Here's an example of how to use the mount function: + + mount(playground => { + const h1 = document.createElement('h1'); + h1.textContent = 'Hell is empty and all the devils are here.'; + playground.appendChild(h1); + }); + + This will add an h1 element to the playground div. + `); + } + + function mount(mountFunction) { + const playground = document.getElementById('playground'); + if (!playground) { + console.error("Couldn't find a div with the id 'playground'! You may need to reload the page."); + return; + } + + if (playground.innerHTML.trim() !== "") { + playground.innerHTML = ""; + } + mountFunction(playground); + } + + + document.getElementById('codeInput').addEventListener('keydown', function(event) { + if (event.metaKey && event.key === 'Enter') { + event.preventDefault(); + evaluateCode(); + } + }); + + window.onload = loadCodeFromHash; + </script> + + +</body></html> \ No newline at end of file |