Change parse of option input to generate a parse tree.

This is I hope less brittle than the previous character-counting, though
it's still brittle because it relies instead on counting form-depth.
This commit is contained in:
John Ralls 2019-12-14 15:43:24 -08:00
parent 51a1430c24
commit 276641ef15
2 changed files with 262 additions and 20 deletions

View File

@ -196,30 +196,265 @@ GncOptionDB::save_option_scheme(std::ostream& oss,
return oss;
}
std::istream&
GncOptionDB::load_option_scheme(std::istream& iss) noexcept
static inline bool constexpr
is_eol(char c)
{
std::string section{classifier_size_max};
std::string name{classifier_size_max};
iss.ignore(strlen(scheme_tags[0]) + 7, '\n'); //throw away the scheme noise;
iss >> section; // Whitespace automatically discarded.
iss >> name; // Ditto
if (section.size() > 2)
section = section.substr(1, section.size() - 2); //Trim the quotes
if (name.size() > 2 + strlen(scheme_tags[2]))
name = name.substr(1, name.size() - (2 + strlen(scheme_tags[2])));
return c == '\n';
}
static inline bool constexpr
is_whitespace(char c)
{
return c == ' ' || c == '\n' || c == '\t';
}
static inline bool constexpr
is_begin_paren(char c)
{
return c == '(';
}
static inline bool constexpr
is_end_paren(char c)
{
return c == ')';
}
static inline bool constexpr
is_double_quote(char c)
{
return c == '"';
}
static inline bool constexpr
is_single_quote(char c)
{
return c == '\'';
}
static inline bool constexpr
is_semicolon(char c)
{
return c == ';';
}
static inline bool constexpr
is_delim(char c)
{
return is_begin_paren(c) || is_end_paren(c) || is_whitespace(c) ||
is_single_quote(c) || is_double_quote(c) || is_semicolon(c);
}
static std::string
scan_scheme_symbol_from_streambuf(std::streambuf* sbuf)
{
std::string retval;
while(sbuf->in_avail() && !is_delim(sbuf->sgetc()))
retval += sbuf->sbumpc();
return retval;
}
static inline void constexpr
consume_scheme_comment(std::streambuf* sbuf)
{
while (sbuf->in_avail() && !is_eol(sbuf->sgetc()))
sbuf->sbumpc();
}
static inline std::string
scan_scheme_string_from_streambuf(std::streambuf* sbuf)
{
std::string retval{static_cast<char>(sbuf->sbumpc())};
while(sbuf->in_avail() && !is_double_quote(sbuf->sgetc()))
retval += sbuf->sbumpc();
retval += sbuf->sbumpc(); // Add the closing quote.
return retval;
}
static inline void constexpr
consume_scheme_whitespace(std::streambuf* sbuf)
{
while (sbuf->in_avail() && is_whitespace(sbuf->sgetc()))
sbuf->sbumpc();
}
enum class IdentType
{
NAME, //no introducing mark
CONST, //introduced with single quote
STRING, //delimited by double-quotes.
LIST, //introduced ' and delimited by parentheses
FORM //delimited by parentheses without ' introduction.
};
struct SchemeId
{
IdentType m_type;
std::string m_name;
std::vector<SchemeId> m_ids;
};
/**
* Scheme Parse Tree
* An identifier is a string and a type (name, const, string, or form). A Form
*
*/
static void scan_scheme_id_from_streambuf(std::streambuf* sbuf, SchemeId& id);
static void
scan_scheme_form_from_streambuf(std::streambuf* sbuf, SchemeId& id)
{
sbuf->sbumpc();
if (!sbuf->in_avail())
return;
char c = sbuf->sgetc();
while (sbuf->in_avail() && !is_end_paren(c))
{
SchemeId next_id;
scan_scheme_id_from_streambuf(sbuf, next_id);
if (id.m_name.empty() && next_id.m_type == IdentType::NAME)
{
id.m_name = std::move(next_id.m_name);
continue;
}
id.m_ids.emplace_back(std::move(next_id));
if (!sbuf->in_avail())
{
std::string err{"End of streambuf before end of form "};
err += id.m_name;
throw std::runtime_error(err);
}
c = sbuf->sgetc();
}
sbuf->sbumpc();
}
static void
scan_scheme_list_from_streambuf(std::streambuf* sbuf, std::string& str)
{
consume_scheme_whitespace(sbuf);
if (!sbuf->in_avail())
return;
char c = sbuf->sgetc();
while (sbuf->in_avail() && !is_end_paren(c))
{
str += static_cast<char>(sbuf->sbumpc());
if (!sbuf->in_avail())
return;
c = sbuf->sgetc();
}
str += static_cast<char>(sbuf->sbumpc());
}
static void
scan_scheme_id_from_streambuf(std::streambuf* sbuf, SchemeId& id)
{
consume_scheme_whitespace(sbuf);
if (!sbuf->in_avail())
return;
auto c{sbuf->sgetc()};
switch(c)
{
case ';':
consume_scheme_comment(sbuf);
break;
case '"':
id.m_type = IdentType::STRING;
id.m_name = scan_scheme_string_from_streambuf(sbuf);
break;
case '\'':
{
std::string value{static_cast<char>(sbuf->sbumpc())};
if (sbuf->sgetc() == '(')
{
id.m_type == IdentType::LIST;
scan_scheme_list_from_streambuf(sbuf, value);
if (value.back() != ')')
throw std::runtime_error("End of streambuf before end of form ");
}
else if (sbuf->sgetc() == '"')
throw std::runtime_error("Malformed scheme particle starts '\"");
else
{
id.m_type = IdentType::CONST;
value += scan_scheme_symbol_from_streambuf(sbuf);
}
id.m_name = std::move(value);
break;
}
case '(':
id.m_type = IdentType::FORM;
scan_scheme_form_from_streambuf(sbuf, id);
break;
default:
id.m_type = IdentType::NAME;
id.m_name = scan_scheme_symbol_from_streambuf(sbuf);
break;
}
return;
}
static inline std::string
unquote_scheme_string(const std::string& str)
{
if (str.front() == '"' && str.back() == '"')
return str.substr(1, str.size() - 2);
return str;
}
std::istream&
GncOptionDB::load_option_scheme(std::istream& iss)
{
auto sbuf{iss.rdbuf()};
SchemeId toplevel;
bool form_found = false;
while (sbuf->in_avail() && !form_found)
{
scan_scheme_id_from_streambuf(sbuf, toplevel);
if (toplevel.m_type == IdentType::FORM &&
toplevel.m_name == "let" && toplevel.m_ids.size() == 2)
if (const auto& let_block{toplevel.m_ids[0]};
let_block.m_ids.size() == 1)
if (const auto& first_form{let_block.m_ids[0]};
first_form.m_name == "option")
form_found = true;
}
const auto& classifier = toplevel.m_ids[0].m_ids[0].m_ids[0].m_ids;
if (classifier.size() != 3)
throw std::runtime_error("Malformed option classifier.");
const auto& section = unquote_scheme_string(classifier[1].m_name);
const auto& name = unquote_scheme_string(classifier[2].m_name);
auto option = find_option(section.c_str(), name.c_str());
std::string option_str{section};
option_str += ':';
option_str += name;
if (!option)
{
std::cerr << "Option " << section << ":" << name << " not found." << std::endl;
iss.ignore(stream_max, '\n'); // No option, discard the line
iss.ignore(stream_max, '\n'); // And the trailing newline
return iss;
std::string err{"Option not found: "};
err += option_str;
throw std::runtime_error(err);
}
iss.ignore(strlen(scheme_tags[2]) +1, '\n');
iss.ignore(strlen(scheme_tags[3]));
option->get().from_scheme(iss);
iss.ignore(strlen(scheme_tags[4]) + 2, '\n'); //discard the noise at the end.
if (!(toplevel.m_type == IdentType::FORM && toplevel.m_ids.size() == 2 &&
toplevel.m_ids[1].m_type == IdentType::FORM &&
toplevel.m_ids[1].m_ids.size() == 2 &&
toplevel.m_ids[1].m_ids[0].m_type == IdentType::FORM &&
toplevel.m_ids[1].m_ids[0].m_ids.size() == 2 &&
toplevel.m_ids[1].m_ids[0].m_ids[1].m_type == IdentType::FORM &&
toplevel.m_ids[1].m_ids[0].m_ids[1].m_ids.size() == 2 &&
toplevel.m_ids[1].m_ids[0].m_ids[1].m_ids[1].m_type == IdentType::FORM &&
toplevel.m_ids[1].m_ids[0].m_ids[1].m_ids[1].m_ids.size() == 2))
{
std::string err{"Option "};
err += option_str;
throw std::runtime_error(err + " malformed value lambda form.");
}
auto value_id = toplevel.m_ids[1].m_ids[0].m_ids[1].m_ids[1].m_ids[1];
std::istringstream value_iss{value_id.m_name};
option->get().from_scheme(value_iss);
return iss;
}

View File

@ -88,11 +88,18 @@ public:
return static_cast<const GncOptionDB&>(*this).find_option(section, name);
}
std::optional<std::reference_wrapper<GncOption>> find_option(const std::string& section, const std::string& name) const;
std::ostream& save_to_scheme(std::ostream& oss,
const char* options_prolog) const noexcept;
std::istream& load_from_scheme(std::istream& iss) noexcept;
std::ostream& save_to_key_value(std::ostream& oss) const noexcept;
std::istream& load_from_key_value(std::istream& iss) noexcept;
void save_to_kvp() const noexcept;
void load_from_kvp() noexcept;
std::ostream& save_option_scheme(std::ostream& oss,
const char* option_prolog,
const std::string& section,
const std::string& name) const noexcept;
std::istream& load_option_scheme(std::istream& iss) noexcept;
std::istream& load_option_scheme(std::istream& iss);
std::ostream& save_option_key_value(std::ostream& oss,
const char* section,
const char* name) const noexcept;