From 615278f3365087e436ee5ea13e0d15bd60718038 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Tue, 10 Oct 2023 23:30:18 -0500 Subject: hscript: Allow multiple inheritance This allows a HorizonScript to inherit from multiple files. Files are parsed in a system-defined, unspecified order, but all scripts of a given depth are guaranteed to be parsed before the next depth. --- hscript/script.cc | 114 +++++++++++++++++++++++++++++++++++++----------------- hscript/script.hh | 4 ++ 2 files changed, 82 insertions(+), 36 deletions(-) (limited to 'hscript') diff --git a/hscript/script.cc b/hscript/script.cc index 59a799a..d86216d 100644 --- a/hscript/script.cc +++ b/hscript/script.cc @@ -203,8 +203,6 @@ Script *Script::load(const std::string &path, const ScriptOptions &opts) { } -Script *Script::load(std::istream &sstream, const ScriptOptions &opts, - const std::string &name) { #define PARSER_ERROR(err_str) \ errors++;\ output_error(pos, err_str, "");\ @@ -216,24 +214,16 @@ Script *Script::load(std::istream &sstream, const ScriptOptions &opts, warnings++;\ output_warning(pos, warn_str, ""); - using namespace Horizon::Keys; - Script *the_script = new Script; - the_script->opts = opts; - - int lineno = 0; +std::pair< bool, std::vector > +Script::readFromStream(std::istream *my_stream, Script *the_script, + const ScriptOptions &opts, const std::string &curr_name, + int &errors, int &warnings, bool inherit) { char nextline[SCRIPT_LINE_MAX]; + int lineno = 0; + const int error_start = errors, warning_start = warnings; const std::string delim(" \t"); - int errors = 0, warnings = 0; - std::string curr_name; - if(name == "/dev/stdin") { - curr_name = ""; - } else { - curr_name = fs::canonical(fs::path(name)).native(); - } - std::set seen = {curr_name}; - bool inherit = false; - std::istream *my_stream = &sstream; + std::vector inherits; while(my_stream->getline(nextline, sizeof(nextline))) { lineno++; @@ -245,15 +235,14 @@ Script *Script::load(std::istream &sstream, const ScriptOptions &opts, const ScriptLocation pos(curr_name, lineno, inherit); const std::string line(nextline); std::string key; - std::string::size_type start, key_end, value_begin; - start = line.find_first_not_of(delim); + const std::string::size_type start{line.find_first_not_of(delim)}, + key_end{line.find_first_of(delim, start)}, + value_begin{line.find_first_not_of(delim, key_end)}; if(start == std::string::npos) { /* This is a blank line; ignore it. */ continue; } - key_end = line.find_first_of(delim, start); - value_begin = line.find_first_not_of(delim, key_end); key = line.substr(start, (key_end - start)); if(key_end == std::string::npos || value_begin == std::string::npos) { /* Key without value */ @@ -273,21 +262,17 @@ Script *Script::load(std::istream &sstream, const ScriptOptions &opts, next_name = fs::absolute(better_path).native(); } - if(seen.find(next_name) != seen.end()) { - PARSER_ERROR("attempt to inherit from already inherited file") - break; - } - seen.insert(next_name); - if(!fs::exists(next_name)) { - PARSER_ERROR("attempt to inherit from non-existent file") - break; - } else { - if(my_stream != &sstream) delete my_stream; - curr_name = next_name; - inherit = true; - my_stream = new std::ifstream(curr_name); + errors++; + output_error(pos, "attempt to inherit from non-existent file", + next_name); + if(!opts.test(ScriptOptionFlags::KeepGoing)) { + break; + } + continue; } + + inherits.push_back(next_name); continue; } @@ -329,6 +314,63 @@ Script *Script::load(std::istream &sstream, const ScriptOptions &opts, errors++; } + return std::make_pair((errors == error_start && warnings == warning_start), + inherits); +} + + +Script *Script::load(std::istream &sstream, const ScriptOptions &opts, + const std::string &name) { + using namespace Horizon::Keys; + + Script *the_script = new Script; + the_script->opts = opts; + + int errors = 0, warnings = 0; + std::string curr_name; + if(name == "/dev/stdin") { + curr_name = ""; + } else { + curr_name = fs::canonical(fs::path(name)).native(); + } + std::set seen = {curr_name}; + std::set this_depth = {curr_name}; + std::set next_depth; + bool inherit = false, keep_going = true; + std::istream *my_stream = &sstream; + + while(keep_going) { + auto result = readFromStream(my_stream, the_script, opts, curr_name, + errors, warnings, inherit); + seen.insert(curr_name); + for(const auto &next_name: result.second) { + if (seen.find(next_name) != seen.end()) { + output_error(curr_name, "attempt to inherit from already "\ + "inherited file", next_name); + keep_going = false; + break; + } + next_depth.insert(next_name); + } + + this_depth.erase(curr_name); + if(this_depth.empty()) { + /* We've processed all scripts at this depth; let's go deeper. */ + this_depth = next_depth; + next_depth = {}; + } + + if(this_depth.empty()) { + /* We are finished. */ + break; + } + + if(my_stream != &sstream) delete my_stream; + curr_name = *this_depth.begin(); + inherit = true; + my_stream = new std::ifstream(curr_name); + } + /* Ensure all required keys are present. */ #define MISSING_ERROR(key) \ output_error(curr_name, "expected value for key '" + std::string(key) + "'",\ @@ -342,13 +384,13 @@ Script *Script::load(std::istream &sstream, const ScriptOptions &opts, if(!the_script->internal->hostname) { MISSING_ERROR("hostname") } - if(the_script->internal->packages.size() == 0) { + if(the_script->internal->packages.empty()) { MISSING_ERROR("pkginstall") } if(!the_script->internal->rootpw) { MISSING_ERROR("rootpw") } - if(the_script->internal->mounts.size() == 0 && !opts.test(ImageOnly)) { + if(the_script->internal->mounts.empty() && !opts.test(ImageOnly)) { MISSING_ERROR("mount") } } diff --git a/hscript/script.hh b/hscript/script.hh index 9befb41..a34668a 100644 --- a/hscript/script.hh +++ b/hscript/script.hh @@ -59,6 +59,10 @@ private: /*! Initialise the Script class. */ Script(); ScriptOptions opts; + + static std::pair< bool, std::vector > + readFromStream(std::istream *, Script *, const ScriptOptions &, + const std::string &, int &, int &, bool); public: /*! Free resources associated with the Script. */ ~Script(); -- cgit v1.2.3-60-g2f50