about summary refs log tree commit diff stats
path: root/html/playground/scheme.html
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-09-07 20:31:40 -0400
committerelioat <elioat@tilde.institute>2024-09-07 20:31:40 -0400
commite59ea3378b22261765f61ee8597bd2b7238cb98a (patch)
tree57b4e9ca2c34d68f9c646d1c11ea9a9784970eb5 /html/playground/scheme.html
parentda6be82d03a94c46069b9a04cb6b61cfe355eaf4 (diff)
downloadtour-e59ea3378b22261765f61ee8597bd2b7238cb98a.tar.gz
*
Diffstat (limited to 'html/playground/scheme.html')
-rw-r--r--html/playground/scheme.html476
1 files changed, 476 insertions, 0 deletions
diff --git a/html/playground/scheme.html b/html/playground/scheme.html
new file mode 100644
index 0000000..fc2d5fa
--- /dev/null
+++ b/html/playground/scheme.html
@@ -0,0 +1,476 @@
+<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...right?
+    'eq?': (...args) => args.every((val, i, arr) => val === arr[0]),
+    'car': (list) => list[0],
+    'cdr': (list) => list.slice(1),
+    'cons': (a, b) => [a].concat(b),
+    'null?': (list) => list.length === 0,
+};
+
+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);
+            }
+        }
+
+        // Evaluate the first element
+        const func = evaluate(first, env);
+
+        // Check that `func` is a function. If it isn't, don't try to apply it
+        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);
+}
+
+/* 
+console.log(evalScheme("(define add1 (lambda (x) (+ x 1)))"));
+console.log(evalScheme("(add1 10)"));
+console.log(evalScheme("(eq? 1 1)"));
+console.log(evalScheme("(eq? 1 2)"));
+console.log(evalScheme("(+ 1 2 3 6)"));
+*/
+
+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