From dfc2cb12d77935c3fc1a8c8235757858aeadbd6e Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Mon, 7 Nov 2022 18:13:14 +0200 Subject: [PATCH] sexp: major rework / API improvements Use a bit nicer/modern c++, since we're using C++17 now. Add more tests. --- lib/utils/mu-sexp.cc | 364 +++++++++++++++++++++------ lib/utils/mu-sexp.hh | 581 +++++++++++++++---------------------------- 2 files changed, 498 insertions(+), 447 deletions(-) diff --git a/lib/utils/mu-sexp.cc b/lib/utils/mu-sexp.cc index a8e1ff65..4ebfd70c 100644 --- a/lib/utils/mu-sexp.cc +++ b/lib/utils/mu-sexp.cc @@ -1,6 +1,5 @@ /* -** Copyright (C) 2020 Dirk-Jan C. Binnema -** +** Copyright (C) 2022 Dirk-Jan C. Binnema ** ** 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 @@ -18,9 +17,11 @@ ** */ + #include "mu-sexp.hh" #include "mu-utils.hh" +#include #include #include @@ -48,36 +49,38 @@ skip_whitespace(const std::string& s, size_t pos) else break; } - return pos; } -static Sexp parse(const std::string& expr, size_t& pos); +static Result parse(const std::string& expr, size_t& pos); -static Sexp +static Result parse_list(const std::string& expr, size_t& pos) { if (expr[pos] != '(') // sanity check. - throw parsing_error(pos, "expected: '(' but got '%c", expr[pos]); + return Err(parsing_error(pos, "expected: '(' but got '%c", expr[pos])); - Sexp::List list; + Sexp lst{}; ++pos; - while (expr[pos] != ')' && pos != expr.size()) - list.add(parse(expr, pos)); + while (expr[pos] != ')' && pos != expr.size()) { + if (auto&& item = parse(expr, pos); item) + lst.add(std::move(*item)); + else + return Err(item.error()); + } if (expr[pos] != ')') - throw parsing_error(pos, "expected: ')' but got '%c'", expr[pos]); + return Err(parsing_error(pos, "expected: ')' but got '%c'", expr[pos])); ++pos; - return Sexp::make_list(std::move(list)); + return Ok(std::move(lst)); } -// parse string -static Sexp +static Result parse_string(const std::string& expr, size_t& pos) { if (expr[pos] != '"') // sanity check. - throw parsing_error(pos, "expected: '\"'' but got '%c", expr[pos]); + return Err(parsing_error(pos, "expected: '\"'' but got '%c", expr[pos])); bool escape{}; std::string str; @@ -101,14 +104,15 @@ parse_string(const std::string& expr, size_t& pos) throw parsing_error(pos, "unterminated string '%s'", str.c_str()); ++pos; - return Sexp::make_string(std::move(str)); + return Ok(Sexp{std::move(str)}); } -static Sexp + +static Result parse_integer(const std::string& expr, size_t& pos) { if (!isdigit(expr[pos]) && expr[pos] != '-') // sanity check. - throw parsing_error(pos, "expected: but got '%c", expr[pos]); + return Err(parsing_error(pos, "expected: but got '%c", expr[pos])); std::string num; // negative number? if (expr[pos] == '-') { @@ -119,32 +123,32 @@ parse_integer(const std::string& expr, size_t& pos) for (; isdigit(expr[pos]); ++pos) num += expr[pos]; - return Sexp::make_number(::atoi(num.c_str())); + return Ok(Sexp{::atoi(num.c_str())}); } -static Sexp +static Result parse_symbol(const std::string& expr, size_t& pos) { if (!isalpha(expr[pos]) && expr[pos] != ':') // sanity check. - throw parsing_error(pos, "expected: |: but got '%c", expr[pos]); + return Err(parsing_error(pos, "expected: |: but got '%c", expr[pos])); - std::string symbol(1, expr[pos]); + std::string symb(1, expr[pos]); for (++pos; isalnum(expr[pos]) || expr[pos] == '-'; ++pos) - symbol += expr[pos]; + symb += expr[pos]; - return Sexp::make_symbol(std::move(symbol)); + return Ok(Sexp{Sexp::Symbol{symb}}); } -static Sexp +static Result parse(const std::string& expr, size_t& pos) { pos = skip_whitespace(expr, pos); if (pos == expr.size()) - throw parsing_error(pos, "expected: character '%c", expr[pos]); + return Err(parsing_error(pos, "expected: character '%c", expr[pos])); const auto kar = expr[pos]; - const auto node = [&]() -> Sexp { + const auto sexp = std::invoke([&]() -> Result { if (kar == '(') return parse_list(expr, pos); else if (kar == '"') @@ -155,55 +159,52 @@ parse(const std::string& expr, size_t& pos) return parse_symbol(expr, pos); else throw parsing_error(pos, "unexpected character '%c", kar); - }(); + }); pos = skip_whitespace(expr, pos); - return node; + return sexp; } -Sexp -Sexp::make_parse(const std::string& expr) +Result +Sexp::parse(const std::string& expr) { size_t pos{}; - auto node{::parse(expr, pos)}; - - if (pos != expr.size()) - throw parsing_error(pos, "trailing data starting with '%c'", expr[pos]); - - return node; + auto res = ::parse(expr, pos); + if (!res) + return res; + else if (pos != expr.size()) + return Err(parsing_error(pos, "trailing data starting with '%c'", expr[pos])); + else + return res; } std::string -Sexp::to_sexp_string() const +Sexp::to_string(Format fopts) const { std::stringstream sstrm; + const auto splitp{any_of(fopts & Format::SplitList)}; + const auto typeinfop{any_of(fopts & Format::TypeInfo)}; - switch (type()) { - case Type::List: { + if (listp()) { sstrm << '('; bool first{true}; - for (auto&& child : list()) { - sstrm << (first ? "" : " ") << child.to_sexp_string(); + for(auto&& elm: list()) { + sstrm << (first ? "" : " ") << elm.to_string(fopts); first = false; } sstrm << ')'; - - if (any_of(formatting_opts & FormattingOptions::SplitList)) + if (splitp) sstrm << '\n'; - break; - } - case Type::String: - sstrm << quote(value()); - break; - case Type::Raw: - sstrm << value(); - break; - case Type::Number: - case Type::Symbol: - case Type::Empty: - default: sstrm << value(); - } + } else if (stringp()) + sstrm << quote(string()); + else if (numberp()) + sstrm << number(); + else if (symbolp()) + sstrm << symbol(); + + if (typeinfop) + sstrm << '<' << Sexp::type_name(type()) << '>'; return sstrm.str(); } @@ -211,26 +212,26 @@ Sexp::to_sexp_string() const // LCOV_EXCL_START std::string -Sexp::to_json_string() const +Sexp::to_json_string(Format fopts) const { std::stringstream sstrm; switch (type()) { case Type::List: { // property-lists become JSON objects - if (is_prop_list()) { + if (plistp()) { sstrm << "{"; auto it{list().begin()}; bool first{true}; while (it != list().end()) { - sstrm << (first ? "" : ",") << quote(it->value()) << ":"; + sstrm << (first ? "" : ",") << quote(it->string()) << ":"; ++it; sstrm << it->to_json_string(); ++it; first = false; } sstrm << "}"; - if (any_of(formatting_opts & FormattingOptions::SplitList)) + if (any_of(fopts & Format::SplitList)) sstrm << '\n'; } else { // other lists become arrays. sstrm << '['; @@ -240,31 +241,254 @@ Sexp::to_json_string() const first = false; } sstrm << ']'; - if (any_of(formatting_opts & FormattingOptions::SplitList)) + if (any_of(fopts & Format::SplitList)) sstrm << '\n'; } break; } case Type::String: - sstrm << quote(value()); + sstrm << quote(string()); break; - case Type::Raw: // FIXME: implement this. - break; - case Type::Symbol: - if (is_nil()) + if (nilp()) sstrm << "false"; - else if (is_t()) + else if (symbol() == "t") sstrm << "true"; else - sstrm << quote(value()); + sstrm << quote(symbol()); break; case Type::Number: - case Type::Empty: - default: sstrm << value(); + sstrm << number(); + break; + default: + break; } return sstrm.str(); } + + +Sexp& +Sexp::del_prop(const std::string& pname) +{ + if (auto kill_it = find_prop(pname, begin(), end()); kill_it != cend()) + list().erase(kill_it, kill_it + 2); + return *this; +} + + +Sexp::const_iterator +Sexp::find_prop(const std::string& s, + Sexp::const_iterator b, Sexp::const_iterator e) const +{ + for (auto&& it = b; it != e && it+1 != e; it += 2) + if (it->symbolp() && it->symbol() == s) + return it; + return e; +} + +Sexp::iterator +Sexp::find_prop(const std::string& s, + Sexp::iterator b, Sexp::iterator e) +{ + for (auto&& it = b; it != e && it+1 != e; it += 2) + if (it->symbolp() && it->symbol() == s) + return it; + return e; +} + + +bool +Sexp::plistp(Sexp::const_iterator b, Sexp::const_iterator e) const +{ + if (b == e) + return true; + else if (b + 1 == e) + return false; + else + return b->symbolp() && plistp(b + 2, e); +} + + // LCOV_EXCL_STOP + +#if BUILD_TESTS + +#include "mu-test-utils.hh" + +static void +test_list() +{ + { + Sexp s; + g_assert_true(s.listp()); + g_assert_true(s.to_string() == "()"); + g_assert_true(s.empty()); + } + + { + Sexp::List items = { + Sexp("hello"), + Sexp(123), + Sexp::Symbol("world") + }; + 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)"); + } + +} + +static void +test_string() +{ + { + Sexp s("hello"); + g_assert_true(s.stringp()); + g_assert_true(s.string()=="hello"); + g_assert_true(s.to_string()=="\"hello\""); + } + + { + // Sexp s(std::string_view("hel\"lo")); + // g_assert_true(s.is_string()); + // g_assert_cmpstr(s.string().c_str(),==,"hel\"lo"); + // g_assert_cmpstr(s.to_string().c_str(),==,"\"hel\\\"lo\""); + } +} + +static void +test_number() +{ + { + Sexp s(123); + g_assert_true(s.numberp()); + g_assert_cmpint(s.number(),==,123); + g_assert_true(s.to_string() == "123"); + } + + { + Sexp s(true); + g_assert_true(s.numberp()); + g_assert_cmpint(s.number(),==,1); + g_assert_true(s.to_string()=="1"); + } +} + +static void +test_symbol() +{ + { + Sexp s{Sexp::Symbol("hello")}; + g_assert_true(s.symbolp()); + g_assert_true(s.symbol()=="hello"); + g_assert_true (s.to_string()=="hello"); + } + + { + Sexp s{"hello"_sym}; + g_assert_true(s.symbolp()); + g_assert_true(s.symbol()=="hello"); + g_assert_true (s.to_string()=="hello"); + } + +} + +static void +test_multi() +{ + Sexp s{"abc", 123, Sexp::Symbol{"def"}}; + g_assert_true(s.to_string() == "(\"abc\" 123 def)"); +} + + +static void +test_add() +{ + { + Sexp s{"abc", 123}; + s.add("def"_sym); + g_assert_true(s.to_string() == "(\"abc\" 123 def)"); + } +} + +static void +test_add_multi() +{ + { + Sexp s{"abc", 123}; + s.add("def"_sym, 456, Sexp{"boo", 2}); + g_assert_true(s.to_string() == "(\"abc\" 123 def 456 (\"boo\" 2))"); + } + + { + Sexp s{"abc", 123}; + Sexp t{"boo", 2}; + s.add("def"_sym, 456, t); + g_assert_true(s.to_string() == "(\"abc\" 123 def 456 (\"boo\" 2))"); + } + +} + +static void +test_plist() +{ + Sexp s; + s.put_props("hello", "world"_sym, "foo", 123, "bar"_sym, "cuux"); + g_assert_true(s.to_string() == R"((hello world foo 123 bar "cuux"))"); + + s.put_props("hello", 12345); + g_assert_true(s.to_string() == R"((foo 123 bar "cuux" hello 12345))"); +} + + +static void +check_parse(const std::string& expr, const std::string& expected) +{ + auto sexp = Sexp::parse(expr); + assert_valid_result(sexp); + assert_equal(to_string(*sexp), expected); +} + +static void +test_parser() +{ + check_parse(":foo-123", ":foo-123"); + check_parse("foo", "foo"); + check_parse(R"(12345)", "12345"); + check_parse(R"(-12345)", "-12345"); + check_parse(R"((123 bar "cuux"))", "(123 bar \"cuux\")"); + + check_parse(R"("foo\"bar\"cuux")", "\"foo\\\"bar\\\"cuux\""); + + check_parse(R"("foo +bar")", + "\"foo\nbar\""); +} + +int +main(int argc, char* argv[]) +try { + mu_test_init(&argc, &argv); + + g_test_add_func("/sexp/list", test_list); + g_test_add_func("/sexp/string", test_string); + g_test_add_func("/sexp/number", test_number); + g_test_add_func("/sexp/symbol", test_symbol); + g_test_add_func("/sexp/multi", test_multi); + g_test_add_func("/sexp/add", test_add); + 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); + return g_test_run(); + +} catch (const std::runtime_error& re) { + std::cerr << re.what() << "\n"; + return 1; +} + + +#endif /*BUILD_TESTS*/ diff --git a/lib/utils/mu-sexp.hh b/lib/utils/mu-sexp.hh index f04b735b..dde4949d 100644 --- a/lib/utils/mu-sexp.hh +++ b/lib/utils/mu-sexp.hh @@ -20,428 +20,255 @@ #ifndef MU_SEXP_HH__ #define MU_SEXP_HH__ -#include -#include -#include +#include "mu-utils.hh" -#include "utils/mu-utils.hh" -#include "utils/mu-error.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include namespace Mu { -/// Simple s-expression parser & list that parses lists () and atoms (strings -/// ("-quoted), (positive) integers ([0..9]+) and symbol starting with alpha or -/// ':', then alphanum and '-') -/// -/// (:foo (1234 "bar" nil) :quux (a b c)) -/// Parse node +/** + * 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 + */ struct Sexp { - /// Node type - enum struct Type { Empty, List, String, Number, Symbol, Raw }; - - /** - * Default CTOR - */ - Sexp() : type_{Type::Empty} {} - - // Underlying data type for list; we'd like to use std::dequeu here, - // but that does not compile with libc++ (it does with libstdc++) - using Seq = std::vector; - - /** - * Make a sexp out of an s-expression string. - * - * @param expr a string containing an s-expression - * - * @return the parsed s-expression, or throw Error. - */ - static Sexp make_parse(const std::string& expr); - - /** - * Make a node for a string/integer/symbol/list value - * - * @param val some value - * @param empty_is_nil turn empty string into a 'nil' symbol - * - * @return a node - */ - static Sexp make_string(std::string&& val, bool empty_is_nil=false) - { - if (empty_is_nil && val.empty()) - return make_symbol("nil"); - else - return Sexp{Type::String, std::move(val)}; - } - static Sexp make_string(const std::string& val, bool empty_is_nil=false) - { - if (empty_is_nil && val.empty()) - return make_symbol("nil"); - else - return Sexp{Type::String, std::string(val)}; - } - - static Sexp make_number(int val) { return Sexp{Type::Number, format("%d", val)}; } - static Sexp make_symbol(std::string&& val) { - if (val.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}); - } - - /** - * 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}); - } - - - /** - * - * - * The value of this node; invalid for list nodes. - * - * @return - */ - const std::string& value() const { - if (is_list()) - throw Error(Error::Code::InvalidArgument, "no value for list"); - if (is_empty()) - throw Error{Error::Code::InvalidArgument, "no value for empty"}; - return value_; - } - - /** - * The underlying container of this list node; only valid for lists - * - * @return - */ - const Seq& list() const { - if (!is_list()) - throw Error(Error::Code::InvalidArgument, "not a list"); - return seq_; - } - - /** - * Convert a Sexp to its S-expression string representation - * - * @return the string representation - */ - std::string to_sexp_string() const; - - /** - * Convert a Sexp::Node to its JSON string representation - * - * @return the string representation - */ - std::string to_json_string() const; - - /** - * Return the type of this Node. - * - * @return the type - */ - Type type() const { return type_; } - - /// - /// Helper struct to build mutable lists. - /// - struct List { - List () = default; - List (const Seq& seq): seq_{seq} {} - - /** - * Add a sexp to the list - * - * @param sexp a sexp - * @param args rest arguments - * - * @return a ref to this List (for chaining) - */ - List& add() { return *this; } - List& add(Sexp&& sexp) - { - seq_.emplace_back(std::move(sexp)); - return *this; - } - template List& add(Sexp&& sexp, Args... args) - { - seq_.emplace_back(std::move(sexp)); - seq_.emplace_back(std::forward(args)...); - return *this; - } - - /** - * 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 - * @param args rest arguments - * - * @return a ref to this List (for chaining) - */ - 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')", - name.c_str()}; - seq_.emplace_back(make_symbol(std::move(name))); - seq_.emplace_back(std::move(sexp)); - return *this; - } - template - 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)...); - 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. - */ - void clear() { seq_.clear(); } - - /** - * Get the number of elements in the list - * - * @return number - */ - size_t size() const { return seq_.size(); } - - /** - * Is the list empty? - * - * @return true or false - */ - size_t empty() const { return seq_.empty(); } - - private: - friend struct Sexp; - Seq seq_; + /// Types + using List = std::vector; + using String = std::string; + using Number = int64_t; + struct Symbol { // distinguish from String. + Symbol(const std::string& s): name{s} {} + Symbol(std::string&& s): name(std::move(s)) {} + Symbol(const char* str): Symbol(std::string{str}) {} + Symbol(std::string_view sv): Symbol(std::string{sv}) {} + operator const std::string&() const {return name; } + std::string name; }; + enum struct Type { List, String, Number, Symbol }; + using Data = std::variant; /** - * Construct a list sexp from a List + * Get the type of data * - * @param list a list-list - * @param sexp a Sexp - * @param args rest arguments - * - * @return a sexp. + * @return type */ - static Sexp make_list(List&& list) { return Sexp{Type::List, std::move(list.seq_)}; } - template static Sexp make_list(Sexp&& sexp, Args... args) - { - List lst; - lst.add(std::move(sexp)).add(std::forward(args)...); - return make_list(std::move(lst)); + constexpr Type type() const { return static_cast(data.index()); } + + + /** + * Get the name for some type + * + * @param t type + * + * @return name + */ + static constexpr std::string_view type_name(Type t) { + switch(t) { + case Type::String: + return "string"; + case Type::Number: + return "number"; + case Type::Symbol: + return "symbol"; + case Type::List: + return "list"; + default: + return ""; + } + } + + + constexpr bool stringp() const { return std::holds_alternative(data); } + constexpr bool numberp() const { return std::holds_alternative(data); } + constexpr bool listp() const { return std::holds_alternative(data); } + constexpr bool symbolp() const { return std::holds_alternative(data); } + + 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(data); } + List& list() { return std::get(data); } + const String& string() const { return std::get(data); } + String& string() { return std::get(data); } + const Number& number() const { return std::get(data); } + Number& number() { return std::get(data); } + const String& symbol() const { return std::get(data).name; } + String& symbol() { return std::get(data).name; } + + /// Default ctor + Sexp():data{List{}} {} // default: an empty list. + + // Copy & move ctors + Sexp(const Sexp& other):data{other.data}{} + Sexp(Sexp&& other):data{std::move(other.data)}{} + + // Assignment + Sexp& operator=(const Sexp& rhs) { + if (this != &rhs) + data = rhs.data; + return *this; + } + Sexp& operator=(Sexp&& rhs) { + if (this != &rhs) + data = std::move(rhs.data); + return *this; + } + + /// Type specific ctors + Sexp(const List& lst): data{lst} {} + Sexp(List&& lst): data{std::move(lst)} {} + + Sexp(const String& str): data{str} {} + Sexp(String&& str): data{std::move(str)} {} + Sexp(const char *str): Sexp{std::string{str}} {} + Sexp(std::string_view sv): Sexp{std::string{sv}} {} + + template> > + Sexp(N n):data{static_cast(n)} {} + + Sexp(const Symbol& sym): data{sym} {} + Sexp(Symbol&& sym): data{std::move(sym)} {} + + /// + template + Sexp(S&& s, T&& t, Args&&... args): data{List()} { + auto& l{std::get(data)}; + l.emplace_back(Sexp(std::forward(s))); + l.emplace_back(Sexp(std::forward(t))); + (l.emplace_back(Sexp(std::forward(args))), ...); } /** - * Construct a property list sexp from a List + * Parse sexp from string * - * @param name the property name; must start wtth ':' - * @param sexp a Sexp - * @param args rest arguments (property list) + * @param str a string * - * @return a sexp. + * @return either an Sexp or an error */ - template - static Sexp make_prop_list(std::string&& name, Sexp&& sexp, Args... args) - { - List list; - list.add_prop(std::move(name), std::move(sexp), std::forward(args)...); - return make_list(std::move(list)); + static Result parse(const std::string& str); + + + /// List specific + using iterator = List::iterator; + using const_iterator = List::const_iterator; + iterator begin() { return list().begin(); } + const_iterator begin() const { return list().begin(); } + const_iterator cbegin() const { return list().cbegin(); } + + iterator end() { return list().end(); } + const_iterator end() const { return list().end(); } + const_iterator cend() const { return list().cend(); } + + bool empty() const { return list().empty(); } + size_t size() const { return list().size(); } + void clear() { list().clear(); } + + /// Adding to lists + Sexp& add(const Sexp& s) { list().emplace_back(s); return *this; } + Sexp& add(Sexp&& s) { list().emplace_back(std::move(s)); return *this; } + Sexp& add() { return *this; } + + template + Sexp& add(V1&& v1, V2&& v2, Args... args) { + return add(std::forward(v1)) + .add(std::forward(v2)) + .add(std::forward(args)...); + } + + // Plist (property lists) + bool plistp() const { return listp() && plistp(cbegin(), cend()); } + Sexp& put_props() { return *this; } // Final case for template pack. + template + Sexp& put_props(PropType&& prop, SexpType&& sexp, Args... args) { + auto&& propname{std::string(prop)}; + return del_prop(propname) + .add(Symbol(std::move(propname)), + std::forward(sexp)) + .put_props(std::forward(args)...); } /** - * Construct a properrty list sexp from a List + * Find the property value for some property by name * - * @param funcname function name for the call - * @param name the property name; must start wtth ':' - * @param sexp a Sexp - * @param args rest arguments (property list) + * @param p property name * - * @return a sexp. + * @return the property if found, or the symbol nil otherwise. */ - template - static Sexp make_call(std::string&& funcname, std::string&& name, Sexp&& sexp, Args... args) - { - List list; - list.add(make_symbol(std::move(funcname))); - list.add_prop(std::move(name), std::move(sexp), std::forward(args)...); - return make_list(std::move(list)); - } - - /// Some type helpers - bool is_list() const { return type() == Type::List; } - bool is_string() const { return type() == Type::String; } - bool is_number() const { return type() == Type::Number; } - bool is_symbol() const { return type() == Type::Symbol; } - bool is_empty() const { return type() == Type::Empty; } - - operator bool() const { return !is_empty(); } - - static constexpr auto SymbolNil{"nil"}; - static constexpr auto SymbolT{"t"}; - bool is_nil() const { return is_symbol() && value() == SymbolNil; } - bool is_t() const { return is_symbol() && value() == SymbolT; } - - /** - * Is this a prop-list? A prop list is a list sexp with alternating - * property / sexp - * - * @return - */ - bool is_prop_list() const - { - if (!is_list() || list().size() % 2 != 0) - return false; + 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 is_prop_list(list().begin(), list().end()); + return Sexp::nil(); + } + bool has_prop(const std::string& s) const { + return find_prop(s, cbegin(), cend())!= cend(); } - /** - * Is this a call? A call is a list sexp with a symbol (function name), - * followed by a prop list - * - * @return - */ - bool is_call() const - { - if (!is_list() || list().size() % 2 != 1 || !list().at(0).is_symbol()) - return false; - else - return is_prop_list(list().begin() + 1, list().end()); - } - - enum struct FormattingOptions { + /// Output to string + enum struct Format { Default = 0, /**< Nothing in particular */ SplitList = 1 << 0, /**< Insert newline after list item */ + TypeInfo = 1 << 1, /**< Show type-info */ }; - FormattingOptions formatting_opts{}; /**< Formatting option for the - * string output */ - -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)} { - if (!is_list()) - throw Error{Error::Code::InvalidArgument, "must be a list type"}; - if (is_empty()) - throw Error{Error::Code::InvalidArgument, "cannot be an empty type"}; - } /** - * Is the sexp a valid property name? + * Get a string representation of the sexp * - * @param sexp a Sexp. - * - * @return true or false. + * @return str */ - static bool is_prop_name(const std::string& str) - { - return str.size() > 1 && str.at(0) == ':'; - } - static bool is_prop_name(const Sexp& sexp) - { - return sexp.is_symbol() && is_prop_name(sexp.value()); - } + std::string to_string(Format fopts=Format::Default) const; + std::string to_json_string(Format fopts=Format::Default) const; - static bool is_prop_list(Seq::const_iterator b, Seq::const_iterator e) - { - while (b != e) { - const Sexp& s{*b}; - if (!is_prop_name(s)) - return false; - if (++b == e) - return false; - ++b; - } - return b == e; - } - - 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) */ + Sexp& del_prop(const std::string& pname); +protected: + const_iterator find_prop(const std::string& s, const_iterator b, + const_iterator e) const; + bool plistp(const_iterator b, const_iterator e) const; +private: + iterator find_prop(const std::string& s,iterator b, + iterator e); + Data data; }; -static inline std::ostream& -operator<<(std::ostream& os, Sexp::Type id) -{ - switch (id) { - case Sexp::Type::List: - os << "list"; - break; - case Sexp::Type::String: - os << "string"; - break; - case Sexp::Type::Number: - os << "number"; - break; - case Sexp::Type::Symbol: - os << "symbol"; - break; - case Sexp::Type::Raw: - os << "raw"; - break; - case Sexp::Type::Empty: - os << "empty"; - break; - default: throw std::runtime_error("unknown node type"); - } +MU_ENABLE_BITOPS(Sexp::Format); +/** + * String-literal; allow for ":foo"_sym to be a symbol + */ +static inline Sexp::Symbol +operator"" _sym(const char* str, std::size_t n) +{ + return Sexp::Symbol{str}; +} + +static inline std::ostream& +operator<<(std::ostream& os, const Sexp::Type& stype) +{ + os << Sexp::type_name(stype); return os; } + static inline std::ostream& operator<<(std::ostream& os, const Sexp& sexp) { - os << sexp.to_sexp_string(); + os << sexp.to_string(); return os; } -static inline std::ostream& -operator<<(std::ostream& os, const Sexp::List& sexp) -{ - os << Sexp::make_list(Sexp::List(sexp)); - return os; -} -MU_ENABLE_BITOPS(Sexp::FormattingOptions); - } // namespace Mu #endif /* MU_SEXP_HH__ */