Refactor the error handling system to have only one llvm error with many variants

This commit is contained in:
Sameer Rahmani 2022-03-10 19:31:48 +00:00
parent 1cd5824608
commit 4605e22e68
16 changed files with 267 additions and 191 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}
};

View File

@ -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

View File

@ -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;

View File

@ -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());

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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);