From e0446f7567733cb4a488f7226635c80880e2dca5 Mon Sep 17 00:00:00 2001 From: elioat Date: Sun, 8 Sep 2024 08:45:14 -0400 Subject: * --- html/playground/scheme.html | 206 ++++++++++++++++++++++++++++++++++++++++---- html/schemer/index.html | 84 ++++++++++++++++-- 2 files changed, 265 insertions(+), 25 deletions(-) (limited to 'html') 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 @@ -
-
-
-
+
@@ -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); -
+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!) +
15
7
20
5
true
false
{ + "type": "NumberLiteral", + "value": 1 +}
{ + "type": "List", + "value": [ + { + "type": "NumberLiteral", + "value": 2 + }, + { + "type": "NumberLiteral", + "value": 3 + } + ] +}
{ + "type": "List", + "value": [ + 1, + { + "type": "NumberLiteral", + "value": 2 + }, + { + "type": "NumberLiteral", + "value": 3 + } + ] +}
true
false
true
false
true
false
true
false
6
4
{ + "type": "List", + "value": [ + { + "type": "NumberLiteral", + "value": 1 + }, + { + "type": "NumberLiteral", + "value": 2 + }, + { + "type": "NumberLiteral", + "value": 3 + } + ] +}
true
false
false
true
11
{ + "type": "Symbol", + "value": "yes" +}
{ + "type": "Symbol", + "value": "no" +}
{ + "type": "Symbol", + "value": "yes" +}
null
120