diff --git a/src/ro.h b/src/ro.h index 1882f4c..ba2716d 100644 --- a/src/ro.h +++ b/src/ro.h @@ -7,12 +7,20 @@ #include #include -#define None(T) (ro::Option()) -#define Some(v) (ro::Option(v)) +#define None(...) ro::Option<__VA_ARGS__>() +#define Some(...) ro::Option(__VA_ARGS__) #define ERR(fmt, ...) Error(ro::sfmt("%s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)) -#define try_unwrap(l,...) try{l=(__VA_ARGS__).unwrap();}catch(ro::Error e){return e;} -// #define Err(e) (ro::Result(e)) -// #define Ok(v) (ro::Result(v)) +#define try_unwrap(l, ...) \ + try \ + { \ + l = (__VA_ARGS__).unwrap(); \ + } \ + catch (ro::Error e) \ + { \ + return e; \ + } + +#define ERR_INVALID_ACESS "Invalid access" namespace ro { @@ -42,13 +50,13 @@ namespace ro return std::string(l.what()) == std::string(r.what()); } - friend inline Error operator+(const Error & l, const Error &r) + friend inline Error operator+(const Error &l, const Error &r) { return Error(std::string(l.what()) + "\n" + r.what()); } }; - template + template class Result; template @@ -56,6 +64,7 @@ namespace ro { public: + using type_t = T; Option(T obj) : m_some(obj), m_notnull(true){}; Option() : m_notnull(false){}; @@ -64,6 +73,7 @@ namespace ro insert(value); return *this; }; + inline friend bool operator==(const Option &l, const Option &r) { if (!l.m_notnull && !r.m_notnull) @@ -111,13 +121,43 @@ namespace ro inline bool is_none() const { return !m_notnull; }; inline bool is_some() const { return m_notnull; }; + + template + inline U match(const std::function &expected, const std::function &unexpected) + { + if (is_some()) + { + return expected(m_some); + } + return unexpected(); + } + + template + inline U match(const std::function &expected, const std::function &unexpected) const + { + if (is_some()) + { + return expected(m_some); + } + return unexpected(); + } + inline const T &unwrap() const { if (m_notnull) { return m_some; } - throw Error("Object is None"); + throw Error(ERR_INVALID_ACESS); + } + + inline T &unwrap() + { + if (m_notnull) + { + return m_some; + } + throw Error(ERR_INVALID_ACESS); } inline const T &unwrap_or(const T &alt) const @@ -138,7 +178,7 @@ namespace ro return fn(); } - inline const T &get_or_insert(const T &value) + inline T &get_or_insert(const T &value) { if (!m_notnull) { @@ -146,7 +186,8 @@ namespace ro } return m_some; } - inline const T &insert(const T &value) + + inline T &insert(const T &value) { m_some = value; m_notnull = true; @@ -184,7 +225,7 @@ namespace ro inline bool into(T &output) const { - if(is_some()) + if (is_some()) { output = m_some; return true; @@ -250,6 +291,62 @@ namespace ro return Option>(); } + template + inline Option zip_with(const Option &other, const std::function &fn) + { + if (m_notnull && other.is_some()) + { + return fn(m_some, other.unwrap()); + } + return Option(); + } + + template + inline typename std::enable_if, T>::value, std::tuple, Option>>::type unzip() + { + if (is_some()) + { + return std::tuple, Option>(Some(std::get(m_some)), Some(std::get(m_some))); + } + return std::tuple, Option>(None(Y), None(Z)); + } + + template + inline typename std::enable_if, X>::value, X>::type flatten() + { + if (is_some()) + { + return m_some; + } + return None(typename X::type_t); + } + + template + inline typename std::enable_if, X>::value, Result, typename X::type_err_t>>::type transpose() + { + if (is_none()) + { + return Option(); + } + if (m_some.is_ok()) + { + return m_some.ok(); + } + return m_some.unwrap_err(); + } + + inline Option replace(const T &value) + { + + Option ret; + if (is_some()) + { + ret = m_some; + } + insert(value); + return ret; + } + private: T m_some; bool m_notnull; @@ -260,6 +357,8 @@ namespace ro { public: + using type_ok_t = T; + using type_err_t = E; Result(T obj) : m_result(obj){}; Result(E err) { m_error.insert(err); }; Result(){}; @@ -329,6 +428,26 @@ namespace ro inline bool is_ok() const { return m_error.is_none(); }; inline bool is_err() const { return m_error.is_some(); }; + template + inline U match(const std::function &expected, const std::function &unexpected) + { + if (is_ok()) + { + return expected(m_result); + } + return unexpected(m_error.unwrap()); + } + + template + inline U match(const std::function &expected, const std::function &unexpected) const + { + if (is_ok()) + { + return expected(m_result); + } + return unexpected(m_error.unwrap()); + } + template inline Result or_else(const std::function(const E &)> &fn) { @@ -338,7 +457,6 @@ namespace ro } return fn(m_error.unwrap()); } - inline const T &unwrap() const { if (is_ok()) @@ -348,6 +466,15 @@ namespace ro throw m_error.unwrap(); } + inline T &unwrap() + { + if (is_ok()) + { + return m_result; + } + throw m_error.unwrap(); + } + inline const E &unwrap_err() const { if (is_err()) @@ -357,7 +484,7 @@ namespace ro throw m_result; } - inline const T &expect(const E& e) const + inline const T &expect(const E &e) const { if (is_ok()) { @@ -417,7 +544,7 @@ namespace ro inline bool into(T &output) const { - if(is_ok()) + if (is_ok()) { output = m_result; return true; @@ -434,6 +561,32 @@ namespace ro return fn(m_error.unwrap()); } + template + inline typename std::enable_if, X>::value, X>::type flatten() + { + if (is_ok()) + { + return m_result; + } + return m_error.unwrap(); + } + + template + inline typename std::enable_if, X>::value, Option>>::type transpose() + { + if (is_err()) + { + return Result(m_error.unwrap()); + } + + if (m_result.is_none()) + { + return None(Result); + } + + return Result(m_result.unwrap()); + } + inline const Option &err() const { return m_error; } inline Option ok() const { return is_ok() ? Option(m_result) : Option(); } diff --git a/test/test.cpp b/test/test.cpp index 5646f69..897ace2 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -1,38 +1,42 @@ #include #include "../src/ro.h" -#include +#include #include #include - using namespace std; using namespace ro; -struct Stat{ +struct Stat +{ int passed; int total; }; -static Stat g_stat = {0,0}; +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__)); \ -} +#define assert(v, fmt, ...) \ + if (!(v)) \ + { \ + throw Error(ro::sfmt("ASSERT ERROR %s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)); \ + } -class TestError: public Error { +class TestError : public Error +{ public: TestError(const char *s) : Error(s){}; }; -void test(const char* desc, const std::function& lambda) +void test(const char *desc, const std::function &lambda) { std::cout << "Run test: " << desc << ".........."; - try { + try + { lambda(); std::cout << "OK" << std::endl; g_stat.passed++; } - catch(ro::Error e) + catch (ro::Error e) { std::cout << "FAILED" << std::endl; std::cout << e.what() << std::endl; @@ -40,47 +44,63 @@ void test(const char* desc, const std::function& lambda) g_stat.total++; } -Result test_macro(const Result & target) +Result test_macro(const Result &target) { int a; - try_unwrap(a,target); + try_unwrap(a, target); return a; } +class Point +{ +private: + int x; + int y; + +public: + Point(int a, int b) : x(a), y(b){}; + Point(){}; + inline friend bool operator==(const Point &l, const Point &r) + { + return l.x == r.x && l.y == r.y; + } +}; + int main(int argc, char const *argv[]) { - test("Test Option", [](){ + 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"); - }); + assert(opt.unwrap() == text,"Value mismatch"); }); - test("Option -> Result convert", [](){ + 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"); + assert( err.find("Option is null") != string::npos, "Error message mismatch"); - opt = "abc"; + opt = string("abc"); ret = opt.ok_or(ERR("Option is null")); assert(ret.is_ok(), "Object should contain data"); - assert(ret.unwrap() == "abc", "Data mistmatch"); - }); + assert(ret.unwrap() == "abc", "Data mismatch"); }); - test("OPtion get_or_insert", [](){ + test("OPtion get_or_insert", []() + { auto x = None(int); - int y = x.get_or_insert(5); + int & y = x.get_or_insert(5); assert(y == 5, "Unexpected value"); - assert(x == 5, "Unexpected value"); - }); + assert(x == 5, "Unexpected value"); }); - test("Option map", [](){ + test("Option map", []() + { auto fn = [](int x){ return "Hello"; }; @@ -91,11 +111,10 @@ int main(int argc, char const *argv[]) opt = 2; opt1 = opt.map(fn); - assert(opt1.unwrap() == "Hello", "Value mismatch"); - }); + assert(opt1.unwrap() == "Hello", "Value mismatch"); }); - - test("Option map_or", [](){ + test("Option map_or", []() + { auto fn = [](int x){ return ++x; }; @@ -106,20 +125,20 @@ int main(int argc, char const *argv[]) opt = 2; v = opt.map_or(0, fn); - assert(v == 3, "Value mismatch %d",v); - }); + assert(v == 3, "Value mismatch %d",v); }); - test("Option filter", [](){ + 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"); - }); + assert(Some(4).filter(fn).is_some(), "Filter value shall not be None"); }); - test("Option OR", [](){ + test("Option OR", []() + { auto x = Some(2); auto y = None(int); assert((x | y) == Some(2), "Unexpected value"); @@ -134,10 +153,10 @@ int main(int argc, char const *argv[]) x = None(int); y = None(int); - assert((x | y) == None(int), "Unexpected value"); - }); + assert((x | y) == None(int), "Unexpected value"); }); - test("Option AND", [](){ + test("Option AND", []() + { auto x = Some(2); auto y = None(int); assert((x & y) == None(int), "Unexpected value"); @@ -152,10 +171,10 @@ int main(int argc, char const *argv[]) x = None(int); y = None(int); - assert((x & y) == None(int), "Unexpected value"); - }); + assert((x & y) == None(int), "Unexpected value"); }); - test("Option XOR", [](){ + test("Option XOR", []() + { auto x = Some(2); auto y = None(int); assert((x ^ y) == Some(2), "Unexpected value"); @@ -170,27 +189,27 @@ int main(int argc, char const *argv[]) x = None(int); y = None(int); - assert((x ^ y) == None(int), "Unexpected value"); - }); + assert((x ^ y) == None(int), "Unexpected value"); }); - test("Option or_else", [](){ + 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"); - }); + assert(Some(2).or_else(vikings) == Some(2), "Unexpected value"); }); - test("Option and_then", [](){ + 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"); - }); + assert(None(int).and_then(fn) == None(string), "Unexpected value"); }); - test("Option take", [](){ + test("Option take", []() + { auto x = Some(2); auto y = x.take(); @@ -200,10 +219,10 @@ int main(int argc, char const *argv[]) auto z = None(int); y = z.take(); assert(x == None(int), "Unexpected value"); - assert(y == None(int), "Unexpected value"); - }); + assert(y == None(int), "Unexpected value"); }); - test("Option take_if", [](){ + test("Option take_if", []() + { auto fn = [](int n) { return n % 2 == 0; }; @@ -216,34 +235,61 @@ int main(int argc, char const *argv[]) auto z = Some(3); y = z.take_if(fn); assert(z == Some(3), "Unexpected value"); - assert(y == None(int), "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"); + 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"); + 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("Option unzip", []() + { + auto x = Some(std::tuple(2, string("Hello"))); + auto y = x.unzip(); + + assert(std::get<0>(y) == Some(2) , "Unexpected value"); + assert(std::get<1>(y) == Some(string("Hello")), "Unexpected value"); + x = None(std::tuple); + y = x.unzip(); + assert(std::get<0>(y) == None(int) , "Unexpected value"); + assert(std::get<1>(y) == None(string), "Unexpected value"); }); + + test("Option zip with", []() + { - }); - 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 fn = [](const int& a, const int & b) {return Point(a,b);}; auto x = Some(2); - auto y = Some(string("Hello")); - auto z = None(int); + auto y = Some(8); + auto z = x.zip_with(y, fn); + assert(z == Some(Point(2,8)), "Unexpected value"); + z = x.zip_with(None(int), fn); + assert(z.is_none(), "Unexpected value"); }); - assert(x.zip(y).unwrap() == std::tuple(2, string("Hello")), "Unexpected value"); - - assert(x.zip(z).is_none(), "Unexpected value"); - - }); - - test("Option Into", [](){ + test("Option Into", []() + { Option x = 2; int y = 0; bool v = x.into(y); @@ -254,18 +300,67 @@ int main(int argc, char const *argv[]) v = x.into(y); y = 0; assert(v == false, "Return value shall not be true"); - assert(y == 0, "Unexpected value"); - }); + assert(y == 0, "Unexpected value"); }); - test("Test Result", []{ + test("Test Option match", [] + { + Option x = 10; + int y = x.match( + [](int & x) { x += 10; return x;}, + []() {return 0; } + ); + assert(x == y, "Data mismatch"); + x = None(int); + y = x.match( + [](int & x) { x += 10; return x;}, + []() {return 0;} + ); + assert(x == None(int), "Data mismatch"); + assert(y == 0, "Data mismatch"); }); + + test("Option flatten", [] + { + Option z = Some(1); + Option> x = Some(Some(6)); + assert(x.flatten() == Some(6), "Unexpected value"); + + Option< Option< Option > > y = Option(Option>(Option(10))); + + assert(y.flatten() == Some(Some(10)), "Unexpected value"); + assert(y.flatten().flatten() == Some(10), "Unexpected value"); }); + + test("Option transpose", [] + { + Option> x = Some(Result(10)); + Result> y = Some(10); + + assert(x.transpose() == y, "Unexpected value"); + + x = Some(Result(Error("error"))); + assert(x.transpose() == Error("error"), "Unexpected value"); }); + + test("Replace", [] + { + Option x = 20; + auto y = x.replace(10); + assert(x == 10, "Unexpected value"); + assert(y == 20, "Unexpected value"); + + x = None(int); + y = x.replace(10); + assert(x == 10, "Unexpected value"); + assert(y == None(int), "Unexpected value"); }); + + 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"); - }); + assert(ret.unwrap() == "Hello", "Data mismatch"); }); - test("Result->option convert",[](){ + test("Result->option convert", []() + { Result ret = ERR("Error"); Option opt_v = ret.ok(); assert(opt_v.is_none(), "Value shall not be present"); @@ -276,10 +371,10 @@ int main(int argc, char const *argv[]) 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"); - }); + assert(opt_err.is_none(), "Erreur shall be none"); }); - test("Result expect", [](){ + test("Result expect", []() + { try{ Result ret = ERR("Error"); auto s = ret.expect(ERR("Expect message")); @@ -289,10 +384,10 @@ int main(int argc, char const *argv[]) { assert(true, "%s", e.what()); cout << e.what() << endl; - } - }); + } }); - test("Result map", [](){ + test("Result map", []() + { auto err = ERR("Error"); auto fn = [](string data){ return 1; @@ -300,29 +395,28 @@ int main(int argc, char const *argv[]) 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"); + assert(ret1.err() == ret.err(), "Error object mismatch"); ret = string("Hello"); ret1 = ret.map(fn); assert(ret1.is_ok(), "mapped result shall not be an error"); - assert(ret1.unwrap() == 1, "Value mistmatch"); - }); + assert(ret1.unwrap() == 1, "Value mismatch"); }); - - test("Result map_err", [](){ + test("Result map_err", []() + { auto fn = [](int x){ return "Hello"; }; Result ret = true; Result ret1 = ret.map_err(fn); - assert(ret1.unwrap() == true, "Value mistmatch"); + assert(ret1.unwrap() == true, "Value mismatch"); ret = 10; ret1 = ret.map_err(fn); - assert(ret1.err().unwrap() == "Hello", "Value mistmatch"); - }); + assert(ret1.err().unwrap() == "Hello", "Value mismatch"); }); - test("Result map_or", [](){ + test("Result map_or", []() + { auto fn = [](int x){ return ++x; }; @@ -333,10 +427,10 @@ int main(int argc, char const *argv[]) ret = 2; v = ret.map_or(0, fn); - assert(v == 3, "Value mismatch %d",v); - }); + assert(v == 3, "Value mismatch %d",v); }); - test("Result AND", [](){ + test("Result AND", []() + { Result x = 2; Result y = Error("Hello"); assert((x&y) == Error("Hello"), "Value mismatch"); @@ -351,10 +445,10 @@ int main(int argc, char const *argv[]) x = 1; y = 10; - assert((x&y) == 10, "Value mismatch"); - }); + assert((x&y) == 10, "Value mismatch"); }); - test("Result OR", [](){ + test("Result OR", []() + { Result x = 2; Result y = Error("Hello"); assert((x|y) == 2, "Value mismatch"); @@ -369,20 +463,20 @@ int main(int argc, char const *argv[]) x = 1; y = 10; - assert((x|y) == 1, "Value mismatch"); - }); + assert((x|y) == 1, "Value mismatch"); }); - test("Result and_then", [](){ + 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"); - }); + assert(x.and_then(fn) == Error("Error"), "Unexpected value"); }); - test("Result or_else", [](){ + test("Result or_else", []() + { auto sq = [](int x){return x*x;}; auto err = [](int x){return x;}; Result x(2.0); @@ -396,33 +490,32 @@ int main(int argc, char const *argv[]) assert(ret == 9, "Unexpected value"); ret = x.or_else(err).or_else(err); - assert(ret == 3, "Unexpected value"); - }); + assert(ret == 3, "Unexpected value"); }); - test("Result unwrap_err", [](){ + test("Result unwrap_err", []() + { Result x = Error("Hello"); - assert(x.unwrap_err() == Error("Hello"), "Unexpected value"); - }); + assert(x.unwrap_err() == Error("Hello"), "Unexpected value"); }); - test("Result unwrap_or", [](){ + 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"); - }); + assert(x.unwrap_or(10) == 10, "Unexpected value"); }); - test("Result unwrap_or_else", [](){ + 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"); - }); + assert(x.unwrap_or_else(fn) == 5, "Unexpected value"); }); - - test("Result Into", [](){ + test("Result Into", []() + { Result x = 2; int y = 0; bool v = x.into(y); @@ -433,16 +526,59 @@ int main(int argc, char const *argv[]) v = x.into(y); y = 0; assert(v == false, "Return value shall not be true"); - assert(y == 0, "Unexpected value"); - }); + assert(y == 0, "Unexpected value"); }); - test("try_unwrap", [](){ + test("try_unwrap", []() + { Result x = test_macro(Result(10)); assert(x == 10, "Unexpected value"); x = test_macro(Result(Error("Hello"))); - assert(x.is_err(), "Unexpected value"); - }); + assert(x.is_err(), "Unexpected value"); }); + + test("Test Result match", [] + { + Result x = 10; + int y = x.match( + [](int & x) { x += 10; return x;}, + [](Error & e) {return 0; } + ); + assert(x == y, "Data mismatch"); + + x = Error("error"); + y = x.match( + [](int & x) { x += 10; return x;}, + [](Error & e) {return 0;} + ); + assert(x == Error("error"), "Data mismatch"); + assert(y == 0, "Data mismatch"); }); + + test("Result Test uwrap mut", [] + { + Result x = 10; + int& y = x.unwrap(); + y = 50; + assert(x == 50, "Value mismatch"); }); + + test("Result flatten", []() + { + auto x = Result>(Result(20)); + assert(x.flatten() == Result(20),"Value mismatch"); + auto z = Result(Error("error")); + x = Result>(Result(z)); + assert(x.flatten() == Error("error"),"Value mismatch"); }); + + test("Result transpose", []() + { + Result>x = Some(5); + Option>y = Some(Result(5)); + assert(x.transpose() == y, "Value mismatch"); + + x = None(int); + assert(x.transpose().is_none(), "Transposed value shall be none"); + x = Error("Error"); + y = Some(Result(Error("Error"))); + assert(x.transpose() == y, "Value mismatch"); }); cout << "REPORT: " << g_stat.passed << " tests passed / " << g_stat.total << " tests." << endl; }