diff options
author | bubnikv <bubnikv@gmail.com> | 2017-11-17 13:15:46 +0300 |
---|---|---|
committer | bubnikv <bubnikv@gmail.com> | 2017-11-17 13:15:46 +0300 |
commit | 47f193fe2dd78fd1f5d058ade3ff897ce85cfdb8 (patch) | |
tree | 7c33c5b647a0381d8949639658d035e2f9c84459 /xs/src/libslic3r/PlaceholderParser.cpp | |
parent | 200f176951d06cea9f2e33b7d8a325fb103d14d4 (diff) |
The PlaceholderParser has been rewritten to use
a real boost::spirit::qi parser, accessing the DynamicConfig repository
directly. This is a first step towards a full fledged expression
interpreter.
Diffstat (limited to 'xs/src/libslic3r/PlaceholderParser.cpp')
-rw-r--r-- | xs/src/libslic3r/PlaceholderParser.cpp | 475 |
1 files changed, 383 insertions, 92 deletions
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 <boost/config/warning_disable.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/spirit/include/qi.hpp> +#include <boost/spirit/include/qi_lit.hpp> +#include <boost/spirit/include/phoenix_core.hpp> +#include <boost/spirit/include/phoenix_operator.hpp> +#include <boost/spirit/include/phoenix_fusion.hpp> +#include <boost/spirit/include/phoenix_stl.hpp> +#include <boost/spirit/include/phoenix_object.hpp> +#include <boost/fusion/include/adapt_struct.hpp> +#include <boost/variant/recursive_variant.hpp> +#include <boost/phoenix/bind/bind_function.hpp> + +#include <iostream> +#include <string> + 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<const ConfigOptionVectorBase*>(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<const ConfigOptionPoint*>(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()); -} + 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; + } -void PlaceholderParser::set(const std::string &key, unsigned int value) -{ - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); + template <typename Iterator> + static void legacy_variable_expansion( + const MyContext *ctx, + boost::iterator_range<Iterator> &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<Iterator>( + 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<Iterator>( + opt_key.begin() + idx + 1, opt_key.end(), boost::spirit::info("Invalid vector index"))); + } + } + } + if (opt == nullptr) + boost::throw_exception(boost::spirit::qi::expectation_failure<Iterator>( + 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<const ConfigOptionVectorBase*>(opt); + if (vec->empty()) + boost::throw_exception(boost::spirit::qi::expectation_failure<Iterator>( + opt_key.begin(), opt_key.end(), boost::spirit::info("Indexing an empty vector variable"))); + output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx]; + } + } + + template <typename Iterator> + static void legacy_variable_expansion2( + const MyContext *ctx, + boost::iterator_range<Iterator> &opt_key, + boost::iterator_range<Iterator> &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<Iterator>( + opt_key.begin(), opt_key.end(), boost::spirit::info("Trying to index a scalar variable"))); + const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt); + if (vec->empty()) + boost::throw_exception(boost::spirit::qi::expectation_failure<Iterator>( + 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<Iterator>( + 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<Iterator>( + 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<Iterator>( + 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 <typename Iterator> + struct calculator : boost::spirit::qi::grammar<Iterator, std::string(const MyContext*), boost::spirit::ascii::space_type> + { + 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<Iterator>, _r1, _1, _val) ] + | (identifier > lit('[') > identifier > ']') + [ boost::phoenix::bind(&MyContext::legacy_variable_expansion2<Iterator>, _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<fail>(start, + phx::ref(std::cout) + << "Error! Expecting " + << boost::spirit::qi::_4 + << " here: '" + << construct<std::string>(boost::spirit::qi::_3, boost::spirit::qi::_2) + << "'\n" + ); +*/ + } + + // The start of the grammar. + boost::spirit::qi::rule<Iterator, std::string(const MyContext*), boost::spirit::ascii::space_type> start; + // A free-form text. + boost::spirit::qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> text; + // Statements enclosed in curely braces {} + boost::spirit::qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> macro; + // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. + boost::spirit::qi::rule<Iterator, std::string(const MyContext*), boost::spirit::ascii::space_type> legacy_variable_expansion; + // Parsed identifier name. + boost::spirit::qi::rule<Iterator, boost::iterator_range<Iterator>, boost::spirit::ascii::space_type> identifier; + + boost::spirit::qi::rule<Iterator, expr(), boost::spirit::ascii::space_type> expression, term, factor; + boost::spirit::qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> text_to_eol; + boost::spirit::qi::rule<Iterator, std::string(bool), boost::spirit::ascii::space_type> expression_with_braces; + boost::spirit::qi::rule<Iterator, bool, boost::spirit::ascii::space_type> bool_expr; + + boost::spirit::qi::rule<Iterator, std::string(bool), boost::spirit::ascii::space_type> if_else_output; + boost::spirit::qi::rule<Iterator, std::string(expr), boost::spirit::ascii::space_type> switch_output; + }; } -void PlaceholderParser::set(const std::string &key, double value) +struct printer { - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} + typedef boost::spirit::utf8_string string; -void PlaceholderParser::set(const std::string &key, std::vector<std::string> values) + 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; + } +}; + +void print_info(boost::spirit::info const& what) { - m_single.erase(key); - if (values.empty()) - m_multiple.erase(key); - else - m_multiple[key] = values; + using boost::spirit::basic_info_walker; + printer pr; + basic_info_walker<printer> walker(pr, what.tag, 0); + boost::apply_visitor(walker, what.value); } -std::string PlaceholderParser::process(std::string str, unsigned int current_extruder_id) const +std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) 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(); - } - } + typedef std::string::const_iterator iterator_type; + typedef client::calculator<iterator_type> calculator; - // 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(); - } + 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<iterator_type> const& x) { + std::cout << "expected: "; print_info(x.what_); + std::cout << "got: \"" << std::string(x.first, x.last) << '"' << std::endl; } - // Replace multiple options like [foo_0]. - for (const auto &key_value : m_multiple) { - sprintf(key, "[%s_", key_value.first.c_str()); - const std::vector<std::string> &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; - } - } - // 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 (r && iter == end) + { +// std::cout << "-------------------------\n"; +// std::cout << "Parsing succeeded\n"; +// std::cout << "result = " << result << std::endl; +// std::cout << "-------------------------\n"; } - - return str; + 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; } } |