about summary refs log blame commit diff stats
path: root/html/schemer/index.html
blob: 2220e57e5db018fa842627f75ababd9e318a2313 (plain) (tree)



























































                                                                               






                                                                   
                                                                                      

























                                                                                     
                                                                          















                                                                   
 





































                                                                                                  
                                                                    
                                                                            




























                                                                                 

          


 














                                                                   
                                                                                    


                                                 
                                              





                                                                
                                              


                                                    
                                                     

                                                         

                                                                                   


                                                                 

                                                                                 



                                                             
                                          




                                                                                                





























                                                                                              

                 
                                             












                                                                            

 




















                                                                                                 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Scheme Interpreter with PDF</title>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            height: 100%;
            font-family: Arial, sans-serif;
        }
        #pdf-container {
            width: 100%;
            height: 67%;
            overflow: hidden;
        }
        #repl-container {
            width: 100%;
            height: 33%;
            display: flex;
            flex-direction: column;
            border-top: 1px solid #ccc;
            padding: 10px;
        }
        textarea {
            flex: 1;
            width: 100%;
            font-family: monospace;
            font-size: 16px;
            padding: 10px;
        }
        button {
            margin-top: 10px;
            padding: 10px;
            font-size: 16px;
        }
        #scheme-output {
            font-family: monospace;
            background-color: #f0f0f0;
            padding: 10px;
            margin-top: 10px;
            overflow-wrap: break-word;
        }
    </style>
</head>
<body>

    <div id="pdf-container">
        <embed src="tls.pdf" type="application/pdf" width="100%" height="100%">
    </div>

    <div id="repl-container">
        <textarea id="scheme-input" placeholder="Scheme here..."></textarea>
        <div id="scheme-output"></div>
        <button onclick="evaluateScheme()">Run Code</button>
    </div>

    <script>
        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 to evaluate the input in the REPL
        function evaluateScheme() {
            const input = document.getElementById('scheme-input').value;
            let output;
            try {
                output = evalScheme(input);
            } catch (error) {
                output = `Error: ${error.message}`;
            }
            document.getElementById('scheme-output').innerText = JSON.stringify(output, null, 2);
        }
    </script>

</body>
</html>