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