mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
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:
parent
51a1430c24
commit
276641ef15
@ -196,30 +196,265 @@ GncOptionDB::save_option_scheme(std::ostream& oss,
|
|||||||
|
|
||||||
return 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};
|
return c == '\n';
|
||||||
std::string name{classifier_size_max};
|
}
|
||||||
iss.ignore(strlen(scheme_tags[0]) + 7, '\n'); //throw away the scheme noise;
|
|
||||||
iss >> section; // Whitespace automatically discarded.
|
static inline bool constexpr
|
||||||
iss >> name; // Ditto
|
is_whitespace(char c)
|
||||||
if (section.size() > 2)
|
{
|
||||||
section = section.substr(1, section.size() - 2); //Trim the quotes
|
return c == ' ' || c == '\n' || c == '\t';
|
||||||
if (name.size() > 2 + strlen(scheme_tags[2]))
|
}
|
||||||
name = name.substr(1, name.size() - (2 + strlen(scheme_tags[2])));
|
|
||||||
|
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());
|
auto option = find_option(section.c_str(), name.c_str());
|
||||||
|
std::string option_str{section};
|
||||||
|
option_str += ':';
|
||||||
|
option_str += name;
|
||||||
if (!option)
|
if (!option)
|
||||||
{
|
{
|
||||||
std::cerr << "Option " << section << ":" << name << " not found." << std::endl;
|
std::string err{"Option not found: "};
|
||||||
iss.ignore(stream_max, '\n'); // No option, discard the line
|
err += option_str;
|
||||||
iss.ignore(stream_max, '\n'); // And the trailing newline
|
throw std::runtime_error(err);
|
||||||
return iss;
|
|
||||||
}
|
}
|
||||||
iss.ignore(strlen(scheme_tags[2]) +1, '\n');
|
if (!(toplevel.m_type == IdentType::FORM && toplevel.m_ids.size() == 2 &&
|
||||||
iss.ignore(strlen(scheme_tags[3]));
|
toplevel.m_ids[1].m_type == IdentType::FORM &&
|
||||||
option->get().from_scheme(iss);
|
toplevel.m_ids[1].m_ids.size() == 2 &&
|
||||||
iss.ignore(strlen(scheme_tags[4]) + 2, '\n'); //discard the noise at the end.
|
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;
|
return iss;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,11 +88,18 @@ public:
|
|||||||
return static_cast<const GncOptionDB&>(*this).find_option(section, name);
|
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::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,
|
std::ostream& save_option_scheme(std::ostream& oss,
|
||||||
const char* option_prolog,
|
const char* option_prolog,
|
||||||
const std::string& section,
|
const std::string& section,
|
||||||
const std::string& name) const noexcept;
|
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,
|
std::ostream& save_option_key_value(std::ostream& oss,
|
||||||
const char* section,
|
const char* section,
|
||||||
const char* name) const noexcept;
|
const char* name) const noexcept;
|
||||||
|
Loading…
Reference in New Issue
Block a user