Change the reader interface to return errors instead of emiting them

* Change the `Result` interface to let the compiler use the concrete
  type in place of a `Result`
This commit is contained in:
Sameer Rahmani 2021-10-24 12:34:20 +01:00
parent 3725d15521
commit 3fb6fb3740
8 changed files with 109 additions and 52 deletions

View File

@ -47,8 +47,11 @@ class Error
std::string message;
public:
Error(reader::LocationRange &loc, ErrorVariant *err, llvm::StringRef msg)
: location(loc), variant(err), message(msg){};
Error(reader::LocationRange &loc, ErrorVariant &err, llvm::StringRef msg)
: location(loc), variant(&err), message(msg){};
Error(reader::LocationRange &loc, ErrorVariant &err)
: location(loc), variant(&err){};
std::string toString() const;
reader::LocationRange &where();

View File

@ -123,9 +123,9 @@ Result<Node, ErrorTree> makeSuccessfulNode(Args &&...args) {
};
/// The hlper function to create an Errorful `Result<T,...>` (`T` would be
/// either
/// `Node` or `Ast` most of the time) with just one error creating from passing
/// any argument to this function to the `serene::errors::Error` constructor.
/// either `Node` or `Ast` most of the time) with just one error created from
/// passing any argument to this function to the `serene::errors::Error`
/// constructor.
template <typename T, typename... Args>
Result<T, ErrorTree> makeErrorful(Args &&...args) {
std::vector<ErrorPtr> v{
@ -133,6 +133,16 @@ Result<T, ErrorTree> makeErrorful(Args &&...args) {
return Result<T, ErrorTree>::error(v);
};
/// The hlper function to create an Error node (The failure case of a MaybeNod)
/// with just one error created from passing any argument to this function to
/// the `serene::errors::Error` constructor.
template <typename... Args>
MaybeNode makeErrorNode(Args &&...args) {
std::vector<ErrorPtr> v{
std::move(makeAndCast<errors::Error>(std::forward<Args>(args)...))};
return MaybeNode::error(v);
};
/// Convert the given AST to string by calling the `toString` method
/// of each node.
SERENE_EXPORT std::string astToString(const Ast *);

View File

@ -27,8 +27,11 @@
* one char at a time till we reach the end of the input. Please note that
* when we call the `advance` function to move forward in the buffer, we
* can't go back. In order to look ahead in the buffer without moving in the
* buffer we use the `nextChar` method-
* buffer we use the `nextChar` method.
*
* We have dedicated methods to read different forms like `list`, `symbol`
* `number` and etc. Each of them return a `MaybeNode` that in the success
* case contains the node and an `Error` on the failure case.
*/
#ifndef SERENE_READER_READER_H
@ -97,10 +100,10 @@ private:
// The property to store the ast tree
exprs::Ast ast;
exprs::Node readSymbol();
exprs::Node readNumber(bool);
exprs::Node readList();
exprs::Node readExpr();
exprs::MaybeNode readSymbol();
exprs::MaybeNode readNumber(bool);
exprs::MaybeNode readList();
exprs::MaybeNode readExpr();
bool isEndOfBuffer(const char *);
@ -114,19 +117,19 @@ public:
/// Parses the the input and creates a possible AST out of it or errors
/// otherwise.
Result<exprs::Ast> read();
exprs::MaybeAst read();
~Reader();
};
/// Parses the given `input` string and returns a `Result<ast>`
/// which may contains an AST or an `llvm::Error`
Result<exprs::Ast> read(SereneContext &ctx, llvm::StringRef input,
llvm::StringRef ns,
llvm::Optional<llvm::StringRef> filename);
Result<exprs::Ast> read(SereneContext &ctx, llvm::MemoryBufferRef input,
llvm::StringRef ns,
llvm::Optional<llvm::StringRef> filename);
exprs::MaybeAst read(SereneContext &ctx, llvm::StringRef input,
llvm::StringRef ns,
llvm::Optional<llvm::StringRef> filename);
exprs::MaybeAst read(SereneContext &ctx, llvm::MemoryBufferRef input,
llvm::StringRef ns,
llvm::Optional<llvm::StringRef> filename);
} // namespace serene::reader
#endif

View File

@ -48,6 +48,15 @@ namespace serene {
///
/// In order check for a value being errorful or successful checkout the `ok`
/// method or simply use the value as a conditiona.
///
/// This class is setup in a way tha you can us a value of type `T` in places
/// that the compiler expects a `Result<T>`. So for example:
///
/// \code
/// Result<int> fn() {return 2;}
/// \endcode
///
/// works perfectly.
template <typename T, typename E = llvm::Error>
class SERENE_EXPORT Result {
@ -60,6 +69,33 @@ class SERENE_EXPORT Result {
Result(InPlace i, Content &&c) : contents(i, std::forward<Content>(c)){};
public:
constexpr Result(const T &v)
: Result(std::in_place_index_t<0>(), std::move(v)){};
/// Return a pointer to the success case value of the result. It is
/// important to check for the success case before calling this function.
constexpr const T *getPointer() const { return &getValue(); }
/// Return a pointer to the success case value of the result. It is
/// important to check for the success case before calling this function.
T *getPointer() { return &getValue(); }
/// Return a pointer to the success case value of the result. It is
/// important to check for the success case before calling this function.
T *operator->() { return getPointer(); }
/// Return a pointer to the success case value of the result. It is
/// important to check for the success case before calling this function.
constexpr const T *operator->() const { return getPointer(); }
/// Dereference the success case and returns the value. It is
/// important to check for the success case before calling this function.
constexpr const T &operator*() const & { return getValue(); }
/// Dereference the success case and returns the value. It is
/// important to check for the success case before calling this function.
T &operator*() & { return getValue(); }
/// Create a succesfull result with the given value of type `T`.
static Result success(T v) {
return Result(std::in_place_index_t<0>(), std::move(v));

View File

@ -97,7 +97,7 @@ MaybeNode Call::make(SereneContext &ctx, List *list) {
if (!maybeResult.hasValue()) {
std::string msg =
llvm::formatv("Can't resolve the symbol '{0}'", sym->name);
return makeErrorful<Node>(sym->location, &errors::CantResolveSymbol, msg);
return makeErrorful<Node>(sym->location, errors::CantResolveSymbol, msg);
}
targetNode = maybeResult.getValue();
@ -122,7 +122,7 @@ MaybeNode Call::make(SereneContext &ctx, List *list) {
default: {
std::string msg = llvm::formatv("Don't know how to call a '{0}'",
stringifyExprType(first->getType()));
return makeErrorful<Node>(first->location, &errors::DontKnowHowToCallNode,
return makeErrorful<Node>(first->location, errors::DontKnowHowToCallNode,
msg);
}
};

View File

@ -53,7 +53,7 @@ MaybeNode Def::make(SereneContext &ctx, List *list) {
if (list->count() != 3) {
std::string msg = llvm::formatv("Expected 3 got {0}", list->count());
return makeErrorful<Node>(list->elements[0]->location,
&errors::DefWrongNumberOfArgs, msg);
errors::DefWrongNumberOfArgs, msg);
}
// Make sure that the list starts with a `def`
@ -67,7 +67,7 @@ MaybeNode Def::make(SereneContext &ctx, List *list) {
Symbol *binding = llvm::dyn_cast<Symbol>(list->elements[1].get());
if (binding == nullptr) {
return makeErrorful<Node>(list->elements[1]->location,
&errors::DefExpectSymbol, "");
errors::DefExpectSymbol, "");
}
// Analyze the value

View File

@ -63,8 +63,7 @@ void Fn::setName(std::string n) { this->name = std::move(n); };
MaybeNode Fn::make(SereneContext &ctx, List *list) {
// TODO: Add support for docstring as the 3rd argument (4th element)
if (list->count() < 2) {
return makeErrorful<Node>(list->elements[0]->location,
&errors::FnNoArgsList,
return makeErrorful<Node>(list->elements[0]->location, errors::FnNoArgsList,
"The argument list is mandatory.");
}
@ -82,7 +81,7 @@ MaybeNode Fn::make(SereneContext &ctx, List *list) {
stringifyExprType(list->elements[1]->getType()));
return makeErrorful<Node>(list->elements[1]->location,
&errors::FnArgsMustBeList, msg);
errors::FnArgsMustBeList, msg);
}
Ast body;

View File

@ -19,6 +19,7 @@
#include "serene/reader/reader.h"
#include "serene/errors/constants.h"
#include "serene/exprs/expression.h"
#include "serene/exprs/list.h"
#include "serene/exprs/number.h"
#include "serene/exprs/symbol.h"
@ -154,7 +155,7 @@ bool Reader::isValidForIdentifier(char c) {
/// Reads a number,
/// \param neg whether to read a negative number or not.
exprs::Node Reader::readNumber(bool neg) {
exprs::MaybeNode Reader::readNumber(bool neg) {
READER_LOG("Reading a number...");
std::string number(neg ? "-" : "");
bool floatNum = false;
@ -166,8 +167,7 @@ exprs::Node Reader::readNumber(bool neg) {
LocationRange loc(getCurrentLocation());
if (isdigit(*c) == 0) {
ctx.diagEngine->emitSyntaxError(loc, errors::InvalidDigitForNumber);
terminate(ctx, 1);
return exprs::makeErrorNode(loc, errors::InvalidDigitForNumber);
}
for (;;) {
@ -205,7 +205,7 @@ exprs::Node Reader::readNumber(bool neg) {
/// Reads a symbol. If the symbol looks like a number
/// If reads it as number
exprs::Node Reader::readSymbol() {
exprs::MaybeNode Reader::readSymbol() {
READER_LOG("Reading a symbol...");
LocationRange loc;
const auto *c = nextChar();
@ -220,9 +220,7 @@ exprs::Node Reader::readSymbol() {
msg = "An extra ')' is detected.";
}
ctx.diagEngine->emitSyntaxError(loc, errors::InvalidCharacterForSymbol,
msg);
terminate(ctx, 1);
return exprs::makeErrorNode(loc, errors::InvalidCharacterForSymbol, msg);
}
if (*c == '-') {
@ -254,11 +252,11 @@ exprs::Node Reader::readSymbol() {
}
loc.end = getCurrentLocation();
return exprs::make<exprs::Symbol>(loc, sym);
return exprs::makeSuccessfulNode<exprs::Symbol>(loc, sym);
};
/// Reads a list recursively
exprs::Node Reader::readList() {
exprs::MaybeNode Reader::readList() {
READER_LOG("Reading a list...");
const auto *c = nextChar();
@ -278,9 +276,7 @@ exprs::Node Reader::readList() {
advance(true);
advance();
list->location.end = getCurrentLocation();
ctx.diagEngine->emitSyntaxError(list->location,
errors::EOFWhileScaningAList);
terminate(ctx, 1);
return exprs::makeErrorNode(list->location, errors::EOFWhileScaningAList);
}
switch (*c) {
@ -293,22 +289,27 @@ exprs::Node Reader::readList() {
default:
advance(true);
list->append(readExpr());
auto expr = readExpr();
if (!expr) {
return expr;
}
list->append(expr.getValue());
}
} while (!list_terminated);
return list;
return exprs::MaybeNode::success(list);
};
/// Reads an expression by dispatching to the proper reader function.
exprs::Node Reader::readExpr() {
exprs::MaybeNode Reader::readExpr() {
const auto *c = nextChar(true);
READER_LOG("Read char at `readExpr`: " << *c);
if (isEndOfBuffer(c)) {
return nullptr;
return exprs::EmptyNode;
}
switch (*c) {
@ -326,7 +327,7 @@ exprs::Node Reader::readExpr() {
/// Reads all the expressions in the reader's buffer as an AST.
/// Each expression type (from the reader perspective) has a
/// reader function.
Result<exprs::Ast> Reader::read() {
exprs::MaybeAst Reader::read() {
for (size_t current_pos = 0; current_pos < buf.size();) {
const auto *c = nextChar(true);
@ -337,29 +338,34 @@ Result<exprs::Ast> Reader::read() {
advance(true);
auto tmp{readExpr()};
auto tmp = readExpr();
if (tmp) {
this->ast.push_back(move(tmp));
if (tmp.getValue() == nullptr) {
break;
}
this->ast.push_back(move(tmp.getValue()));
} else {
break;
return exprs::MaybeAst::error(tmp.getError());
}
}
return Result<exprs::Ast>::success(std::move(this->ast));
return exprs::MaybeAst::success(std::move(this->ast));
};
Result<exprs::Ast> read(SereneContext &ctx, const llvm::StringRef input,
llvm::StringRef ns,
llvm::Optional<llvm::StringRef> filename) {
exprs::MaybeAst read(SereneContext &ctx, const llvm::StringRef input,
llvm::StringRef ns,
llvm::Optional<llvm::StringRef> filename) {
reader::Reader r(ctx, input, ns, filename);
auto ast = r.read();
return ast;
}
Result<exprs::Ast> read(SereneContext &ctx, const llvm::MemoryBufferRef input,
llvm::StringRef ns,
llvm::Optional<llvm::StringRef> filename) {
exprs::MaybeAst read(SereneContext &ctx, const llvm::MemoryBufferRef input,
llvm::StringRef ns,
llvm::Optional<llvm::StringRef> filename) {
reader::Reader r(ctx, input, ns, filename);
auto ast = r.read();