diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f24ee4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +test/test +.vscode \ No newline at end of file diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..01493fe --- /dev/null +++ b/src/error.h @@ -0,0 +1,45 @@ +#ifndef NEER_ERROR_H +#define NEER_ERROR_H + +#include +#include +#include + +#define ERR(fmt,...) Error(nerr::sfmt("%s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)) + +namespace nerr +{ + template + std::string sfmt(const std::string &format, Args... args) + { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) + { + throw std::runtime_error("Error during formatting string."); + } + auto size = static_cast(size_s); + std::unique_ptr buf(new char[size]); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside + } + + class Error : public std::runtime_error + { + public: + Error(const std::string &s) : runtime_error(s){}; + Error(const char *s) : runtime_error(s){}; + Error(): runtime_error(""){}; + + inline friend bool operator==(const Error& l, const Error& r) + { + return std::string(l.what()) == std::string(r.what()); + } + + inline Error operator+(const Error& other) + { + return Error(std::string(this->what()) + "\n" + other.what()); + } + }; +} + +#endif \ No newline at end of file diff --git a/src/nerr.h b/src/nerr.h new file mode 100644 index 0000000..aa37bf0 --- /dev/null +++ b/src/nerr.h @@ -0,0 +1,10 @@ +#ifndef NEER_H +#define NEER_H +#include +#include + +#include "error.h" +#include "option.h" +#include "result.h" + +#endif \ No newline at end of file diff --git a/src/option.h b/src/option.h new file mode 100644 index 0000000..4a11cb2 --- /dev/null +++ b/src/option.h @@ -0,0 +1,92 @@ +#ifndef NEER_OPTION_H +#define NEER_OPTION_H + +#define None(T) (nerr::Option()) +#define Some(v) (nerr::Option(v)) + +namespace nerr +{ + template class Result; + + template + class Option + { + + public: + Option(T obj) : m_some(obj), m_notnull(true){}; + Option() : m_notnull(false){}; + + Option& operator=(const T& value) {insert(value); return *this;}; + inline friend bool operator==(const Option& l, const Option& r) { + if(!l.m_notnull && !r.m_notnull) + { + return true; + } + if(l.m_notnull && r.m_notnull) + { + return l.m_some == r.m_some; + } + return false; + }; + + inline bool is_none(){return !m_notnull;}; + inline bool is_some(){return m_notnull;}; + inline T& unwrap() + { + if(m_notnull) + { + return m_some; + } + throw Error("Object is None"); + } + inline T& get_or_insert(T value) + { + if(!m_notnull) + { + insert(value); + } + return m_some; + } + inline T& insert(T value) + { + m_some = value; + m_notnull = true; + return m_some; + } + + template + inline Result ok_or(E err) + { + if(m_notnull) + { + return Result(m_some); + } + return Result(err); + } + + template + inline Option map(const std::function& fn) + { + if(is_some()) + { + return fn(m_some); + } + return Option(); + } + + template + inline U map_or(U defv, const std::function& fn) + { + if(is_some()) + { + return fn(m_some); + } + return defv; + } + + private: + T m_some; + bool m_notnull; + }; +} +#endif \ No newline at end of file diff --git a/src/result.h b/src/result.h new file mode 100644 index 0000000..05b9e42 --- /dev/null +++ b/src/result.h @@ -0,0 +1,82 @@ +#ifndef NEER_RESULT_H +#define NEER_RESULT_H +//#define Ok(v) (nerr::Result(v)) +//#define Err(e) (nerr::Result(e)) + +namespace nerr +{ + //template class Option; + + template + class Result + { + + public: + Result(T obj) : m_result(obj) {}; + Result(E err) { m_error.insert(err); }; + Result(){}; + + inline Result& operator=(const T& value) {m_result = value; m_error = None(E); return *this;}; + inline Result& operator=(const E& err) {m_error.insert(err); return *this;}; + + inline bool is_ok(){return m_error.is_none();}; + inline bool is_err(){return m_error.is_some();}; + + inline T& unwrap() + { + if(is_ok()) + { + return m_result; + } + throw m_error.unwrap(); + } + + inline T& expect(E e) + { + if(is_ok()) + { + return m_result; + } + throw e + m_error.unwrap(); + } + + template + inline Result map(const std::function& fn) + { + if(is_ok()) + { + return fn(m_result); + } + return m_error.unwrap(); + } + + template + inline Result map_err(const std::function& fn) + { + if(!is_ok()) + { + return fn(m_error.unwrap()); + } + return m_result; + } + + template + inline U map_or(U defv, const std::function& fn) + { + if(is_ok()) + { + return fn(m_result); + } + return defv; + } + + Option err() { return m_error; } + Option ok() { return is_ok()?Option(m_result): Option(); } + + private: + T m_result; + Option m_error; + }; +} + +#endif \ No newline at end of file diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..25fbab1 --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,168 @@ +#include +#include "../src/nerr.h" +#include +#include + + + +using namespace std; +using namespace nerr; + +#define assert(v,fmt,...) if(!(v)) { \ + throw Error(nerr::sfmt("ASSERT ERROR %s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)); \ +} + + +void test(const char* desc, const std::function& lambda) +{ + std::cout << "Run test: " << desc << ".........."; + try { + lambda(); + std::cout << "OK" << std::endl; + } + catch(nerr::Error e) + { + std::cout << "FAILED" << std::endl; + std::cout << e.what() << std::endl; + } +} + +Result test_return() +{ + return string("Error"); +} + +int main(int argc, char const *argv[]) +{ + test("Test Option", [](){ + Option opt; + assert(opt.is_none(), "Option is not empty"); + assert(! opt.is_some(), "Option has some value"); + string text = "This is some data"; + opt = text; + assert(opt.is_some(), "Option shall has some value"); + assert(opt.unwrap() == text,"Value mistmatch"); + }); + + test("Option -> Result convert", [](){ + Option opt; + Result ret = opt.ok_or(ERR("Option is null")); + assert(ret.is_err(), "Object should containe error object"); + string err = ret.err().unwrap().what(); + assert( err.find("Option is null") != string::npos, "Error message mistmatch"); + + opt = "abc"; + ret = opt.ok_or(ERR("Option is null")); + assert(ret.is_ok(), "Object should contain data"); + assert(ret.unwrap() == "abc", "Data mistmatch"); + }); + + test("Option map", [](){ + auto fn = [](int x){ + return "Hello"; + }; + + Option opt; + Option opt1 = opt.map(fn); + assert(opt.is_none() == opt1.is_none(), "Nothing shall be changed"); + + opt = 2; + opt1 = opt.map(fn); + assert(opt1.unwrap() == "Hello", "Value mismatch"); + }); + + + test("Option map or", [](){ + auto fn = [](int x){ + return ++x; + }; + + Option opt; + int v = opt.map_or(0, fn); + assert(v == 0, "Value mismatch"); + + opt = 2; + v = opt.map_or(0, fn); + assert(v == 3, "Value mismatch %d",v); + }); + + test("Test Result", []{ + Result ret = ERR("Error"); + assert(ret.is_err(), "Object should containe error object"); + ret = string("Hello"); + assert(ret.is_ok(), "Object should contain data"); + assert(ret.unwrap() == "Hello", "Data mistmatch"); + }); + + test("Result->option convert",[](){ + Result ret = ERR("Error"); + Option opt_v = ret.ok(); + assert(opt_v.is_none(), "Value shall not be present"); + Option opt_err = ret.err(); + assert(opt_err.is_some(), "Erreur shall not be none"); + + ret = string("Hello"); + opt_v = ret.ok(); + assert(opt_v.is_some(), "Value shall be present"); + opt_err = ret.err(); + assert(opt_err.is_none(), "Erreur shall be none"); + }); + + test("Result expect", [](){ + try{ + Result ret = ERR("Error"); + auto s = ret.expect(ERR("Expect message")); + assert(false, "Should not happend"); + } + catch(Error e) + { + assert(true, "%s", e.what()); + } + }); + + test("Result map", [](){ + auto err = ERR("Error"); + auto fn = [](string data){ + cout << data << endl; + return 1; + }; + Result ret = err; + Result ret1 = ret.map(fn); + assert(ret1.is_err(), "mapped result shall be an error"); + assert(ret1.err() == ret.err(), "Error object mistmatch"); + + ret = string("Hello"); + ret1 = ret.map(fn); + assert(ret1.is_ok(), "mapped result shall not be an error"); + assert(ret1.unwrap() == 1, "Value mistmatch"); + }); + + + test("Result map err", [](){ + auto fn = [](int x){ + cout << "Error code is " << x << endl; + return "Hello"; + }; + Result ret = true; + Result ret1 = ret.map_err(fn); + assert(ret1.unwrap() == true, "Value mistmatch"); + + ret = 10; + ret1 = ret.map_err(fn); + assert(ret1.err().unwrap() == "Hello", "Value mistmatch"); + }); + + test("Result map or", [](){ + auto fn = [](int x){ + return ++x; + }; + + Result ret = ERR("Error"); + int v = ret.map_or(0, fn); + assert(v == 0, "Value mismatch"); + + ret = 2; + v = ret.map_or(0, fn); + assert(v == 3, "Value mismatch %d",v); + }); +}