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
24 changes: 24 additions & 0 deletions src/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,30 @@ void Lambda::dump() const {

void Lambda::write() const { std::cout << "#<procedure>"; }

//
// Implementation of CaseLambda node.
//

CaseLambda::CaseLambda(CaseLambda const &CL)
: ClonableNode(ASTNodeKind::AST_CaseLambda) {
Clauses.reserve(CL.Clauses.size());
for (auto const &C : CL.Clauses) {
Clauses.push_back(
std::unique_ptr<Lambda>(static_cast<Lambda *>(C->clone())));
}
}

void CaseLambda::dump() const {
llvm::dbgs() << "(case-lambda";
for (auto const &C : Clauses) {
llvm::dbgs() << " ";
C->dump();
}
llvm::dbgs() << ")";
}

void CaseLambda::write() const { std::cout << "#<procedure>"; }

//
// Implementation of DefineValues node.
//
Expand Down
41 changes: 40 additions & 1 deletion src/ASTRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,43 @@ void Closure::dump() const {
// TODO: Implement.
llvm::dbgs() << "<closure: not implemented>\n";
}
void Closure::write() const {}
void Closure::write() const {}

CaseLambdaClosure::CaseLambdaClosure(const CaseLambda &CLbd,
const std::vector<Environment> &Envs)
: ClonableNode(ASTNodeKind::AST_CaseLambdaClosure),
CL(std::unique_ptr<CaseLambda>(static_cast<CaseLambda *>(CLbd.clone()))) {

// Capture the free variables across all clauses, mirroring Closure.

// 1. Find the free variables in the case-lambda.
AnalysisFreeVars AFV;
CL->accept(AFV);
auto const &FreeVars = AFV.getResult();

// 2. Find in the current environment, the values of the free variables
// and save them.
for (auto const &Var : FreeVars) {
for (auto const &E : llvm::reverse(Envs)) {
auto const &Val = E.lookup(Var);
if (Val) {
Env.add(Var, std::unique_ptr<ValueNode>(Val->clone()));
break;
}
}
}
}

CaseLambdaClosure::CaseLambdaClosure(const CaseLambdaClosure &Other)
: ClonableNode(ASTNodeKind::AST_CaseLambdaClosure),
CL(std::unique_ptr<CaseLambda>(
static_cast<CaseLambda *>(Other.CL->clone()))) {
for (auto const &E : Other.Env) {
Env.add(E.first, std::unique_ptr<ValueNode>(E.second->clone()));
}
}

void CaseLambdaClosure::dump() const {
llvm::dbgs() << "<case-lambda closure: not implemented>\n";
}
void CaseLambdaClosure::write() const {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Print case-lambda closures

When a case-lambda value is returned instead of immediately applied, such as (linklet () () (case-lambda ((x) x))), interpretation produces a CaseLambdaClosure and main prints it through ValueNode::write(). This override is empty, so the program emits only the trailing newline instead of the procedure representation provided by CaseLambda::write(), making procedure-valued results disappear.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope for this PR — you're right that a bare top-level case-lambda prints only a newline because CaseLambdaClosure::write() is empty. But that is exact parity with the existing Closure::write() (the plain-lambda runtime value, also empty at src/ASTRuntime.cpp:45), so this PR introduces no case-lambda-specific regression. The proper fix gives both procedure writers a #<procedure> representation together — changing only the case-lambda one would make the two procedure kinds inconsistent — which belongs in a separate printing-focused change rather than in "Interpret case-lambda".

Tracked as #85 (#85).

This assessment was made by two independent AI reviewers (Claude Opus 4.8 and Codex). If you disagree, please reply and we'll re-evaluate.

13 changes: 13 additions & 0 deletions src/AnalysisFreeVars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,24 @@ void AnalysisFreeVars::visit(ast::Lambda const &L) {
Vars.pop_back();
}

void AnalysisFreeVars::visit(ast::CaseLambda const &CL) {
// A case-lambda's free variables are the union over all its clauses. Each
// clause is a Lambda that binds its own formals around its own body.
for (size_t Idx = 0; Idx < CL.size(); ++Idx) {
CL[Idx].accept(*this);
}
}

void AnalysisFreeVars::visit(ast::Closure const &L) {
// Closures by definition do not have free variables.
// Nothing to do.
}

void AnalysisFreeVars::visit(ast::CaseLambdaClosure const &CL) {
// Case-lambda closures by definition do not have free variables.
// Nothing to do.
}

void AnalysisFreeVars::visit(ast::Begin const &B) {
// Iterate through all the begin expressions and check for free variables.
for (auto const &Expr : B.getBody()) {
Expand Down
165 changes: 109 additions & 56 deletions src/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ void Interpreter::visit(ast::Closure const &C) {
Result = std::unique_ptr<ast::ValueNode>(C.clone());
}

void Interpreter::visit(ast::CaseLambda const &CL) {
// The interpretation of a case-lambda expression is a closure,
// even if no variables are captured.
LLVM_DEBUG(llvm::dbgs() << "Interpreting CaseLambda\n");
Result = std::make_unique<ast::CaseLambdaClosure>(CL, Envs);
}

void Interpreter::visit(ast::CaseLambdaClosure const &C) {
LLVM_DEBUG(llvm::dbgs() << "Interpreting CaseLambdaClosure\n");
Result = std::unique_ptr<ast::ValueNode>(C.clone());
}

void Interpreter::visit(ast::Begin const &B) {
LLVM_DEBUG(llvm::dbgs() << "Interpreting Begin\n");

Expand Down Expand Up @@ -215,15 +227,76 @@ void Interpreter::visit(ast::Vector const &Vec) {
Result = std::unique_ptr<ast::ValueNode>(Vec.clone());
}

// Returns true if the formals F accept NArgs supplied arguments.
static bool formalsAccept(const ast::Formal &F, size_t NArgs) {
switch (F.getType()) {
case ast::Formal::Type::List:
return static_cast<const ast::ListFormal &>(F).size() == NArgs;
case ast::Formal::Type::ListRest:
return static_cast<const ast::ListRestFormal &>(F).size() <= NArgs;
case ast::Formal::Type::Identifier:
return true;
}
llvm_unreachable("unknown formal type");
}

void Interpreter::applyFormals(
const ast::Formal &F, const ast::ExprNode &Body,
const Environment &CapturedEnv,
std::vector<std::unique_ptr<ast::ValueNode>> &Args) {
// Create an environment where each argument is bound to the corresponding
// value. Then evaluate the body in this environment.
Environment Env;
if (F.getType() == ast::Formal::Type::List) {
auto LF = static_cast<const ast::ListFormal &>(F);
for (size_t Idx = 0; Idx < Args.size(); ++Idx) {
Env.add(LF[Idx], std::move(Args[Idx]));
}
} else if (F.getType() == ast::Formal::Type::ListRest) {
auto LRF = static_cast<const ast::ListRestFormal &>(F);
size_t Idx = 0;
for (; Idx < LRF.size(); ++Idx) {
Env.add(LRF[Idx], std::move(Args[Idx]));
}
// Create a list of the remaining arguments.
auto L = std::make_unique<ast::List>();
for (; Idx < Args.size(); ++Idx) {
L->appendExpr(std::move(Args[Idx]));
}
Env.add(LRF.getRestFormal(), std::unique_ptr<ast::ValueNode>(L->clone()));
} else if (F.getType() == ast::Formal::Type::Identifier) {
auto IF = static_cast<const ast::IdentifierFormal &>(F);
auto L = std::make_unique<ast::List>();
for (size_t Idx = 0; Idx < Args.size(); ++Idx) {
L->appendExpr(std::move(Args[Idx]));
}
Env.add(IF.getIdentifier(), std::unique_ptr<ast::ValueNode>(L->clone()));
} else {
llvm_unreachable("unknown formal type");
}

Envs.push_back(CapturedEnv); // Pushes the closure environment first.
Envs.push_back(Env); // Then pushes the environment with the args.

Body.accept(*this);

Envs.pop_back();
Envs.pop_back();
}

void Interpreter::visit(ast::Application const &A) {
// 1. Evaluate the first expression.
// which should evaluate to a lambda expression.
A[0].accept(*this);
std::unique_ptr<ast::ValueNode> D = std::move(Result);

// Error out if not a Closure expression or Runtime expression.
// Error out if not a Closure, CaseLambdaClosure, or Runtime function.
std::unique_ptr<ast::Closure> C = dyn_castU<ast::Closure>(D);
std::unique_ptr<ast::CaseLambdaClosure> CLC;
if (!C) {
CLC = dyn_castU<ast::CaseLambdaClosure>(D);
}
if (!C && !CLC) {
// maybe a runtime function?
std::unique_ptr<ast::RuntimeFunction> RF =
dyn_castU<ast::RuntimeFunction>(D);
Expand Down Expand Up @@ -276,70 +349,50 @@ void Interpreter::visit(ast::Application const &A) {
assert(Result && "Expected result from expression.");
Args.emplace_back(std::move(Result));
}
// 3. Single-clause closure: check arity and apply.
// If we have a list formals then, error out of args diff than formals.
// If we have a list rest formals then, error out if args less than formals.
// If it's identifier formals then it does not matter.
const ast::Lambda &L = C->getLambda();
const ast::Formal &F = L.getFormals();
if (F.getType() == ast::Formal::Type::List) {
auto LF = static_cast<const ast::ListFormal &>(F);
if (Args.size() != LF.size()) {
std::cerr << "Expected " << LF.size() << " arguments, got " << Args.size()
<< std::endl;
Result = nullptr;
return;
}
} else if (F.getType() == ast::Formal::Type::ListRest) {
auto LRF = static_cast<const ast::ListRestFormal &>(F);
if (Args.size() < LRF.size()) {
std::cerr << "Expected at least " << LRF.size() << " arguments, got "
<< Args.size() << std::endl;
Result = nullptr;
return;
if (C) {
const ast::Lambda &L = C->getLambda();
const ast::Formal &F = L.getFormals();
if (F.getType() == ast::Formal::Type::List) {
auto LF = static_cast<const ast::ListFormal &>(F);
if (Args.size() != LF.size()) {
std::cerr << "Expected " << LF.size() << " arguments, got "
<< Args.size() << std::endl;
Result = nullptr;
return;
}
} else if (F.getType() == ast::Formal::Type::ListRest) {
auto LRF = static_cast<const ast::ListRestFormal &>(F);
if (Args.size() < LRF.size()) {
std::cerr << "Expected at least " << LRF.size() << " arguments, got "
<< Args.size() << std::endl;
Result = nullptr;
return;
}
}
}

// 3. Apply the lambda expression to the evaluated expressions.
// Create an environment where each argument is bound to the corresponding
// value. Then evaluate the lambda body in this environment.
Environment Env;
if (F.getType() == ast::Formal::Type::List) {
auto LF = static_cast<const ast::ListFormal &>(F);
for (size_t Idx = 0; Idx < Args.size(); ++Idx) {
Env.add(LF[Idx], std::move(Args[Idx]));
}
} else if (F.getType() == ast::Formal::Type::ListRest) {
auto LRF = static_cast<const ast::ListRestFormal &>(F);
size_t Idx = 0;
for (; Idx < LRF.size(); ++Idx) {
Env.add(LRF[Idx], std::move(Args[Idx]));
}
// Create a list of the remaining arguments.
auto L = std::make_unique<ast::List>();
for (; Idx < Args.size(); ++Idx) {
L->appendExpr(std::move(Args[Idx]));
}
applyFormals(F, L.getBody(), C->getEnvironment(), Args);
return;
}

Env.add(LRF.getRestFormal(), std::unique_ptr<ast::ValueNode>(L->clone()));
} else if (F.getType() == ast::Formal::Type::Identifier) {
auto IF = static_cast<const ast::IdentifierFormal &>(F);
auto L = std::make_unique<ast::List>();
for (size_t Idx = 0; Idx < Args.size(); ++Idx) {
L->appendExpr(std::move(Args[Idx]));
// 4. Case-lambda closure: apply the first clause whose formals accept the
// number of supplied arguments.
const ast::CaseLambda &CL = CLC->getCaseLambda();
for (size_t Idx = 0; Idx < CL.size(); ++Idx) {
const ast::Lambda &Clause = CL[Idx];
if (formalsAccept(Clause.getFormals(), Args.size())) {
applyFormals(Clause.getFormals(), Clause.getBody(), CLC->getEnvironment(),
Args);
return;
}
Env.add(IF.getIdentifier(), std::unique_ptr<ast::ValueNode>(L->clone()));
} else {
llvm_unreachable("unknown formal type");
}

Envs.push_back(C->getEnvironment()); // Pushes the closure environment first.
Envs.push_back(Env); // Then pushes the environment with the args.

// 4. Return the result of the application.
L.getBody().accept(*this);

Envs.pop_back();
Envs.pop_back();
std::cerr << "case-lambda: no matching clause for " << Args.size()
<< " arguments" << std::endl;
Result = nullptr;
}

// To interpret a set! expression we set the value of the identifier in the
Expand Down
66 changes: 65 additions & 1 deletion src/Parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ using namespace Lex;
//
// expr := <id> - parseIdentifier
// | (lambda <formals> <expr>) - parseLambda
// | (case-lambda (<formals> <expr>) ...)
// | (case-lambda (<formals> <expr>) ...) - parseCaseLambda
// | (if <expr> <expr> <expr>) - parseIfCond
// | (begin <expr> ...+) - parseBegin
// | (begin0 <expr> ...+) - parseBegin
Expand Down Expand Up @@ -201,6 +201,11 @@ std::unique_ptr<ast::ExprNode> Parse::parseExpr(SourceStream &S) {
return L;
}

std::unique_ptr<ast::CaseLambda> CL = parseCaseLambda(S);
if (CL) {
return CL;
}

std::unique_ptr<ast::Begin> B = parseBegin(S);
if (B) {
return B;
Expand Down Expand Up @@ -621,6 +626,65 @@ std::unique_ptr<ast::Lambda> Parse::parseLambda(SourceStream &S) {
return Lambda;
}

// Parse case-lambda expression of the form:
// (case-lambda (<formals> <expr>) ...)
std::unique_ptr<ast::CaseLambda> Parse::parseCaseLambda(SourceStream &S) {
size_t Start = S.getPosition();

Tok T = gettok(S);
if (!T.is(Tok::TokType::LPAREN)) {
S.rewindTo(Start);
return nullptr;
}

T = gettok(S);
if (!T.is(Tok::TokType::CASE_LAMBDA)) {
S.rewindTo(Start);
return nullptr;
}

auto CaseLambda = std::make_unique<ast::CaseLambda>();

// Parse zero or more clauses of the form ( <formals> <expr> ).
while (true) {
T = gettok(S);
if (T.is(Tok::TokType::RPAREN)) {
break; // End of the case-lambda.
}
if (!T.is(Tok::TokType::LPAREN)) {
S.rewindTo(Start);
return nullptr;
}

// Each clause reuses the lambda machinery: formals plus a single body.
auto Clause = std::make_unique<ast::Lambda>();

std::unique_ptr<ast::Formal> Formals = parseFormals(S);
if (!Formals) {
S.rewindTo(Start);
return nullptr;
}
Clause->setFormals(std::move(Formals));

std::unique_ptr<ast::ExprNode> Body = parseExpr(S);
if (!Body) {
S.rewindTo(Start);
return nullptr;
}
Clause->setBody(std::move(Body));

T = gettok(S);
if (!T.is(Tok::TokType::RPAREN)) {
S.rewindTo(Start);
return nullptr;
}

CaseLambda->addClause(std::move(Clause));
}

return CaseLambda;
}

// Parse formals of the form:
// (id ...)
// (id ... . id)
Expand Down
Loading
Loading