Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions include/cons_expr/cons_expr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<identifier_type>(&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<identifier_type>(&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);
}
};

Expand Down Expand Up @@ -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)]) {
Expand Down
2 changes: 1 addition & 1 deletion src/ccons_expr/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<lefticus::cons_expr<>::list_type>(evaluator.parse(content_1).first.value)));
} catch (const std::exception &e) {
Expand Down
6 changes: 3 additions & 3 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -115,4 +115,4 @@ catch_discover_tests(
OUTPUT_SUFFIX
.xml)

target_include_directories(relaxed_constexpr_tests PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include")
target_include_directories(relaxed_constexpr_tests PRIVATE "${CMAKE_BINARY_DIR}/configured_files/include")
72 changes: 72 additions & 0 deletions test/recursion_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>

#include <cons_expr/cons_expr.hpp>
#include <cons_expr/utility.hpp>
#include <internal_use_only/config.hpp>

using IntType = int;
using FloatType = double;

template<typename Result> constexpr Result evaluate_to(std::string_view input)
{
lefticus::cons_expr<std::uint16_t, char, IntType, FloatType> evaluator;
return evaluator.evaluate_to<Result>(input).value();
}

template<typename Result> constexpr bool evaluate_expected(std::string_view input, auto result)
{
lefticus::cons_expr<std::uint16_t, char, IntType, FloatType> evaluator;
return evaluator.evaluate_to<Result>(input).value() == result;
}

TEST_CASE("Y-Combinator", "[recursion]")
{
STATIC_CHECK(evaluate_to<int>(
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<int>(
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<int>(
R"(
(define factorial
(lambda (n)
(if (== n 0)
1
(* n (factorial (- n 1))))))

(factorial 5)
)") == 120);
}