mirror of https://github.com/djcb/mu.git
utils/sexp: Clean up API and implementation
Also update the tests and command-parser.
This commit is contained in:
parent
de8f1d3e6a
commit
31dd4e2104
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "mu-command-parser.hh"
|
||||
#include "mu-error.hh"
|
||||
#include "mu-utils.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
@ -25,27 +26,22 @@
|
|||
|
||||
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)
|
||||
Command::invoke(const Command::CommandMap& cmap, const Sexp& call)
|
||||
{
|
||||
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");
|
||||
if (!call.is_call()) {
|
||||
throw Mu::Error{Error::Code::Command,
|
||||
"expected call-sexpr but got %s",
|
||||
call.to_string().c_str()};
|
||||
}
|
||||
|
||||
const auto& params{call.elements()};
|
||||
const auto& params{call.list()};
|
||||
const auto cmd_it = cmap.find(params.at(0).value());
|
||||
if (cmd_it == cmap.end())
|
||||
throw command_error("unknown command '" + params.at(0).value() + "'");
|
||||
throw Mu::Error{Error::Code::Command,
|
||||
"unknown command in call %s",
|
||||
call.to_string().c_str()};
|
||||
|
||||
const auto& cinfo{cmd_it->second};
|
||||
|
||||
|
@ -59,8 +55,7 @@ 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.at(i).type() == Type::Symbol &&
|
||||
params.at(i).value() == argname)
|
||||
if (params.at(i).is_symbol() && params.at(i).value() == argname)
|
||||
return params.begin() + i + 1;
|
||||
|
||||
return params.end();
|
||||
|
@ -70,24 +65,29 @@ Command::invoke(const Command::CommandMap& cmap, const Node& call)
|
|||
// it's an error when a required parameter is missing.
|
||||
if (param_it == params.end()) {
|
||||
if (arginfo.required)
|
||||
throw command_error("missing required parameter '" + argname + "'");
|
||||
throw Mu::Error{Error::Code::Command,
|
||||
"missing required parameter %s in call %s",
|
||||
argname.c_str(), call.to_string().c_str()};
|
||||
continue; // not required
|
||||
}
|
||||
|
||||
// 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"))
|
||||
throw command_error("parameter '" + argname + "' expects type " +
|
||||
to_string(arginfo.type) +
|
||||
" but got " + to_string(param_it->type()));
|
||||
if (param_it->type() != arginfo.type && !(param_it->is_nil()))
|
||||
throw Mu::Error{Error::Code::Command,
|
||||
"parameter %s expects type %s, but got %s in call %s",
|
||||
argname.c_str(), to_string(arginfo.type).c_str(),
|
||||
to_string(param_it->type()).c_str(),
|
||||
call.to_string().c_str()};
|
||||
}
|
||||
|
||||
// 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.at(i).value() == arg.first;}))
|
||||
throw command_error("unknown parameter '" + params.at(i).value() + "'");
|
||||
throw Mu::Error{Error::Code::Command,
|
||||
"unknown parameter %s in call %s",
|
||||
params.at(i).value().c_str(), call.to_string().c_str()};
|
||||
}
|
||||
|
||||
if (cinfo.handler)
|
||||
|
@ -108,36 +108,35 @@ 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.at(i).type() == Type::Symbol &&
|
||||
params.at(i).value() == argname)
|
||||
params.at(i).is_symbol() && params.at(i).value() == argname)
|
||||
return params.begin() + i + 1;
|
||||
}
|
||||
|
||||
return params.end();
|
||||
}
|
||||
|
||||
constexpr auto Nil = "nil";
|
||||
|
||||
static bool
|
||||
is_nil(const Node& node)
|
||||
static Error
|
||||
wrong_type (Sexp::Type expected, Sexp::Type got)
|
||||
{
|
||||
return node.type() == Type::Symbol && node.value() == Nil;
|
||||
return Error(Error::Code::InvalidArgument,
|
||||
"expected <%s> but got <%s>",
|
||||
to_string(expected).c_str(),
|
||||
to_string(got).c_str());
|
||||
}
|
||||
|
||||
|
||||
const std::string&
|
||||
Command::get_string_or (const Parameters& params, const std::string& argname,
|
||||
const std::string& alt)
|
||||
{
|
||||
const auto it = find_param_node (params, argname);
|
||||
if (it == params.end() || is_nil(*it))
|
||||
if (it == params.end() || it->is_nil())
|
||||
return alt;
|
||||
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());
|
||||
|
||||
return it->value();
|
||||
else if (!it->is_string())
|
||||
throw wrong_type(Sexp::Type::String, it->type());
|
||||
else
|
||||
return it->value();
|
||||
}
|
||||
|
||||
const std::string&
|
||||
|
@ -145,15 +144,12 @@ Command::get_symbol_or (const Parameters& params, const std::string& argname,
|
|||
const std::string& alt)
|
||||
{
|
||||
const auto it = find_param_node (params, argname);
|
||||
if (it == params.end() || is_nil(*it))
|
||||
if (it == params.end() || it->is_nil())
|
||||
return alt;
|
||||
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());
|
||||
|
||||
return it->value();
|
||||
else if (!it->is_symbol())
|
||||
throw wrong_type(Sexp::Type::Symbol, it->type());
|
||||
else
|
||||
return it->value();
|
||||
}
|
||||
|
||||
|
||||
|
@ -162,12 +158,10 @@ Command::get_int_or (const Parameters& params, const std::string& argname,
|
|||
int alt)
|
||||
{
|
||||
const auto it = find_param_node (params, argname);
|
||||
if (it == params.end() || is_nil(*it))
|
||||
if (it == params.end() || it->is_nil())
|
||||
return alt;
|
||||
else if (it->type() != Type::Number)
|
||||
throw Error(Error::Code::InvalidArgument,
|
||||
"expected <integer> but got %s",
|
||||
to_string(it->type()).c_str());
|
||||
else if (!it->is_number())
|
||||
throw wrong_type(Sexp::Type::Number, it->type());
|
||||
else
|
||||
return ::atoi(it->value().c_str());
|
||||
}
|
||||
|
@ -179,31 +173,25 @@ 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)
|
||||
throw Error(Error::Code::InvalidArgument,
|
||||
"expected <symbol> but got %s",
|
||||
to_string(it->type()).c_str());
|
||||
else if (!it->is_symbol())
|
||||
throw wrong_type(Sexp::Type::Symbol, it->type());
|
||||
else
|
||||
return it->value() != Nil;
|
||||
return it->is_nil() ? false : true;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
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))
|
||||
if (it == params.end() || it->is_nil())
|
||||
return {};
|
||||
else if (it->type() != Type::List)
|
||||
throw Error(Error::Code::InvalidArgument,
|
||||
"expected <list> but got %s",
|
||||
to_string(it->type()).c_str());
|
||||
else if (!it->is_list())
|
||||
throw wrong_type(Sexp::Type::List, it->type());
|
||||
|
||||
std::vector<std::string> vec;
|
||||
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());
|
||||
for (const auto& n: it->list()) {
|
||||
if (!n.is_string())
|
||||
throw wrong_type(Sexp::Type::String, n.type());
|
||||
vec.emplace_back (n.value());
|
||||
}
|
||||
|
||||
|
|
|
@ -49,10 +49,10 @@ namespace Command {
|
|||
|
||||
/// Information about a function argument
|
||||
struct ArgInfo {
|
||||
ArgInfo (Sexp::Node::Type typearg, bool requiredarg, std::string&& docarg):
|
||||
ArgInfo (Sexp::Type typearg, bool requiredarg, std::string&& docarg):
|
||||
type{typearg}, required{requiredarg},docstring{std::move(docarg)}
|
||||
{}
|
||||
const Sexp::Node::Type type; /**< Sexp::Node::Type of the argument */
|
||||
const Sexp::Type type; /**< Sexp::Type of the argument */
|
||||
const bool required; /**< Is this argument required? */
|
||||
const std::string docstring; /**< Documentation */
|
||||
};
|
||||
|
@ -60,7 +60,7 @@ struct ArgInfo {
|
|||
/// 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 = Sexp::Node::Seq;
|
||||
using Parameters = Sexp::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);
|
||||
|
@ -108,7 +108,7 @@ struct CommandInfo {
|
|||
using CommandMap = std::unordered_map<std::string, CommandInfo>;
|
||||
|
||||
/**
|
||||
* Validate that the call (a Sexp::Node) specifies a valid call, then invoke it.
|
||||
* Validate that the call (a Sexp) specifies a valid call, then invoke it.
|
||||
*
|
||||
* A call uses keyword arguments, e.g. something like:
|
||||
* (foo :bar 1 :cuux "fnorb")
|
||||
|
@ -118,7 +118,7 @@ using CommandMap = std::unordered_map<std::string, CommandInfo>;
|
|||
* @param cmap map of commands
|
||||
* @param call node describing a call.
|
||||
*/
|
||||
void invoke(const Command::CommandMap& cmap, const Sexp::Node& call);
|
||||
void invoke(const Command::CommandMap& cmap, const Sexp& call);
|
||||
|
||||
|
||||
static inline std::ostream&
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
#include "mu-utils.hh"
|
||||
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
|
||||
using namespace Mu;
|
||||
using namespace Sexp;
|
||||
|
||||
__attribute__((format(printf, 2, 0))) static Mu::Error
|
||||
parsing_error(size_t pos, const char* frm, ...)
|
||||
|
@ -53,28 +53,28 @@ skip_whitespace (const std::string& s, size_t pos)
|
|||
return pos;
|
||||
}
|
||||
|
||||
static Node parse (const std::string& expr, size_t& pos);
|
||||
static Sexp parse (const std::string& expr, size_t& pos);
|
||||
|
||||
static Node
|
||||
static Sexp
|
||||
parse_list (const std::string& expr, size_t& pos)
|
||||
{
|
||||
if (expr[pos] != '(') // sanity check.
|
||||
throw parsing_error(pos, "expected: '(' but got '%c", expr[pos]);
|
||||
|
||||
Node::Seq children;
|
||||
Sexp::List list;
|
||||
|
||||
++pos;
|
||||
while (expr[pos] != ')' && pos != expr.size())
|
||||
children.add(parse(expr, pos));
|
||||
list.add(parse(expr, pos));
|
||||
|
||||
if (expr[pos] != ')')
|
||||
throw parsing_error(pos, "expected: ')' but got '%c'", expr[pos]);
|
||||
++pos;
|
||||
return Node::make_list(std::move(children));
|
||||
return Sexp::make_list(std::move(list));
|
||||
}
|
||||
|
||||
// parse string
|
||||
static Node
|
||||
static Sexp
|
||||
parse_string (const std::string& expr, size_t& pos)
|
||||
{
|
||||
if (expr[pos] != '"') // sanity check.
|
||||
|
@ -103,10 +103,10 @@ parse_string (const std::string& expr, size_t& pos)
|
|||
throw parsing_error(pos, "unterminated string '%s'", str.c_str());
|
||||
|
||||
++pos;
|
||||
return Node::make_string(std::move(str));
|
||||
return Sexp::make_string(std::move(str));
|
||||
}
|
||||
|
||||
static Node
|
||||
static Sexp
|
||||
parse_integer (const std::string& expr, size_t& pos)
|
||||
{
|
||||
if (!isdigit(expr[pos]) && expr[pos] != '-') // sanity check.
|
||||
|
@ -121,10 +121,10 @@ parse_integer (const std::string& expr, size_t& pos)
|
|||
for (; isdigit(expr[pos]); ++pos)
|
||||
num += expr[pos];
|
||||
|
||||
return Node::make_number(::atoi(num.c_str()));
|
||||
return Sexp::make_number(::atoi(num.c_str()));
|
||||
}
|
||||
|
||||
static Node
|
||||
static Sexp
|
||||
parse_symbol (const std::string& expr, size_t& pos)
|
||||
{
|
||||
if (!isalpha(expr[pos]) && expr[pos] != ':') // sanity check.
|
||||
|
@ -134,11 +134,11 @@ parse_symbol (const std::string& expr, size_t& pos)
|
|||
for (++pos; isalnum(expr[pos]) || expr[pos] == '-'; ++pos)
|
||||
symbol += expr[pos];
|
||||
|
||||
return Node::make_symbol(std::move(symbol));
|
||||
return Sexp::make_symbol(std::move(symbol));
|
||||
}
|
||||
|
||||
|
||||
static Node
|
||||
static Sexp
|
||||
parse (const std::string& expr, size_t& pos)
|
||||
{
|
||||
pos = skip_whitespace(expr, pos);
|
||||
|
@ -147,7 +147,7 @@ parse (const std::string& expr, size_t& pos)
|
|||
throw parsing_error(pos, "expected: character '%c", expr[pos]);
|
||||
|
||||
const auto kar = expr[pos];
|
||||
const auto node =[&]() -> Node {
|
||||
const auto node =[&]() -> Sexp {
|
||||
if (kar == '(')
|
||||
return parse_list (expr, pos);
|
||||
else if (kar == '"')
|
||||
|
@ -165,8 +165,8 @@ parse (const std::string& expr, size_t& pos)
|
|||
return node;
|
||||
}
|
||||
|
||||
Node
|
||||
Sexp::Node::make (const std::string& expr)
|
||||
Sexp
|
||||
Sexp::make_parse (const std::string& expr)
|
||||
{
|
||||
size_t pos{};
|
||||
auto node{::parse (expr, pos)};
|
||||
|
@ -179,7 +179,7 @@ Sexp::Node::make (const std::string& expr)
|
|||
|
||||
|
||||
std::string
|
||||
Sexp::Node::to_string () const
|
||||
Sexp::to_string () const
|
||||
{
|
||||
std::stringstream sstrm;
|
||||
|
||||
|
@ -187,7 +187,7 @@ Sexp::Node::to_string () const
|
|||
case Type::List: {
|
||||
sstrm << '(';
|
||||
bool first{true};
|
||||
for (auto&& child : elements()) {
|
||||
for (auto&& child : list()) {
|
||||
sstrm << (first ? "" : " ") << child.to_string();
|
||||
first = false;
|
||||
}
|
||||
|
@ -199,6 +199,7 @@ Sexp::Node::to_string () const
|
|||
break;
|
||||
case Type::Number:
|
||||
case Type::Symbol:
|
||||
case Type::Empty:
|
||||
default:
|
||||
sstrm << value();
|
||||
}
|
||||
|
|
|
@ -21,17 +21,17 @@
|
|||
#ifndef MU_SEXP_HH__
|
||||
#define MU_SEXP_HH__
|
||||
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#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
|
||||
/// 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 '-')
|
||||
///
|
||||
|
@ -39,18 +39,27 @@ namespace Sexp {
|
|||
|
||||
|
||||
/// Parse node
|
||||
struct Node {
|
||||
struct Sexp {
|
||||
/// Node type
|
||||
enum struct Type { List, String, Number, Symbol };
|
||||
enum struct Type { Empty, List, String, Number, Symbol };
|
||||
|
||||
|
||||
/**
|
||||
* Make a node of out of an s-expression string.
|
||||
* Default CTOR
|
||||
*/
|
||||
Sexp():type_{Type::Empty}{}
|
||||
|
||||
/// Underlying data type for list
|
||||
using Seq = std::deque<Sexp>;
|
||||
|
||||
/**
|
||||
* 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 Node make (const std::string& expr);
|
||||
static Sexp make_parse (const std::string& expr);
|
||||
|
||||
/**
|
||||
* Make a node for a string/integer/symbol/list value
|
||||
|
@ -59,97 +68,40 @@ struct Node {
|
|||
*
|
||||
* @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 ':' ('%s')",
|
||||
name.c_str()};
|
||||
|
||||
add(make_symbol(std::move(name)));
|
||||
add(std::move(val));
|
||||
}
|
||||
void add_prop(std::string&& name, const std::string& val) {
|
||||
add_prop(std::move(name), std::string(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_;
|
||||
};
|
||||
static Sexp make_string (std::string&& val) { return Sexp{Type::String, std::move(val)}; }
|
||||
static Sexp make_string (const std::string& val) { 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)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a list node from a sequence
|
||||
*
|
||||
* @param seq a sequence of nodes
|
||||
*
|
||||
* @return a node
|
||||
* The value of this node; invalid for list nodes.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static Node make_list (Seq&& seq) { return Node{std::move(seq)}; }
|
||||
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::Node to its string representation
|
||||
|
@ -165,75 +117,220 @@ struct Node {
|
|||
*/
|
||||
Type type() const { return type_; }
|
||||
|
||||
///
|
||||
/// Helper struct to build mutable lists.
|
||||
///
|
||||
struct List {
|
||||
/**
|
||||
* 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 <typename... Args> List& add (Sexp&& sexp, Args... args) {
|
||||
seq_.emplace_back(std::move(sexp));
|
||||
seq_.emplace_back(std::forward<Args>(args)...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property (i.e., :key sexp ) to the list
|
||||
*
|
||||
* @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) {
|
||||
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 <typename... Args>
|
||||
List& add_prop (std::string&& name, Sexp&& sexp, Args... args) {
|
||||
add_prop(std::move(name), std::move(sexp));
|
||||
add_prop(std::forward<Args>(args)...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 class Sexp;
|
||||
Seq seq_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a list sexp from a List
|
||||
*
|
||||
* @param list a list-list
|
||||
* @param sexp a Sexp
|
||||
* @param args rest arguments
|
||||
*
|
||||
* @return a sexp.
|
||||
*/
|
||||
static Sexp make_list(List&& list) {
|
||||
return Sexp{Type::List, std::move(list.seq_)};
|
||||
}
|
||||
template <typename ... Args>
|
||||
static Sexp make_list(Sexp&& sexp, Args... args) {
|
||||
List lst;
|
||||
lst.add(std::move(sexp)).add(std::forward<Args>(args)...);
|
||||
return make_list(std::move(lst));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a properrty list sexp from a List
|
||||
*
|
||||
* @param name the property name; must start wtth ':'
|
||||
* @param sexp a Sexp
|
||||
* @param args rest arguments (property list)
|
||||
*
|
||||
* @return a sexp.
|
||||
*/
|
||||
template <typename ... Args>
|
||||
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>(args)...);
|
||||
return make_list(std::move(list));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a properrty list sexp from a List
|
||||
*
|
||||
* @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)
|
||||
*
|
||||
* @return a sexp.
|
||||
*/
|
||||
template <typename ... Args>
|
||||
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>(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_nil() const { return is_symbol() && value() == "nil"; }
|
||||
bool is_t() const { return is_symbol() && value() == "t"; }
|
||||
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; }
|
||||
|
||||
/**
|
||||
* The elements of this node; invalid unless this is a list node.
|
||||
* Is this a prop-list? A prop list is a list sexp with alternating
|
||||
* property / sexp
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
const Seq& elements() const {
|
||||
if (type() != Type::List)
|
||||
throw Error(Error::Code::InvalidArgument, "no elements for non-list");
|
||||
return seq_;
|
||||
bool is_prop_list() const {
|
||||
if (!is_list() || list().size() % 2 != 0)
|
||||
return false;
|
||||
else
|
||||
return is_prop_list(list().begin(), list().end());
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of this node; invalid for list nodes.
|
||||
* Is this a call? A call is a list sexp with a symbol (function name),
|
||||
* followed by a prop list
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
const std::string& value() const {
|
||||
if (type_ == Type::List)
|
||||
throw Error(Error::Code::InvalidArgument, "no value for list");
|
||||
return value_;
|
||||
}
|
||||
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());
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Construct a new non-list node
|
||||
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?
|
||||
*
|
||||
* @param typearg the type of node
|
||||
* @param valuearg the value
|
||||
* @param sexp a Sexp.
|
||||
*
|
||||
* @return true or false.
|
||||
*/
|
||||
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>");
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a list node
|
||||
*
|
||||
* @param kids the list's children
|
||||
*/
|
||||
explicit Node(Seq&& seq):
|
||||
type_{Type::List}, seq_{std::move(seq)}
|
||||
{}
|
||||
static bool is_prop_list (Seq::const_iterator b, Seq::const_iterator e) {
|
||||
while (b != e) {
|
||||
if (!is_prop_name(*b))
|
||||
return false;
|
||||
if (++b == e)
|
||||
return false;
|
||||
++b;
|
||||
}
|
||||
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::Lst) */
|
||||
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)
|
||||
operator<<(std::ostream& os, Sexp::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;
|
||||
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::Empty: os << "empty"; break;
|
||||
default: throw std::runtime_error ("unknown node type");
|
||||
}
|
||||
|
||||
|
@ -241,15 +338,12 @@ operator<<(std::ostream& os, Sexp::Node::Type id)
|
|||
}
|
||||
|
||||
static inline std::ostream&
|
||||
operator<<(std::ostream& os, const Sexp::Node& node)
|
||||
operator<<(std::ostream& os, const Sexp& sexp)
|
||||
{
|
||||
os << node.to_string();
|
||||
os << sexp.to_string();
|
||||
return os;
|
||||
}
|
||||
|
||||
} // Sexp
|
||||
|
||||
|
||||
} // Mu
|
||||
|
||||
#endif /* MU_SEXP_HH__ */
|
||||
|
|
|
@ -31,26 +31,27 @@ using namespace Mu;
|
|||
static void
|
||||
test_param_getters()
|
||||
{
|
||||
const auto node { Sexp::Node::make(R"((foo :bar 123 :cuux "456" :boo nil :bah true))")};
|
||||
const auto sexp { Sexp::make_parse(R"((foo :bar 123 :cuux "456" :boo nil :bah true))")};
|
||||
|
||||
std::cout << node << "\n";
|
||||
if (g_test_verbose())
|
||||
std::cout << sexp << "\n";
|
||||
|
||||
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_cmpint(Command::get_int_or(sexp.list(), ":bar"), ==, 123);
|
||||
assert_equal(Command::get_string_or(sexp.list(), ":bra", "bla"), "bla");
|
||||
assert_equal(Command::get_string_or(sexp.list(), ":cuux"), "456");
|
||||
|
||||
g_assert_true(Command::get_bool_or(node.elements(),":boo") == false);
|
||||
g_assert_true(Command::get_bool_or(node.elements(),":bah") == true);
|
||||
g_assert_true(Command::get_bool_or(sexp.list(),":boo") == false);
|
||||
g_assert_true(Command::get_bool_or(sexp.list(),":bah") == true);
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
call (const Command::CommandMap& cmap, const std::string& sexp) try
|
||||
call (const Command::CommandMap& cmap, const std::string& str) try
|
||||
{
|
||||
const auto node{Sexp::Node::make(sexp)};
|
||||
g_message ("invoking %s", to_string(node).c_str());
|
||||
const auto sexp{Sexp::make_parse(str)};
|
||||
g_message ("invoking %s", to_string(sexp).c_str());
|
||||
|
||||
invoke (cmap, node);
|
||||
invoke (cmap, sexp);
|
||||
|
||||
return true;
|
||||
|
||||
|
@ -69,8 +70,8 @@ test_command()
|
|||
|
||||
cmap.emplace("my-command",
|
||||
CommandInfo{
|
||||
ArgMap{ {":param1", ArgInfo{Sexp::Node::Type::String, true, "some string" }},
|
||||
{":param2", ArgInfo{Sexp::Node::Type::Number, false, "some integer"}}},
|
||||
ArgMap{ {":param1", ArgInfo{Sexp::Type::String, true, "some string" }},
|
||||
{":param2", ArgInfo{Sexp::Type::Number, false, "some integer"}}},
|
||||
"My command,",
|
||||
{}});
|
||||
|
||||
|
@ -92,8 +93,8 @@ test_command2()
|
|||
cmap.emplace("bla",
|
||||
CommandInfo{
|
||||
ArgMap{
|
||||
{":foo", ArgInfo{Sexp::Node::Type::Number, false, "foo"}},
|
||||
{":bar", ArgInfo{Sexp::Node::Type::String, false, "bar"}},
|
||||
{":foo", ArgInfo{Sexp::Type::Number, false, "foo"}},
|
||||
{":bar", ArgInfo{Sexp::Type::String, false, "bar"}},
|
||||
},"yeah",
|
||||
[&](const auto& params){}});
|
||||
|
||||
|
@ -114,8 +115,8 @@ test_command_fail()
|
|||
|
||||
cmap.emplace("my-command",
|
||||
CommandInfo{
|
||||
ArgMap{ {":param1", ArgInfo{Sexp::Node::Type::String, true, "some string" }},
|
||||
{":param2", ArgInfo{Sexp::Node::Type::Number, false, "some integer"}}},
|
||||
ArgMap{ {":param1", ArgInfo{Sexp::Type::String, true, "some string" }},
|
||||
{":param2", ArgInfo{Sexp::Type::Number, false, "some integer"}}},
|
||||
"My command,",
|
||||
{}});
|
||||
|
||||
|
|
|
@ -27,14 +27,13 @@
|
|||
#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());
|
||||
const auto parsed{to_string(Sexp::make_parse(expr))};
|
||||
assert_equal(parsed, expected);
|
||||
return true;
|
||||
|
||||
} catch (const Error& err) {
|
||||
|
@ -59,54 +58,71 @@ bar")", "\"foo\nbar\"");
|
|||
}
|
||||
|
||||
static void
|
||||
test_builder()
|
||||
test_list()
|
||||
{
|
||||
const auto nstr{Node::make_string("foo")};
|
||||
const auto nstr{Sexp::make_string("foo")};
|
||||
g_assert_true(nstr.value() == "foo");
|
||||
g_assert_true(nstr.type() == Node::Type::String);
|
||||
g_assert_true(nstr.type() == Sexp::Type::String);
|
||||
assert_equal(nstr.to_string(), "\"foo\"");
|
||||
|
||||
const auto nnum{Node::make_number(123)};
|
||||
const auto nnum{Sexp::make_number(123)};
|
||||
g_assert_true(nnum.value() == "123");
|
||||
g_assert_true(nnum.type() == Node::Type::Number);
|
||||
g_assert_true(nnum.type() == Sexp::Type::Number);
|
||||
assert_equal(nnum.to_string(), "123");
|
||||
|
||||
const auto nsym{Node::make_symbol("blub")};
|
||||
const auto nsym{Sexp::make_symbol("blub")};
|
||||
g_assert_true(nsym.value() == "blub");
|
||||
g_assert_true(nsym.type() == Node::Type::Symbol);
|
||||
g_assert_true(nsym.type() == Sexp::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"));
|
||||
Sexp::List list;
|
||||
list .add(Sexp::make_string("foo"))
|
||||
.add(Sexp::make_number(123))
|
||||
.add(Sexp::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");
|
||||
const auto nlst = Sexp::make_list(std::move(list));
|
||||
g_assert_true(nlst.list().size() == 3);
|
||||
g_assert_true(nlst.type() == Sexp::Type::List);
|
||||
g_assert_true(nlst.list().at(1).value() == "123");
|
||||
|
||||
assert_equal(nlst.to_string(),"(\"foo\" 123 blub)");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_prop_list()
|
||||
{
|
||||
Sexp::List l1;
|
||||
l1.add_prop(":foo", Sexp::make_string("bar"));
|
||||
Sexp s2{Sexp::make_list(std::move(l1))};
|
||||
assert_equal(s2.to_string(), "(:foo \"bar\")");
|
||||
|
||||
|
||||
Sexp::List l2;
|
||||
const std::string x{"bar"};
|
||||
l2.add_prop(":foo", Sexp::make_string(x));
|
||||
l2.add_prop(":bar", Sexp::make_number(77));
|
||||
Sexp::List l3;
|
||||
l3.add_prop(":cuux", Sexp::make_list(std::move(l2)));
|
||||
Sexp s3{Sexp::make_list(std::move(l3))};
|
||||
assert_equal(s3.to_string(), "(:cuux (:foo \"bar\" :bar 77))");
|
||||
}
|
||||
|
||||
static void
|
||||
test_props()
|
||||
{
|
||||
Node::Seq seq;
|
||||
auto sexp2 = Sexp::make_list(
|
||||
Sexp::make_string("foo"),
|
||||
Sexp::make_number(123),
|
||||
Sexp::make_symbol("blub"));
|
||||
|
||||
seq.add_prop(":foo", "bär");
|
||||
seq.add_prop(":cuux", 123);
|
||||
seq.add_prop(":flub", Node::make_symbol("fnord"));
|
||||
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));
|
||||
|
||||
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(),
|
||||
assert_equal(sexp.to_string(),
|
||||
"(:foo \"b\303\244r\" :cuux 123 :flub fnord :boo (\"foo\" 123 blub))");
|
||||
}
|
||||
|
||||
|
@ -116,13 +132,14 @@ main (int argc, char *argv[]) try
|
|||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
if (argc == 2) {
|
||||
std::cout << Sexp::Node::make(argv[1]) << '\n';
|
||||
std::cout << Sexp::make_parse(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);
|
||||
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/props", test_props);
|
||||
|
||||
return g_test_run ();
|
||||
|
||||
|
|
Loading…
Reference in New Issue