utils/sexp: extend and improve tests

Extend functionality for parsing; update documentation, tests.
This commit is contained in:
Dirk-Jan C. Binnema 2023-06-27 00:52:07 +03:00
parent 73f0691662
commit ae9607530f
2 changed files with 167 additions and 95 deletions

View File

@ -101,7 +101,7 @@ parse_string(const std::string& expr, size_t& pos)
} }
if (escape || expr[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; ++pos;
return Ok(Sexp{std::move(str)}); return Ok(Sexp{std::move(str)});
@ -158,10 +158,11 @@ parse(const std::string& expr, size_t& pos)
else if (isalpha(kar) || kar == ':') else if (isalpha(kar) || kar == ':')
return parse_symbol(expr, pos); return parse_symbol(expr, pos);
else 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; return sexp;
} }
@ -324,6 +325,7 @@ test_list()
Sexp s; Sexp s;
g_assert_true(s.listp()); g_assert_true(s.listp());
g_assert_true(s.to_string() == "()"); g_assert_true(s.to_string() == "()");
g_assert_true(Sexp::type_name(s.type()) == "list");
g_assert_true(s.empty()); g_assert_true(s.empty());
} }
@ -333,11 +335,22 @@ test_list()
Sexp(123), Sexp(123),
Sexp::Symbol("world") Sexp::Symbol("world")
}; };
Sexp s{std::move(items)}; const Sexp s{std::move(items)};
g_assert_false(s.empty()); g_assert_false(s.empty());
g_assert_cmpuint(s.size(),==,3); g_assert_cmpuint(s.size(),==,3);
g_assert_true(s.to_string() == "(\"hello\" 123 world)"); 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.stringp());
g_assert_true(s.string()=="hello"); g_assert_true(s.string()=="hello");
g_assert_true(s.to_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_true(s.numberp());
g_assert_cmpint(s.number(),==,123); g_assert_cmpint(s.number(),==,123);
g_assert_true(s.to_string() == "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.symbolp());
g_assert_true(s.symbol()=="hello"); g_assert_true(s.symbol()=="hello");
g_assert_true (s.to_string()=="hello"); g_assert_true (s.to_string()=="hello");
g_assert_true(Sexp::type_name(s.type()) == "symbol");
} }
{ {
@ -469,6 +485,19 @@ bar")",
"\"foo\nbar\""); "\"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<Sexp::Type>(-1)) == "<error>");
}
int int
main(int argc, char* argv[]) main(int argc, char* argv[])
try { try {
@ -483,6 +512,8 @@ try {
g_test_add_func("/sexp/add-multi", test_add_multi); g_test_add_func("/sexp/add-multi", test_add_multi);
g_test_add_func("/sexp/plist", test_plist); g_test_add_func("/sexp/plist", test_plist);
g_test_add_func("/sexp/parser", test_parser); g_test_add_func("/sexp/parser", test_parser);
g_test_add_func("/sexp/parser-fail", test_parser_fail);
return g_test_run(); return g_test_run();
} catch (const std::runtime_error& re) { } catch (const std::runtime_error& re) {

View File

@ -41,10 +41,13 @@ namespace Mu {
* A structure somewhat similar to a Lisp s-expression and which can be * A structure somewhat similar to a Lisp s-expression and which can be
* constructed from/to an s-expressing string representation. * 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 { struct Sexp {
/// Types /**
* Types
*
*/
using List = std::vector<Sexp>; using List = std::vector<Sexp>;
using String = std::string; using String = std::string;
using Number = int64_t; using Number = int64_t;
@ -64,6 +67,83 @@ struct Sexp {
enum struct Type { List, String, Number, Symbol }; enum struct Type { List, String, Number, Symbol };
using ValueType = std::variant<List, String, Number, Symbol>; using ValueType = std::variant<List, String, Number, Symbol>;
/**
* Is some Sexp of the given type?
*
* @return true or false
*/
constexpr bool stringp() const { return std::holds_alternative<String>(value); }
constexpr bool numberp() const { return std::holds_alternative<Number>(value); }
constexpr bool listp() const { return std::holds_alternative<List>(value); }
constexpr bool symbolp() const { return std::holds_alternative<Symbol>(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<List>(value); }
List& list() { return std::get<List>(value); }
const String& string() const { return std::get<String>(value); }
String& string() { return std::get<String>(value); }
const Number& number() const { return std::get<Number>(value); }
Number& number() { return std::get<Number>(value); }
const Symbol& symbol() const { return std::get<Symbol>(value); }
Symbol& symbol() { return std::get<Symbol>(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<typename N, typename = std::enable_if_t<std::is_integral_v<N>> >
Sexp(N n):value{static_cast<Number>(n)} {}
Sexp(const Symbol& sym): value{sym} {}
Sexp(Symbol&& sym): value{std::move(sym)} {}
template<typename S, typename T, typename... Args>
Sexp(S&& s, T&& t, Args&&... args): value{List()} {
auto& l{std::get<List>(value)};
l.emplace_back(Sexp(std::forward<S>(s)));
l.emplace_back(Sexp(std::forward<T>(t)));
(l.emplace_back(Sexp(std::forward<Args>(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 * Get the type of value
* *
@ -92,70 +172,6 @@ struct Sexp {
} }
} }
constexpr bool stringp() const { return std::holds_alternative<String>(value); }
constexpr bool numberp() const { return std::holds_alternative<Number>(value); }
constexpr bool listp() const { return std::holds_alternative<List>(value); }
constexpr bool symbolp() const { return std::holds_alternative<Symbol>(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<List>(value); }
List& list() { return std::get<List>(value); }
const String& string() const { return std::get<String>(value); }
String& string() { return std::get<String>(value); }
const Number& number() const { return std::get<Number>(value); }
Number& number() { return std::get<Number>(value); }
const Symbol& symbol() const { return std::get<Symbol>(value); }
Symbol& symbol() { return std::get<Symbol>(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<typename N, typename = std::enable_if_t<std::is_integral_v<N>> >
Sexp(N n):value{static_cast<Number>(n)} {}
Sexp(const Symbol& sym): value{sym} {}
Sexp(Symbol&& sym): value{std::move(sym)} {}
///
template<typename S, typename T, typename... Args>
Sexp(S&& s, T&& t, Args&&... args): value{List()} {
auto& l{std::get<List>(value)};
l.emplace_back(Sexp(std::forward<S>(s)));
l.emplace_back(Sexp(std::forward<T>(t)));
(l.emplace_back(Sexp(std::forward<Args>(args))), ...);
}
/** /**
* Parse sexp from string * Parse sexp from string
* *
@ -166,12 +182,14 @@ struct Sexp {
static Result<Sexp> parse(const std::string& str); static Result<Sexp> parse(const std::string& str);
/// List specific /**
* List specific functionality
*
*/
using iterator = List::iterator; using iterator = List::iterator;
using const_iterator = List::const_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 begin() const { return list().begin(); }
const_iterator cbegin() const { return list().cbegin(); } const_iterator cbegin() const { return list().cbegin(); }
@ -179,19 +197,6 @@ struct Sexp {
const_iterator end() const { return list().end(); } const_iterator end() const { return list().end(); }
const_iterator cend() const { return list().cend(); } 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<Sexp&> head() { if (listp()&&!empty()) return front(); else return Nothing; }
Option<const Sexp&> head() const { if (listp()&&!empty()) return front(); else return Nothing; }
Option<Sexp&> tail() {
if (listp()&&!empty()&&cbegin()+1!=cend()) return *(begin()+1); else return Nothing; }
Option<const Sexp&> tail() const {
if (listp()&&!empty()&&cbegin()+1!=cend()) return *(cbegin()+1); else return Nothing; }
bool empty() const { return list().empty(); } bool empty() const { return list().empty(); }
size_t size() const { return list().size(); } size_t size() const { return list().size(); }
void clear() { list().clear(); } void clear() { list().clear(); }
@ -208,7 +213,37 @@ struct Sexp {
.add(std::forward<Args>(args)...); .add(std::forward<Args>(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<Sexp&> head() { if (listp()&&!empty()) return front(); else return Nothing; }
Option<const Sexp&> 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<Sexp&> tail() {
if (listp()&&!empty()&&cbegin()+1!=cend()) return *(begin()+1); else return Nothing; }
Option<const Sexp&> 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()); } bool plistp() const { return listp() && plistp(cbegin(), cend()); }
Sexp& put_props() { return *this; } // Final case for template pack. Sexp& put_props() { return *this; } // Final case for template pack.
template <class PropType, class SexpType, typename... Args> template <class PropType, class SexpType, typename... Args>
@ -225,18 +260,14 @@ struct Sexp {
* *
* @param p property name * @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<const Sexp&> get_prop(const std::string& p) const {
if (auto&& it = find_prop(p, cbegin(), cend()); it != cend()) if (auto&& it = find_prop(p, cbegin(), cend()); it != cend())
return *(std::next(it)); return *(std::next(it));
else else
return Sexp::nil(); return Nothing;
} }
bool has_prop(const std::string& s) const {
return find_prop(s, cbegin(), cend())!= cend();
}
/// Output to string /// Output to string
enum struct Format { enum struct Format {
Default = 0, /**< Nothing in particular */ Default = 0, /**< Nothing in particular */
@ -253,6 +284,14 @@ struct Sexp {
std::string to_json_string(Format fopts=Format::Default) const; std::string to_json_string(Format fopts=Format::Default) const;
Sexp& del_prop(const std::string& pname); 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: protected:
const_iterator find_prop(const std::string& s, const_iterator b, const_iterator find_prop(const std::string& s, const_iterator b,
const_iterator e) const; const_iterator e) const;
@ -261,6 +300,8 @@ private:
iterator find_prop(const std::string& s,iterator b, iterator find_prop(const std::string& s,iterator b,
iterator e); iterator e);
ValueType value; ValueType value;
}; };
MU_ENABLE_BITOPS(Sexp::Format); MU_ENABLE_BITOPS(Sexp::Format);