diff options
author | elioat <elioat@tilde.institute> | 2024-09-08 08:45:14 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2024-09-08 08:45:14 -0400 |
commit | e0446f7567733cb4a488f7226635c80880e2dca5 (patch) | |
tree | 6dfe278176059555476115e155b9851e4d329ff9 /html/schemer | |
parent | e59ea3378b22261765f61ee8597bd2b7238cb98a (diff) | |
download | tour-e0446f7567733cb4a488f7226635c80880e2dca5.tar.gz |
*
Diffstat (limited to 'html/schemer')
-rw-r--r-- | html/schemer/index.html | 84 |
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); |