diff --git a/include/cons_expr/cons_expr.hpp b/include/cons_expr/cons_expr.hpp index 810a5ab..b7c0fa2 100644 --- a/include/cons_expr/cons_expr.hpp +++ b/include/cons_expr/cons_expr.hpp @@ -648,21 +648,18 @@ struct cons_expr } // Closures contain all of their own scope - LexicalScope new_scope; + LexicalScope param_scope = scope; + + // overwrite scope with the things we know we need params to be named // set up params // technically I'm evaluating the params lazily while invoking the lambda, not before. Does it matter? for (const auto [name, parameter] : std::views::zip(engine.values[parameter_names], engine.values[params])) { - new_scope.emplace_back(engine.get_if(&name)->value, engine.eval(scope, parameter)); - } - - Scratch fixed_statements{ engine.object_scratch }; - for (const auto &statement : engine.values[statements]) { - fixed_statements.push_back(engine.fix_identifiers(statement, {}, new_scope)); + param_scope.emplace_back(engine.get_if(&name)->value, engine.eval(scope, parameter)); } // TODO set up tail call elimination for last element of the sequence being evaluated? - return engine.sequence(new_scope, engine.values.insert_or_find(fixed_statements)); + return engine.sequence(param_scope, statements); } }; @@ -908,6 +905,7 @@ struct cons_expr auto locals = engine.get_lambda_parameter_names(engine.values[params[0]]); // replace all references to captured values with constant copies + // this is how we create the closure object Scratch fixed_statements{ engine.object_scratch }; for (const auto &statement : engine.values[params.sublist(1)]) { diff --git a/src/ccons_expr/main.cpp b/src/ccons_expr/main.cpp index 170b8b2..8bd30d4 100644 --- a/src/ccons_expr/main.cpp +++ b/src/ccons_expr/main.cpp @@ -67,7 +67,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char *argv[]) try { content_2 += to_string(evaluator, - false, + true, evaluator.sequence( evaluator.global_scope, std::get::list_type>(evaluator.parse(content_1).first.value))); } catch (const std::exception &e) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 16c1005..289e629 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -65,7 +65,7 @@ catch_discover_tests( .xml) # Add a file containing a set of constexpr tests -add_executable(constexpr_tests constexpr_tests.cpp list_tests.cpp parser_tests.cpp) +add_executable(constexpr_tests constexpr_tests.cpp list_tests.cpp parser_tests.cpp recursion_tests.cpp) target_link_libraries( constexpr_tests PRIVATE cons_expr::cons_expr @@ -93,7 +93,7 @@ catch_discover_tests( # Disable the constexpr portion of the test, and build again this allows us to have an executable that we can debug when # things go wrong with the constexpr testing -add_executable(relaxed_constexpr_tests constexpr_tests.cpp list_tests.cpp parser_tests.cpp) +add_executable(relaxed_constexpr_tests constexpr_tests.cpp list_tests.cpp parser_tests.cpp recursion_tests.cpp) target_link_libraries( relaxed_constexpr_tests PRIVATE cons_expr::cons_expr @@ -115,4 +115,4 @@ catch_discover_tests( OUTPUT_SUFFIX .xml) -target_include_directories(relaxed_constexpr_tests PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include") \ No newline at end of file +target_include_directories(relaxed_constexpr_tests PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include") diff --git a/test/recursion_tests.cpp b/test/recursion_tests.cpp new file mode 100644 index 0000000..a6fe0c9 --- /dev/null +++ b/test/recursion_tests.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include +#include +#include + +using IntType = int; +using FloatType = double; + +template constexpr Result evaluate_to(std::string_view input) +{ + lefticus::cons_expr evaluator; + return evaluator.evaluate_to(input).value(); +} + +template constexpr bool evaluate_expected(std::string_view input, auto result) +{ + lefticus::cons_expr evaluator; + return evaluator.evaluate_to(input).value() == result; +} + +TEST_CASE("Y-Combinator", "[recursion]") +{ + STATIC_CHECK(evaluate_to( + R"( +;; Y combinator definition +(define Y + (lambda (f) + ((lambda (x) (f (lambda (y) ((x x) y)))) + (lambda (x) (f (lambda (y) ((x x) y))))))) + +;; Factorial using Y combinator +(define factorial + (Y (lambda (fact) + (lambda (n) + (if (== n 0) + 1 + (* n (fact (- n 1)))))))) + +(factorial 5) +)") == 120); +} + + +TEST_CASE("expressive 'define' 1 level", "[recursion]") +{ + STATIC_CHECK(evaluate_to( + R"( +(define factorial + (lambda (n) + (if (== n 0) + 1 + (* n (factorial (- n 1)))))) + +(factorial 1) +)") == 1); +} + +TEST_CASE("expressive 'define' 5 levels", "[recursion]") +{ + STATIC_CHECK(evaluate_to( + R"( +(define factorial + (lambda (n) + (if (== n 0) + 1 + (* n (factorial (- n 1)))))) + +(factorial 5) +)") == 120); +}