From c49baacb2f3935861c9b680564aaa39a53b1d293 Mon Sep 17 00:00:00 2001 From: Keldan Chapman Date: Sat, 6 Jan 2024 09:08:32 +0100 Subject: [PATCH 1/2] Add test to ensure no regression on side effect bug --- tests/known-bugs/solution.txt | 1 + tests/known-bugs/test.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/known-bugs/solution.txt create mode 100644 tests/known-bugs/test.js diff --git a/tests/known-bugs/solution.txt b/tests/known-bugs/solution.txt new file mode 100644 index 0000000..4822a76 --- /dev/null +++ b/tests/known-bugs/solution.txt @@ -0,0 +1 @@ +foo = \ x j c . x c () diff --git a/tests/known-bugs/test.js b/tests/known-bugs/test.js new file mode 100644 index 0000000..8f7f7c1 --- /dev/null +++ b/tests/known-bugs/test.js @@ -0,0 +1,18 @@ +import {readFileSync} from "fs"; +import {assert, config as chaiConfig} from "chai"; +chaiConfig.truncateThreshold = 0; + +import * as LC from "../../src/lambda-calculus.js"; +LC.configure({ purity: "Let", numEncoding: "None" }); + +const solutionText = readFileSync(new URL("./solution.txt", import.meta.url), {encoding: "utf8"}); +const { foo } = LC.compile(solutionText); + +describe("No side effects", () => { + it("The initial failed call used to cause the second call to behave weirdly", () => { + try { + foo("hi")("there")("world") + } catch {} + assert.strictEqual( foo(null).term.toString(), "\\ j c . x c ()" ); + }); +}); From d8546b0f92424c82bfb27373c5fb277e9e034546 Mon Sep 17 00:00:00 2001 From: Keldan Chapman Date: Sat, 6 Jan 2024 17:58:42 +0100 Subject: [PATCH 2/2] Remove redundant stack passing --- src/lambda-calculus.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lambda-calculus.js b/src/lambda-calculus.js index 6e08e5d..9c0da6b 100644 --- a/src/lambda-calculus.js +++ b/src/lambda-calculus.js @@ -345,16 +345,16 @@ export function compile(code) { function evalLC(term) { // builds function to return to user ( representing an abstraction awaiting input ) - function awaitArg(term, stack, env) { + function awaitArg(term, env) { // callback function which will evaluate term.body in an env with the input function result(arg) { let argEnv; if ( arg?.term && arg?.env ) ({ term: arg, env: argEnv } = arg); // if callback is passed another callback, or a term const termVal = new Tuple( typeof arg === 'number' ? fromInt(arg) : arg , new Env(argEnv) ); if ( term.name==="_" ) - return runEval( new Tuple(term.body, new Env(env)), stack ); + return runEval( new Tuple(term.body, new Env(env)) ); else - return runEval( new Tuple(term.body, new Env(env).setThunk(term.name, termVal)), stack ); + return runEval( new Tuple(term.body, new Env(env).setThunk(term.name, termVal)) ); } return Object.assign( result, {term,env} ); } @@ -363,8 +363,8 @@ function evalLC(term) { // isRight :: bool (indicating whether the term is left or right side of an Application) // isEvaluated :: bool (indicating whether the current term should be stored in the Env) // callstack :: [String] (History of var lookups, for better error messages) - function runEval({term,env},stack) { // stack: [[term, isRight, isEvaluated]], term: LC term, env = Env { name => term } - const callstack = []; // Does not persist between requests for arguments + function runEval({term,env}) { // stack: [[term, isRight, isEvaluated]], term: LC term, env = Env { name => term } + const callstack = [], stack = []; // Does not persist between requests for arguments while ( ! ( term instanceof L ) || stack.length > 0 ) { if ( term instanceof V ) if ( term.name==="()" ) @@ -377,7 +377,7 @@ function evalLC(term) { else { if (res.term instanceof V || res.term instanceof A) // Push a frame to the stack to indicate when the value should be stored back - stack.push( [new Tuple( term, env ), false, true ] ); + stack.push( [new Tuple( term, env ), true, true ] ); ({term, env} = res); } } @@ -395,7 +395,7 @@ function evalLC(term) { env = new Env(env).setThunk(term.name, new Tuple(lastTerm, lastEnv)); term = term.body; } else { // Pass the function some other function. - term = lastTerm(awaitArg(term, [], env)); + term = lastTerm(awaitArg(term, env)); } } else if ( term instanceof Tuple ) { // for primitives @@ -421,9 +421,9 @@ function evalLC(term) { } } // We need input - return awaitArg(term, stack, env); + return awaitArg(term, env); } - return runEval(term, []); + return runEval(term); } // Print an error, with stack trace according to verbosity level