Refactor the error handling system to have only one llvm error with many variants
This commit is contained in:
parent
1cd5824608
commit
4605e22e68
|
@ -24,24 +24,29 @@
|
|||
#include "serene/export.h"
|
||||
|
||||
#include <llvm/Support/Casting.h>
|
||||
|
||||
#define GET_CLASS_DEFS
|
||||
#include "serene/errors/errs.h.inc"
|
||||
|
||||
#include <llvm/Support/Error.h>
|
||||
|
||||
namespace serene {
|
||||
class SereneContext;
|
||||
} // namespace serene
|
||||
|
||||
namespace serene::errors {
|
||||
|
||||
/// Create and return a Serene flavored `llvm::Error` by passing the parameters
|
||||
/// directly to the constructor of type `E`.
|
||||
///
|
||||
/// This is the official way of creating error objects in Serene.
|
||||
template <typename E, typename... Args>
|
||||
SERENE_EXPORT llvm::Error makeError(Args &&...args) {
|
||||
return llvm::make_error<E>(std::forward<Args>(args)...);
|
||||
template <typename... Args>
|
||||
SERENE_EXPORT llvm::Error makeError(SereneContext &ctx, ErrorType errtype,
|
||||
Args &&...args) {
|
||||
return llvm::make_error<Error>(ctx, errtype, std::forward<Args>(args)...);
|
||||
};
|
||||
|
||||
/// Returns the messange that the given error \p e is holding. It doesn't cast
|
||||
/// the error to a concrete error type.
|
||||
SERENE_EXPORT std::string getMessage(const llvm::Error &e);
|
||||
|
||||
SERENE_EXPORT const ErrorVariant *getVariant(ErrorType t);
|
||||
} // namespace serene::errors
|
||||
|
||||
#endif
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
#ifndef SERENE_ERRORS_BASE_H
|
||||
#define SERENE_ERRORS_BASE_H
|
||||
|
||||
#include "serene/export.h"
|
||||
#include "serene/reader/location.h"
|
||||
|
||||
#define GET_CLASS_DEFS
|
||||
#include "serene/errors/errs.h.inc"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#include <llvm/Support/Error.h>
|
||||
|
@ -28,59 +32,31 @@
|
|||
|
||||
namespace serene::errors {
|
||||
|
||||
// This class is used in the generated code
|
||||
struct ErrorVariant {
|
||||
const int id;
|
||||
const std::string title;
|
||||
const std::string desc;
|
||||
const std::string help;
|
||||
class SERENE_EXPORT Error : public llvm::ErrorInfo<Error> {
|
||||
public:
|
||||
static char ID;
|
||||
ErrorType errorType;
|
||||
|
||||
static ErrorVariant make(const int id, const char *t, const char *d,
|
||||
const char *h) {
|
||||
return ErrorVariant(id, t, d, h);
|
||||
};
|
||||
|
||||
private:
|
||||
ErrorVariant(const int id, const char *t, const char *d, const char *h)
|
||||
: id(id), title(t), desc(d), help(h){};
|
||||
};
|
||||
|
||||
class SereneError : public llvm::ErrorInfoBase {
|
||||
SereneContext &ctx;
|
||||
reader::LocationRange location;
|
||||
std::string msg;
|
||||
|
||||
public:
|
||||
constexpr static const int ID = -1;
|
||||
|
||||
virtual void log(llvm::raw_ostream &os) const override { os << msg; };
|
||||
virtual std::string message() const override { return msg; };
|
||||
void log(llvm::raw_ostream &os) const override { os << msg; }
|
||||
|
||||
std::error_code convertToErrorCode() const override {
|
||||
return std::error_code();
|
||||
};
|
||||
|
||||
SereneError(reader::LocationRange &loc, std::string &msg)
|
||||
: location(loc), msg(msg){};
|
||||
|
||||
SereneError(reader::LocationRange &loc, const char *msg)
|
||||
: location(loc), msg(msg){};
|
||||
|
||||
SereneError(reader::LocationRange &loc, llvm::StringRef msg)
|
||||
: location(loc), msg(msg.str()){};
|
||||
|
||||
SereneError(reader::LocationRange &loc) : location(loc){};
|
||||
|
||||
SereneError(SereneError &e) = delete;
|
||||
|
||||
reader::LocationRange &where() { return location; };
|
||||
|
||||
static const void *classID() { return &ID; }
|
||||
|
||||
bool isA(const void *const id) const override {
|
||||
return id == classID() || llvm::ErrorInfoBase::isA(id);
|
||||
// TODO: Fix this by creating a mapping from ErrorType to standard
|
||||
// errc or return the ErrorType number instead
|
||||
return std::make_error_code(std::errc::io_error);
|
||||
}
|
||||
|
||||
~SereneError() = default;
|
||||
Error(SereneContext &ctx, ErrorType errtype, reader::LocationRange &loc)
|
||||
: errorType(errtype), ctx(ctx), location(loc){};
|
||||
|
||||
Error(SereneContext &ctx, ErrorType errtype, reader::LocationRange &loc,
|
||||
llvm::StringRef msg)
|
||||
: errorType(errtype), ctx(ctx), location(loc), msg(msg.str()){};
|
||||
|
||||
reader::LocationRange &where() { return location; };
|
||||
};
|
||||
|
||||
}; // namespace serene::errors
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* -*- C++ -*-
|
||||
* Serene Programming Language
|
||||
*
|
||||
* Copyright (c) 2019-2022 Sameer Rahmani <lxsameer@gnu.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SERENE_ERRORS_VARIANT_H
|
||||
#define SERENE_ERRORS_VARIANT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace serene::errors {
|
||||
|
||||
// This class is used in the generated code
|
||||
struct ErrorVariant {
|
||||
const int id;
|
||||
const std::string title;
|
||||
const std::string desc;
|
||||
const std::string help;
|
||||
|
||||
static ErrorVariant make(const int id, const char *t, const char *d,
|
||||
const char *h) {
|
||||
return ErrorVariant(id, t, d, h);
|
||||
};
|
||||
|
||||
private:
|
||||
ErrorVariant(const int id, const char *t, const char *d, const char *h)
|
||||
: id(id), title(t), desc(d), help(h){};
|
||||
};
|
||||
} // namespace serene::errors
|
||||
#endif
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "serene/environment.h"
|
||||
#include "serene/errors.h"
|
||||
#include "serene/export.h"
|
||||
#include "serene/utils.h"
|
||||
|
||||
#include <llvm/ADT/StringRef.h>
|
||||
|
@ -67,7 +68,7 @@ std::unique_ptr<AnalysisState> makeAnalysisState(Args &&...args) {
|
|||
///
|
||||
/// \param state The semantic analysis state that keep track of the envs.
|
||||
/// \param form The actual AST in question.
|
||||
AnalyzeResult analyze(AnalysisState &state, exprs::Ast &forms);
|
||||
SERENE_EXPORT AnalyzeResult analyze(AnalysisState &state, exprs::Ast &forms);
|
||||
|
||||
} // namespace semantics
|
||||
} // namespace serene
|
||||
|
|
|
@ -18,12 +18,27 @@
|
|||
|
||||
#include "serene/errors.h"
|
||||
|
||||
#include "serene/errors/base.h"
|
||||
|
||||
#include <llvm/Support/Casting.h>
|
||||
#include <llvm/Support/Error.h>
|
||||
|
||||
namespace serene::errors {
|
||||
|
||||
// We need this to make Error class a llvm::Error friendy implementation
|
||||
char Error::ID;
|
||||
|
||||
std::string getMessage(const llvm::Error &e) {
|
||||
std::string msg;
|
||||
llvm::raw_string_ostream os(msg);
|
||||
os << e;
|
||||
return os.str();
|
||||
};
|
||||
|
||||
const ErrorVariant *getVariant(ErrorType t) {
|
||||
if ((0 <= (int)t) && (t < NUMBER_OF_ERRORS)) {
|
||||
return &errorVariants[t];
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
} // namespace serene::errors
|
||||
|
|
|
@ -52,6 +52,7 @@ bool Call::classof(const Expression *e) {
|
|||
|
||||
MaybeNode Call::make(semantics::AnalysisState &state, List *list) {
|
||||
|
||||
auto &ctx = state.ns.getContext();
|
||||
// TODO: replace this with a runtime check
|
||||
assert((list->count() != 0) && "Empty call? Seriously ?");
|
||||
|
||||
|
@ -97,7 +98,8 @@ MaybeNode Call::make(semantics::AnalysisState &state, List *list) {
|
|||
if (!maybeResult.hasValue()) {
|
||||
std::string msg =
|
||||
llvm::formatv("Can't resolve the symbol '{0}'", sym->name);
|
||||
return errors::makeError<errors::CantResolveSymbol>(sym->location, msg);
|
||||
return errors::makeError(ctx, errors::CantResolveSymbol, sym->location,
|
||||
msg);
|
||||
}
|
||||
|
||||
targetNode = std::move(maybeResult.getValue());
|
||||
|
@ -122,8 +124,8 @@ MaybeNode Call::make(semantics::AnalysisState &state, List *list) {
|
|||
default: {
|
||||
std::string msg = llvm::formatv("Don't know how to call a '{0}'",
|
||||
stringifyExprType(first->getType()));
|
||||
return errors::makeError<errors::DontKnowHowToCallNode>(first->location,
|
||||
msg);
|
||||
return errors::makeError(ctx, errors::DontKnowHowToCallNode,
|
||||
first->location, msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -52,11 +52,13 @@ bool Def::classof(const Expression *e) {
|
|||
};
|
||||
|
||||
MaybeNode Def::make(semantics::AnalysisState &state, List *list) {
|
||||
auto &ctx = state.ns.getContext();
|
||||
|
||||
// TODO: Add support for docstring as the 3rd argument (4th element)
|
||||
if (list->count() != 3) {
|
||||
std::string msg = llvm::formatv("Expected 3 got {0}", list->count());
|
||||
return errors::makeError<errors::DefWrongNumberOfArgs>(
|
||||
list->elements[0]->location, msg);
|
||||
return errors::makeError(ctx, errors::DefWrongNumberOfArgs,
|
||||
list->elements[0]->location, msg);
|
||||
}
|
||||
|
||||
// Make sure that the list starts with a `def`
|
||||
|
@ -68,8 +70,8 @@ MaybeNode Def::make(semantics::AnalysisState &state, List *list) {
|
|||
// Make sure that the first argument is a Symbol
|
||||
Symbol *binding = llvm::dyn_cast<Symbol>(list->elements[1].get());
|
||||
if (binding == nullptr) {
|
||||
return errors::makeError<errors::DefExpectSymbol>(
|
||||
list->elements[1]->location);
|
||||
return errors::makeError(ctx, errors::DefExpectSymbol,
|
||||
list->elements[1]->location);
|
||||
}
|
||||
|
||||
// Analyze the value
|
||||
|
|
|
@ -66,8 +66,9 @@ MaybeNode Fn::make(semantics::AnalysisState &state, List *list) {
|
|||
|
||||
// TODO: Add support for docstring as the 3rd argument (4th element)
|
||||
if (list->count() < 2) {
|
||||
return errors::makeError<errors::FnNoArgsList>(
|
||||
list->elements[0]->location, "The argument list is mandatory.");
|
||||
return errors::makeError(ctx, errors::FnNoArgsList,
|
||||
list->elements[0]->location,
|
||||
"The argument list is mandatory.");
|
||||
}
|
||||
|
||||
Symbol *fnSym = llvm::dyn_cast<Symbol>(list->elements[0].get());
|
||||
|
@ -83,8 +84,8 @@ MaybeNode Fn::make(semantics::AnalysisState &state, List *list) {
|
|||
llvm::formatv("Arguments of a function has to be a list, got '{0}'",
|
||||
stringifyExprType(list->elements[1]->getType()));
|
||||
|
||||
return errors::makeError<errors::FnArgsMustBeList>(
|
||||
list->elements[1]->location, msg);
|
||||
return errors::makeError(ctx, errors::FnArgsMustBeList,
|
||||
list->elements[1]->location, msg);
|
||||
}
|
||||
|
||||
Ast body;
|
||||
|
|
|
@ -192,16 +192,17 @@ MaybeJITPtr Halley::lookup(exprs::Symbol &sym) const {
|
|||
auto *ns = ctx.getNS(sym.nsName);
|
||||
|
||||
if (ns == nullptr) {
|
||||
return errors::makeError<errors::CantResolveSymbol>(
|
||||
sym.location, "Can't find the namespace in the context: " + sym.nsName);
|
||||
return errors::makeError(ctx, errors::CantResolveSymbol, sym.location,
|
||||
"Can't find the namespace in the context: " +
|
||||
sym.nsName);
|
||||
}
|
||||
|
||||
auto *dylib = ctx.getLatestJITDylib(*ns);
|
||||
//
|
||||
|
||||
if (dylib == nullptr) {
|
||||
return errors::makeError<errors::CantResolveSymbol>(
|
||||
sym.location, "Don't know about namespace: " + sym.nsName);
|
||||
return errors::makeError(ctx, errors::CantResolveSymbol, sym.location,
|
||||
"Don't know about namespace: " + sym.nsName);
|
||||
}
|
||||
|
||||
auto expectedSymbol =
|
||||
|
@ -218,7 +219,8 @@ MaybeJITPtr Halley::lookup(exprs::Symbol &sym) const {
|
|||
llvm::raw_string_ostream os(errorMessage);
|
||||
llvm::handleAllErrors(expectedSymbol.takeError(),
|
||||
[&os](llvm::ErrorInfoBase &ei) { ei.log(os); });
|
||||
return errors::makeError<errors::CantResolveSymbol>(sym.location, os.str());
|
||||
return errors::makeError(ctx, errors::CantResolveSymbol, sym.location,
|
||||
os.str());
|
||||
}
|
||||
|
||||
auto rawFPtr = expectedSymbol->getAddress();
|
||||
|
@ -226,8 +228,8 @@ MaybeJITPtr Halley::lookup(exprs::Symbol &sym) const {
|
|||
auto fptr = reinterpret_cast<void (*)(void **)>(rawFPtr);
|
||||
|
||||
if (fptr == nullptr) {
|
||||
return errors::makeError<errors::CantResolveSymbol>(
|
||||
sym.location, "Lookup function is null!");
|
||||
return errors::makeError(ctx, errors::CantResolveSymbol, sym.location,
|
||||
"Lookup function is null!");
|
||||
}
|
||||
|
||||
return fptr;
|
||||
|
@ -277,8 +279,8 @@ llvm::Error Halley::addNS(Namespace &ns, reader::LocationRange &loc) {
|
|||
llvm::formatv("{0}#{1}", ns.name, ctx.getNumberOfJITDylibs(ns) + 1));
|
||||
|
||||
if (!newDylib) {
|
||||
return errors::makeError<errors::CompilationError>(
|
||||
loc, "Filed to create dylib for " + ns.name);
|
||||
return errors::makeError(ctx, errors::CompilationError, loc,
|
||||
"Filed to create dylib for " + ns.name);
|
||||
}
|
||||
|
||||
ctx.pushJITDylib(ns, &(*newDylib));
|
||||
|
@ -287,7 +289,7 @@ llvm::Error Halley::addNS(Namespace &ns, reader::LocationRange &loc) {
|
|||
auto maybeModule = ns.compileToLLVM();
|
||||
|
||||
if (!maybeModule.hasValue()) {
|
||||
return errors::makeError<errors::CompilationError>(loc);
|
||||
return errors::makeError(ctx, errors::CompilationError, loc);
|
||||
}
|
||||
|
||||
auto tsm = std::move(maybeModule.getValue());
|
||||
|
|
|
@ -227,7 +227,7 @@ exprs::MaybeNode Reader::readNumber(bool neg) {
|
|||
LocationRange loc(getCurrentLocation());
|
||||
|
||||
if (isdigit(*c) == 0) {
|
||||
return errors::makeError<errors::InvalidDigitForNumber>(loc);
|
||||
return errors::makeError(ctx, errors::InvalidDigitForNumber, loc);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
|
@ -238,7 +238,7 @@ exprs::MaybeNode Reader::readNumber(bool neg) {
|
|||
if ((isdigit(*c) != 0) || *c == '.') {
|
||||
if (*c == '.' && floatNum) {
|
||||
loc = LocationRange(getCurrentLocation());
|
||||
return errors::makeError<errors::TwoFloatPoints>(loc);
|
||||
return errors::makeError(ctx, errors::TwoFloatPoints, loc);
|
||||
}
|
||||
|
||||
if (*c == '.') {
|
||||
|
@ -254,7 +254,7 @@ exprs::MaybeNode Reader::readNumber(bool neg) {
|
|||
if (((std::isalpha(*c) != 0) && !empty) || empty) {
|
||||
advance();
|
||||
loc.start = getCurrentLocation();
|
||||
return errors::makeError<errors::InvalidDigitForNumber>(loc);
|
||||
return errors::makeError(ctx, errors::InvalidDigitForNumber, loc);
|
||||
}
|
||||
|
||||
loc.end = getCurrentLocation();
|
||||
|
@ -278,7 +278,7 @@ exprs::MaybeNode Reader::readSymbol() {
|
|||
msg = "An extra ')' is detected.";
|
||||
}
|
||||
|
||||
return errors::makeError<errors::InvalidCharacterForSymbol>(loc, msg);
|
||||
return errors::makeError(ctx, errors::InvalidCharacterForSymbol, loc, msg);
|
||||
}
|
||||
|
||||
if (*c == '-') {
|
||||
|
@ -338,7 +338,8 @@ exprs::MaybeNode Reader::readList() {
|
|||
advance(true);
|
||||
advance();
|
||||
list->location.end = getCurrentLocation();
|
||||
return errors::makeError<errors::EOFWhileScaningAList>(list->location);
|
||||
return errors::makeError(ctx, errors::EOFWhileScaningAList,
|
||||
list->location);
|
||||
}
|
||||
|
||||
switch (*c) {
|
||||
|
|
|
@ -84,7 +84,7 @@ MaybeNS SourceMgr::readNamespace(SereneContext &ctx, std::string name,
|
|||
|
||||
if (newBufOrErr == nullptr) {
|
||||
auto msg = llvm::formatv("Couldn't find namespace '{0}'", name).str();
|
||||
return errors::makeError<errors::NSLoadError>(importLoc, msg);
|
||||
return errors::makeError(ctx, errors::NSLoadError, importLoc, msg);
|
||||
}
|
||||
|
||||
auto bufferId = AddNewSourceBuffer(std::move(newBufOrErr), importLoc);
|
||||
|
@ -93,7 +93,7 @@ MaybeNS SourceMgr::readNamespace(SereneContext &ctx, std::string name,
|
|||
|
||||
if (bufferId == 0) {
|
||||
auto msg = llvm::formatv("Couldn't add namespace '{0}'", name).str();
|
||||
return errors::makeError<errors::NSAddToSMError>(importLoc, msg);
|
||||
return errors::makeError(ctx, errors::NSAddToSMError, importLoc, msg);
|
||||
}
|
||||
|
||||
// Since we moved the buffer to be added as the source storage we
|
||||
|
|
|
@ -19,8 +19,10 @@
|
|||
#ifndef SERENE_TEST_ERRORS_H
|
||||
#define SERENE_TEST_ERRORS_H
|
||||
|
||||
#include "serene/context.h"
|
||||
#include "serene/errors.h"
|
||||
|
||||
#include "../test_helpers.cpp.inc"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <llvm/ADT/StringMap.h>
|
||||
|
@ -33,25 +35,31 @@ namespace errors {
|
|||
TEST_CASE("Serene Error construction", "[errors]") {
|
||||
|
||||
std::unique_ptr<reader::LocationRange> range(dummyLocation());
|
||||
llvm::Error err = llvm::make_error<PassFailureError>(*range, "test error");
|
||||
auto ctx = makeSereneContext();
|
||||
llvm::Error err = makeError(*ctx, PassFailureError, *range, "test error");
|
||||
|
||||
auto unhandled =
|
||||
llvm::handleErrors(std::move(err), [&](const PassFailureError &e) {
|
||||
REQUIRE(e.message() == "test error");
|
||||
CHECK(errorVariants[e.ID].title == "PassFailureError");
|
||||
CHECK(errorVariants[e.ID].desc == "Pass Failure.");
|
||||
CHECK(errorVariants[e.ID].help.empty());
|
||||
});
|
||||
auto unhandled = llvm::handleErrors(std::move(err), [&](const Error &e) {
|
||||
REQUIRE(e.message() == "test error");
|
||||
const auto *v = getVariant(e.errorType);
|
||||
REQUIRE(v != nullptr);
|
||||
CHECK(v->title == "PassFailureError");
|
||||
CHECK(v->desc == "Pass Failure.");
|
||||
CHECK(v->help.empty());
|
||||
});
|
||||
|
||||
CHECK(!unhandled);
|
||||
}
|
||||
|
||||
TEST_CASE("getMessage function", "[errors]") {
|
||||
std::unique_ptr<reader::LocationRange> range(dummyLocation());
|
||||
if (auto err = llvm::make_error<PassFailureError>(*range, "test error")) {
|
||||
CHECK(getMessage(err) == "test error");
|
||||
}
|
||||
auto ctx = makeSereneContext();
|
||||
llvm::Error err = makeError(*ctx, PassFailureError, *range, "test error");
|
||||
|
||||
CHECK(getMessage(err) == "test error");
|
||||
|
||||
CHECK_ERR(PassFailureError, std::move(err));
|
||||
}
|
||||
|
||||
}; // namespace errors
|
||||
} // namespace serene
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "serene/errors.h"
|
||||
#include "serene/exprs/expression.h"
|
||||
#include "serene/exprs/list.h"
|
||||
#include "serene/exprs/symbol.h"
|
||||
|
@ -33,8 +34,8 @@ namespace exprs {
|
|||
TEST_CASE("List Expression", "[expression]") {
|
||||
std::unique_ptr<reader::LocationRange> range(dummyLocation());
|
||||
|
||||
Node sym = make<Symbol>(*range, "example");
|
||||
Node sym1 = make<Symbol>(*range, "example1");
|
||||
Node sym = make<Symbol>(*range, "example", "user");
|
||||
Node sym1 = make<Symbol>(*range, "example1", "user");
|
||||
|
||||
Node list = make<List>(*range);
|
||||
|
||||
|
@ -52,21 +53,21 @@ TEST_CASE("List Expression", "[expression]") {
|
|||
auto list3 = make<List>(*range, elements);
|
||||
|
||||
CHECK(list3->toString() ==
|
||||
"<List <List -> <List <List ->> <Symbol example>>");
|
||||
"<List <List -> <List <List ->> <Symbol user/example>>");
|
||||
|
||||
auto l = llvm::dyn_cast<List>(list.get());
|
||||
|
||||
l->append(sym1);
|
||||
|
||||
REQUIRE(list->getType() == ExprType::List);
|
||||
CHECK(list->toString() == "<List <Symbol example1>>");
|
||||
CHECK(list->toString() == "<List <Symbol user/example1>>");
|
||||
|
||||
l->append(sym);
|
||||
REQUIRE(l->count() == 2);
|
||||
|
||||
auto expr = l->at(1);
|
||||
REQUIRE(expr.hasValue());
|
||||
CHECK(expr.getValue()->toString() == "<Symbol example>");
|
||||
CHECK(expr.getValue()->toString() == "<Symbol user/example>");
|
||||
|
||||
expr = l->at(2);
|
||||
REQUIRE_FALSE(expr.hasValue());
|
||||
|
@ -80,111 +81,116 @@ TEST_CASE("List semantic analysis of 'def'", "[semantic,expression,list]") {
|
|||
|
||||
auto ctx = makeSereneContext();
|
||||
auto ns = ctx->makeNamespace("user", llvm::None);
|
||||
auto ast =
|
||||
llvm::cantFail(reader::read(*ctx, "(def (a) b)", "user", llvm::None));
|
||||
auto ast = llvm::cantFail(READ("(def (a) b)"));
|
||||
|
||||
SemanticEnv env;
|
||||
semantics::AnalysisState state(ns, env);
|
||||
semantics::AnalysisState state(*ns, env);
|
||||
auto afterAst = semantics::analyze(state, ast);
|
||||
|
||||
REQUIRE_FALSE(afterAst);
|
||||
// Fetch the first error
|
||||
CHECK(afterAst.takeError() == "<Error E1: >");
|
||||
CHECK_ERR(llvm::ErrorList, afterAst.takeError());
|
||||
|
||||
ast = reader::read("(def a)");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
ast = llvm::cantFail(READ("(def a)"));
|
||||
afterAst = semantics::analyze(state, ast);
|
||||
REQUIRE_FALSE(afterAst);
|
||||
CHECK(afterAst.getError()[0]->toString() == "<Error E2: Expected 3 got 2>");
|
||||
CHECK(errors::getMessage(afterAst.takeError()) ==
|
||||
"<Error E2: Expected 3 got 2>");
|
||||
|
||||
ast = reader::read("(def a b c)");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
ast = llvm::cantFail(READ("(def a b c)"));
|
||||
afterAst = semantics::analyze(state, ast);
|
||||
REQUIRE_FALSE(afterAst);
|
||||
CHECK(afterAst.getError()[0]->toString() == "<Error E2: Expected 3 got 4>");
|
||||
CHECK(errors::getMessage(afterAst.takeError()) ==
|
||||
"<Error E2: Expected 3 got 4>");
|
||||
|
||||
ast = reader::read("(def a b)");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
ast = llvm::cantFail(READ("(def a b)"));
|
||||
afterAst = semantics::analyze(state, ast);
|
||||
REQUIRE(afterAst);
|
||||
CHECK(astToString(&afterAst.getValue()) == "<Def a -> <Symbol b>>");
|
||||
CHECK(astToString(&(*afterAst)) == "<Def a -> <Symbol b>>");
|
||||
|
||||
ast = reader::read("(def a (fn () a))");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
ast = llvm::cantFail(READ("(def a (fn () a))"));
|
||||
afterAst = semantics::analyze(state, ast);
|
||||
REQUIRE(afterAst);
|
||||
CHECK(astToString(&afterAst.getValue()) ==
|
||||
CHECK(astToString(&(*afterAst)) ==
|
||||
"<Def a -> <Fn a <List -> to <Symbol a>>>");
|
||||
}
|
||||
|
||||
TEST_CASE("List semantic analysis for 'fn'", "[semantic]") {
|
||||
auto ctx = makeSereneContext();
|
||||
auto ns = makeNamespace(*ctx, "user", llvm::None);
|
||||
auto ast = reader::read("(fn)");
|
||||
auto afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE_FALSE(afterAst);
|
||||
REQUIRE(afterAst.getError().size() == 1);
|
||||
CHECK(afterAst.getError()[0]->toString() ==
|
||||
"<Error E3: The argument list is mandatory.>");
|
||||
// TEST_CASE("List semantic analysis for 'fn'", "[semantic]") {
|
||||
// auto ctx = makeSereneContext();
|
||||
// auto ns = ctx->makeNamespace("user", llvm::None);
|
||||
// auto ast = READ("(fn)");
|
||||
// auto afterAst = semantics::analyze(*ctx, *ast);
|
||||
|
||||
ast = reader::read("(fn ())");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE(afterAst);
|
||||
CHECK(astToString(&afterAst.getValue()) == "<Fn ___fn___0 <List -> to <>>");
|
||||
// REQUIRE_FALSE(afterAst);
|
||||
// REQUIRE(afterAst.takeError().size() == 1);
|
||||
// CHECK(afterAst.getError()[0]->toString() ==
|
||||
// "<Error E3: The argument list is mandatory.>");
|
||||
|
||||
ast = reader::read("(fn (a b c) a a a)");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE(afterAst);
|
||||
CHECK(astToString(&afterAst.getValue()) ==
|
||||
"<Fn ___fn___1 <List <Symbol a> <Symbol b> <Symbol c>> to <Symbol a> "
|
||||
"<Symbol a> <Symbol a>>");
|
||||
// ast = reader::read("(fn ())");
|
||||
// afterAst = semantics::analyze(*ctx, ast.getValue());
|
||||
// REQUIRE(afterAst);
|
||||
// CHECK(astToString(&afterAst.getValue()) == "<Fn ___fn___0 <List -> to
|
||||
// <>>");
|
||||
|
||||
ast = reader::read("(fn () a b)");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE(afterAst);
|
||||
CHECK(astToString(&afterAst.getValue()) ==
|
||||
"<Fn ___fn___2 <List -> to <Symbol a> <Symbol b>>");
|
||||
// ast = reader::read("(fn (a b c) a a a)");
|
||||
// afterAst = semantics::analyze(*ctx, ast.getValue());
|
||||
// REQUIRE(afterAst);
|
||||
// CHECK(astToString(&afterAst.getValue()) ==
|
||||
// "<Fn ___fn___1 <List <Symbol a> <Symbol b> <Symbol c>> to <Symbol a>
|
||||
// "
|
||||
// "<Symbol a> <Symbol a>>");
|
||||
|
||||
ast = reader::read("(fn (x) (fn (y) x) z)");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE(afterAst);
|
||||
CHECK(astToString(&afterAst.getValue()) ==
|
||||
"<Fn ___fn___4 <List <Symbol x>> to <Fn ___fn___3 <List <Symbol y>> "
|
||||
"to <Symbol x>> <Symbol z>>");
|
||||
// ast = reader::read("(fn () a b)");
|
||||
// afterAst = semantics::analyze(*ctx, ast.getValue());
|
||||
// REQUIRE(afterAst);
|
||||
// CHECK(astToString(&afterAst.getValue()) ==
|
||||
// "<Fn ___fn___2 <List -> to <Symbol a> <Symbol b>>");
|
||||
|
||||
ast = reader::read("(fn (x) (def a b) (def b c))");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE(afterAst);
|
||||
CHECK(astToString(&afterAst.getValue()) ==
|
||||
"<Fn ___fn___5 <List <Symbol x>> to <Def a -> <Symbol b>> <Def b -> "
|
||||
"<Symbol c>>>");
|
||||
}
|
||||
// ast = reader::read("(fn (x) (fn (y) x) z)");
|
||||
// afterAst = semantics::analyze(*ctx, ast.getValue());
|
||||
// REQUIRE(afterAst);
|
||||
// CHECK(astToString(&afterAst.getValue()) ==
|
||||
// "<Fn ___fn___4 <List <Symbol x>> to <Fn ___fn___3 <List <Symbol y>>
|
||||
// " "to <Symbol x>> <Symbol z>>");
|
||||
|
||||
TEST_CASE("Complex semantic analysis", "[semantic]") {
|
||||
auto ctx = makeSereneContext();
|
||||
auto ns = makeNamespace(*ctx, "user", llvm::None);
|
||||
auto ast =
|
||||
reader::read("(def a (fn (x) x))\n((def b (fn (x) (fn (y) y))))\n\n");
|
||||
auto afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE(afterAst);
|
||||
CHECK(astToString(&afterAst.getValue()) ==
|
||||
"<Def a -> <Fn a <List <Symbol x>> to <Symbol x>>> <Call <Def b -> "
|
||||
"<Fn b <List <Symbol x>> to <Fn ___fn___1 <List <Symbol y>> to "
|
||||
"<Symbol y>>>> >");
|
||||
// ast = reader::read("(fn (x) (def a b) (def b c))");
|
||||
// afterAst = semantics::analyze(*ctx, ast.getValue());
|
||||
// REQUIRE(afterAst);
|
||||
// CHECK(astToString(&afterAst.getValue()) ==
|
||||
// "<Fn ___fn___5 <List <Symbol x>> to <Def a -> <Symbol b>> <Def b ->
|
||||
// "
|
||||
// "<Symbol c>>>");
|
||||
// }
|
||||
|
||||
ctx = makeSereneContext();
|
||||
ns = makeNamespace(*ctx, "user", llvm::None);
|
||||
ast = reader::read("((a b))");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE_FALSE(afterAst);
|
||||
auto errs = afterAst.getError();
|
||||
CHECK(errs[0]->toString() == "<Error E5: Can't resolve the symbol 'a'>");
|
||||
// TEST_CASE("Complex semantic analysis", "[semantic]") {
|
||||
// auto ctx = makeSereneContext();
|
||||
// auto ns = makeNamespace(*ctx, "user", llvm::None);
|
||||
// auto ast =
|
||||
// reader::read("(def a (fn (x) x))\n((def b (fn (x) (fn (y) y))))\n\n");
|
||||
// auto afterAst = semantics::analyze(*ctx, ast.getValue());
|
||||
// REQUIRE(afterAst);
|
||||
// CHECK(astToString(&afterAst.getValue()) ==
|
||||
// "<Def a -> <Fn a <List <Symbol x>> to <Symbol x>>> <Call <Def b -> "
|
||||
// "<Fn b <List <Symbol x>> to <Fn ___fn___1 <List <Symbol y>> to "
|
||||
// "<Symbol y>>>> >");
|
||||
|
||||
ctx = makeSereneContext();
|
||||
ns = makeNamespace(*ctx, "user", llvm::None);
|
||||
ast = reader::read("(def a (fn (x) x)) (a b)");
|
||||
afterAst = reader::analyze(*ctx, ast.getValue());
|
||||
REQUIRE(afterAst);
|
||||
// ctx = makeSereneContext();
|
||||
// ns = makeNamespace(*ctx, "user", llvm::None);
|
||||
// ast = reader::read("((a b))");
|
||||
// afterAst = semantics::analyze(*ctx, ast.getValue());
|
||||
// REQUIRE_FALSE(afterAst);
|
||||
// auto errs = afterAst.getError();
|
||||
// CHECK(errs[0]->toString() == "<Error E5: Can't resolve the symbol 'a'>");
|
||||
|
||||
CHECK(astToString(&afterAst.getValue()) ==
|
||||
"<Def a -> <Fn a <List <Symbol x>> to <Symbol x>>> <Call <Fn a <List "
|
||||
"<Symbol x>> to <Symbol x>> <Symbol b>>");
|
||||
}
|
||||
// ctx = makeSereneContext();
|
||||
// ns = makeNamespace(*ctx, "user", llvm::None);
|
||||
// ast = reader::read("(def a (fn (x) x)) (a b)");
|
||||
// afterAst = semantics::analyze(*ctx, ast.getValue());
|
||||
// REQUIRE(afterAst);
|
||||
|
||||
// CHECK(astToString(&afterAst.getValue()) ==
|
||||
// "<Def a -> <Fn a <List <Symbol x>> to <Symbol x>>> <Call <Fn a <List
|
||||
// "
|
||||
// "<Symbol x>> to <Symbol x>> <Symbol b>>");
|
||||
// }
|
||||
} // namespace exprs
|
||||
} // namespace serene
|
||||
|
|
|
@ -21,13 +21,9 @@
|
|||
|
||||
#include "serene/reader/reader.h"
|
||||
|
||||
#include "../test_helpers.cpp.inc"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
// *IMPORTANT NOTE:* The `READ` macro is just a quick way to eliminate
|
||||
// the overhead of writing the same function signature
|
||||
// over and over again. Nothing special about it.
|
||||
#define READ(input) reader::read(*ctx, input, "user", llvm::None)
|
||||
|
||||
namespace serene {
|
||||
namespace reader {
|
||||
|
||||
|
|
|
@ -21,7 +21,19 @@
|
|||
|
||||
#include "serene/reader/location.h"
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
// *IMPORTANT NOTE:* The `READ` macro is just a quick way to eliminate
|
||||
// the overhead of writing the same function signature
|
||||
// over and over again. Nothing special about it.
|
||||
#define READ(input) reader::read(*ctx, input, "user", llvm::None)
|
||||
|
||||
#define CHECK_ERR_MSG(e, s) CHECK(serene::errors::getMessage(e) == s)
|
||||
// `llvm::Error`s has to be checked in the same scope. This macro makes
|
||||
// the check easy while were testing the other aspects of the error.
|
||||
// `t` is the concrete error type and `e` is the error instance.
|
||||
#define CHECK_ERR(t, e) \
|
||||
auto unhandled = \
|
||||
llvm::handleErrors(e, [&](const Error &x) { CHECK(x.errorType == t); }); \
|
||||
CHECK(!unhandled);
|
||||
|
||||
namespace serene {
|
||||
|
||||
|
|
|
@ -65,15 +65,18 @@ static void inNamespace(llvm::StringRef name, llvm::raw_ostream &os,
|
|||
void ErrorsBackend::createErrorClass(const int id, llvm::Record &defRec,
|
||||
llvm::raw_ostream &os) {
|
||||
(void)records;
|
||||
(void)id;
|
||||
|
||||
const auto recName = defRec.getName();
|
||||
|
||||
os << "class " << recName << " : public llvm::ErrorInfo<" << recName << ", "
|
||||
<< "SereneError> {\n"
|
||||
<< "public:\n"
|
||||
<< " using llvm::ErrorInfo<" << recName << ", "
|
||||
<< "SereneError>::ErrorInfo;\n"
|
||||
<< " constexpr static const int ID = " << id << ";\n};\n\n";
|
||||
os << " " << recName << ",\n";
|
||||
// os << "class " << recName << " : public llvm::ErrorInfo<" << recName << ",
|
||||
// "
|
||||
// << "SereneError> {\n"
|
||||
// << "public:\n"
|
||||
// << " using llvm::ErrorInfo<" << recName << ", "
|
||||
// << "SereneError>::ErrorInfo;\n"
|
||||
// << " constexpr static const int ID = " << id << ";\n};\n\n";
|
||||
};
|
||||
|
||||
void ErrorsBackend::createNSBody(llvm::raw_ostream &os) {
|
||||
|
@ -93,6 +96,7 @@ void ErrorsBackend::createNSBody(llvm::raw_ostream &os) {
|
|||
|
||||
os << "#ifdef GET_CLASS_DEFS\n";
|
||||
inNamespace("serene::errors", os, [&](llvm::raw_ostream &os) {
|
||||
os << "enum ErrorType {\n";
|
||||
for (size_t i = 0; i < indexList->size(); i++) {
|
||||
llvm::Record *defRec = indexList->getElementAsRecord(i);
|
||||
|
||||
|
@ -102,7 +106,9 @@ void ErrorsBackend::createNSBody(llvm::raw_ostream &os) {
|
|||
|
||||
createErrorClass(i, *defRec, os);
|
||||
}
|
||||
os << "};\n\n";
|
||||
|
||||
os << "#define NUMBER_OF_ERRORS " << indexList->size() << "\n";
|
||||
os << "static const ErrorVariant errorVariants[" << indexList->size()
|
||||
<< "] = {\n";
|
||||
|
||||
|
@ -164,7 +170,7 @@ void ErrorsBackend::run(llvm::raw_ostream &os) {
|
|||
llvm::emitSourceFileHeader("Serene's Errors collection", os);
|
||||
|
||||
// DO NOT GUARD THE HEADER WITH #ifndef ...
|
||||
os << "#include \"serene/errors/base.h\"\n\n#include "
|
||||
os << "#include \"serene/errors/variant.h\"\n\n#include "
|
||||
"<llvm/Support/Error.h>\n\n";
|
||||
|
||||
createNSBody(os);
|
||||
|
|
Loading…
Reference in New Issue