Refactor OptionsList class

In particular, use a std::variant to store the values of options. This ensures
that a given option name can have only one value (previously, for a given
option name, it was possible to store several values as long as they were of
different types).
master
Sébastien Villemot 2022-09-18 09:30:13 +02:00
parent e4449f1c4a
commit 7f1b4fcc20
No known key found for this signature in database
GPG Key ID: 2CECE9350ECEBE4A
7 changed files with 723 additions and 777 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1004,13 +1004,13 @@ private:
const vector<string> joint_parameters;
const PriorDistributions prior_shape;
const OptionsList options_list;
void writeOutputHelper(ostream &output, const string &field, const string &lhs_field) const;
public:
JointPriorStatement(vector<string> joint_parameters_arg,
PriorDistributions prior_shape_arg,
OptionsList options_list_arg);
void checkPass(ModFileStructure &mod_file_struct, WarningConsolidation &warnings) override;
void writeOutput(ostream &output, const string &basename, bool minimal_workspace) const override;
void writeOutputHelper(ostream &output, const string &field, const string &lhs_field) const;
void writeJsonOutput(ostream &output) const override;
};

View File

@ -497,7 +497,7 @@ void
InitvalFileStatement::writeJsonOutput(ostream &output) const
{
output << R"({"statementName": "initval_file")";
if (options_list.getNumberOfOptions())
if (!options_list.empty())
{
output << ", ";
options_list.writeJsonOutput(output);
@ -526,7 +526,7 @@ void
HistvalFileStatement::writeJsonOutput(ostream &output) const
{
output << R"({"statementName": "histval_file")";
if (options_list.getNumberOfOptions())
if (!options_list.empty())
{
output << ", ";
options_list.writeJsonOutput(output);

View File

@ -1365,43 +1365,43 @@ ParsingDriver::steady()
void
ParsingDriver::option_num(string name_option, string opt1, string opt2)
{
if (options_list.paired_num_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
options_list.paired_num_options[move(name_option)] = { move(opt1), move(opt2) };
options_list.set(move(name_option), pair{move(opt1), move(opt2)});
}
void
ParsingDriver::option_num(string name_option, string opt)
{
if (options_list.num_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
options_list.num_options[move(name_option)] = move(opt);
options_list.set(move(name_option), OptionsList::NumVal{move(opt)});
}
void
ParsingDriver::option_str(string name_option, string opt)
{
if (options_list.string_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
options_list.string_options[move(name_option)] = move(opt);
options_list.set(move(name_option), OptionsList::StringVal{move(opt)});
}
void
ParsingDriver::option_date(string name_option, string opt)
{
if (options_list.date_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
options_list.date_options[move(name_option)] = move(opt);
options_list.set(move(name_option), OptionsList::DateVal{move(opt)});
}
void
ParsingDriver::option_symbol_list(string name_option, vector<string> symbol_list)
{
if (options_list.symbol_list_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
if (name_option == "irf_shocks")
@ -1418,67 +1418,67 @@ ParsingDriver::option_symbol_list(string name_option, vector<string> symbol_list
if (mod_file->symbol_table.getType(it) != SymbolType::parameter)
error("Variables passed to the parameters option of the markov_switching statement must be parameters. Caused by: " + it);
options_list.symbol_list_options[move(name_option)] = move(symbol_list);
options_list.set(move(name_option), OptionsList::SymbolListVal{move(symbol_list)});
}
void
ParsingDriver::option_vec_int(string name_option, vector<int> opt)
{
if (options_list.vector_int_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
if (opt.empty())
error("option " + name_option + " was passed an empty vector.");
options_list.vector_int_options[move(name_option)] = move(opt);
options_list.set(move(name_option), move(opt));
}
void
ParsingDriver::option_vec_str(string name_option, vector<string> opt)
{
if (options_list.vector_str_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
if (opt.empty())
error("option " + name_option + " was passed an empty vector.");
options_list.vector_str_options[move(name_option)] = move(opt);
options_list.set(move(name_option), OptionsList::VecStrVal{move(opt)});
}
void
ParsingDriver::option_vec_cellstr(string name_option, vector<string> opt)
{
if (options_list.vector_cellstr_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
if (opt.empty())
error("option " + name_option + " was passed an empty vector.");
options_list.vector_cellstr_options[move(name_option)] = move(opt);
options_list.set(move(name_option), OptionsList::VecCellStrVal{move(opt)});
}
void
ParsingDriver::option_vec_value(string name_option, vector<string> opt)
{
if (options_list.vector_value_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
if (opt.empty())
error("option " + name_option + " was passed an empty vector.");
options_list.vector_value_options[move(name_option)] = move(opt);
options_list.set(move(name_option), OptionsList::VecValueVal{move(opt)});
}
void
ParsingDriver::option_vec_of_vec_value(string name_option, vector<vector<string>> opt)
{
if (options_list.vector_of_vector_value_options.contains(name_option))
if (options_list.contains(name_option))
error("option " + name_option + " declared twice");
if (opt.empty())
error("option " + name_option + " was passed an empty vector.");
options_list.vector_of_vector_value_options[move(name_option)] = move(opt);
options_list.set(move(name_option), move(opt));
}
void
@ -1497,8 +1497,8 @@ void
ParsingDriver::stoch_simul(SymbolList symbol_list)
{
//make sure default order is known to preprocessor, see #49
if (!options_list.num_options.contains("order"))
options_list.num_options["order"] = "2";
if (!options_list.contains("order"))
options_list.set("order", OptionsList::NumVal{"2"});
symbol_list.removeDuplicates("stoch_simul", warnings);
@ -1510,44 +1510,36 @@ ParsingDriver::stoch_simul(SymbolList symbol_list)
void
ParsingDriver::trend_component_model()
{
auto its = options_list.string_options.find("trend_component.name");
if (its == options_list.string_options.end())
error("You must pass the model_name option to the trend_component_model statement.");
auto &name = its->second;
try
{
mod_file->trend_component_model_table.addTrendComponentModel(options_list.get<OptionsList::StringVal>("trend_component.name"),
options_list.get<OptionsList::VecStrVal>("trend_component.eqtags"),
options_list.get<OptionsList::VecStrVal>("trend_component.targets"));
}
catch (OptionsList::UnknownOptionException &e)
{
string name {e.name.substr(16)};
if (name == "name")
name = "model_name";
error("You must pass the '" + name + "' option to the 'trend_component_model' statement.");
}
auto itvs = options_list.vector_str_options.find("trend_component.eqtags");
if (itvs == options_list.vector_str_options.end())
error("You must pass the eqtags option to the trend_component_model statement.");
auto &eqtags = itvs->second;
auto itvs1 = options_list.vector_str_options.find("trend_component.targets");
if (itvs1 == options_list.vector_str_options.end())
error("You must pass the targets option to the trend_component_model statement.");
auto &targets = itvs1->second;
mod_file->trend_component_model_table.addTrendComponentModel(move(name), move(eqtags), move(targets));
options_list.clear();
}
void
ParsingDriver::var_model()
{
auto its = options_list.string_options.find("var.model_name");
if (its == options_list.string_options.end())
error("You must pass the model_name option to the var_model statement.");
auto &name = its->second;
auto itvs = options_list.vector_str_options.find("var.eqtags");
if (itvs == options_list.vector_str_options.end())
error("You must pass the eqtags option to the var_model statement.");
auto &eqtags = itvs->second;
bool structural = false;
if (auto itn = options_list.num_options.find("var.structural");
itn != options_list.num_options.end() && itn->second == "true")
structural = true;
mod_file->var_model_table.addVarModel(move(name), structural, move(eqtags));
try
{
mod_file->var_model_table.addVarModel(options_list.get<OptionsList::StringVal>("var.model_name"),
options_list.get_if<OptionsList::NumVal>("var.structural").value_or(OptionsList::NumVal{"false"}) == "true",
options_list.get<OptionsList::VecStrVal>("var.eqtags"));
}
catch (OptionsList::UnknownOptionException &e)
{
error("You must pass the '" + e.name.substr(4) + "' option to the 'var_model' statement.");
}
options_list.clear();
}
@ -2208,9 +2200,8 @@ ParsingDriver::ramsey_model()
error("ramsey_model: the 'planner_discount' option cannot be used when the 'optimal_policy_discount_factor' parameter is explicitly declared.");
// Check that instruments are declared endogenous (#72)
if (auto it = options_list.symbol_list_options.find("instruments");
it != options_list.symbol_list_options.end())
for (const auto &s : it->second.getSymbols())
if (options_list.contains("instruments"))
for (const auto &s : options_list.get<OptionsList::SymbolListVal>("instruments").getSymbols())
check_symbol_is_endogenous(s);
mod_file->addStatement(make_unique<RamseyModelStatement>(options_list));
@ -2242,9 +2233,8 @@ ParsingDriver::ramsey_policy(vector<string> symbol_list)
error("ramsey_policy: the 'planner_discount' option cannot be used when the 'optimal_policy_discount_factor' parameter is explicitly declared.");
// Check that instruments are declared endogenous (#72)
if (auto it = options_list.symbol_list_options.find("instruments");
it != options_list.symbol_list_options.end())
for (const auto &s : it->second.getSymbols())
if (options_list.contains("instruments"))
for (const auto &s : options_list.get<OptionsList::SymbolListVal>("instruments").getSymbols())
check_symbol_is_endogenous(s);
mod_file->addStatement(make_unique<RamseyPolicyStatement>(move(symbol_list), options_list,
@ -2302,9 +2292,8 @@ ParsingDriver::discretionary_policy(vector<string> symbol_list)
init_param("optimal_policy_discount_factor", planner_discount);
// Check that instruments are declared endogenous (#72)
if (auto it = options_list.symbol_list_options.find("instruments");
it != options_list.symbol_list_options.end())
for (const auto &s : it->second.getSymbols())
if (options_list.contains("instruments"))
for (const auto &s : options_list.get<OptionsList::SymbolListVal>("instruments").getSymbols())
check_symbol_is_endogenous(s);
mod_file->addStatement(make_unique<DiscretionaryPolicyStatement>(move(symbol_list), options_list,
@ -2411,9 +2400,9 @@ ParsingDriver::ms_variance_decomposition()
void
ParsingDriver::svar()
{
bool has_coefficients = options_list.string_options.contains("ms.coefficients"),
has_variances = options_list.string_options.contains("ms.variances"),
has_constants = options_list.string_options.contains("ms.constants");
bool has_coefficients = options_list.contains("ms.coefficients"),
has_variances = options_list.contains("ms.variances"),
has_constants = options_list.contains("ms.constants");
if (!has_coefficients && !has_variances && !has_constants)
error("You must pass one of 'coefficients', 'variances', or 'constants'.");
@ -2421,17 +2410,20 @@ ParsingDriver::svar()
|| (has_coefficients && has_constants))
error("You may only pass one of 'coefficients', 'variances', or 'constants'.");
if (auto itn = options_list.num_options.find("ms.chain");
itn == options_list.num_options.end())
error("A chain option must be passed to the svar statement.");
else if (stoi(itn->second) <= 0)
error("The value passed to the chain option must be greater than zero.");
try
{
if (stoi(options_list.get<OptionsList::NumVal>("ms.chain")) <= 0)
error("The value passed to the 'chain' option must be greater than zero.");
}
catch (OptionsList::UnknownOptionException &)
{
error("A 'chain' option must be passed to the 'svar' statement.");
}
if (auto itv = options_list.vector_int_options.find("ms.equations");
itv != options_list.vector_int_options.end())
for (int viit : itv->second)
if (options_list.contains("ms.equations"))
for (int viit : options_list.get<vector<int>>("ms.equations"))
if (viit <= 0)
error("The value(s) passed to the equation option must be greater than zero.");
error("The value(s) passed to the 'equations' option must be greater than zero.");
mod_file->addStatement(make_unique<SvarStatement>(options_list));
options_list.clear();
@ -2440,20 +2432,18 @@ ParsingDriver::svar()
void
ParsingDriver::markov_switching()
{
auto it0 = options_list.num_options.find("ms.chain");
if (it0 == options_list.num_options.end())
error("A chain option must be passed to the markov_switching statement.");
else if (stoi(it0->second) <= 0)
error("The value passed to the chain option must be greater than zero.");
it0 = options_list.num_options.find("ms.number_of_regimes");
if (it0 == options_list.num_options.end())
error("A number_of_regimes option must be passed to the markov_switching statement.");
else if (stoi(it0->second) <= 0)
error("The value passed to the number_of_regimes option must be greater than zero.");
if (!options_list.num_options.contains("ms.duration"))
error("A duration option must be passed to the markov_switching statement.");
try
{
if (stoi(options_list.get<OptionsList::NumVal>("ms.chain")) <= 0)
error("The value passed to the chain option must be greater than zero.");
if (stoi(options_list.get<OptionsList::NumVal>("ms.number_of_regimes")) <= 0)
error("The value passed to the number_of_regimes option must be greater than zero.");
options_list.get<OptionsList::NumVal>("ms.duration"); // Just check its presence
}
catch (OptionsList::UnknownOptionException &e)
{
error("A '" + e.name.substr(3) + "' option must be passed to the 'markov_switching' statement.");
}
mod_file->addStatement(make_unique<MarkovSwitchingStatement>(options_list));
options_list.clear();
@ -2785,24 +2775,20 @@ ParsingDriver::begin_pac_model()
void
ParsingDriver::pac_model()
{
auto it = options_list.string_options.find("pac.model_name");
if (it == options_list.string_options.end())
error("You must pass the model_name option to the pac_model statement.");
auto &name = it->second;
try
{
auto discount {options_list.get<OptionsList::StringVal>("pac.discount")};
check_symbol_is_parameter(discount);
mod_file->pac_model_table.addPacModel(options_list.get<OptionsList::StringVal>("pac.model_name"),
options_list.get_if<OptionsList::StringVal>("pac.aux_model_name").value_or(OptionsList::StringVal{}),
move(discount), pac_growth,
pac_auxname, pac_kind);
}
catch (OptionsList::UnknownOptionException &e)
{
error("You must pass the '" + e.name.substr(4) + "' option to the 'pac_model' statement.");
}
string aux_model_name;
it = options_list.string_options.find("pac.aux_model_name");
if (it != options_list.string_options.end())
aux_model_name = it->second;
it = options_list.string_options.find("pac.discount");
if (it == options_list.string_options.end())
error("You must pass the discount option to the pac_model statement.");
auto &discount = it->second;
check_symbol_is_parameter(discount);
mod_file->pac_model_table.addPacModel(move(name), move(aux_model_name), move(discount), pac_growth,
pac_auxname, pac_kind);
options_list.clear();
parsing_pac_model = false;
}
@ -3310,21 +3296,21 @@ ParsingDriver::add_graph_format(string name)
void
ParsingDriver::process_graph_format_option()
{
options_list.symbol_list_options["graph_format"] = graph_formats;
options_list.set("graph_format", OptionsList::SymbolListVal{move(graph_formats)});
graph_formats.clear();
}
void
ParsingDriver::initial_condition_decomp_process_graph_format_option()
{
options_list.symbol_list_options["initial_condition_decomp.graph_format"] = graph_formats;
options_list.set("initial_condition_decomp.graph_format", OptionsList::SymbolListVal{move(graph_formats)});
graph_formats.clear();
}
void
ParsingDriver::plot_shock_decomp_process_graph_format_option()
{
options_list.symbol_list_options["plot_shock_decomp.graph_format"] = graph_formats;
options_list.set("plot_shock_decomp.graph_format", OptionsList::SymbolListVal{move(graph_formats)});
graph_formats.clear();
}
@ -3553,30 +3539,18 @@ ParsingDriver::end_init2shocks(const string &name)
void
ParsingDriver::var_expectation_model()
{
auto it = options_list.string_options.find("variable");
if (it == options_list.string_options.end() && !var_expectation_model_expression)
error("You must pass either the 'variable' or the 'expression' option to the var_expectation_model statement.");
if (it != options_list.string_options.end())
try
{
string v {options_list.get<OptionsList::StringVal>("variable")};
if (var_expectation_model_expression)
error("You can't pass both the 'variable' or the 'expression' options to the var_expectation_model statement.");
var_expectation_model_expression = data_tree->AddVariable(mod_file->symbol_table.getID(it->second));
var_expectation_model_expression = data_tree->AddVariable(mod_file->symbol_table.getID(v));
}
catch (OptionsList::UnknownOptionException &)
{
if (!var_expectation_model_expression)
error("You must pass either the 'variable' or the 'expression' option to the var_expectation_model statement.");
}
it = options_list.string_options.find("auxiliary_model_name");
if (it == options_list.string_options.end())
error("You must pass the auxiliary_model_name option to the var_expectation_model statement.");
auto &var_model_name = it->second;
it = options_list.string_options.find("model_name");
if (it == options_list.string_options.end())
error("You must pass the model_name option to the var_expectation_model statement.");
auto &model_name = it->second;
it = options_list.num_options.find("horizon");
if (it == options_list.num_options.end())
error("You must pass the horizon option to the var_expectation_model statement.");
auto &horizon = it->second;
if (var_expectation_model_discount)
{
@ -3589,16 +3563,23 @@ ParsingDriver::var_expectation_model()
else
var_expectation_model_discount = data_tree->One;
int time_shift = 0;
it = options_list.num_options.find("time_shift");
if (it != options_list.num_options.end())
time_shift = stoi(it->second);
int time_shift { stoi(options_list.get_if<OptionsList::NumVal>("time_shift").value_or(OptionsList::NumVal{"0"})) };
if (time_shift > 0)
error("The 'time_shift' option must be a non-positive integer");
mod_file->var_expectation_model_table.addVarExpectationModel(move(model_name), var_expectation_model_expression,
move(var_model_name), move(horizon),
var_expectation_model_discount, time_shift);
try
{
mod_file->var_expectation_model_table.addVarExpectationModel(options_list.get<OptionsList::StringVal>("model_name"),
var_expectation_model_expression,
options_list.get<OptionsList::StringVal>("auxiliary_model_name"),
options_list.get<OptionsList::NumVal>("horizon"),
var_expectation_model_discount,
time_shift);
}
catch (OptionsList::UnknownOptionException &e)
{
error("You must pass the '" + e.name + "' option to the 'var_expectation_model' statement.");
}
options_list.clear();
var_expectation_model_discount = nullptr;

View File

@ -741,7 +741,7 @@ void
IrfCalibration::writeJsonOutput(ostream &output) const
{
output << R"({"statementName": "irf_calibration")";
if (options_list.getNumberOfOptions())
if (!options_list.empty())
{
output << ", ";
options_list.writeJsonOutput(output);

View File

@ -184,226 +184,144 @@ OptionsList::writeOutput(ostream &output, const string &option_group) const
void
OptionsList::writeOutputCommon(ostream &output, const string &option_group) const
{
for (const auto & [name, val] : num_options)
output << option_group << "." << name << " = " << val << ";" << endl;
for (const auto & [name, vals] : paired_num_options)
output << option_group << "." << name << " = [" << vals.first << "; "
<< vals.second << "];" << endl;
for (const auto & [name, val] : string_options)
output << option_group << "." << name << " = '" << val << "';" << endl;
for (const auto & [name, val] : date_options)
output << option_group << "." << name << " = " << val << ";" << endl;
for (const auto & [name, list] : symbol_list_options)
list.writeOutput(option_group + "." + name, output);
for (const auto & [name, vals] : vector_int_options)
for (const auto &[name, val] : options)
std::visit([&]<class T>(const T &v)
{
output << option_group << "." << name << " = ";
if (vals.size() > 1)
{
output << "[";
for (int viit : vals)
output << viit << ";";
output << "];" << endl;
}
if constexpr(is_same_v<T, SymbolListVal>)
v.writeOutput(option_group + "." + name, output);
else
output << vals.front() << ";" << endl;
}
for (const auto & [name, vals] : vector_str_options)
{
output << option_group << "." << name << " = ";
if (vals.size() > 1)
{
output << "{";
for (const auto &viit : vals)
output << "'" << viit << "';";
output << "};" << endl;
output << option_group << "." << name << " = ";
if constexpr(is_same_v<T, NumVal> || is_same_v<T, DateVal>)
output << v;
else if constexpr(is_same_v<T, pair<string, string>>)
output << '[' << v.first << "; " << v.second << ']';
else if constexpr(is_same_v<T, StringVal>)
output << "'" << v << "'";
else if constexpr(is_same_v<T, vector<int>>)
{
if (v.size() > 1)
{
output << '[';
for (int it : v)
output << it << ";";
output << ']';
}
else
output << v.front();
}
else if constexpr(is_same_v<T, VecStrVal>)
{
if (v.size() > 1)
{
output << '{';
for (const auto &it : v)
output << "'" << it << "';";
output << '}';
}
else
output << v.front();
}
else if constexpr(is_same_v<T, VecCellStrVal>)
{
/* VecCellStrVal should ideally be merged into VecStrVal.
only difference is treatment of v.size==1, where VecStrVal
does not add quotes and curly brackets, i.e. allows for type conversion of
'2' into the number 2 */
output << '{';
for (const auto &it : v)
output << "'" << it << "';";
output << '}';
}
else if constexpr(is_same_v<T, VecValueVal>)
{
/* For historical reason, those vectors are output as row vectors (contrary
to vectors of integers which are output as column vectors) */
output << '[';
for (const auto &it : v)
output << it << ',';
output << ']';
}
else if constexpr(is_same_v<T, vector<vector<string>>>)
{
// Same remark as for VecValueVal
output << '{';
for (const auto &v2 : v)
{
output << '[';
for (const auto &it : v2)
output << it << ',';
output << "], ";
}
output << '}';
}
else
static_assert(always_false_v<T>, "Non-exhaustive visitor!");
output << ";" << endl;
}
else
output << vals.front() << ";" << endl;
}
/* vector_cellstr_options should ideally be merged into vector_str_options
only difference is treatment of vals.size==1, where vector_str_options
does not add quotes and curly brackets, i.e. allows for type conversion of
'2' into the number 2
*/
for (const auto & [name, vals] : vector_cellstr_options)
{
output << option_group << "." << name << " = {";
for (const auto &viit : vals)
output << "'" << viit << "';";
output << "};" << endl;
}
/* For historical reason, those vectors are output as row vectors (contrary
to vectors of integers which are output as column vectors) */
for (const auto &[name, vals] : vector_value_options)
{
output << option_group << "." << name << " = [";
for (const auto &viit : vals)
output << viit << ",";
output << "];" << endl;
}
// Same remark as for vectors of (floating point) values
for (const auto &[name, vec_vals] : vector_of_vector_value_options)
{
output << option_group << "." << name << " = {";
for (const auto &vals : vec_vals)
{
output << "[";
for (const auto &viit : vals)
output << viit << ",";
output << "], ";
}
output << "};" << endl;
}
}, val);
}
void
OptionsList::writeJsonOutput(ostream &output) const
{
if (getNumberOfOptions() == 0)
if (empty())
return;
bool opt_written{false};
output << R"("options": {)";
for (const auto &[name, val] : num_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": )" << val;
opt_written = true;
}
for (const auto &[name, vals] : paired_num_options)
for (bool opt_written {false};
const auto &[name, val] : options)
{
if (opt_written)
if (exchange(opt_written, true))
output << ", ";
output << R"(")" << name << R"(": [)" << vals.first << ", " << vals.second << "]";
opt_written = true;
}
for (const auto &[name, val] : string_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": ")" << val << R"(")";
opt_written = true;
}
for (const auto &[name, val] : date_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": ")" << val << R"(")";
opt_written = true;
}
for (const auto &[name, vals] : symbol_list_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": {)";
vals.writeJsonOutput(output);
output << "}";
opt_written = true;
}
for (const auto &[name, vals] : vector_int_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": [)";
for (bool printed_something{false};
int val : vals)
{
if (exchange(printed_something, true))
output << ", ";
output << val;
}
output << "]";
opt_written = true;
}
for (const auto &[name, vals] : vector_str_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": [)";
for (bool printed_something{false};
const auto &val : vals)
{
if (exchange(printed_something, true))
output << ", ";
output << R"(")" << val << R"(")";
}
output << "]";
opt_written = true;
}
for (const auto &[name, vals] : vector_cellstr_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": [)";
for (bool printed_something{false};
const auto &val : vals)
{
if (exchange(printed_something, true))
output << ", ";
output << R"(")" << val << R"(")";
}
output << "]";
opt_written = true;
}
for (const auto &[name, vals] : vector_value_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": [)";
for (bool printed_something{false};
const auto &val : vals)
{
if (exchange(printed_something, true))
output << ", ";
output << val;
}
output << "]";
opt_written = true;
}
for (const auto &[name, vec_vals] : vector_of_vector_value_options)
{
if (opt_written)
output << ", ";
output << R"(")" << name << R"(": [)";
for (bool printed_something{false};
const auto &vals : vec_vals)
{
if (exchange(printed_something, true))
output << ", ";
output << "[";
for (bool printed_something2{false};
const auto &val : vals)
{
if (exchange(printed_something2, true))
output << ", ";
output << val;
}
output << "]";
}
output << "]";
opt_written = true;
output << R"(")" << name << R"(": )";
std::visit([&]<class T>(const T &v)
{
if constexpr(is_same_v<T, NumVal> || is_same_v<T, DateVal>)
output << v;
else if constexpr(is_same_v<T, pair<string, string>>)
output << '[' << v.first << ", " << v.second << ']';
else if constexpr(is_same_v<T, StringVal>)
output << '"' << v << '"';
else if constexpr(is_same_v<T, SymbolListVal>)
{
output << '{';
v.writeJsonOutput(output);
output << '}';
}
else if constexpr(is_same_v<T, vector<int>> || is_same_v<T, VecStrVal>
|| is_same_v<T, VecCellStrVal> || is_same_v<T, VecValueVal>
|| is_same_v<T, vector<vector<string>>>)
{
output << '[';
for (bool printed_something{false};
const auto &it : v)
{
if (exchange(printed_something, true))
output << ", ";
if constexpr(is_same_v<T, vector<int>> || is_same_v<T, VecValueVal>)
output << it;
else if constexpr(is_same_v<T, VecStrVal> || is_same_v<T, VecCellStrVal>)
output << '"' << it << '"';
else // vector<vector<string>>
{
output << '[';
for (bool printed_something2{false};
const auto &it2 : it)
{
if (exchange(printed_something2, true))
output << ", ";
output << it2;
}
output << ']';
}
}
output << ']';
}
else
static_assert(always_false_v<T>, "Non-exhaustive visitor!");
}, val);
}
output << "}";
@ -412,29 +330,23 @@ OptionsList::writeJsonOutput(ostream &output) const
void
OptionsList::clear()
{
num_options.clear();
paired_num_options.clear();
string_options.clear();
date_options.clear();
symbol_list_options.clear();
vector_int_options.clear();
vector_str_options.clear();
vector_cellstr_options.clear();
vector_value_options.clear();
vector_of_vector_value_options.clear();
options.clear();
}
int
OptionsList::getNumberOfOptions() const
bool
OptionsList::contains(const string &name) const
{
return num_options.size()
+ paired_num_options.size()
+ string_options.size()
+ date_options.size()
+ symbol_list_options.size()
+ vector_int_options.size()
+ vector_str_options.size()
+ vector_cellstr_options.size()
+ vector_value_options.size()
+ vector_of_vector_value_options.size();
return options.contains(name);
}
void
OptionsList::erase(const string &name)
{
options.erase(name);
}
bool
OptionsList::empty() const
{
return options.empty();
}

View File

@ -24,6 +24,8 @@
#include <string>
#include <map>
#include <set>
#include <optional>
#include <variant>
#include "SymbolList.hh"
#include "WarningConsolidation.hh"
@ -204,36 +206,115 @@ public:
void writeJsonOutput(ostream &output) const override;
};
/* Stores a list of named options with their values.
The values are stored using an std::variant; see the options data member
for the list of available types. */
class OptionsList
{
public:
using num_options_t = map<string, string>;
using paired_num_options_t = map<string, pair<string, string>>;
using string_options_t = map<string, string>;
using date_options_t = map<string, string>;
using symbol_list_options_t = map<string, SymbolList>;
using vec_int_options_t = map<string, vector<int>>;
using vec_str_options_t = map<string, vector<string >>;
using vec_cellstr_options_t = map<string, vector<string >>;
using vec_value_options_t = map<string, vector<string>>;
using vec_of_vec_value_options_t = map<string, vector<vector<string>>>;
num_options_t num_options;
paired_num_options_t paired_num_options;
string_options_t string_options;
date_options_t date_options;
symbol_list_options_t symbol_list_options;
vec_int_options_t vector_int_options;
vec_str_options_t vector_str_options;
vec_cellstr_options_t vector_cellstr_options;
vec_value_options_t vector_value_options;
vec_of_vec_value_options_t vector_of_vector_value_options;
int getNumberOfOptions() const;
// Some types to lift ambiguities
struct NumVal : string
{
};
struct StringVal : string
{
};
struct DateVal : string
{
};
struct SymbolListVal : SymbolList
{
/* This one is needed because vector<string> implicitly converts to
SymbolList. Otherwise adding a vector<string> to the variant would add a
SymbolList, which is probably not the intended meaning. */
};
struct VecStrVal : vector<string>
{
};
struct VecCellStrVal : vector<string>
{
};
struct VecValueVal : vector<string>
{
};
bool empty() const;
void clear();
// Whether there is an option with that name that has been given a value
bool contains(const string &name) const;
// Erase the option with that name
void erase(const string &name);
/* Declares an option with a name and value. Overwrite any previous value for
that name. */
template<class T>
void
set(string name, T &&val)
{
options.insert_or_assign(move(name), forward<T>(val));
}
struct UnknownOptionException
{
const string name;
UnknownOptionException(string name_arg) : name{move(name_arg)}
{
}
};
/* Retrieves the value of the option with that name.
Throws UnknownOptionException if there is no option with that name.
Throws bad_variant_access if the option has a value of a different type. */
template<class T>
T
get(const string &name) const
{
auto it = options.find(name);
if (it != options.end())
return std::get<T>(it->second);
else
throw UnknownOptionException{name};
}
/* Retrieves the value of the option with that name.
Returns nullopt if there is no option with that name.
Throws bad_variant_access if the option has a value of a different type. */
template<class T>
optional<T>
get_if(const string &name) const
{
auto it = options.find(name);
if (it != options.end())
return std::get<T>(it->second);
else
return nullopt;
}
/* Applies a variant visitor to the value of the option with that name.
Throws UnknownOptionException if there is no option with that name. */
template<class Visitor>
decltype(auto)
visit(const string &name, Visitor &&vis) const
{
auto it = options.find(name);
if (it != options.end())
return std::visit(forward<Visitor>(vis), it->second);
else
throw UnknownOptionException{name};
}
void writeOutput(ostream &output) const;
void writeOutput(ostream &output, const string &option_group) const;
void writeJsonOutput(ostream &output) const;
void clear();
private:
// pair<string, string> corresponds to a pair of numerical values
// vector<vector<string>> corresponds to a vector of vectors of numerical values
map<string, variant<NumVal, pair<string, string>, StringVal, DateVal, SymbolListVal, vector<int>,
VecStrVal, VecCellStrVal, VecValueVal, vector<vector<string>>>> options;
void writeOutputCommon(ostream &output, const string &option_group) const;
// Helper constant for visitors
template<class> static constexpr bool always_false_v {false};
};
#endif // ! _STATEMENT_HH