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] != '"')
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<Sexp::Type>(-1)) == "<error>");
}
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) {

View File

@ -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<Sexp>;
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<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
*
@ -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
*
@ -166,12 +182,14 @@ struct Sexp {
static Result<Sexp> 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<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(); }
size_t size() const { return list().size(); }
void clear() { list().clear(); }
@ -208,7 +213,37 @@ struct Sexp {
.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()); }
Sexp& put_props() { return *this; } // Final case for template pack.
template <class PropType, class SexpType, typename... Args>
@ -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<const Sexp&> 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);