about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--html/playground/scheme.html206
-rw-r--r--html/schemer/index.html84
2 files changed, 265 insertions, 25 deletions
diff --git a/html/playground/scheme.html b/html/playground/scheme.html
index fc2d5fa..dc6fb55 100644
--- a/html/playground/scheme.html
+++ b/html/playground/scheme.html
@@ -83,10 +83,7 @@
 </head>
 <body>
 
-    <div class="playground" id="playground">    
-        <div class="seesaw" id="seesaw"></div>
-        <div class="slide" id="slide"></div>
-    </div>
+    <div class="playground" id="playground"><div style="display: flex; flex-direction: column; width: 100%;"><textarea placeholder="Scheme here..." style="width: 100%; height: 100px; margin-bottom: 10px; font-family: monospace;"></textarea><button>Evaluate</button><pre style="width: 100%; height: 200px; overflow-y: auto; background-color: rgb(240, 240, 240); padding: 10px; font-family: monospace;"></pre></div></div>
 
     <div class="button-container">
         <button onclick="clearEverything()">Clear</button>
@@ -184,14 +181,42 @@ 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?
+    '/': (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;
@@ -242,12 +267,41 @@ function evaluate(node, env = globalEnv) {
                 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);
 
-        // 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}`);
         }
@@ -259,19 +313,19 @@ function evaluate(node, env = globalEnv) {
     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
@@ -328,8 +382,122 @@ function mountRepl(playground) {
 }
 
 
-mount(mountRepl);</textarea>
-    <div id="console"></div>
+mount(mountRepl);
+
+
+
+// Testing 'define' and arithmetic operations
+console.log(evalScheme("(define x 10)")); // Should define 'x' as 10
+console.log(evalScheme("(+ x 5)")); // Should return 15
+console.log(evalScheme("(- x 3)")); // Should return 7
+console.log(evalScheme("(* x 2)")); // Should return 20
+console.log(evalScheme("(/ x 2)")); // Should return 5
+
+// Testing 'eq?' for equality
+console.log(evalScheme("(eq? 1 1)")); // Should return true
+console.log(evalScheme("(eq? 1 2)")); // Should return false
+
+// Testing 'car', 'cdr', 'cons', 'null?'
+console.log(evalScheme("(car (quote (1 2 3)))")); // Should return 1
+console.log(evalScheme("(cdr (quote (1 2 3)))")); // Should return (2 3)
+console.log(evalScheme("(cons 1 (quote (2 3)))")); // Should return (1 2 3)
+console.log(evalScheme("(null? (quote ()))")); // Should return true
+console.log(evalScheme("(null? (quote (1 2 3)))")); // Should return false
+
+// Testing 'zero?' for checking zero
+console.log(evalScheme("(zero? 0)")); // Should return true
+console.log(evalScheme("(zero? 1)")); // Should return false
+
+// Testing 'atom?' for checking if an element is an atom
+console.log(evalScheme("(atom? 1)")); // Should return true
+console.log(evalScheme("(atom? (quote (1 2 3)))")); // Should return false
+
+// Testing 'number?' for checking if an element is a number
+console.log(evalScheme("(number? 42)")); // Should return true
+console.log(evalScheme("(number? (quote hello))")); // Should return false
+
+// Testing 'add1' and 'sub1'
+console.log(evalScheme("(add1 5)")); // Should return 6
+console.log(evalScheme("(sub1 5)")); // Should return 4
+
+// Testing 'quote' for returning unevaluated expressions
+console.log(evalScheme("(quote (1 2 3))")); // Should return (1 2 3)
+
+// Testing 'and' and 'or'
+console.log(evalScheme("(and true true)")); // Should return true
+console.log(evalScheme("(and true false)")); // Should return false
+console.log(evalScheme("(or false false)")); // Should return false
+console.log(evalScheme("(or true false)")); // Should return true
+
+// Testing 'lambda' with a function definition
+console.log(evalScheme("(define add1 (lambda (x) (+ x 1)))")); // Should define add1 function
+console.log(evalScheme("(add1 10)")); // Should return 11
+
+// Testing 'if' for conditional expressions
+console.log(evalScheme("(if (eq? 1 1) (quote yes) (quote no))")); // Should return 'yes
+console.log(evalScheme("(if (eq? 1 2) (quote yes) (quote no))")); // Should return 'no
+
+// Testing 'cond' for multiple conditions
+console.log(evalScheme("(cond ((eq? 1 2) (quote no)) ((eq? 2 2) (quote yes)))")); // Should return 'yes
+console.log(evalScheme("(cond ((eq? 1 2) (quote no)) ((eq? 3 2) (quote nope)))")); // Should return null (no matching condition)
+
+// Testing 'letrec' for recursive bindings
+console.log(evalScheme("(letrec ((factorial (lambda (n) (if (zero? n) 1 (* n (factorial (sub1 n))))))) (factorial 5))"));
+// Should return 120 (5!)</textarea>
+    <div id="console"><div></div><div>15</div><div>7</div><div>20</div><div>5</div><div>true</div><div>false</div><div>{
+  "type": "NumberLiteral",
+  "value": 1
+}</div><div>{
+  "type": "List",
+  "value": [
+    {
+      "type": "NumberLiteral",
+      "value": 2
+    },
+    {
+      "type": "NumberLiteral",
+      "value": 3
+    }
+  ]
+}</div><div>{
+  "type": "List",
+  "value": [
+    1,
+    {
+      "type": "NumberLiteral",
+      "value": 2
+    },
+    {
+      "type": "NumberLiteral",
+      "value": 3
+    }
+  ]
+}</div><div>true</div><div>false</div><div>true</div><div>false</div><div>true</div><div>false</div><div>true</div><div>false</div><div>6</div><div>4</div><div>{
+  "type": "List",
+  "value": [
+    {
+      "type": "NumberLiteral",
+      "value": 1
+    },
+    {
+      "type": "NumberLiteral",
+      "value": 2
+    },
+    {
+      "type": "NumberLiteral",
+      "value": 3
+    }
+  ]
+}</div><div>true</div><div>false</div><div>false</div><div>true</div><div></div><div>11</div><div>{
+  "type": "Symbol",
+  "value": "yes"
+}</div><div>{
+  "type": "Symbol",
+  "value": "no"
+}</div><div>{
+  "type": "Symbol",
+  "value": "yes"
+}</div><div>null</div><div>120</div></div>
 
     <script>
         function evaluateCode() {
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);