diff --git a/src/ro.h b/src/ro.h index 14a40ad..15189c7 100644 --- a/src/ro.h +++ b/src/ro.h @@ -4,13 +4,14 @@ #include #include #include -#include +#include +#include #define None(T) (ro::Option()) #define Some(v) (ro::Option(v)) -#define ERR(fmt,...) Error(ro::sfmt("%s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)) -#define Err(e) (ro::Result(e)) -#define Ok(v) (ro::Result(v)) +#define ERR(fmt, ...) Error(ro::sfmt("%s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)) +// #define Err(e) (ro::Result(e)) +// #define Ok(v) (ro::Result(v)) namespace ro { @@ -33,20 +34,21 @@ namespace ro public: Error(const std::string &s) : runtime_error(s){}; Error(const char *s) : runtime_error(s){}; - Error(): runtime_error(""){}; + Error() : runtime_error(""){}; - inline friend bool operator==(const Error& l, const Error& r) + 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) + inline Error operator+(const Error &other) { return Error(std::string(this->what()) + "\n" + other.what()); } }; - template class Result; + template + class Result; template class Option @@ -56,38 +58,94 @@ namespace ro 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) + 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) + 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() + inline friend Option operator|(const Option &l, const Option &r) { - if(m_notnull) + if (l.is_some()) + { + return l; + } + return r; + } + + template + inline friend Option operator&(const Option &l, const Option &r) + { + if (l.is_some()) + { + return r; + } + return Option(); + } + + inline friend Option operator^(const Option &l, const Option &r) + { + if (l.is_some() != r.is_some()) + { + if (l.is_some()) + { + return l; + } + return r; + } + return Option(); + } + + inline bool is_none() const { return !m_notnull; }; + inline bool is_some() const { return m_notnull; }; + inline const T &unwrap() const + { + if (m_notnull) { return m_some; } throw Error("Object is None"); } - inline T& get_or_insert(T value) + + inline const T &unwrap_or(const T &alt) const { - if(!m_notnull) + if (m_notnull) + { + return m_some; + } + return alt; + } + + inline T unwrap_or_else(const std::function &fn) const + { + if (m_notnull) + { + return m_some; + } + return fn(); + } + + inline const T &get_or_insert(const T &value) + { + if (!m_notnull) { insert(value); } return m_some; } - inline T& insert(T value) + inline const T &insert(const T &value) { m_some = value; m_notnull = true; @@ -95,35 +153,92 @@ namespace ro } template - inline Result ok_or(E err) + inline Result ok_or(const E &err) { - if(m_notnull) + if (m_notnull) { return Result(m_some); } return Result(err); } - template - inline Option map(const std::function& fn) + template + inline Option and_then(const std::function(const T &)> &fn) { - if(is_some()) + if (m_notnull) { return fn(m_some); } return Option(); } - template - inline U map_or(U defv, const std::function& fn) + inline Option or_else(const std::function()> &fn) { - if(is_some()) + if (m_notnull) + { + return *this; + } + return fn(); + } + + template + inline Option map(const std::function &fn) + { + if (is_some()) + { + return fn(m_some); + } + return Option(); + } + + template + inline U map_or(const U &defv, const std::function &fn) + { + if (is_some()) { return fn(m_some); } return defv; } + inline Option filter(const std::function &fn) + { + if (is_some() && fn(m_some)) + { + return Option(m_some); + } + return Option(); + } + + inline Option take() + { + if (m_notnull) + { + m_notnull = false; + return Option(m_some); + } + return Option(); + } + + inline Option take_if(const std::function &fn) + { + if (m_notnull && fn(m_some)) + { + return take(); + } + return Option(); + } + + template + inline Option> zip(const Option &other) + { + if (m_notnull && other.is_some()) + { + return Option>(std::tuple(m_some, other.unwrap())); + } + return Option>(); + } + private: T m_some; bool m_notnull; @@ -134,66 +249,172 @@ namespace ro { public: - Result(T obj) : m_result(obj) {}; + 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() + inline Result &operator=(const T &value) { - if(is_ok()) + m_result = value; + m_error = None(E); + return *this; + }; + inline Result &operator=(const E &err) + { + m_error.insert(err); + return *this; + }; + + inline friend bool operator==(const Result &l, const Result &r) + { + if (l.is_ok() != r.is_ok()) + { + return false; + } + if (l.is_ok()) + { + return l.m_result == r.m_result; + } + return l.m_error == r.m_error; + }; + + inline friend bool operator==(const Result &l, const E &r) + { + if (l.is_ok()) + { + return false; + } + return l.m_error == r; + }; + + inline friend bool operator==(const Result &l, const T &r) + { + if (l.is_err()) + { + return false; + } + return l.m_result == r; + }; + + inline friend Result operator|(const Result &l, const Result &r) + { + if (l.is_ok()) + { + return l; + } + return r; + } + + template + inline friend Result operator&(const Result &l, const Result &r) + { + if (l.is_ok()) + { + return r; + } + return Result(l.m_error.unwrap()); + } + + inline bool is_ok() const { return m_error.is_none(); }; + inline bool is_err() const { return m_error.is_some(); }; + + template + inline Result or_else(const std::function(const E &)> &fn) + { + if (is_ok()) + { + return Result(m_result); + } + return fn(m_error.unwrap()); + } + + inline const T &unwrap() const + { + if (is_ok()) { return m_result; } throw m_error.unwrap(); } - - inline T& expect(E e) + + inline const E &unwrap_err() const { - if(is_ok()) + if (is_err()) + { + return m_error.unwrap(); + } + throw m_result; + } + + inline const T &expect(E e) const + { + if (is_ok()) { return m_result; } throw e + m_error.unwrap(); } - template - inline Result map(const std::function& fn) + template + inline Result map(const std::function &fn) { - if(is_ok()) + if (is_ok()) { return fn(m_result); } return m_error.unwrap(); } - template - inline Result map_err(const std::function& fn) + template + inline Result map_err(const std::function &fn) { - if(!is_ok()) + if (!is_ok()) { return fn(m_error.unwrap()); } return m_result; } - template - inline U map_or(U defv, const std::function& fn) + template + inline U map_or(const U &defv, const std::function &fn) { - if(is_ok()) + if (is_ok()) { return fn(m_result); } return defv; } - Option err() { return m_error; } - Option ok() { return is_ok()?Option(m_result): Option(); } + template + inline Result and_then(const std::function(const T &)> &fn) + { + if (is_ok()) + { + return fn(m_result); + } + return Result(m_error.unwrap()); + } + + inline const T &unwrap_or(const T &alt) const + { + if (is_ok()) + { + return m_result; + } + return alt; + } + + inline T unwrap_or_else(const std::function &fn) + { + if (is_ok()) + { + return m_result; + } + return fn(m_error.unwrap()); + } + + inline const Option &err() const { return m_error; } + inline Option ok() const { return is_ok() ? Option(m_result) : Option(); } private: T m_result; diff --git a/test/test.cpp b/test/test.cpp index fb3e494..37394a1 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -2,12 +2,19 @@ #include "../src/ro.h" #include #include - +#include using namespace std; using namespace ro; +struct Stat{ + int passed; + int total; +}; + +static Stat g_stat = {0,0}; + #define assert(v,fmt,...) if(!(v)) { \ throw Error(ro::sfmt("ASSERT ERROR %s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)); \ } @@ -19,12 +26,14 @@ void test(const char* desc, const std::function& lambda) try { lambda(); std::cout << "OK" << std::endl; + g_stat.passed++; } catch(ro::Error e) { std::cout << "FAILED" << std::endl; std::cout << e.what() << std::endl; } + g_stat.total++; } int main(int argc, char const *argv[]) @@ -52,6 +61,14 @@ int main(int argc, char const *argv[]) assert(ret.unwrap() == "abc", "Data mistmatch"); }); + test("OPtion get_or_insert", [](){ + auto x = None(int); + int y = x.get_or_insert(5); + assert(y == 5, "Unexpected value"); + + assert(x == 5, "Unexpected value"); + }); + test("Option map", [](){ auto fn = [](int x){ return "Hello"; @@ -67,7 +84,7 @@ int main(int argc, char const *argv[]) }); - test("Option map or", [](){ + test("Option map_or", [](){ auto fn = [](int x){ return ++x; }; @@ -81,6 +98,140 @@ int main(int argc, char const *argv[]) assert(v == 3, "Value mismatch %d",v); }); + test("Option filter", [](){ + auto fn = [](int n) { + return n % 2 == 0; + }; + //Option opt = None(int).filter(fn); + assert(None(int).filter(fn).is_none(), "Filter value shall be None"); + assert(Some(3).filter(fn).is_none(), "Filter value shall be None"); + assert(Some(4).filter(fn).is_some(), "Filter value shall not be None"); + }); + + test("Option OR", [](){ + auto x = Some(2); + auto y = None(int); + assert((x | y) == Some(2), "Unexpected value"); + + x = None(int); + y = Some(100); + assert((x | y) == Some(100), "Unexpected value"); + + x = Some(2); + y = Some(100); + assert((x|y) == Some(2), "Unexpected value"); + + x = None(int); + y = None(int); + assert((x | y) == None(int), "Unexpected value"); + }); + + test("Option AND", [](){ + auto x = Some(2); + auto y = None(int); + assert((x & y) == None(int), "Unexpected value"); + + x = None(int); + y = Some(100); + assert((x & y) == None(int), "Unexpected value"); + + x = Some(2); + y = Some(100); + assert((x&y) == Some(100), "Unexpected value"); + + x = None(int); + y = None(int); + assert((x & y) == None(int), "Unexpected value"); + }); + + test("Option XOR", [](){ + auto x = Some(2); + auto y = None(int); + assert((x ^ y) == Some(2), "Unexpected value"); + + x = None(int); + y = Some(100); + assert((x ^ y) == Some(100), "Unexpected value"); + + x = Some(2); + y = Some(100); + assert((x^y) == None(int), "Unexpected value"); + + x = None(int); + y = None(int); + assert((x ^ y) == None(int), "Unexpected value"); + }); + + test("Option or_else", [](){ + auto nobody = [](){return None(int);}; + auto vikings = [](){return Some(100);}; + + assert(None(int).or_else(vikings) == Some(100), "Unexpected value"); + assert(None(int).or_else(nobody) == None(int), "Unexpected value"); + + assert(Some(2).or_else(vikings) == Some(2), "Unexpected value"); + }); + + test("Option and_then", [](){ + auto fn = [](int x){return string("Hello");}; + + assert(Some(1).and_then(fn) == Some(string("Hello")), "Unexpected value"); + assert(None(int).and_then(fn) == None(string), "Unexpected value"); + }); + + test("Option take", [](){ + auto x = Some(2); + auto y = x.take(); + + assert(x == None(int), "Unexpected value"); + assert(y == Some(2), "Unexpected value"); + + auto z = None(int); + y = z.take(); + assert(x == None(int), "Unexpected value"); + assert(y == None(int), "Unexpected value"); + }); + + test("Option take_if", [](){ + auto fn = [](int n) { + return n % 2 == 0; + }; + auto x = Some(2); + auto y = x.take_if(fn); + + assert(x == None(int), "Unexpected value"); + assert(y == Some(2), "Unexpected value"); + + auto z = Some(3); + y = z.take_if(fn); + assert(z == Some(3), "Unexpected value"); + assert(y == None(int), "Unexpected value"); + }); + + test("Option unwrap_or", [](){ + assert(Some(2).unwrap_or(4) == 2, "Unexpected value"); + assert(None(int).unwrap_or(4) == 4, "Unexpected value"); + + assert(None(Error).unwrap_or(Error("Hello")) == Error("Hello"), "Unexpected value"); + + }); + test("Option unwrap_or_else", [](){ + assert(Some(2).unwrap_or_else([](){ return 4; }) == 2, "Unexpected value"); + assert(None(int).unwrap_or_else([](){ return 4; }) == 4, "Unexpected value"); + + }); + + test("Option zip", [](){ + auto x = Some(2); + auto y = Some(string("Hello")); + auto z = None(int); + + assert(x.zip(y).unwrap() == std::tuple(2, string("Hello")), "Unexpected value"); + + assert(x.zip(z).is_none(), "Unexpected value"); + + }); + test("Test Result", []{ Result ret = ERR("Error"); assert(ret.is_err(), "Object should containe error object"); @@ -132,7 +283,7 @@ int main(int argc, char const *argv[]) }); - test("Result map err", [](){ + test("Result map_err", [](){ auto fn = [](int x){ return "Hello"; }; @@ -145,7 +296,7 @@ int main(int argc, char const *argv[]) assert(ret1.err().unwrap() == "Hello", "Value mistmatch"); }); - test("Result map or", [](){ + test("Result map_or", [](){ auto fn = [](int x){ return ++x; }; @@ -159,4 +310,91 @@ int main(int argc, char const *argv[]) assert(v == 3, "Value mismatch %d",v); }); + test("Result AND", [](){ + Result x = 2; + Result y = Error("Hello"); + assert((x&y) == Error("Hello"), "Value mismatch"); + + x = Error("Error1"); + y = 10; + assert((x&y) == Error("Error1"), "Value mismatch"); + + x = Error("Error1"); + y = Error("Error2"); + assert((x&y) == Error("Error1"), "Value mismatch"); + + x = 1; + y = 10; + assert((x&y) == 10, "Value mismatch"); + }); + + test("Result OR", [](){ + Result x = 2; + Result y = Error("Hello"); + assert((x|y) == 2, "Value mismatch"); + + x = Error("Error1"); + y = 10; + assert((x|y) == 10, "Value mismatch"); + + x = Error("Error1"); + y = Error("Error2"); + assert((x|y) == Error("Error2"), "Value mismatch"); + + x = 1; + y = 10; + assert((x|y) == 1, "Value mismatch"); + }); + + test("Result and_then", [](){ + auto fn = [](int x){return string("Hello");}; + + Result x = 2; + auto test = Result(string("Hello")); + assert(x.and_then(fn) == string("Hello"), "Unexpected value"); + x = Error("Error"); + assert(x.and_then(fn) == Error("Error"), "Unexpected value"); + }); + + test("Result or_else", [](){ + auto sq = [](int x){return x*x;}; + auto err = [](int x){return x;}; + Result x(2.0); + auto ret = x.or_else(sq).or_else(sq); + assert(ret == 2.0, "Unexpected value"); + ret = x.or_else(err).or_else(sq); + assert(ret == 2.0, "Unexpected value"); + + x = 3; + ret = x.or_else(sq).or_else(err); + assert(ret == 9, "Unexpected value"); + + ret = x.or_else(err).or_else(err); + assert(ret == 3, "Unexpected value"); + }); + + test("Result unwrap_err", [](){ + Result x = Error("Hello"); + assert(x.unwrap_err() == Error("Hello"), "Unexpected value"); + }); + + test("Result unwrap_or", [](){ + Result x = 2; + assert(x.unwrap_or(10) == 2, "Unexpected value"); + + x = ERR("Error"); + assert(x.unwrap_or(10) == 10, "Unexpected value"); + }); + + test("Result unwrap_or_else", [](){ + auto fn = [](const Error& x){return strlen(x.what());}; + Result x = 2; + assert(x.unwrap_or_else(fn) == 2, "Unexpected value"); + + x = Error("HELLO"); + assert(x.unwrap_or_else(fn) == 5, "Unexpected value"); + }); + + + cout << "REPORT: " << g_stat.passed << " tests passed / " << g_stat.total << " tests." << endl; }