utils/sexp: Clean up API and implementation
Also update the tests and command-parser.
This commit is contained in:
@ -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__ */
|
||||
|
||||
Reference in New Issue
Block a user