Clear
Download
Share
Run Code
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 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);