2020-01-18 12:35:01 +01:00
|
|
|
/*
|
2020-06-01 18:01:03 +02:00
|
|
|
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
|
|
**
|
2020-01-18 12:35:01 +01:00
|
|
|
**
|
|
|
|
** This program is free software; you can redistribute it and/or modify it
|
|
|
|
** under the terms of the GNU General Public License as published by the
|
|
|
|
** Free Software Foundation; either version 3, or (at your option) any
|
|
|
|
** later version.
|
|
|
|
**
|
|
|
|
** This program is distributed in the hope that it will be useful,
|
|
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
** GNU General Public License for more details.
|
|
|
|
**
|
|
|
|
** You should have received a copy of the GNU General Public License
|
|
|
|
** along with this program; if not, write to the Free Software Foundation,
|
|
|
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2020-06-01 18:01:03 +02:00
|
|
|
#include "mu-sexp.hh"
|
2020-01-18 12:35:01 +01:00
|
|
|
#include "mu-utils.hh"
|
|
|
|
|
2020-06-01 18:01:03 +02:00
|
|
|
#include <sstream>
|
2020-07-12 14:09:05 +02:00
|
|
|
#include <array>
|
2020-06-01 18:01:03 +02:00
|
|
|
|
2020-01-18 12:35:01 +01:00
|
|
|
using namespace Mu;
|
|
|
|
|
|
|
|
__attribute__((format(printf, 2, 0))) static Mu::Error
|
|
|
|
parsing_error(size_t pos, const char* frm, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, frm);
|
|
|
|
auto msg = format(frm, args);
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
if (pos == 0)
|
2020-01-23 23:21:53 +01:00
|
|
|
return Mu::Error(Error::Code::Parsing, "%s", msg.c_str());
|
2020-01-18 12:35:01 +01:00
|
|
|
else
|
2020-01-25 18:31:20 +01:00
|
|
|
return Mu::Error(Error::Code::Parsing, "%zu: %s", pos, msg.c_str());
|
2020-01-18 12:35:01 +01:00
|
|
|
}
|
|
|
|
static size_t
|
|
|
|
skip_whitespace (const std::string& s, size_t pos)
|
|
|
|
{
|
|
|
|
while (pos != s.size()) {
|
|
|
|
if (s[pos] == ' ' || s[pos] == '\t' || s[pos] == '\n')
|
|
|
|
++pos;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
static Sexp parse (const std::string& expr, size_t& pos);
|
2020-01-18 12:35:01 +01:00
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
static Sexp
|
2020-01-18 12:35:01 +01:00
|
|
|
parse_list (const std::string& expr, size_t& pos)
|
|
|
|
{
|
|
|
|
if (expr[pos] != '(') // sanity check.
|
|
|
|
throw parsing_error(pos, "expected: '(' but got '%c", expr[pos]);
|
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
Sexp::List list;
|
2020-01-18 12:35:01 +01:00
|
|
|
|
|
|
|
++pos;
|
|
|
|
while (expr[pos] != ')' && pos != expr.size())
|
2020-07-12 14:09:05 +02:00
|
|
|
list.add(parse(expr, pos));
|
2020-01-18 12:35:01 +01:00
|
|
|
|
|
|
|
if (expr[pos] != ')')
|
|
|
|
throw parsing_error(pos, "expected: ')' but got '%c'", expr[pos]);
|
|
|
|
++pos;
|
2020-07-12 14:09:05 +02:00
|
|
|
return Sexp::make_list(std::move(list));
|
2020-01-18 12:35:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// parse string
|
2020-07-12 14:09:05 +02:00
|
|
|
static Sexp
|
2020-01-18 12:35:01 +01:00
|
|
|
parse_string (const std::string& expr, size_t& pos)
|
|
|
|
{
|
|
|
|
if (expr[pos] != '"') // sanity check.
|
|
|
|
throw parsing_error(pos, "expected: '\"'' but got '%c", expr[pos]);
|
|
|
|
|
|
|
|
bool escape{};
|
|
|
|
std::string str;
|
|
|
|
for (++pos; pos != expr.size(); ++pos) {
|
|
|
|
|
|
|
|
auto kar = expr[pos];
|
|
|
|
if (escape && (kar == '"' || kar == '\\')) {
|
|
|
|
str += kar;
|
|
|
|
escape = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (kar == '"')
|
|
|
|
break;
|
|
|
|
else if (kar == '\\')
|
|
|
|
escape = true;
|
|
|
|
else
|
|
|
|
str += kar;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (escape || expr[pos] != '"')
|
|
|
|
throw parsing_error(pos, "unterminated string '%s'", str.c_str());
|
|
|
|
|
|
|
|
++pos;
|
2020-07-12 14:09:05 +02:00
|
|
|
return Sexp::make_string(std::move(str));
|
2020-01-18 12:35:01 +01:00
|
|
|
}
|
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
static Sexp
|
2020-01-18 12:35:01 +01:00
|
|
|
parse_integer (const std::string& expr, size_t& pos)
|
|
|
|
{
|
2020-01-25 18:54:37 +01:00
|
|
|
if (!isdigit(expr[pos]) && expr[pos] != '-') // sanity check.
|
2020-01-18 12:35:01 +01:00
|
|
|
throw parsing_error(pos, "expected: <digit> but got '%c", expr[pos]);
|
|
|
|
|
2020-01-25 18:54:37 +01:00
|
|
|
std::string num; // negative number?
|
|
|
|
if (expr[pos] == '-') {
|
|
|
|
num = "-";
|
|
|
|
++pos;
|
|
|
|
}
|
|
|
|
|
2020-01-18 12:35:01 +01:00
|
|
|
for (; isdigit(expr[pos]); ++pos)
|
|
|
|
num += expr[pos];
|
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
return Sexp::make_number(::atoi(num.c_str()));
|
2020-01-18 12:35:01 +01:00
|
|
|
}
|
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
static Sexp
|
2020-01-18 12:35:01 +01:00
|
|
|
parse_symbol (const std::string& expr, size_t& pos)
|
|
|
|
{
|
|
|
|
if (!isalpha(expr[pos]) && expr[pos] != ':') // sanity check.
|
|
|
|
throw parsing_error(pos, "expected: <alpha>|: but got '%c", expr[pos]);
|
|
|
|
|
|
|
|
std::string symbol(1, expr[pos]);
|
|
|
|
for (++pos; isalnum(expr[pos]) || expr[pos] == '-'; ++pos)
|
|
|
|
symbol += expr[pos];
|
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
return Sexp::make_symbol(std::move(symbol));
|
2020-01-18 12:35:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
static Sexp
|
2020-01-18 12:35:01 +01:00
|
|
|
parse (const std::string& expr, size_t& pos)
|
|
|
|
{
|
|
|
|
pos = skip_whitespace(expr, pos);
|
|
|
|
|
|
|
|
if (pos == expr.size())
|
|
|
|
throw parsing_error(pos, "expected: character '%c", expr[pos]);
|
|
|
|
|
|
|
|
const auto kar = expr[pos];
|
2020-07-12 14:09:05 +02:00
|
|
|
const auto node =[&]() -> Sexp {
|
2020-01-18 12:35:01 +01:00
|
|
|
if (kar == '(')
|
|
|
|
return parse_list (expr, pos);
|
|
|
|
else if (kar == '"')
|
|
|
|
return parse_string(expr, pos);
|
2020-01-25 18:54:37 +01:00
|
|
|
else if (isdigit(kar) || kar == '-')
|
2020-01-18 12:35:01 +01:00
|
|
|
return parse_integer(expr, pos);
|
|
|
|
else if (isalpha(kar) || kar == ':')
|
|
|
|
return parse_symbol(expr, pos);
|
|
|
|
else
|
2020-01-23 23:21:53 +01:00
|
|
|
throw parsing_error(pos, "unexpected character '%c", kar);
|
2020-01-18 12:35:01 +01:00
|
|
|
}();
|
|
|
|
|
|
|
|
pos = skip_whitespace(expr, pos);
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
2020-07-12 14:09:05 +02:00
|
|
|
Sexp
|
|
|
|
Sexp::make_parse (const std::string& expr)
|
2020-01-18 12:35:01 +01:00
|
|
|
{
|
|
|
|
size_t pos{};
|
|
|
|
auto node{::parse (expr, pos)};
|
|
|
|
|
|
|
|
if (pos != expr.size())
|
|
|
|
throw parsing_error(pos, "trailing data starting with '%c'", expr[pos]);
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
2020-06-01 18:01:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
std::string
|
2020-10-26 17:39:56 +01:00
|
|
|
Sexp::to_sexp_string () const
|
2020-06-01 18:01:03 +02:00
|
|
|
{
|
|
|
|
std::stringstream sstrm;
|
|
|
|
|
|
|
|
switch (type()) {
|
|
|
|
case Type::List: {
|
|
|
|
sstrm << '(';
|
|
|
|
bool first{true};
|
2020-07-12 14:09:05 +02:00
|
|
|
for (auto&& child : list()) {
|
2020-10-26 17:39:56 +01:00
|
|
|
sstrm << (first ? "" : " ") << child.to_sexp_string();
|
2020-06-01 18:01:03 +02:00
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
sstrm << ')';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::String:
|
2020-06-08 22:04:05 +02:00
|
|
|
sstrm << quote(value());
|
2020-06-01 18:01:03 +02:00
|
|
|
break;
|
|
|
|
case Type::Number:
|
|
|
|
case Type::Symbol:
|
2020-07-12 14:09:05 +02:00
|
|
|
case Type::Empty:
|
2020-06-01 18:01:03 +02:00
|
|
|
default:
|
|
|
|
sstrm << value();
|
|
|
|
}
|
|
|
|
|
|
|
|
return sstrm.str();
|
|
|
|
}
|
2020-10-26 17:39:56 +01:00
|
|
|
|
|
|
|
|
|
|
|
std::string
|
|
|
|
Sexp::to_json_string () const
|
|
|
|
{
|
|
|
|
std::stringstream sstrm;
|
|
|
|
|
|
|
|
switch (type()) {
|
|
|
|
case Type::List: {
|
|
|
|
// property-lists become JSON objects
|
|
|
|
if (is_prop_list()) {
|
|
|
|
sstrm << "{";
|
|
|
|
auto it{list().begin()};
|
|
|
|
bool first{true};
|
|
|
|
while (it != list().end()) {
|
|
|
|
sstrm << (first?"":",") << quote(it->value()) << ":";
|
|
|
|
++it;
|
|
|
|
sstrm << it->to_json_string();
|
|
|
|
++it;
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
sstrm << "}";
|
|
|
|
} else { // other lists become arrays.
|
|
|
|
sstrm << '[';
|
|
|
|
bool first{true};
|
|
|
|
for (auto&& child : list()) {
|
|
|
|
sstrm << (first ? "" : ", ") << child.to_json_string();
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
sstrm << ']';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::String:
|
|
|
|
sstrm << quote(value());
|
|
|
|
break;
|
|
|
|
case Type::Symbol:
|
|
|
|
if (is_nil())
|
|
|
|
sstrm << "false";
|
|
|
|
else if (is_t())
|
|
|
|
sstrm << "true";
|
|
|
|
else
|
|
|
|
sstrm << quote(value());
|
|
|
|
break;
|
|
|
|
case Type::Number:
|
|
|
|
case Type::Empty:
|
|
|
|
default:
|
|
|
|
sstrm << value();
|
|
|
|
}
|
|
|
|
|
|
|
|
return sstrm.str();
|
|
|
|
}
|