diff --git a/lib/utils/mu-sexp.cc b/lib/utils/mu-sexp.cc index a588f7c0..cfc9812e 100644 --- a/lib/utils/mu-sexp.cc +++ b/lib/utils/mu-sexp.cc @@ -101,7 +101,7 @@ parse_string(const std::string& expr, size_t& pos) } if (escape || expr[pos] != '"') - throw parsing_error(pos, "unterminated string '%s'", str.c_str()); + return Err(parsing_error(pos, "unterminated string '%s'", str.c_str())); ++pos; return Ok(Sexp{std::move(str)}); @@ -158,10 +158,11 @@ parse(const std::string& expr, size_t& pos) else if (isalpha(kar) || kar == ':') return parse_symbol(expr, pos); else - throw parsing_error(pos, "unexpected character '%c", kar); + return Err(parsing_error(pos, "unexpected character '%c", kar)); }); - pos = skip_whitespace(expr, pos); + if (sexp) + pos = skip_whitespace(expr, pos); return sexp; } @@ -324,6 +325,7 @@ test_list() Sexp s; g_assert_true(s.listp()); g_assert_true(s.to_string() == "()"); + g_assert_true(Sexp::type_name(s.type()) == "list"); g_assert_true(s.empty()); } @@ -333,11 +335,22 @@ test_list() Sexp(123), Sexp::Symbol("world") }; - Sexp s{std::move(items)}; + const Sexp s{std::move(items)}; g_assert_false(s.empty()); g_assert_cmpuint(s.size(),==,3); g_assert_true(s.to_string() == "(\"hello\" 123 world)"); - //g_assert_true(s.to_string() == "(\"hello\" 123 world)"); + + + /* copy */ + Sexp s2 = s; + g_assert_true(s2.to_string() == "(\"hello\" 123 world)"); + + /* move */ + Sexp s3 = std::move(s2); + g_assert_true(s3.to_string() == "(\"hello\" 123 world)"); + + s3.clear(); + g_assert_true(s3.empty()); } } @@ -350,6 +363,7 @@ test_string() g_assert_true(s.stringp()); g_assert_true(s.string()=="hello"); g_assert_true(s.to_string()=="\"hello\""); + g_assert_true(Sexp::type_name(s.type()) == "string"); } { @@ -368,6 +382,7 @@ test_number() g_assert_true(s.numberp()); g_assert_cmpint(s.number(),==,123); g_assert_true(s.to_string() == "123"); + g_assert_true(Sexp::type_name(s.type()) == "number"); } { @@ -386,6 +401,7 @@ test_symbol() g_assert_true(s.symbolp()); g_assert_true(s.symbol()=="hello"); g_assert_true (s.to_string()=="hello"); + g_assert_true(Sexp::type_name(s.type()) == "symbol"); } { @@ -469,6 +485,19 @@ bar")", "\"foo\nbar\""); } +static void +test_parser_fail() +{ + g_assert_false(!!Sexp::parse("\"")); + g_assert_false(!!Sexp::parse("123abc")); + g_assert_false(!!Sexp::parse("(")); + g_assert_false(!!Sexp::parse(")")); + g_assert_false(!!Sexp::parse("(hello (boo))))")); + + g_assert_true(Sexp::type_name(static_cast(-1)) == ""); +} + + int main(int argc, char* argv[]) try { @@ -483,6 +512,8 @@ try { g_test_add_func("/sexp/add-multi", test_add_multi); g_test_add_func("/sexp/plist", test_plist); g_test_add_func("/sexp/parser", test_parser); + g_test_add_func("/sexp/parser-fail", test_parser_fail); + return g_test_run(); } catch (const std::runtime_error& re) { diff --git a/lib/utils/mu-sexp.hh b/lib/utils/mu-sexp.hh index 57ca3607..d570efcb 100644 --- a/lib/utils/mu-sexp.hh +++ b/lib/utils/mu-sexp.hh @@ -41,10 +41,13 @@ namespace Mu { * A structure somewhat similar to a Lisp s-expression and which can be * constructed from/to an s-expressing string representation. * - * A sexp is either an atom (String, Number Symbol) or a List + * A sexp is either an atom (String, Number, Symbol) or a List. */ struct Sexp { - /// Types + /** + * Types + * + */ using List = std::vector; using String = std::string; using Number = int64_t; @@ -64,6 +67,83 @@ struct Sexp { enum struct Type { List, String, Number, Symbol }; using ValueType = std::variant; + /** + * Is some Sexp of the given type? + * + * @return true or false + */ + constexpr bool stringp() const { return std::holds_alternative(value); } + constexpr bool numberp() const { return std::holds_alternative(value); } + constexpr bool listp() const { return std::holds_alternative(value); } + constexpr bool symbolp() const { return std::holds_alternative(value); } + constexpr bool symbolp(const Sexp::Symbol& sym) const {return symbolp() && symbol() == sym; } + constexpr bool nilp() const { return symbolp(nil_sym); } + + // Get the specific variant type. + const List& list() const { return std::get(value); } + List& list() { return std::get(value); } + const String& string() const { return std::get(value); } + String& string() { return std::get(value); } + const Number& number() const { return std::get(value); } + Number& number() { return std::get(value); } + const Symbol& symbol() const { return std::get(value); } + Symbol& symbol() { return std::get(value); } + + /** + * Constructors + */ + Sexp():value{List{}} {} // default: an empty list. + // Copy & move ctors + Sexp(const Sexp& other):value{other.value}{} + Sexp(Sexp&& other):value{std::move(other.value)}{} + // From various types + Sexp(const List& lst): value{lst} {} + Sexp(List&& lst): value{std::move(lst)} {} + Sexp(const String& str): value{str} {} + Sexp(String&& str): value{std::move(str)} {} + Sexp(const char *str): Sexp{std::string{str}} {} + Sexp(std::string_view sv): Sexp{std::string{sv}} {} + + template> > + Sexp(N n):value{static_cast(n)} {} + + Sexp(const Symbol& sym): value{sym} {} + Sexp(Symbol&& sym): value{std::move(sym)} {} + + template + Sexp(S&& s, T&& t, Args&&... args): value{List()} { + auto& l{std::get(value)}; + l.emplace_back(Sexp(std::forward(s))); + l.emplace_back(Sexp(std::forward(t))); + (l.emplace_back(Sexp(std::forward(args))), ...); + } + + /** + * Copy-assignment + * + * @param rhs another sexp + * + * @return the sexp + */ + Sexp& operator=(const Sexp& rhs) { + if (this != &rhs) + value = rhs.value; + return *this; + } + + /** + * Move-assignment + * + * @param rhs another sexp + * + * @return the sexp + */ + Sexp& operator=(Sexp&& rhs) { + if (this != &rhs) + value = std::move(rhs.value); + return *this; + } + /** * Get the type of value * @@ -92,70 +172,6 @@ struct Sexp { } } - constexpr bool stringp() const { return std::holds_alternative(value); } - constexpr bool numberp() const { return std::holds_alternative(value); } - constexpr bool listp() const { return std::holds_alternative(value); } - constexpr bool symbolp() const { return std::holds_alternative(value); } - - constexpr bool symbolp(const Sexp::Symbol& sym) const {return symbolp() && symbol() == sym; } - - constexpr bool nilp() const { return symbolp() && symbol() == "nil"; } - static const Sexp& nil() { static const Sexp nilsym(Symbol{"nil"}); return nilsym; } - static const Sexp& t() { static const Sexp tsym(Symbol{"t"}); return tsym; } - - // Get the specific variant type. - const List& list() const { return std::get(value); } - List& list() { return std::get(value); } - const String& string() const { return std::get(value); } - String& string() { return std::get(value); } - const Number& number() const { return std::get(value); } - Number& number() { return std::get(value); } - const Symbol& symbol() const { return std::get(value); } - Symbol& symbol() { return std::get(value); } - - /// Default ctor - Sexp():value{List{}} {} // default: an empty list. - - // Copy & move ctors - Sexp(const Sexp& other):value{other.value}{} - Sexp(Sexp&& other):value{std::move(other.value)}{} - - // Assignment - Sexp& operator=(const Sexp& rhs) { - if (this != &rhs) - value = rhs.value; - return *this; - } - Sexp& operator=(Sexp&& rhs) { - if (this != &rhs) - value = std::move(rhs.value); - return *this; - } - - /// Type specific ctors - Sexp(const List& lst): value{lst} {} - Sexp(List&& lst): value{std::move(lst)} {} - - Sexp(const String& str): value{str} {} - Sexp(String&& str): value{std::move(str)} {} - Sexp(const char *str): Sexp{std::string{str}} {} - Sexp(std::string_view sv): Sexp{std::string{sv}} {} - - template> > - Sexp(N n):value{static_cast(n)} {} - - Sexp(const Symbol& sym): value{sym} {} - Sexp(Symbol&& sym): value{std::move(sym)} {} - - /// - template - Sexp(S&& s, T&& t, Args&&... args): value{List()} { - auto& l{std::get(value)}; - l.emplace_back(Sexp(std::forward(s))); - l.emplace_back(Sexp(std::forward(t))); - (l.emplace_back(Sexp(std::forward(args))), ...); - } - /** * Parse sexp from string * @@ -166,12 +182,14 @@ struct Sexp { static Result parse(const std::string& str); - /// List specific + /** + * List specific functionality + * + */ using iterator = List::iterator; using const_iterator = List::const_iterator; - - iterator begin() { return list().begin(); } + iterator begin() { return list().begin(); } const_iterator begin() const { return list().begin(); } const_iterator cbegin() const { return list().cbegin(); } @@ -179,19 +197,6 @@ struct Sexp { const_iterator end() const { return list().end(); } const_iterator cend() const { return list().cend(); } - - Sexp& front() { return list().front(); } - const Sexp& front() const { return list().front(); } - void pop_front() { list().erase(list().begin()); } - - Option head() { if (listp()&&!empty()) return front(); else return Nothing; } - Option head() const { if (listp()&&!empty()) return front(); else return Nothing; } - - Option tail() { - if (listp()&&!empty()&&cbegin()+1!=cend()) return *(begin()+1); else return Nothing; } - Option tail() const { - if (listp()&&!empty()&&cbegin()+1!=cend()) return *(cbegin()+1); else return Nothing; } - bool empty() const { return list().empty(); } size_t size() const { return list().size(); } void clear() { list().clear(); } @@ -208,7 +213,37 @@ struct Sexp { .add(std::forward(args)...); } - // Plist (property lists) + /// Adding list elements + Sexp& add_list(Sexp&& l) { for (auto&& e: l) add(std::move(e)); return *this;}; + + /// Use list as stack. + Sexp& prepend(Sexp&& e) { list().insert(list().begin(), std::move(e)); return *this;}; + Sexp& prepend(const Sexp& e) { list().insert(list().begin(), e); return *this;}; + + /// Some convenience for the query parser + Sexp& front() { return list().front(); } + const Sexp& front() const { return list().front(); } + void pop_front() { list().erase(list().begin()); } + + Option head() { if (listp()&&!empty()) return front(); else return Nothing; } + Option head() const { if (listp()&&!empty()) return front(); else return Nothing; } + + bool head_symbolp() const { + if (auto&& h{head()}; h) return h->symbolp(); else return false; + } + bool head_symbolp(const Symbol& sym) const { + if (head_symbolp()) return head()->symbolp(sym); else return false; + } + Option tail() { + if (listp()&&!empty()&&cbegin()+1!=cend()) return *(begin()+1); else return Nothing; } + Option tail() const { + if (listp()&&!empty()&&cbegin()+1!=cend()) return *(cbegin()+1); else return Nothing; } + + + /** + * Property lists (aka plists) + */ + bool plistp() const { return listp() && plistp(cbegin(), cend()); } Sexp& put_props() { return *this; } // Final case for template pack. template @@ -225,18 +260,14 @@ struct Sexp { * * @param p property name * - * @return the property if found, or the symbol nil otherwise. + * @return the property if found, or nothing */ - const Sexp& get_prop(const std::string& p) const { + const Option get_prop(const std::string& p) const { if (auto&& it = find_prop(p, cbegin(), cend()); it != cend()) return *(std::next(it)); else - return Sexp::nil(); + return Nothing; } - bool has_prop(const std::string& s) const { - return find_prop(s, cbegin(), cend())!= cend(); - } - /// Output to string enum struct Format { Default = 0, /**< Nothing in particular */ @@ -253,6 +284,14 @@ struct Sexp { std::string to_json_string(Format fopts=Format::Default) const; Sexp& del_prop(const std::string& pname); + + /** + * Some useful constants + * + */ + static inline const auto nil_sym = Sexp::Symbol{"nil"}; + static inline const auto t_sym = Sexp::Symbol{"t"}; + protected: const_iterator find_prop(const std::string& s, const_iterator b, const_iterator e) const; @@ -261,6 +300,8 @@ private: iterator find_prop(const std::string& s,iterator b, iterator e); ValueType value; + + }; MU_ENABLE_BITOPS(Sexp::Format);