From 050ba79e9ae0376abc883230652ca638bfc75150 Mon Sep 17 00:00:00 2001
From: Paulo Matos
Date: Thu, 15 Jun 2023 16:51:21 +0200
Subject: [PATCH] Implement closures
---
src/ASTRuntime.cpp | 42 ++++++++++
src/AnalysisFreeVars.cpp | 134 +++++++++++++++++++++++++++++++
src/CMakeLists.txt | 2 +
src/include/ASTRuntime.h | 36 +++++++++
src/include/ASTVisitor.h | 1 +
src/include/AnalysisFreeVars.h | 42 ++++++++++
src/include/ast.h | 30 +++++--
src/include/ast_fwd.h | 1 +
src/include/environment.h | 4 +
src/include/interpreter.h | 2 +
src/interpreter.cpp | 54 ++++++++-----
test/integration/closure.rkt | 8 ++
test/integration/let-values3.rkt | 7 ++
13 files changed, 335 insertions(+), 28 deletions(-)
create mode 100644 src/ASTRuntime.cpp
create mode 100644 src/AnalysisFreeVars.cpp
create mode 100644 src/include/ASTRuntime.h
create mode 100644 src/include/AnalysisFreeVars.h
create mode 100644 test/integration/closure.rkt
create mode 100644 test/integration/let-values3.rkt
diff --git a/src/ASTRuntime.cpp b/src/ASTRuntime.cpp
new file mode 100644
index 0000000..aa7ab27
--- /dev/null
+++ b/src/ASTRuntime.cpp
@@ -0,0 +1,42 @@
+#include "ASTRuntime.h"
+
+#include "AnalysisFreeVars.h"
+
+#include
+
+using namespace ast;
+
+Closure::Closure(const Lambda &Lbd, const std::vector &Envs)
+ : ClonableNode(ASTNodeKind::AST_Closure),
+ L(std::unique_ptr(static_cast(Lbd.clone()))) {
+
+ // To create a closure we need to:
+
+ // 1. Find the free variables in the lambda.
+ AnalysisFreeVars AFV;
+ L->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(Val->clone()));
+ break;
+ }
+ }
+ }
+}
+
+Closure::Closure(const Closure &Other)
+ : ClonableNode(ASTNodeKind::AST_Closure),
+ L(std::unique_ptr(static_cast(Other.L->clone()))) {
+ for (auto const &E : Other.Env) {
+ Env.add(E.first, std::unique_ptr(E.second->clone()));
+ }
+}
+
+void Closure::dump() const {}
+void Closure::write() const {}
\ No newline at end of file
diff --git a/src/AnalysisFreeVars.cpp b/src/AnalysisFreeVars.cpp
new file mode 100644
index 0000000..0bb2a45
--- /dev/null
+++ b/src/AnalysisFreeVars.cpp
@@ -0,0 +1,134 @@
+#include "AnalysisFreeVars.h"
+
+void AnalysisFreeVars::visit(ast::Identifier const &Id) {
+ // If the identifier is not in the environment, then it is a free variable.
+
+ for (auto const &Var : llvm::reverse(Vars)) {
+ if (Var.count(Id) == 0) {
+ Result.insert(Id);
+ }
+ }
+}
+
+void AnalysisFreeVars::visit(ast::Integer const &Int) {
+ // Integers do not have free variables.
+ // Nothing to do.
+}
+
+void AnalysisFreeVars::visit(ast::Linklet const &Linklet) {
+ llvm::errs() << "Free variable analysis only applies to expressions.\n";
+}
+
+void AnalysisFreeVars::visit(ast::DefineValues const &DV) {
+ llvm::errs() << "Free variable analysis only applies to expressions.\n";
+}
+
+void AnalysisFreeVars::visit(ast::Values const &V) {
+ // Need to check for free variable in each expression of the Values
+ // expression.
+ for (auto const &Expr : V.getExprs()) {
+ Expr->accept(*this);
+ }
+}
+
+void AnalysisFreeVars::visit(ast::Void const &Vd) {
+ // Void expressions have no free variables.
+ // Nothing to do.
+}
+
+void AnalysisFreeVars::visit(ast::Lambda const &L) {
+ const ast::Formal &F = L.getFormals();
+ std::set FormalVars;
+
+ if (F.getType() == ast::Formal::Type::Identifier) {
+ auto IF = static_cast(F);
+ FormalVars.insert(IF.getIdentifier());
+ } else if (F.getType() == ast::Formal::Type::List) {
+ auto LF = static_cast(F);
+ for (auto const &Id : LF.getIds()) {
+ FormalVars.insert(Id);
+ }
+ } else if (F.getType() == ast::Formal::Type::ListRest) {
+ auto LRF = static_cast(F);
+ for (auto const &Id : LRF.getIds()) {
+ FormalVars.insert(Id);
+ }
+ FormalVars.insert(LRF.getRestFormal());
+ }
+
+ // Save the current environment.
+ Vars.push_back(FormalVars);
+
+ // Check for free variables in the body of the lambda.
+ L.getBody().accept(*this);
+
+ // Restore the environment.
+ Vars.pop_back();
+}
+
+void AnalysisFreeVars::visit(ast::Closure const &L) {
+ // 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()) {
+ Expr->accept(*this);
+ }
+}
+
+void AnalysisFreeVars::visit(ast::List const &L) {
+ // Iterate through all the List expressions and check for free variables.
+ for (auto const &Expr : L.values()) {
+ Expr->accept(*this);
+ }
+}
+
+void AnalysisFreeVars::visit(ast::Application const &A) {
+ // Iterate through all the Application expressions and check for free
+ // variables.
+ for (auto const &Expr : A.getExprs()) {
+ Expr->accept(*this);
+ }
+}
+
+void AnalysisFreeVars::visit(ast::SetBang const &SB) {
+ // Check for free variables on the right hand side expression of SetBang
+ // expression.
+ SB.getExpr().accept(*this);
+}
+
+void AnalysisFreeVars::visit(ast::IfCond const &If) {
+ // Check for free variables on the condition expression of IfCond expression.
+ If.getCond().accept(*this);
+ // Check for free variables on the consequent expression of IfCond expression.
+ If.getThen().accept(*this);
+ // Check for free variables on the alternative expression of IfCond
+ // expression.
+ If.getElse().accept(*this);
+}
+
+void AnalysisFreeVars::visit(ast::BooleanLiteral const &Bool) {
+ // Boolean literals have no free variables.
+ // Nothing to do.
+}
+
+void AnalysisFreeVars::visit(ast::LetValues const &LV) {
+ std::set LVVars;
+ for (size_t Idx = 0; Idx < LV.bindingCount(); Idx++)
+ for (auto const &Var : LV.getBindingIds(Idx))
+ LVVars.insert(Var);
+
+ Vars.push_back(LVVars);
+
+ for (size_t Idx = 0; Idx < LV.bodyCount(); Idx++)
+ LV.getBodyExpr(Idx).accept(*this);
+
+ Vars.pop_back();
+}
+
+void AnalysisFreeVars::visit(ast::RuntimeFunction const &LV) {
+ // Runtime Functions have no free variables.
+ // Nothing to do.
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a51b558..f226075 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -18,6 +18,8 @@ add_llvm_executable(norac
main.cpp
environment.cpp
ast.cpp
+ ASTRuntime.cpp
+ AnalysisFreeVars.cpp
idpool.cpp
Parse.cpp
Lex.cpp
diff --git a/src/include/ASTRuntime.h b/src/include/ASTRuntime.h
new file mode 100644
index 0000000..7350877
--- /dev/null
+++ b/src/include/ASTRuntime.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "ast.h"
+#include "environment.h"
+
+#include
+
+namespace ast {
+//
+// This file includes the structures that are used in addition to
+// those in ast.h during runtime interpretation.
+//
+// The simplest example is the Closure.
+
+// A Closure is a runtime manifestation of a Lambda.
+class Closure : public ClonableNode {
+public:
+ Closure(const Lambda &Lbd, const std::vector &Envs);
+ Closure(const Closure &Other);
+
+ static bool classof(const ASTNode *N) {
+ return N->getKind() == ASTNodeKind::AST_Closure;
+ }
+
+ void dump() const override;
+ void write() const override;
+
+ const Lambda &getLambda() const { return *L; }
+ const Environment &getEnvironment() const { return Env; }
+
+private:
+ std::unique_ptr L;
+ Environment Env;
+};
+
+}; // namespace ast
\ No newline at end of file
diff --git a/src/include/ASTVisitor.h b/src/include/ASTVisitor.h
index 6c02ce1..daf54e1 100644
--- a/src/include/ASTVisitor.h
+++ b/src/include/ASTVisitor.h
@@ -14,6 +14,7 @@ class ASTVisitor {
virtual void visit(ast::Values const &V) = 0;
virtual void visit(ast::Void const &Vd) = 0;
virtual void visit(ast::Lambda const &L) = 0;
+ virtual void visit(ast::Closure const &L) = 0;
virtual void visit(ast::Begin const &B) = 0;
virtual void visit(ast::List const &L) = 0;
virtual void visit(ast::Application const &A) = 0;
diff --git a/src/include/AnalysisFreeVars.h b/src/include/AnalysisFreeVars.h
new file mode 100644
index 0000000..9060f2a
--- /dev/null
+++ b/src/include/AnalysisFreeVars.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "ASTVisitor.h"
+#include "ast.h"
+
+#include
+
+#include
+#include