diff --git a/t/custom_gcode.t b/t/custom_gcode.t index ed74e750c..a0d96736a 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -48,7 +48,7 @@ use Slic3r::Test; { my $parser = Slic3r::GCode::PlaceholderParser->new; $parser->apply_config(my $config = Slic3r::Config::new_from_defaults); - $parser->set('foo' => '0'); + $parser->set('foo' => 0); is $parser->process('[temperature_[foo]]'), $config->temperature->[0], "nested config options"; diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index cb8dd98f7..d7671c82f 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -234,10 +234,13 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con { t_config_option_key opt_key = opt_key_src; // Try to deserialize the option by its name. - const ConfigOptionDef* optdef = this->def()->get(opt_key); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) { // If we didn't find an option, look for any other option having this as an alias. - for (const auto &opt : this->def()->options) { + for (const auto &opt : def->options) { for (const t_config_option_key &opt_key2 : opt.second.aliases) { if (opt_key2 == opt_key) { opt_key = opt_key2; @@ -277,10 +280,16 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const return static_cast(raw_opt)->value; if (raw_opt->type() == coFloatOrPercent) { // Get option definition. - const ConfigOptionDef *def = this->def()->get(opt_key); - assert(def != nullptr); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *opt_def = def->get(opt_key); + assert(opt_def != nullptr); // Compute absolute value over the absolute value of the base option. - return static_cast(raw_opt)->get_abs_value(this->get_abs_value(def->ratio_over)); + //FIXME there are some ratio_over chains, which end with empty ratio_with. + // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. + return opt_def->ratio_over.empty() ? 0. : + static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); } @@ -453,7 +462,10 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // Option was not found and a new option shall not be created. return nullptr; // Try to create a new ConfigOption. - const ConfigOptionDef *optdef = this->def()->get(opt_key); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) // throw std::runtime_error(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index d37f03c4f..8b384c9b5 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -126,6 +126,10 @@ public: virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; + // Get size of this vector. + virtual size_t size() const = 0; + // Is this vector empty? + virtual bool empty() const = 0; protected: // Used to verify type compatibility when assigning to / from a scalar ConfigOption. @@ -140,6 +144,8 @@ public: ConfigOptionVector() {} explicit ConfigOptionVector(size_t n, const T &value) : values(n, value) {} explicit ConfigOptionVector(std::initializer_list il) : values(std::move(il)) {} + explicit ConfigOptionVector(const std::vector &values) : values(values) {} + explicit ConfigOptionVector(std::vector &&values) : values(std::move(values)) {} std::vector values; void set(const ConfigOption *rhs) override @@ -227,6 +233,9 @@ public: } } + size_t size() const override { return this->values.size(); } + bool empty() const override { return this->values.empty(); } + bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) @@ -445,6 +454,8 @@ class ConfigOptionStrings : public ConfigOptionVector public: ConfigOptionStrings() : ConfigOptionVector() {} explicit ConfigOptionStrings(size_t n, const std::string &value) : ConfigOptionVector(n, value) {} + explicit ConfigOptionStrings(const std::vector &values) : ConfigOptionVector(values) {} + explicit ConfigOptionStrings(std::vector &&values) : ConfigOptionVector(std::move(values)) {} explicit ConfigOptionStrings(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} static ConfigOptionType static_type() { return coStrings; } @@ -1049,23 +1060,71 @@ private: class DynamicConfig : public virtual ConfigBase { public: + DynamicConfig() {} DynamicConfig(const DynamicConfig& other) { *this = other; } DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); } virtual ~DynamicConfig() { clear(); } - DynamicConfig& operator=(const DynamicConfig &other) + // Copy a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator=(const DynamicConfig &rhs) { + assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); - for (const auto &kvp : other.options) + for (const auto &kvp : rhs.options) this->options[kvp.first] = kvp.second->clone(); return *this; } - DynamicConfig& operator=(DynamicConfig &&other) + // Move a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator=(DynamicConfig &&rhs) { + assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); - this->options = std::move(other.options); - other.options.clear(); + this->options = std::move(rhs.options); + rhs.options.clear(); + return *this; + } + + // Add a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator+=(const DynamicConfig &rhs) + { + assert(this->def() == nullptr || this->def() == rhs.def()); + for (const auto &kvp : rhs.options) { + auto it = this->options.find(kvp.first); + if (it == this->options.end()) + this->options[kvp.first] = kvp.second->clone(); + else { + assert(it->second->type() == kvp.second->type()); + if (it->second->type() == kvp.second->type()) + *it->second = *kvp.second; + else { + delete it->second; + it->second = kvp.second->clone(); + } + } + } + return *this; + } + + // Move a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator+=(DynamicConfig &&rhs) + { + assert(this->def() == nullptr || this->def() == rhs.def()); + for (const auto &kvp : rhs.options) { + auto it = this->options.find(kvp.first); + if (it == this->options.end()) { + this->options[kvp.first] = kvp.second; + } else { + assert(it->second->type() == kvp.second->type()); + delete it->second; + it->second = kvp.second; + } + } + rhs.options.clear(); return *this; } @@ -1094,13 +1153,32 @@ public: return true; } - template T* opt(const t_config_option_key &opt_key, bool create = false) + // Allow DynamicConfig to be instantiated on ints own without a definition. + // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. + const ConfigDef* def() const override { return nullptr; }; + template T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast(this->option(opt_key, create)); } // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override; // Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. t_config_option_keys keys() const override; + // Set a value for an opt_key. Returns true if the value did not exist yet. + // This DynamicConfig will take ownership of opt. + // Be careful, as this method does not test the existence of opt_key in this->def(). + bool set_key_value(const std::string &opt_key, ConfigOption *opt) + { + auto it = this->options.find(opt_key); + if (it == this->options.end()) { + this->options[opt_key] = opt; + return true; + } else { + delete it->second; + it->second = opt; + return false; + } + } + std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option(opt_key, create)->value; } const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } @@ -1119,9 +1197,6 @@ public: bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } -protected: - DynamicConfig() {} - private: typedef std::map t_options_map; t_options_map options; @@ -1150,6 +1225,13 @@ public: const char* what() const noexcept override { return "Unknown config option"; } }; +/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). +class NoDefinitionException : public std::exception +{ +public: + const char* what() const noexcept override { return "No config definition"; } +}; + } #endif diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index f46d35657..12444d1a5 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -887,18 +887,22 @@ void GCode::process_layer( // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. if (! print.config.before_layer_gcode.value.empty()) { - PlaceholderParser pp(m_placeholder_parser); - pp.set("layer_num", m_layer_index + 1); - pp.set("layer_z", print_z); - gcode += pp.process(print.config.before_layer_gcode.value, m_writer.extruder()->id()) + "\n"; + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += m_placeholder_parser.process( + print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config) + + "\n"; } gcode += this->change_layer(print_z); // this will increase m_layer_index m_layer = &layer; if (! print.config.layer_gcode.value.empty()) { - PlaceholderParser pp(m_placeholder_parser); - pp.set("layer_num", m_layer_index); - pp.set("layer_z", print_z); - gcode += pp.process(print.config.layer_gcode.value, m_writer.extruder()->id()) + "\n"; + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += m_placeholder_parser.process( + print.config.layer_gcode.value, m_writer.extruder()->id(), &config) + + "\n"; } if (! first_layer && ! m_second_layer_things_done) { @@ -2091,10 +2095,12 @@ std::string GCode::set_extruder(unsigned int extruder_id) // append custom toolchange G-code if (m_writer.extruder() != nullptr && !m_config.toolchange_gcode.value.empty()) { - PlaceholderParser pp = m_placeholder_parser; - pp.set("previous_extruder", m_writer.extruder()->id()); - pp.set("next_extruder", extruder_id); - gcode += pp.process(m_config.toolchange_gcode.value, extruder_id) + '\n'; + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); + config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + gcode += m_placeholder_parser.process( + m_config.toolchange_gcode.value, extruder_id, &config) + + '\n'; } // if ooze prevention is enabled, park current extruder in the nearest diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index e25ec9526..5967f6a7e 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -21,6 +21,29 @@ #endif #endif +// Spirit v2.5 allows you to suppress automatic generation +// of predefined terminals to speed up complation. With +// BOOST_SPIRIT_NO_PREDEFINED_TERMINALS defined, you are +// responsible in creating instances of the terminals that +// you need (e.g. see qi::uint_type uint_ below). +//#define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + namespace Slic3r { PlaceholderParser::PlaceholderParser() @@ -61,130 +84,398 @@ void PlaceholderParser::update_timestamp() // are expected to be addressed by the extruder ID, therefore // if a vector configuration value is addressed without an index, // a current extruder ID is used. -void PlaceholderParser::apply_config(const DynamicPrintConfig &config) +void PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) { - for (const t_config_option_key &opt_key : config.keys()) { - const ConfigOptionDef* def = config.def()->get(opt_key); - if (def->multiline || opt_key == "post_process") + const ConfigDef *def = rhs.def(); + for (const t_config_option_key &opt_key : rhs.keys()) { + const ConfigOptionDef *opt_def = def->get(opt_key); + if (opt_def->multiline || opt_key == "post_process") continue; - - const ConfigOption* opt = config.option(opt_key); - const ConfigOptionVectorBase* optv = dynamic_cast(opt); - if (optv != nullptr && opt_key != "bed_shape") { - // set placeholders for options with multiple values - this->set(opt_key, optv->vserialize()); - } else if (const ConfigOptionPoint* optp = dynamic_cast(opt)) { - this->set(opt_key, optp->serialize()); - Pointf val = *optp; - this->set(opt_key + "_X", val.x); - this->set(opt_key + "_Y", val.y); - } else { - // set single-value placeholders - this->set(opt_key, opt->serialize()); - } + const ConfigOption *opt = rhs.option(opt_key); + // Store a copy of the config option. + // Convert FloatOrPercent values to floats first. + //FIXME there are some ratio_over chains, which end with empty ratio_with. + // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. + this->set(opt_key, (opt->type() == coFloatOrPercent) ? + new ConfigOptionFloat(rhs.get_abs_value(opt_key)) : + opt->clone()); } } void PlaceholderParser::apply_env_variables() { - for (char** env = environ; *env; env++) { + for (char** env = environ; *env; ++ env) { if (strncmp(*env, "SLIC3R_", 7) == 0) { std::stringstream ss(*env); std::string key, value; std::getline(ss, key, '='); ss >> value; - this->set(key, value); } } } -void PlaceholderParser::set(const std::string &key, const std::string &value) +namespace client { - m_single[key] = value; - m_multiple.erase(key); -} + struct MyContext { + const PlaceholderParser *pp = nullptr; + const DynamicConfig *config_override = nullptr; + const size_t current_extruder_id = 0; -void PlaceholderParser::set(const std::string &key, int value) -{ - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} - -void PlaceholderParser::set(const std::string &key, unsigned int value) -{ - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} - -void PlaceholderParser::set(const std::string &key, double value) -{ - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} - -void PlaceholderParser::set(const std::string &key, std::vector values) -{ - m_single.erase(key); - if (values.empty()) - m_multiple.erase(key); - else - m_multiple[key] = values; -} - -std::string PlaceholderParser::process(std::string str, unsigned int current_extruder_id) const -{ - char key[2048]; - - // Replace extruder independent single options, like [foo]. - for (const auto &key_value : m_single) { - sprintf(key, "[%s]", key_value.first.c_str()); - const std::string &replace = key_value.second; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - str.replace(i, key_value.first.size() + 2, replace); - i += replace.size(); + const ConfigOption* resolve_symbol(const std::string &opt_key) const + { + const ConfigOption *opt = nullptr; + if (config_override != nullptr) + opt = config_override->option(opt_key); + if (opt == nullptr) + opt = pp->option(opt_key); + return opt; } - } - // Replace extruder dependent single options with the value for the active extruder. - // For example, [temperature] will be replaced with the current extruder temperature. - for (const auto &key_value : m_multiple) { - sprintf(key, "[%s]", key_value.first.c_str()); - const std::string &replace = key_value.second[(current_extruder_id < key_value.second.size()) ? current_extruder_id : 0]; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - str.replace(i, key_value.first.size() + 2, replace); - i += replace.size(); - } - } - - // Replace multiple options like [foo_0]. - for (const auto &key_value : m_multiple) { - sprintf(key, "[%s_", key_value.first.c_str()); - const std::vector &values = key_value.second; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - size_t k = str.find(']', i + key_value.first.size() + 2); - if (k != std::string::npos) { - // Parse the key index and the closing bracket. - ++ k; - int idx = 0; - if (sscanf(str.c_str() + i + key_value.first.size() + 2, "%d]", &idx) == 1 && idx >= 0) { - if (idx >= int(values.size())) - idx = 0; - str.replace(i, k - i, values[idx]); - i += values[idx].size(); - continue; + template + static void legacy_variable_expansion( + const MyContext *ctx, + boost::iterator_range &opt_key, + std::string &output) + { + std::string opt_key_str(opt_key.begin(), opt_key.end()); + const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); + size_t idx = ctx->current_extruder_id; + if (opt == nullptr) { + // Check whether this is a legacy vector indexing. + idx = opt_key_str.rfind('_'); + if (idx != std::string::npos) { + opt = ctx->resolve_symbol(opt_key_str.substr(0, idx)); + if (opt != nullptr) { + if (! opt->is_vector()) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin(), opt_key.end(), boost::spirit::info("Trying to index a scalar variable"))); + char *endptr = nullptr; + idx = strtol(opt_key_str.c_str() + idx + 1, &endptr, 10); + if (endptr == nullptr || *endptr != 0) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin() + idx + 1, opt_key.end(), boost::spirit::info("Invalid vector index"))); + } } } - // The key does not match the pattern [foo_%d]. Skip just [foo_.] with the hope that there was a missing ']', - // so an opening '[' may be found somewhere before the position k. - i += key_value.first.size() + 3; + if (opt == nullptr) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin(), opt_key.end(), boost::spirit::info("Variable does not exist"))); + if (opt->is_scalar()) + output = opt->serialize(); + else { + const ConfigOptionVectorBase *vec = static_cast(opt); + if (vec->empty()) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin(), opt_key.end(), boost::spirit::info("Indexing an empty vector variable"))); + output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx]; + } } + + template + static void legacy_variable_expansion2( + const MyContext *ctx, + boost::iterator_range &opt_key, + boost::iterator_range &opt_vector_index, + std::string &output) + { + std::string opt_key_str(opt_key.begin(), opt_key.end()); + const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); + if (opt == nullptr) { + // Check whether the opt_key ends with '_'. + if (opt_key_str.back() == '_') + opt_key_str.resize(opt_key_str.size() - 1); + opt = ctx->resolve_symbol(opt_key_str); + } + if (! opt->is_vector()) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin(), opt_key.end(), boost::spirit::info("Trying to index a scalar variable"))); + const ConfigOptionVectorBase *vec = static_cast(opt); + if (vec->empty()) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin(), opt_key.end(), boost::spirit::info("Indexing an empty vector variable"))); + const ConfigOption *opt_index = ctx->resolve_symbol(std::string(opt_vector_index.begin(), opt_vector_index.end())); + if (opt_index == nullptr) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin(), opt_key.end(), boost::spirit::info("Variable does not exist"))); + if (opt_index->type() != coInt) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin(), opt_key.end(), boost::spirit::info("Indexing variable has to be integer"))); + int idx = opt_index->getInt(); + if (idx < 0) + boost::throw_exception(boost::spirit::qi::expectation_failure( + opt_key.begin(), opt_key.end(), boost::spirit::info("Negative vector index"))); + output = vec->vserialize()[(idx >= (int)vec->size()) ? 0 : idx]; + } + }; + + struct expr + { + expr(int i = 0) : type(TYPE_INT) { data.i = i; } + expr(double d) : type(TYPE_DOUBLE) { data.d = d; } + expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); } + expr(std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); } + expr(const expr &rhs) : type(rhs.type) { data.s = (rhs.type == TYPE_STRING) ? new std::string(*rhs.data.s) : rhs.data.s; } + expr(expr &&rhs) : type(rhs.type) { data.s = rhs.data.s; rhs.data.s = nullptr; rhs.type = TYPE_EMPTY; } + ~expr() { if (type == TYPE_STRING) delete data.s; data.s = nullptr; } + + expr &operator=(const expr &rhs) + { + type = rhs.type; + data.s = (type == TYPE_STRING) ? new std::string(*rhs.data.s) : rhs.data.s; + return *this; + } + + expr &operator=(expr &&rhs) + { + type = rhs.type; + data.s = rhs.data.s; + rhs.data.s = nullptr; + rhs.type = TYPE_EMPTY; + return *this; + } + + int& i() { return data.i; } + int i() const { return data.i; } + double& d() { return data.d; } + double d() const { return data.d; } + std::string& s() { return *data.s; } + const std::string& s() const { return *data.s; } + + std::string to_string() const + { + std::string out; + switch (type) { + case TYPE_INT: out = boost::to_string(data.i); break; + case TYPE_DOUBLE: out = boost::to_string(data.d); break; + case TYPE_STRING: out = *data.s; break; + } + return out; + } + + union { + int i; + double d; + std::string *s; + } data; + + enum Type { + TYPE_EMPTY = 0, + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING, + }; + + int type; + }; + + /////////////////////////////////////////////////////////////////////////// + // Our calculator grammar + /////////////////////////////////////////////////////////////////////////// + template + struct calculator : boost::spirit::qi::grammar + { + calculator() : calculator::base_type(start) + { + using boost::spirit::qi::alpha; + using boost::spirit::qi::alnum; + using boost::spirit::qi::eol; + using boost::spirit::qi::eoi; + using boost::spirit::qi::eps; + using boost::spirit::qi::raw; + using boost::spirit::qi::lit; + using boost::spirit::qi::lexeme; + using boost::spirit::qi::on_error; + using boost::spirit::qi::fail; + using boost::spirit::ascii::char_; + using boost::spirit::int_; + using boost::spirit::double_; + using boost::spirit::ascii::string; + using namespace boost::spirit::qi::labels; + + using boost::phoenix::construct; + using boost::phoenix::val; + using boost::phoenix::begin; + + boost::spirit::qi::_val_type _val; + boost::spirit::qi::_1_type _1; + boost::spirit::qi::_2_type _2; + boost::spirit::qi::_r1_type _r1; + boost::spirit::qi::uint_type uint_; + + // Starting symbol of the grammer. + // The leading eps is required by the "expectation point" operator ">". + // Without it, some of the errors would not trigger the error handler. + start = eps > *(text [_val+=_1] + || ((lit('{') > macro [_val+=_1] > '}') + | (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']'))); + start.name("start"); + + // Free-form text up to a first brace, including spaces and newlines. + // The free-form text will be inserted into the processed text without a modification. + text = raw[+(char_ - '[' - '{')]; + text.name("text"); + + // New style of macro expansion. + // The macro expansion may contain numeric or string expressions, ifs and cases. + macro = identifier; + macro.name("macro"); + + // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. + legacy_variable_expansion = + (identifier >> &lit(']')) + [ boost::phoenix::bind(&MyContext::legacy_variable_expansion, _r1, _1, _val) ] + | (identifier > lit('[') > identifier > ']') + [ boost::phoenix::bind(&MyContext::legacy_variable_expansion2, _r1, _1, _2, _val) ] + ; + legacy_variable_expansion.name("legacy_variable_expansion"); + + identifier = +// !expr.keywords >> + raw[lexeme[(alpha | '_') >> *(alnum | '_')]] + ; + identifier.name("identifier"); + +/* + bool_expr = + (expression >> '=' >> '=' >> expression) | + (expression >> '!' >> '=' >> expression) | + (expression >> '<' >> '>' >> expression) + ; + + expression = + term //[_val = _1] + >> *( ('+' >> term ) //[_val += _1]) + | ('-' >> term )//[_val -= _1]) + ) + ; + + term = + factor //[_val = _1] + >> *( ('*' >> factor )//[_val *= _1]) + | ('/' >> factor )//[_val /= _1]) + ) + ; + + factor = + int_ //[_val = expr(_1)] + | double_ //[_val = expr(_1)] + | '(' >> expression >> ')' // [_val = std::move(_1)] >> ')' + | ('-' >> factor ) //[_val = -_1]) + | ('+' >> factor ) //[_val = std::move(_1)]) + ; +*/ + +// text %= lexeme[+(char_ - '<')]; + +// text_to_eol %= lexeme[*(char_ - eol) >> (eol | eoi)]; + /* + expression_with_braces = lit('{') >> ( + string("if") >> if_else_output [_val = _1] | + string("switch") >> switch_output [_val = _1] | + expression [_val = boost::to_string(_1)] >> '}' + ); + if_else_output = + bool_expr[_r1 = _1] >> '}' >> text_to_eol[_val = _r1 ? _1 : std::string()] >> + *(lit('{') >> "elsif" >> bool_expr[_r1 = !_r1 && _1] >> '}' >> text_to_eol[_val = _r1 ? _1 : std::string()]) >> + -(lit('{') >> "else" >> '}' >> text_to_eol[_val = _r1 ? std::string() : _1]); +*/ +/* + on_error(start, + phx::ref(std::cout) + << "Error! Expecting " + << boost::spirit::qi::_4 + << " here: '" + << construct(boost::spirit::qi::_3, boost::spirit::qi::_2) + << "'\n" + ); +*/ + } + + // The start of the grammar. + boost::spirit::qi::rule start; + // A free-form text. + boost::spirit::qi::rule text; + // Statements enclosed in curely braces {} + boost::spirit::qi::rule macro; + // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. + boost::spirit::qi::rule legacy_variable_expansion; + // Parsed identifier name. + boost::spirit::qi::rule, boost::spirit::ascii::space_type> identifier; + + boost::spirit::qi::rule expression, term, factor; + boost::spirit::qi::rule text_to_eol; + boost::spirit::qi::rule expression_with_braces; + boost::spirit::qi::rule bool_expr; + + boost::spirit::qi::rule if_else_output; + boost::spirit::qi::rule switch_output; + }; +} + +struct printer +{ + typedef boost::spirit::utf8_string string; + + void element(string const& tag, string const& value, int depth) const + { + for (int i = 0; i < (depth*4); ++i) // indent to depth + std::cout << ' '; + std::cout << "tag: " << tag; + if (value != "") + std::cout << ", value: " << value; + std::cout << std::endl; } - - return str; +}; + +void print_info(boost::spirit::info const& what) +{ + using boost::spirit::basic_info_walker; + printer pr; + basic_info_walker walker(pr, what.tag, 0); + boost::apply_visitor(walker, what.value); +} + +std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const +{ + typedef std::string::const_iterator iterator_type; + typedef client::calculator calculator; + + boost::spirit::ascii::space_type space; // Our skipper + calculator calc; // Our grammar + + std::string::const_iterator iter = templ.begin(); + std::string::const_iterator end = templ.end(); + //std::string result; + std::string result; + bool r = false; + try { + client::MyContext context; + context.pp = this; + context.config_override = config_override; + r = phrase_parse(iter, end, calc(&context), space, result); + } catch (boost::spirit::qi::expectation_failure const& x) { + std::cout << "expected: "; print_info(x.what_); + std::cout << "got: \"" << std::string(x.first, x.last) << '"' << std::endl; + } + + if (r && iter == end) + { +// std::cout << "-------------------------\n"; +// std::cout << "Parsing succeeded\n"; +// std::cout << "result = " << result << std::endl; +// std::cout << "-------------------------\n"; + } + else + { + std::string rest(iter, end); + std::cout << "-------------------------\n"; + std::cout << "Parsing failed\n"; + std::cout << "stopped at: \" " << rest << "\"\n"; + std::cout << "source: \n" << templ; + std::cout << "-------------------------\n"; + } + return result; } } diff --git a/xs/src/libslic3r/PlaceholderParser.hpp b/xs/src/libslic3r/PlaceholderParser.hpp index a8abf8871..9ec2eedea 100644 --- a/xs/src/libslic3r/PlaceholderParser.hpp +++ b/xs/src/libslic3r/PlaceholderParser.hpp @@ -11,23 +11,27 @@ namespace Slic3r { class PlaceholderParser { -public: - std::map m_single; - std::map> m_multiple; - +public: PlaceholderParser(); + void update_timestamp(); void apply_config(const DynamicPrintConfig &config); void apply_env_variables(); - void set(const std::string &key, const std::string &value); - void set(const std::string &key, int value); - void set(const std::string &key, unsigned int value); - void set(const std::string &key, double value); - void set(const std::string &key, std::vector values); - std::string process(std::string str, unsigned int current_extruder_id) const; + + // Add new ConfigOption values to m_config. + void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); } + void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); } + void set(const std::string &key, unsigned int value) { this->set(key, int(value)); } + void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); } + void set(const std::string &key, const std::vector &values) { this->set(key, new ConfigOptionStrings(values)); } + void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); } + const ConfigOption* option(const std::string &key) const { return m_config.option(key); } + + // Fill in the template. + std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const; private: - bool find_and_replace(std::string &source, std::string const &find, std::string const &replace) const; + DynamicConfig m_config; }; } diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp index 3f1588ace..08630f9d5 100644 --- a/xs/xsp/PlaceholderParser.xsp +++ b/xs/xsp/PlaceholderParser.xsp @@ -9,15 +9,10 @@ %name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser { PlaceholderParser(); ~PlaceholderParser(); - Clone clone() - %code{% RETVAL = THIS; %}; - void update_timestamp(); - void apply_env_variables(); void apply_config(DynamicPrintConfig *config) %code%{ THIS->apply_config(*config); %}; - void set(std::string key, std::string value); - %name{set_multiple} void set(std::string key, std::vector values); + void set(std::string key, int value); std::string process(std::string str) const %code%{ RETVAL = THIS->process(str, 0); %}; };