Setup the Environment test cases
This commit is contained in:
parent
6f4c6b3398
commit
500f366bab
2
dev.org
2
dev.org
|
@ -55,6 +55,8 @@ Then here is the list or parsers that we have considered
|
|||
- [[https://julialang.org/research/julia-fresh-approach-BEKS.pdf][Julia: A Fresh Approach toNumerical Computing]]
|
||||
** Cranelift
|
||||
- [[https://github.com/bytecodealliance/wasmtime/tree/master/cranelift][Source tree]]
|
||||
** Type Systems
|
||||
- [[https://www.cs.cmu.edu/~rwh/courses/hott/][Homotopy Type Theory]]
|
||||
** Memory management
|
||||
- [[https://deepu.tech/memory-management-in-golang/][Visualizing memory management in Golang]]
|
||||
- [[http://goog-perftools.sourceforge.net/doc/tcmalloc.html][TCMalloc : Thread-Caching Malloc]]
|
||||
|
|
|
@ -50,7 +50,7 @@ constexpr static auto EmptyNode = nullptr;
|
|||
|
||||
/// The base class of the expressions which provides the common interface for
|
||||
/// the expressions to implement.
|
||||
class Expression {
|
||||
class SERENE_EXPORT Expression {
|
||||
public:
|
||||
/// The location range provide information regarding to where in the input
|
||||
/// string the current expression is used.
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "serene/exprs/expression.h"
|
||||
#include "serene/namespace.h"
|
||||
|
||||
#include <serene/export.h>
|
||||
|
||||
#include <llvm/ADT/StringRef.h>
|
||||
|
||||
#include <string>
|
||||
|
@ -33,25 +35,25 @@ namespace exprs {
|
|||
|
||||
/// This data structure represent the Lisp symbol. Just a symbol
|
||||
/// in the context of the AST and nothing else.
|
||||
class Symbol : public Expression {
|
||||
class SERENE_EXPORT Symbol : public Expression {
|
||||
|
||||
public:
|
||||
std::string name;
|
||||
std::string nsName;
|
||||
|
||||
Symbol(reader::LocationRange &loc, llvm::StringRef name,
|
||||
Symbol(const reader::LocationRange &loc, llvm::StringRef name,
|
||||
llvm::StringRef currentNS)
|
||||
: Expression(loc) {
|
||||
// IMPORTANT NOTE: the `name` and `currentNS` should be valid string and
|
||||
// already validated.
|
||||
auto partDelimiter = name.find('/');
|
||||
if (partDelimiter == std::string::npos) {
|
||||
nsName = currentNS;
|
||||
this->name = name;
|
||||
nsName = currentNS.str();
|
||||
this->name = name.str();
|
||||
|
||||
} else {
|
||||
this->name = name.substr(partDelimiter + 1, name.size());
|
||||
nsName = name.substr(0, partDelimiter);
|
||||
this->name = name.substr(partDelimiter + 1, name.size()).str();
|
||||
nsName = name.substr(0, partDelimiter).str();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
*
|
||||
* How to create a namespace ?
|
||||
* The official way to create a namespace object is to use the `SereneContext`
|
||||
* object and call `readNamespace` or `importNamespace`.
|
||||
* object and call `readNamespace`, `importNamespace` or `makeNamespace`.
|
||||
*/
|
||||
|
||||
// TODO: Add a mechanism to figure out whether a namespace has changed or not
|
||||
|
@ -88,7 +88,8 @@ using Forms = std::vector<Form>;
|
|||
|
||||
/// Serene's namespaces are the unit of compilation. Any code that needs to be
|
||||
/// compiled has to be in a namespace. The official way to create a new
|
||||
/// namespace is to use the `makeNamespace` member function of `SereneContext`.
|
||||
/// namespace is to use the `readNamespace`, `importNamespace` and
|
||||
/// `makeNamespace` member functions of `SereneContext`.
|
||||
class SERENE_EXPORT Namespace {
|
||||
friend SereneContext;
|
||||
|
||||
|
@ -100,14 +101,18 @@ private:
|
|||
std::atomic<uint> fn_counter = 0;
|
||||
|
||||
/// The content of the namespace. It should alway hold a semantically
|
||||
/// correct AST. It means thet the AST that we want to stor here has
|
||||
/// to pass the semantic analyzer.
|
||||
/// correct AST. It means thet the AST that we want to store here has
|
||||
/// to pass the semantic analyzer checks.
|
||||
exprs::Ast tree;
|
||||
|
||||
SemanticEnvironments environments;
|
||||
|
||||
std::vector<llvm::StringRef> symbolList;
|
||||
|
||||
public:
|
||||
std::string name;
|
||||
llvm::Optional<std::string> filename;
|
||||
|
||||
/// Create a naw namespace with the given `name` and optional `filename` and
|
||||
/// return a shared pointer to it in the given Serene context.
|
||||
static NSPtr make(SereneContext &ctx, llvm::StringRef name,
|
||||
|
@ -116,10 +121,6 @@ private:
|
|||
Namespace(SereneContext &ctx, llvm::StringRef ns_name,
|
||||
llvm::Optional<llvm::StringRef> filename);
|
||||
|
||||
public:
|
||||
std::string name;
|
||||
llvm::Optional<std::string> filename;
|
||||
|
||||
/// Create a new environment with the give \p parent as the parent,
|
||||
/// push the environment to the internal environment storage and
|
||||
/// return a reference to it. The namespace itself is the owner of
|
||||
|
@ -136,13 +137,12 @@ public:
|
|||
mlir::LogicalResult define(std::string &name, exprs::Node &node);
|
||||
|
||||
/// Add the given \p ast to the namespace and return any possible error.
|
||||
/// The given \p ast will be added to a vector of ASTs that the namespace
|
||||
/// have. In a normal compilation a Namespace will have a vector of ASTs
|
||||
/// with only one element, but in a REPL like environment it might have
|
||||
/// many elements.
|
||||
/// The given \p ast will be added to a vector of ASTs by expanding
|
||||
/// the tree vector to contain \p ast.
|
||||
///
|
||||
/// This function runs the semantic analyzer on the \p ast as well.
|
||||
llvm::Error addTree(exprs::Ast &ast);
|
||||
|
||||
exprs::Ast &getTree();
|
||||
|
||||
const std::vector<llvm::StringRef> &getSymList() { return symbolList; };
|
||||
|
@ -170,8 +170,6 @@ public:
|
|||
/// Dumps the namespace with respect to the compilation phase
|
||||
void dump();
|
||||
|
||||
void enqueueError(llvm::StringRef e) const;
|
||||
|
||||
~Namespace();
|
||||
};
|
||||
|
||||
|
|
|
@ -60,10 +60,6 @@ Namespace::Namespace(SereneContext &ctx, llvm::StringRef ns_name,
|
|||
createEnv(nullptr);
|
||||
};
|
||||
|
||||
void Namespace::enqueueError(llvm::StringRef e) const {
|
||||
ctx.diagEngine->enqueueError(e);
|
||||
}
|
||||
|
||||
SemanticEnv &Namespace::createEnv(SemanticEnv *parent) {
|
||||
auto env = std::make_unique<SemanticEnv>(parent);
|
||||
environments.push_back(std::move(env));
|
||||
|
@ -89,14 +85,17 @@ mlir::LogicalResult Namespace::define(std::string &name, exprs::Node &node) {
|
|||
}
|
||||
|
||||
exprs::Ast &Namespace::getTree() { return this->tree; }
|
||||
|
||||
llvm::Error Namespace::addTree(exprs::Ast &ast) {
|
||||
|
||||
// TODO: Remove the parse phase
|
||||
// If the target phase is just parsing we don't want
|
||||
// to run the semantic analyzer or anything beyond parser
|
||||
if (ctx.getTargetPhase() == CompilationPhase::Parse) {
|
||||
// we just want the raw AST
|
||||
this->tree.insert(this->tree.end(), ast.begin(), ast.end());
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
auto &rootEnv = getRootEnv();
|
||||
|
||||
auto state = semantics::makeAnalysisState(*this, rootEnv);
|
||||
|
@ -119,7 +118,13 @@ uint Namespace::nextFnCounter() { return fn_counter++; };
|
|||
SereneContext &Namespace::getContext() { return this->ctx; };
|
||||
|
||||
MaybeModuleOp Namespace::generate(unsigned offset) {
|
||||
// The reason why we return an optional value instead of Errors
|
||||
// is the way MLIR's diagnostic engine works. Passes may use
|
||||
// the `emit` function of operations to report errors to the
|
||||
// diagnostic engine. So we can't return any error diractly.
|
||||
|
||||
mlir::OpBuilder builder(&ctx.mlirContext);
|
||||
|
||||
// TODO: Fix the unknown location by pointing to the `ns` form
|
||||
auto module = mlir::ModuleOp::create(builder.getUnknownLoc(),
|
||||
llvm::Optional<llvm::StringRef>(name));
|
||||
|
@ -160,6 +165,7 @@ void Namespace::dump() {
|
|||
auto maybeModuleOp = generate();
|
||||
|
||||
if (!maybeModuleOp) {
|
||||
|
||||
llvm::errs() << "Failed to generate the IR.\n";
|
||||
return;
|
||||
}
|
||||
|
@ -171,6 +177,11 @@ void Namespace::dump() {
|
|||
};
|
||||
|
||||
MaybeModule Namespace::compileToLLVM() {
|
||||
// The reason why we return an optional value instead of Errors
|
||||
// is the way MLIR's diagnostic engine works. Passes may use
|
||||
// the `emit` function of operations to report errors to the
|
||||
// diagnostic engine. So we can't return any error diractly.
|
||||
|
||||
auto maybeModule = generate();
|
||||
|
||||
if (!maybeModule) {
|
||||
|
@ -187,6 +198,11 @@ MaybeModule Namespace::compileToLLVM() {
|
|||
};
|
||||
|
||||
MaybeModule Namespace::compileToLLVMFromOffset(unsigned offset) {
|
||||
// The reason why we return an optional value instead of Errors
|
||||
// is the way MLIR's diagnostic engine works. Passes may use
|
||||
// the `emit` function of operations to report errors to the
|
||||
// diagnostic engine. So we can't return any error diractly.
|
||||
|
||||
auto maybeModule = generate(offset);
|
||||
|
||||
if (!maybeModule) {
|
||||
|
|
|
@ -25,19 +25,21 @@
|
|||
#include "serene/environment.h"
|
||||
#include "serene/exprs/expression.h"
|
||||
#include "serene/exprs/symbol.h"
|
||||
#include "serene/reader/location.h"
|
||||
#include "serene/serene.h"
|
||||
|
||||
#include "./test_helpers.cpp.inc"
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <llvm/ADT/StringRef.h>
|
||||
#include <llvm/Support/Casting.h>
|
||||
|
||||
namespace serene {
|
||||
|
||||
TEST_CASE("Environment tests", "[environment]") {
|
||||
std::unique_ptr<reader::LocationRange> range(dummyLocation());
|
||||
|
||||
exprs::Node sym =
|
||||
exprs::make<exprs::Symbol>(*range.get(), llvm::StringRef("example"));
|
||||
exprs::Node sym = exprs::make<exprs::Symbol>(*range, "example", "ns");
|
||||
|
||||
Environment<llvm::StringRef, exprs::Node> e;
|
||||
llvm::Optional<exprs::Node> result = e.lookup("a");
|
||||
|
@ -53,15 +55,47 @@ TEST_CASE("Environment tests", "[environment]") {
|
|||
REQUIRE(result.hasValue());
|
||||
CHECK(result.getValue() == sym);
|
||||
|
||||
Environment<llvm::StringRef, exprs::Node> e1(&e);
|
||||
auto *fetchedSym = llvm::dyn_cast<exprs::Symbol>(result.getValue().get());
|
||||
REQUIRE(fetchedSym != nullptr);
|
||||
CHECK(fetchedSym->name == "example");
|
||||
CHECK(fetchedSym->nsName == "ns");
|
||||
|
||||
result = e1.lookup("b");
|
||||
REQUIRE_FALSE(result.hasValue());
|
||||
SECTION("Testing the environment copy") {
|
||||
Environment<llvm::StringRef, exprs::Node> e1(&e);
|
||||
|
||||
// It should lookup the value in the parent environment
|
||||
result = e1.lookup("a");
|
||||
REQUIRE(result.hasValue());
|
||||
CHECK(result.getValue() == sym);
|
||||
result = e1.lookup("b");
|
||||
REQUIRE_FALSE(result.hasValue());
|
||||
|
||||
// It should lookup the value in the parent environment
|
||||
result = e1.lookup("a");
|
||||
REQUIRE(result.hasValue());
|
||||
CHECK(result.getValue() == sym);
|
||||
};
|
||||
};
|
||||
|
||||
TEST_CASE("Test generic Environment usage", "[environment]") {
|
||||
Environment<int, char> env;
|
||||
|
||||
auto result = env.lookup(3);
|
||||
REQUIRE_FALSE(result);
|
||||
|
||||
auto status = env.insert_symbol(3, 'A');
|
||||
REQUIRE(status.succeeded());
|
||||
|
||||
result = env.lookup(3);
|
||||
REQUIRE(result);
|
||||
CHECK(*result == 'A');
|
||||
|
||||
result = env.lookup(3);
|
||||
REQUIRE(result);
|
||||
CHECK(*result == 'A');
|
||||
|
||||
// Test the overwrite functionality
|
||||
status = env.insert_symbol(3, 'B');
|
||||
REQUIRE(status.succeeded());
|
||||
|
||||
result = env.lookup(3);
|
||||
REQUIRE(result);
|
||||
CHECK(*result == 'B');
|
||||
}
|
||||
} // namespace serene
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "./context_tests.cpp.inc"
|
||||
#include "./environment_tests.cpp.inc"
|
||||
#include "./setup.cpp.inc"
|
||||
//#include "./environment_tests.cpp.inc"
|
||||
// #include "./errors/error_tests.cpp.inc"
|
||||
// #include "./exprs/expression_tests.cpp.inc"
|
||||
// #include "./exprs/list_tests.cpp.inc"
|
||||
|
|
Loading…
Reference in New Issue