From 3afdc08d50f9a73e53285b5eb0e38e60ad10576d Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Mon, 1 Jun 2020 19:01:03 +0300 Subject: [PATCH] lib/utils: build s-expression programmatically building Allow for programmatically buildings sexps, rather that using raw strings. --- .gitignore | 1 + lib/utils/Makefile.am | 12 +- lib/utils/mu-command-parser.cc | 75 +++--- lib/utils/mu-command-parser.hh | 13 +- lib/utils/mu-sexp-parser.hh | 115 --------- lib/utils/{mu-sexp-parser.cc => mu-sexp.cc} | 60 ++++- lib/utils/mu-sexp.hh | 252 ++++++++++++++++++++ lib/utils/test-command-parser.cc | 29 ++- lib/utils/test-sexp-parser.cc | 76 ------ lib/utils/test-sexp.cc | 133 +++++++++++ mu/mu-cmd-server.cc | 11 +- 11 files changed, 507 insertions(+), 270 deletions(-) delete mode 100644 lib/utils/mu-sexp-parser.hh rename lib/utils/{mu-sexp-parser.cc => mu-sexp.cc} (76%) create mode 100644 lib/utils/mu-sexp.hh delete mode 100644 lib/utils/test-sexp-parser.cc create mode 100644 lib/utils/test-sexp.cc diff --git a/.gitignore b/.gitignore index 62e23bc8..3e76d235 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,4 @@ perf.data.old mu-*-coverage mu*tar.xz compile_commands.json +/lib/utils/test-sexp diff --git a/lib/utils/Makefile.am b/lib/utils/Makefile.am index 9c5681d3..04f616d7 100644 --- a/lib/utils/Makefile.am +++ b/lib/utils/Makefile.am @@ -55,8 +55,8 @@ libmu_utils_la_SOURCES= \ mu-command-parser.hh \ mu-readline.cc \ mu-readline.hh \ - mu-sexp-parser.cc \ - mu-sexp-parser.hh \ + mu-sexp.cc \ + mu-sexp.hh \ mu-str.c \ mu-str.h \ mu-util.c \ @@ -94,10 +94,10 @@ test_mu_str_LDADD= \ libmu-utils.la TEST_PROGS+= \ - test-sexp-parser -test_sexp_parser_SOURCES= \ - test-sexp-parser.cc -test_sexp_parser_LDADD= \ + test-sexp +test_sexp_SOURCES= \ + test-sexp.cc +test_sexp_LDADD= \ libmu-utils.la TEST_PROGS+= \ diff --git a/lib/utils/mu-command-parser.cc b/lib/utils/mu-command-parser.cc index 7c4aa4a9..596be055 100644 --- a/lib/utils/mu-command-parser.cc +++ b/lib/utils/mu-command-parser.cc @@ -27,24 +27,25 @@ using namespace Mu; using namespace Command; using namespace Sexp; +using Type = Node::Type; + static Mu::Error command_error(const std::string& msg) { return Mu::Error(Error::Code::Command, msg); } - void Command::invoke(const Command::CommandMap& cmap, const Node& call) { - if (call.type != Type::List || call.children.empty() || - call.children[0].type != Type::Symbol) + if (call.type() != Type::List || call.elements().empty() || + call.elements().at(0).type() != Type::Symbol) throw command_error("call must be a list starting with a symbol"); - const auto& params{call.children}; - const auto cmd_it = cmap.find(params[0].value); + const auto& params{call.elements()}; + const auto cmd_it = cmap.find(params.at(0).value()); if (cmd_it == cmap.end()) - throw command_error("unknown command '" + params[0].value + "'"); + throw command_error("unknown command '" + params.at(0).value() + "'"); const auto& cinfo{cmd_it->second}; @@ -58,8 +59,8 @@ Command::invoke(const Command::CommandMap& cmap, const Node& call) // so, we're looking for the odd-numbered parameters. const auto param_it = [&]() { for (size_t i = 1; i < params.size(); i += 2) - if (params[i].type == Type::Symbol && - params[i].value == ':' + argname) + if (params.at(i).type() == Type::Symbol && + params.at(i).value() == ':' + argname) return params.begin() + i + 1; return params.end(); @@ -75,31 +76,31 @@ Command::invoke(const Command::CommandMap& cmap, const Node& call) // the types must match, but the 'nil' symbol is acceptable as // "no value" - if (param_it->type != arginfo.type && - !(param_it->type == Type::Symbol && param_it->value == "nil")) + if (param_it->type() != arginfo.type && + !(param_it->type() == Type::Symbol && param_it->value() == "nil")) throw command_error("parameter '" + argname + "' expects type " + to_string(arginfo.type) + - " but got " + to_string(param_it->type)); + " but got " + to_string(param_it->type())); } // all passed parameters must be known for (size_t i = 1; i < params.size(); i += 2) { if (std::none_of(cinfo.args.begin(), cinfo.args.end(), - [&](auto&& arg) {return params[i].value == ":" + arg.first;})) - throw command_error("unknown parameter '" + params[i].value + "'"); + [&](auto&& arg) {return params.at(i).value() == ":" + arg.first;})) + throw command_error("unknown parameter '" + params.at(i).value() + "'"); } if (cinfo.handler) cinfo.handler(params); } -static Parameters::const_iterator +static auto find_param_node (const Parameters& params, const std::string& argname) { for (size_t i = 1; i < params.size(); i += 2) { if (i + 1 != params.size() && - params[i].type == Type::Symbol && - params[i].value == ':' + argname) + params.at(i).type() == Type::Symbol && + params.at(i).value() == ':' + argname) return params.begin() + i + 1; } @@ -111,7 +112,7 @@ constexpr auto Nil = "nil"; static bool is_nil(const Node& node) { - return node.type == Type::Symbol && node.value == Nil; + return node.type() == Type::Symbol && node.value() == Nil; } const std::string& @@ -121,12 +122,12 @@ Command::get_string_or (const Parameters& params, const std::string& argname, const auto it = find_param_node (params, argname); if (it == params.end() || is_nil(*it)) return alt; - else if (it->type != Type::String) + else if (it->type() != Type::String) throw Error(Error::Code::InvalidArgument, "expected but got %s (value: '%s')", - to_string(it->type).c_str(), - it->value.c_str()); + to_string(it->type()).c_str(), + it->value().c_str()); - return it->value; + return it->value(); } const std::string& @@ -136,12 +137,12 @@ Command::get_symbol_or (const Parameters& params, const std::string& argname, const auto it = find_param_node (params, argname); if (it == params.end() || is_nil(*it)) return alt; - else if (it->type != Type::Symbol) + else if (it->type() != Type::Symbol) throw Error(Error::Code::InvalidArgument, "expected but got %s (value: '%s')", - to_string(it->type).c_str(), - it->value.c_str()); + to_string(it->type()).c_str(), + it->value().c_str()); - return it->value; + return it->value(); } @@ -152,11 +153,11 @@ Command::get_int_or (const Parameters& params, const std::string& argname, const auto it = find_param_node (params, argname); if (it == params.end() || is_nil(*it)) return alt; - else if (it->type != Type::Integer) + else if (it->type() != Type::Number) throw Error(Error::Code::InvalidArgument, "expected but got %s", - to_string(it->type).c_str()); + to_string(it->type()).c_str()); else - return ::atoi(it->value.c_str()); + return ::atoi(it->value().c_str()); } bool @@ -166,11 +167,11 @@ Command::get_bool_or (const Parameters& params, const std::string& argname, const auto it = find_param_node (params, argname); if (it == params.end()) return alt; - else if (it->type != Type::Symbol) + else if (it->type() != Type::Symbol) throw Error(Error::Code::InvalidArgument, "expected but got %s", - to_string(it->type).c_str()); + to_string(it->type()).c_str()); else - return it->value != Nil; + return it->value() != Nil; } std::vector @@ -179,17 +180,17 @@ Command::get_string_vec (const Parameters& params, const std::string& argname) const auto it = find_param_node (params, argname); if (it == params.end() || is_nil(*it)) return {}; - else if (it->type != Type::List) + else if (it->type() != Type::List) throw Error(Error::Code::InvalidArgument, "expected but got %s", - to_string(it->type).c_str()); + to_string(it->type()).c_str()); std::vector vec; - for (const auto& n: it->children) { - if (n.type != Type::String) + for (const auto& n: it->elements()) { + if (n.type() != Type::String) throw Error(Error::Code::InvalidArgument, "expected string element but got %s", - to_string(n.type).c_str()); - vec.emplace_back (n.value); + to_string(n.type()).c_str()); + vec.emplace_back (n.value()); } return vec; diff --git a/lib/utils/mu-command-parser.hh b/lib/utils/mu-command-parser.hh index 58c2f115..9ad8110d 100644 --- a/lib/utils/mu-command-parser.hh +++ b/lib/utils/mu-command-parser.hh @@ -28,7 +28,7 @@ #include #include "utils/mu-error.hh" -#include "utils/mu-sexp-parser.hh" +#include "utils/mu-sexp.hh" namespace Mu { @@ -49,18 +49,18 @@ namespace Command { /// Information about a function argument struct ArgInfo { - ArgInfo (Sexp::Type typearg, bool requiredarg, std::string&& docarg): + ArgInfo (Sexp::Node::Type typearg, bool requiredarg, std::string&& docarg): type{typearg}, required{requiredarg},docstring{std::move(docarg)} {} - const Sexp::Type type; /**< Sexp::Type of the argument */ - const bool required; /**< Is this argument required? */ - const std::string docstring; /**< Documentation */ + const Sexp::Node::Type type; /**< Sexp::Node::Type of the argument */ + const bool required; /**< Is this argument required? */ + const std::string docstring; /**< Documentation */ }; /// The arguments for a function, which maps their names to the information. using ArgMap = std::unordered_map; // The parameters to a Handler. -using Parameters = std::vector; +using Parameters = Sexp::Node::Seq; int get_int_or (const Parameters& parms, const std::string& argname, int alt=0); bool get_bool_or (const Parameters& parms, const std::string& argname, bool alt=false); @@ -149,7 +149,6 @@ operator<<(std::ostream& os, const Command::CommandMap& map) return os; } - } // namespace Command } // namespace Mu diff --git a/lib/utils/mu-sexp-parser.hh b/lib/utils/mu-sexp-parser.hh deleted file mode 100644 index ecb97f2d..00000000 --- a/lib/utils/mu-sexp-parser.hh +++ /dev/null @@ -1,115 +0,0 @@ -/* -** Copyright (C) 2020 djcb -** -** 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 -** Free Software Foundation; either version 3, or (at your option) any -** later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software Foundation, -** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -** -*/ - -#ifndef MU_SEXP_PARSER_HH__ -#define MU_SEXP_PARSER_HH__ - -#include -#include - -#include "utils/mu-error.hh" - -namespace Mu { -namespace Sexp { - -/// Simple s-expression parser 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)) - -/// Node type -enum struct Type { List, String, Integer, Symbol }; - -/// Parse node -struct Node { - /** - * Construct a new non-list node - * - * @param typearg the type of node - * @param valuearg the value - */ - Node(Type typearg, std::string&& valuearg): - type{typearg}, value{std::move(valuearg)} { - if (typearg == Type::List) - throw Error(Error::Code::Parsing, - "atomic type cannot be a "); - } - - /** - * Construct a list node - - * @param childrenarg the list children - * - * @return - */ - explicit Node(std::vector&& childrenarg): - type{Type::List}, children{std::move(childrenarg)} - {} - - const Type type; /**< Type of node */ - const std::string value; /**< String value of node (only for non-Type::List)*/ - const std::vector children; /**< Chiidren of node (only for Type::List) */ -}; - -/** - * Parse the string as an s-expressi9on. - * - * @param expr an s-expression string - * - * @return the parsed s-expression, or throw Error. - */ -Node parse(const std::string& expr); - -static inline std::ostream& -operator<<(std::ostream& os, Sexp::Type id) -{ - switch (id) { - case Sexp::Type::List: os << ""; break; - case Sexp::Type::String: os << ""; break; - case Sexp::Type::Integer: os << ""; break; - case Sexp::Type::Symbol: os << ""; break; - default: throw std::runtime_error ("unknown node type"); - } - - return os; -} - -static inline std::ostream& -operator<<(std::ostream& os, const Sexp::Node& node) -{ - os << node.type; - if (node.type == Sexp::Type::List) { - os << '('; - for (auto&& elm: node.children) - os << elm; - os << ')'; - } else - os << '{' << node.value << '}'; - - return os; -} - - -} // Sexp - - -} // Mu - -#endif /* MU_SEXP_PARSER_HH__ */ diff --git a/lib/utils/mu-sexp-parser.cc b/lib/utils/mu-sexp.cc similarity index 76% rename from lib/utils/mu-sexp-parser.cc rename to lib/utils/mu-sexp.cc index 52f418ad..c516a889 100644 --- a/lib/utils/mu-sexp-parser.cc +++ b/lib/utils/mu-sexp.cc @@ -1,5 +1,6 @@ /* -** Copyright (C) 2020 djcb +** Copyright (C) 2020 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 +19,11 @@ */ -#include "mu-sexp-parser.hh" +#include "mu-sexp.hh" #include "mu-utils.hh" +#include + using namespace Mu; using namespace Sexp; @@ -58,16 +61,16 @@ parse_list (const std::string& expr, size_t& pos) if (expr[pos] != '(') // sanity check. throw parsing_error(pos, "expected: '(' but got '%c", expr[pos]); - std::vector children; + Node::Seq children; ++pos; while (expr[pos] != ')' && pos != expr.size()) - children.emplace_back(parse(expr, pos)); + children.add(parse(expr, pos)); if (expr[pos] != ')') throw parsing_error(pos, "expected: ')' but got '%c'", expr[pos]); ++pos; - return Node{std::move(children)}; + return Node::make_list(std::move(children)); } // parse string @@ -100,7 +103,7 @@ parse_string (const std::string& expr, size_t& pos) throw parsing_error(pos, "unterminated string '%s'", str.c_str()); ++pos; - return Node{Type::String, std::move(str)}; + return Node::make_string(std::move(str)); } static Node @@ -118,7 +121,7 @@ parse_integer (const std::string& expr, size_t& pos) for (; isdigit(expr[pos]); ++pos) num += expr[pos]; - return Node {Type::Integer, std::move(num)}; + return Node::make_number(::atoi(num.c_str())); } static Node @@ -131,7 +134,7 @@ parse_symbol (const std::string& expr, size_t& pos) for (++pos; isalnum(expr[pos]) || expr[pos] == '-'; ++pos) symbol += expr[pos]; - return Node { Type::Symbol, std::move(symbol)}; + return Node::make_symbol(std::move(symbol)); } @@ -163,7 +166,7 @@ parse (const std::string& expr, size_t& pos) } Node -Sexp::parse (const std::string& expr) +Sexp::Node::make (const std::string& expr) { size_t pos{}; auto node{::parse (expr, pos)}; @@ -173,3 +176,42 @@ Sexp::parse (const std::string& expr) return node; } + + +std::string +Sexp::Node::to_string () const +{ + std::stringstream sstrm; + + switch (type()) { + case Type::List: { + sstrm << '('; + bool first{true}; + for (auto&& child : elements()) { + sstrm << (first ? "" : " ") << child.to_string(); + first = false; + } + sstrm << ')'; + break; + } + case Type::String: + //sstrm << quote(value()); + sstrm << "\""; + for (auto&& k: value()) { + switch (k) { + case '"' : sstrm << "\\\""; break; + case '\\': sstrm << "\\\\"; break; + default: sstrm << k; + } + } + sstrm << "\""; + break; + + case Type::Number: + case Type::Symbol: + default: + sstrm << value(); + } + + return sstrm.str(); +} diff --git a/lib/utils/mu-sexp.hh b/lib/utils/mu-sexp.hh new file mode 100644 index 00000000..b2aad10f --- /dev/null +++ b/lib/utils/mu-sexp.hh @@ -0,0 +1,252 @@ +/* +** Copyright (C) 2020 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 +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + + +#ifndef MU_SEXP_HH__ +#define MU_SEXP_HH__ + +#include +#include +#include + +#include "utils/mu-utils.hh" +#include "utils/mu-error.hh" + +namespace Mu { +namespace Sexp { + +/// Simple s-expression parser & builder 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 +struct Node { + /// Node type + enum struct Type { List, String, Number, Symbol }; + + /** + * Make a node of out of an s-expression string. + * + * @param expr a string containing an s-expression + * + * @return the parsed s-expression, or throw Error. + */ + static Node make (const std::string& expr); + + /** + * Make a node for a string/integer/symbol/list value + * + * @param val some value + * + * @return a node + */ + static Node make_string (std::string&& val) { return Node{Type::String, std::move(val)}; } + static Node make_string (const std::string& val) { return Node{Type::String, std::string{val}}; } + static Node make_number (int val) { return Node{Type::Number, format("%d", val)}; } + static Node make_symbol (std::string&& val) { return Node{Type::Symbol, std::move(val)}; } + + + /// sequence of node objects + struct Seq { + /** + * Add an item to a node-sequence. + * + * @param node item to add; moved/consumed. + */ + void add(Node&& node) {nodes_.emplace_back(std::move(node));} + void add(Seq&& seq) {add(make_list(std::move(seq)));} + void add(std::string&& s) {add(make_string(std::move(s)));} + void add(int i) {add(make_number(i));} + // disambiguate. + void add_symbol(std::string&& s) {add(make_symbol(std::move(s)));} + + /** + * Add a property to tne node sequence; i.e. a property-symbol + * (starting with ':') and some node + * + * @param name name (must start with ":"), and some value or list + * @param val + */ + template void add_prop(std::string&& name, T&& val) { + + if (name.empty() || name[0] != ':') + throw Error{Error::Code::InvalidArgument, + "property names must start with ':'"}; + + add(make_symbol(std::move(name))); + add(std::move(val)); + } + + + // deliberately limited stl-like + + /** + * Is this an empty sequence? + * + * @return true or false + */ + bool empty() const {return nodes_.empty();} + + /** + * Get the number of elsements in the sequence + * + * @return number of elmement. + */ + size_t size() const {return nodes_.size();} + + /** + * Get begin iterator of the sequence + * + * @return iterator + */ + const auto begin() const { return nodes_.begin(); } + /** + * Get the end iterator of the sequnce + * + * @return an iterator + */ + const auto end() const { return nodes_.end(); } + + /** + * Get a const ref to the item at idx. + * + * @param idx index, must be < size() + * + * @return const ref to the item. + */ + const auto at(size_t idx) const { return nodes_.at(idx);} + + private: + std::vector nodes_; + }; + + /** + * Make a list node from a sequence + * + * @param seq a sequence of nodes + * + * @return a node + */ + static Node make_list (Seq&& seq) { return Node{std::move(seq)}; } + + /** + * Convert a Sexp::Node to its string representation + * + * @return the string representation + */ + std::string to_string() const; + + /** + * Return the type of this Node. + * + * @return the type + */ + Type type() const { return type_; } + + /// 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_nil() const { return is_symbol() && value() == "nil"; } + bool is_t() const { return is_symbol() && value() == "t"; } + + /** + * The elements of this node; invalid unless this is a list node. + * + * @return + */ + const Seq& elements() const { + if (type() != Type::List) + throw Error(Error::Code::InvalidArgument, "no elements for non-list"); + return seq_; + } + + /** + * The value of this node; invalid for list nodes. + * + * @return + */ + const std::string& value() const { + if (type_ == Type::List) + throw Error(Error::Code::InvalidArgument, "no value for list"); + return value_; + } + +private: + /** + * Construct a new non-list node + * + * @param typearg the type of node + * @param valuearg the value + */ + Node(Type typearg, std::string&& valuearg): + type_{typearg}, value_{std::move(valuearg)} { + if (typearg == Type::List) + throw Error(Error::Code::Parsing, + "atomic type cannot be a "); + } + + /** + * Construct a list node + * + * @param kids the list's children + */ + explicit Node(Seq&& seq): + type_{Type::List}, seq_{std::move(seq)} + {} + + + 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::Lst) */ +}; + +static inline std::ostream& +operator<<(std::ostream& os, Sexp::Node::Type id) +{ + switch (id) { + case Sexp::Node::Type::List: os << ""; break; + case Sexp::Node::Type::String: os << ""; break; + case Sexp::Node::Type::Number: os << ""; break; + case Sexp::Node::Type::Symbol: os << ""; break; + default: throw std::runtime_error ("unknown node type"); + } + + return os; +} + +static inline std::ostream& +operator<<(std::ostream& os, const Sexp::Node& node) +{ + os << node.to_string(); + return os; +} + +} // Sexp + + +} // Mu + +#endif /* MU_SEXP_HH__ */ diff --git a/lib/utils/test-command-parser.cc b/lib/utils/test-command-parser.cc index f18e6425..00da3163 100644 --- a/lib/utils/test-command-parser.cc +++ b/lib/utils/test-command-parser.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2017 Dirk-Jan C. Binnema +** Copyright (C) 2020 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License @@ -31,23 +31,23 @@ using namespace Mu; static void test_param_getters() { - const auto node { Sexp::parse(R"((foo :bar 123 :cuux "456" :boo nil :bah true))")}; + const auto node { Sexp::Node::make(R"((foo :bar 123 :cuux "456" :boo nil :bah true))")}; std::cout << node << "\n"; - g_assert_cmpint(Command::get_int_or(node.children,"bar"), ==, 123); - assert_equal(Command::get_string_or(node.children, "bra", "bla"), "bla"); - assert_equal(Command::get_string_or(node.children, "cuux"), "456"); + g_assert_cmpint(Command::get_int_or(node.elements(), "bar"), ==, 123); + assert_equal(Command::get_string_or(node.elements(), "bra", "bla"), "bla"); + assert_equal(Command::get_string_or(node.elements(), "cuux"), "456"); - g_assert_true(Command::get_bool_or(node.children,"boo") == false); - g_assert_true(Command::get_bool_or(node.children,"bah") == true); + g_assert_true(Command::get_bool_or(node.elements(),"boo") == false); + g_assert_true(Command::get_bool_or(node.elements(),"bah") == true); } static bool call (const Command::CommandMap& cmap, const std::string& sexp) try { - const auto node{Sexp::parse(sexp)}; + const auto node{Sexp::Node::make(sexp)}; g_message ("invoking %s", to_string(node).c_str()); invoke (cmap, node); @@ -70,8 +70,8 @@ test_command() cmap.emplace("my-command", CommandInfo{ - ArgMap{ {"param1", ArgInfo{Sexp::Type::String, true, "some string" }}, - {"param2", ArgInfo{Sexp::Type::Integer, false, "some integer"}}}, + ArgMap{ {"param1", ArgInfo{Sexp::Node::Type::String, true, "some string" }}, + {"param2", ArgInfo{Sexp::Node::Type::Number, false, "some integer"}}}, "My command,", {}}); @@ -93,8 +93,8 @@ test_command2() cmap.emplace("bla", CommandInfo{ ArgMap{ - {"foo", ArgInfo{Sexp::Type::Integer, false, "foo"}}, - {"bar", ArgInfo{Sexp::Type::String, false, "bar"}}, + {"foo", ArgInfo{Sexp::Node::Type::Number, false, "foo"}}, + {"bar", ArgInfo{Sexp::Node::Type::String, false, "bar"}}, },"yeah", [&](const auto& params){}}); @@ -115,8 +115,8 @@ test_command_fail() cmap.emplace("my-command", CommandInfo{ - ArgMap{ {"param1", ArgInfo{Sexp::Type::String, true, "some string" }}, - {"param2", ArgInfo{Sexp::Type::Integer, false, "some integer"}}}, + ArgMap{ {"param1", ArgInfo{Sexp::Node::Type::String, true, "some string" }}, + {"param2", ArgInfo{Sexp::Node::Type::Number, false, "some integer"}}}, "My command,", {}}); @@ -124,7 +124,6 @@ test_command_fail() g_assert_false (call(cmap, "(my-command2)")); g_assert_false(call(cmap, "(my-command :param1 123 :param2 123)")); g_assert_false(call(cmap, "(my-command :param1 \"hello\" :param2 \"123\")")); - } diff --git a/lib/utils/test-sexp-parser.cc b/lib/utils/test-sexp-parser.cc deleted file mode 100644 index 0dccf963..00000000 --- a/lib/utils/test-sexp-parser.cc +++ /dev/null @@ -1,76 +0,0 @@ -/* -** Copyright (C) 2020 Dirk-Jan C. Binnema -** -** This library is free software; you can redistribute it and/or -** modify it under the terms of the GNU Lesser General Public License -** as published by the Free Software Foundation; either version 2.1 -** of the License, or (at your option) any later version. -** -** This library is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -** Lesser General Public License for more details. -** -** You should have received a copy of the GNU Lesser General Public -** License along with this library; if not, write to the Free -** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA -** 02110-1301, USA. -*/ - -#include -#include - -#include -#include - -#include "mu-command-parser.hh" -#include "mu-utils.hh" - -using namespace Mu; - -static bool -check_parse (const std::string& expr, const std::string& expected) -{ - try { - const auto parsed{to_string(Sexp::parse(expr))}; - g_assert_cmpstr(parsed.c_str(), ==, expected.c_str()); - return true; - - } catch (const Error& err) { - g_warning ("caught exception parsing '%s': %s", expr.c_str(), err.what()); - return false; - } -} - -static void -test_parser() -{ - check_parse(R"(:foo-123)", "{:foo-123}"); - check_parse(R"("foo")", "{foo}"); - check_parse(R"(12345)", "{12345}"); - check_parse(R"(-12345)", "{-12345}"); - check_parse(R"((123 bar "cuux"))", "({123}{bar}{cuux})"); - - check_parse(R"("\"")", "{\"}"); - check_parse(R"("\\")", "{\\}"); -} - -int -main (int argc, char *argv[]) try -{ - g_test_init (&argc, &argv, NULL); - - if (argc == 2) { - std::cout << Sexp::parse(argv[1]) << '\n'; - return 0; - } - - g_test_add_func ("/utils/command-parser/parse", test_parser); - - return g_test_run (); - - -} catch (const std::runtime_error& re) { - std::cerr << re.what() << "\n"; - return 1; -} diff --git a/lib/utils/test-sexp.cc b/lib/utils/test-sexp.cc new file mode 100644 index 00000000..1bbbfe85 --- /dev/null +++ b/lib/utils/test-sexp.cc @@ -0,0 +1,133 @@ +/* +** Copyright (C) 2020 Dirk-Jan C. Binnema +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public License +** as published by the Free Software Foundation; either version 2.1 +** of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; if not, write to the Free +** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA +** 02110-1301, USA. +*/ + +#include +#include + +#include +#include + +#include "mu-command-parser.hh" +#include "mu-utils.hh" + +using namespace Mu; +using namespace Sexp; + +static bool +check_parse (const std::string& expr, const std::string& expected) +{ + try { + const auto parsed{to_string(Node::make(expr))}; + g_assert_cmpstr(parsed.c_str(), ==, expected.c_str()); + return true; + + } catch (const Error& err) { + g_warning ("caught exception parsing '%s': %s", expr.c_str(), err.what()); + return false; + } +} + +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\""); +} + +static void +test_builder() +{ + const auto nstr{Node::make_string("foo")}; + g_assert_true(nstr.value() == "foo"); + g_assert_true(nstr.type() == Node::Type::String); + assert_equal(nstr.to_string(), "\"foo\""); + + const auto nnum{Node::make_number(123)}; + g_assert_true(nnum.value() == "123"); + g_assert_true(nnum.type() == Node::Type::Number); + assert_equal(nnum.to_string(), "123"); + + const auto nsym{Node::make_symbol("blub")}; + g_assert_true(nsym.value() == "blub"); + g_assert_true(nsym.type() == Node::Type::Symbol); + assert_equal(nsym.to_string(), "blub"); + + Node::Seq seq; + seq.add(Node::make_string("foo")); + seq.add(Node::make_number(123)); + seq.add(Node::make_symbol("blub")); + + const auto nlst = Node::make_list(std::move(seq)); + g_assert_true(nlst.elements().size() == 3); + g_assert_true(nlst.type() == Node::Type::List); + g_assert_true(nlst.elements().at(1).value() == "123"); + + assert_equal(nlst.to_string(),"(\"foo\" 123 blub)"); +} + +static void +test_props() +{ + Node::Seq seq; + + seq.add_prop(":foo", "bär"); + seq.add_prop(":cuux", 123); + seq.add_prop(":flub", Node::make_symbol("fnord")); + + Node::Seq seq2; + seq2.add(Node::make_string("foo")); + seq2.add(Node::make_number(123)); + seq2.add(Node::make_symbol("blub")); + + seq.add_prop(":boo", std::move(seq2)); + + Node expr = Node::make_list(std::move(seq)); + assert_equal(expr.to_string(), + "(:foo \"b\\303\\244r\" :cuux 123 :flub fnord :boo (\"foo\" 123 blub))"); +} + +int +main (int argc, char *argv[]) try +{ + g_test_init (&argc, &argv, NULL); + + if (argc == 2) { + std::cout << Sexp::Node::make(argv[1]) << '\n'; + return 0; + } + + g_test_add_func ("/utils/sexp/parser", test_parser); + g_test_add_func ("/utils/sexp/builder", test_builder); + g_test_add_func ("/utils/sexp/props", test_props); + + return g_test_run (); + + +} catch (const std::runtime_error& re) { + std::cerr << re.what() << "\n"; + return 1; +} diff --git a/mu/mu-cmd-server.cc b/mu/mu-cmd-server.cc index 83ca3bc2..e0867f5b 100644 --- a/mu/mu-cmd-server.cc +++ b/mu/mu-cmd-server.cc @@ -1131,9 +1131,11 @@ make_command_map (Context& context) { CommandMap cmap; + using Type = Node::Type; + cmap.emplace("add", CommandInfo{ - ArgMap{ {"path", ArgInfo{Type::String, true, "file system path to the message" }}}, + ArgMap{ {"path", ArgInfo{Type::String, true, "file system path to the message" }}}, "add a message to the store", [&](const auto& params){add_handler(context, params);}}); @@ -1277,7 +1279,7 @@ mu_cmd_server (MuConfig *opts, GError **err) try if (opts->commands) { Context ctx{}; auto cmap = make_command_map(ctx); - invoke(cmap, Sexp::parse("(help :full t)")); + invoke(cmap, Sexp::Node::make("(help :full t)")); return MU_OK; } @@ -1285,7 +1287,7 @@ mu_cmd_server (MuConfig *opts, GError **err) try context.command_map = make_command_map (context); if (opts->eval) { // evaluate command-line command & exit - auto call{Sexp::parse(opts->eval)}; + auto call{Sexp::Node::make(opts->eval)}; invoke(context.command_map, call); return MU_OK; } @@ -1306,8 +1308,7 @@ mu_cmd_server (MuConfig *opts, GError **err) try if (line.find_first_not_of(" \t") == std::string::npos) continue; // skip whitespace-only lines - auto call{Sexp::parse(line)}; - + auto call{Sexp::Node::make(line)}; invoke(context.command_map, call); } catch (const Error& er) {