<!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>