about summary refs log tree commit diff stats
path: root/html/schemer
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-09-08 08:45:14 -0400
committerelioat <elioat@tilde.institute>2024-09-08 08:45:14 -0400
commite0446f7567733cb4a488f7226635c80880e2dca5 (patch)
tree6dfe278176059555476115e155b9851e4d329ff9 /html/schemer
parente59ea3378b22261765f61ee8597bd2b7238cb98a (diff)
downloadtour-e0446f7567733cb4a488f7226635c80880e2dca5.tar.gz
*
Diffstat (limited to 'html/schemer')
-rw-r--r--html/schemer/index.html84
1 files changed, 78 insertions, 6 deletions
diff --git a/html/schemer/index.html b/html/schemer/index.html
index ebadf83..2220e57 100644
--- a/html/schemer/index.html
+++ b/html/schemer/index.html
@@ -58,7 +58,6 @@
     </div>
 
     <script>
-        // Scheme interpreter (unchanged)
         function tokenizeScheme(input) {
             const tokens = [];
             let current = 0;
@@ -66,6 +65,7 @@
             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) {
@@ -92,6 +92,7 @@
                     continue;
                 }
 
+                // Handle symbols, including letters, numbers, punctuation
                 if (isSymbolChar(char)) {
                     let symbol = '';
                     while (isSymbolChar(char)) {
@@ -108,6 +109,7 @@
             return tokens;
         }
 
+
         function parseScheme(tokens) {
             let current = 0;
 
@@ -146,14 +148,42 @@
             '+': (...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,
+            '/': (a, b) => a / b, // Only two arguments for division
             '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,
+            '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;
@@ -169,34 +199,74 @@
             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') {
@@ -210,6 +280,8 @@
             throw new Error(`Unexpected node type: ${node.type}`);
         }
 
+
+
         function evalScheme(input) {
             const tokens = tokenizeScheme(input);
             const ast = parseScheme(tokens);