store: ensure updates update message sexp too

And turn all "add" into "replace" so old messages get removed.
Update tests too.
This commit is contained in:
Dirk-Jan C. Binnema
2022-05-05 01:27:08 +03:00
parent c0ae7e6860
commit 6126d7ee62
8 changed files with 329 additions and 170 deletions

View File

@ -190,7 +190,12 @@ Sexp::to_sexp_string() const
sstrm << ')';
break;
}
case Type::String: sstrm << quote(value()); break;
case Type::String:
sstrm << quote(value());
break;
case Type::Raw:
sstrm << value();
break;
case Type::Number:
case Type::Symbol:
case Type::Empty:
@ -231,7 +236,12 @@ Sexp::to_json_string() const
}
break;
}
case Type::String: sstrm << quote(value()); break;
case Type::String:
sstrm << quote(value());
break;
case Type::Raw: // FIXME: implement this.
break;
case Type::Symbol:
if (is_nil())
sstrm << "false";

View File

@ -1,5 +1,5 @@
/*
** Copyright (C) 2021 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** Copyright (C) 2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
@ -37,7 +37,7 @@ namespace Mu {
/// Parse node
struct Sexp {
/// Node type
enum struct Type { Empty, List, String, Number, Symbol };
enum struct Type { Empty, List, String, Number, Symbol, Raw };
/**
* Default CTOR
@ -81,13 +81,30 @@ struct Sexp {
}
static Sexp make_number(int val) { return Sexp{Type::Number, format("%d", val)}; }
static Sexp make_symbol(std::string&& val)
{
static Sexp make_symbol(std::string&& val) {
if (val.empty())
throw Error(Error::Code::InvalidArgument, "symbol must be non-empty");
throw Error(Error::Code::InvalidArgument,
"symbol must be non-empty");
return Sexp{Type::Symbol, std::move(val)};
}
static Sexp make_symbol_sv(std::string_view val) { return make_symbol(std::string{val}); }
static Sexp make_symbol_sv(std::string_view val) {
return make_symbol(std::string{val});
}
/**
* Add a raw string sexp.
*
* @param val value
*
* @return A sexp
*/
static Sexp make_raw(std::string&& val) {
return Sexp{Type::Raw, std::string{val}};
}
static Sexp make_raw(const std::string& val) {
return make_raw(std::string{val});
}
/**
*
@ -96,8 +113,7 @@ struct Sexp {
*
* @return
*/
const std::string& value() const
{
const std::string& value() const {
if (is_list())
throw Error(Error::Code::InvalidArgument, "no value for list");
if (is_empty())
@ -110,15 +126,14 @@ struct Sexp {
*
* @return
*/
const Seq& list() const
{
const Seq& list() const {
if (!is_list())
throw Error(Error::Code::InvalidArgument, "not a list");
return seq_;
}
/**
* Convert a Sexp::Node to its S-expression string representation
* Convert a Sexp to its S-expression string representation
*
* @return the string representation
*/
@ -142,6 +157,9 @@ struct Sexp {
/// Helper struct to build mutable lists.
///
struct List {
List () = default;
List (const Seq& seq): seq_{seq} {}
/**
* Add a sexp to the list
*
@ -164,7 +182,8 @@ struct Sexp {
}
/**
* Add a property (i.e., :key sexp ) to the list
* Add a property (i.e., :key sexp ) to the list. Remove any
* prop with the same name
*
* @param name a property-name. Must start with ':', length > 1
* @param sexp a sexp
@ -172,8 +191,8 @@ struct Sexp {
*
* @return a ref to this List (for chaining)
*/
List& add_prop(std::string&& name, Sexp&& sexp)
{
List& add_prop(std::string&& name, Sexp&& sexp) {
remove_prop(name);
if (!is_prop_name(name))
throw Error{Error::Code::InvalidArgument,
"invalid property name ('%s')",
@ -183,13 +202,27 @@ struct Sexp {
return *this;
}
template <typename... Args>
List& add_prop(std::string&& name, Sexp&& sexp, Args... args)
{
List& add_prop(std::string&& name, Sexp&& sexp, Args... args) {
remove_prop(name);
add_prop(std::move(name), std::move(sexp));
add_prop(std::forward<Args>(args)...);
return *this;
}
void remove_prop(const std::string& name) {
if (!is_prop_name(name))
throw Error{Error::Code::InvalidArgument,
"invalid property name ('%s')", name.c_str()};
auto it = std::find_if(seq_.begin(), seq_.end(), [&](auto&& elm) {
return elm.type() == Sexp::Type::Symbol &&
elm.value() == name;
});
if (it != seq_.cend() && it + 1 != seq_.cend()) {
/* erase propname and value.*/
seq_.erase(it, it + 2);
}
}
/**
* Remove all elements from the list.
*/
@ -209,7 +242,7 @@ struct Sexp {
*/
size_t empty() const { return seq_.empty(); }
private:
private:
friend struct Sexp;
Seq seq_;
};
@ -309,16 +342,14 @@ struct Sexp {
return is_prop_list(list().begin() + 1, list().end());
}
private:
Sexp(Type typearg, std::string&& valuearg) : type_{typearg}, value_{std::move(valuearg)}
{
private:
Sexp(Type typearg, std::string&& valuearg) : type_{typearg}, value_{std::move(valuearg)} {
if (is_list())
throw Error{Error::Code::InvalidArgument, "cannot be a list type"};
if (is_empty())
throw Error{Error::Code::InvalidArgument, "cannot be an empty type"};
}
Sexp(Type typearg, Seq&& seq) : type_{Type::List}, seq_{std::move(seq)}
{
Sexp(Type typearg, Seq&& seq) : type_{Type::List}, seq_{std::move(seq)} {
if (!is_list())
throw Error{Error::Code::InvalidArgument, "must be a list type"};
if (is_empty())
@ -353,10 +384,10 @@ struct Sexp {
return b == e;
}
const Type type_; /**< Type of node */
const std::string value_; /**< String value of node (only for
* non-Type::Lst)*/
const Seq seq_; /**< Children of node (only for
Type type_; /**< Type of node */
std::string value_; /**< String value of node (only for
* non-Type::Lst)*/
Seq seq_; /**< Children of node (only for
* Type::Lst) */
};

View File

@ -55,7 +55,7 @@ test_parser()
check_parse(R"("foo
bar")",
"\"foo\nbar\"");
"\"foo\nbar\"");
}
static void
@ -111,20 +111,47 @@ static void
test_props()
{
auto sexp2 = Sexp::make_list(Sexp::make_string("foo"),
Sexp::make_number(123),
Sexp::make_symbol("blub"));
Sexp::make_number(123),
Sexp::make_symbol("blub"));
auto sexp = Sexp::make_prop_list(":foo",
Sexp::make_string("bär"),
":cuux",
Sexp::make_number(123),
":flub",
Sexp::make_symbol("fnord"),
":boo",
std::move(sexp2));
Sexp::make_string("bär"),
":cuux",
Sexp::make_number(123),
":flub",
Sexp::make_symbol("fnord"),
":boo",
std::move(sexp2));
assert_equal(sexp.to_sexp_string(),
"(:foo \"b\303\244r\" :cuux 123 :flub fnord :boo (\"foo\" 123 blub))");
"(:foo \"b\303\244r\" :cuux 123 :flub fnord :boo (\"foo\" 123 blub))");
}
static void
test_prop_list_remove()
{
{
Sexp::List lst;
lst.add_prop(":foo", Sexp::make_string("123"))
.add_prop(":bar", Sexp::make_number(123));
assert_equal(Sexp::make_list(std::move(lst)).to_sexp_string(),
R"((:foo "123" :bar 123))");
}
{
Sexp::List lst;
lst.add_prop(":foo", Sexp::make_string("123"))
.add_prop(":bar", Sexp::make_number(123));
assert_equal(Sexp::make_list(Sexp::List{lst}).to_sexp_string(),
R"((:foo "123" :bar 123))");
lst.remove_prop(":bar");
assert_equal(Sexp::make_list(Sexp::List{lst}).to_sexp_string(),
R"((:foo "123"))");
}
}
int
@ -140,6 +167,7 @@ try {
g_test_add_func("/utils/sexp/parser", test_parser);
g_test_add_func("/utils/sexp/list", test_list);
g_test_add_func("/utils/sexp/proplist", test_prop_list);
g_test_add_func("/utils/sexp/proplist-remove", test_prop_list_remove);
g_test_add_func("/utils/sexp/props", test_props);
return g_test_run();