lib/utils: build s-expression programmatically building

Allow for programmatically buildings sexps, rather that using raw
strings.
This commit is contained in:
Dirk-Jan C. Binnema 2020-06-01 19:01:03 +03:00
parent b672f6bc1d
commit 3afdc08d50
11 changed files with 507 additions and 270 deletions

1
.gitignore vendored
View File

@ -121,3 +121,4 @@ perf.data.old
mu-*-coverage
mu*tar.xz
compile_commands.json
/lib/utils/test-sexp

View File

@ -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+= \

View File

@ -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 <string> 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 <symbol> 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 <integer> 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 <symbol> 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<std::string>
@ -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 <list> but got %s",
to_string(it->type).c_str());
to_string(it->type()).c_str());
std::vector<std::string> 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;

View File

@ -28,7 +28,7 @@
#include <algorithm>
#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<std::string, ArgInfo>;
// The parameters to a Handler.
using Parameters = std::vector<Sexp::Node>;
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

View File

@ -1,115 +0,0 @@
/*
** Copyright (C) 2020 djcb <djcb@evergrey>
**
** 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 <string>
#include <vector>
#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 <list>");
}
/**
* Construct a list node
* @param childrenarg the list children
*
* @return
*/
explicit Node(std::vector<Node>&& 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<Node> 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 << "<list>"; break;
case Sexp::Type::String: os << "<string>"; break;
case Sexp::Type::Integer: os << "<integer>"; break;
case Sexp::Type::Symbol: os << "<symbol>"; 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__ */

View File

@ -1,5 +1,6 @@
/*
** Copyright (C) 2020 djcb <djcb@evergrey>
** Copyright (C) 2020 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
@ -18,9 +19,11 @@
*/
#include "mu-sexp-parser.hh"
#include "mu-sexp.hh"
#include "mu-utils.hh"
#include <sstream>
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<Node> 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();
}

252
lib/utils/mu-sexp.hh Normal file
View File

@ -0,0 +1,252 @@
/*
** Copyright (C) 2020 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
** 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 <string>
#include <vector>
#include <type_traits>
#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<typename T> 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<Node> 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 <list>");
}
/**
* 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 << "<list>"; break;
case Sexp::Node::Type::String: os << "<string>"; break;
case Sexp::Node::Type::Number: os << "<number>"; break;
case Sexp::Node::Type::Symbol: os << "<symbol>"; 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__ */

View File

@ -1,5 +1,5 @@
/*
** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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\")"));
}

View File

@ -1,76 +0,0 @@
/*
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <vector>
#include <glib.h>
#include <iostream>
#include <sstream>
#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)", "<symbol>{:foo-123}");
check_parse(R"("foo")", "<string>{foo}");
check_parse(R"(12345)", "<integer>{12345}");
check_parse(R"(-12345)", "<integer>{-12345}");
check_parse(R"((123 bar "cuux"))", "<list>(<integer>{123}<symbol>{bar}<string>{cuux})");
check_parse(R"("\"")", "<string>{\"}");
check_parse(R"("\\")", "<string>{\\}");
}
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;
}

133
lib/utils/test-sexp.cc Normal file
View File

@ -0,0 +1,133 @@
/*
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <vector>
#include <glib.h>
#include <iostream>
#include <sstream>
#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;
}

View File

@ -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) {