2023-06-10 10:14:12 +08:00
# include "PlaceholderParser.hpp"
# include "Exception.hpp"
# include "Flow.hpp"
# include "Utils.hpp"
# include <cstring>
# include <ctime>
# include <iomanip>
# include <sstream>
# include <map>
# ifdef _MSC_VER
# include <stdlib.h> // provides **_environ
# else
# include <unistd.h> // provides **environ
# endif
# ifdef __APPLE__
# include <crt_externs.h>
# undef environ
# define environ (*_NSGetEnviron())
# else
# ifdef _MSC_VER
# define environ _environ
# else
extern char * * environ ;
# endif
# endif
# include <boost/algorithm/string.hpp>
# include <boost/nowide/convert.hpp>
// 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
# define BOOST_RESULT_OF_USE_DECLTYPE
# define BOOST_SPIRIT_USE_PHOENIX_V3
# 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/phoenix/core.hpp>
# include <boost/phoenix/operator.hpp>
# include <boost/phoenix/fusion.hpp>
# include <boost/phoenix/stl.hpp>
# include <boost/phoenix/object.hpp>
# include <boost/fusion/include/adapt_struct.hpp>
# include <boost/spirit/repository/include/qi_distinct.hpp>
# include <boost/spirit/repository/include/qi_iter_pos.hpp>
# include <boost/variant/recursive_variant.hpp>
# include <boost/phoenix/bind/bind_function.hpp>
# include <iostream>
# include <string>
// #define USE_CPP11_REGEX
# ifdef USE_CPP11_REGEX
# include <regex>
# define SLIC3R_REGEX_NAMESPACE std
# else /* USE_CPP11_REGEX */
# include <boost/regex.hpp>
# define SLIC3R_REGEX_NAMESPACE boost
# endif /* USE_CPP11_REGEX */
namespace Slic3r {
PlaceholderParser : : PlaceholderParser ( const DynamicConfig * external_config ) : m_external_config ( external_config )
{
this - > set ( " version " , std : : string ( SLIC3R_VERSION ) ) ;
this - > apply_env_variables ( ) ;
this - > update_timestamp ( ) ;
}
void PlaceholderParser : : update_timestamp ( DynamicConfig & config )
{
time_t rawtime ;
time ( & rawtime ) ;
struct tm * timeinfo = localtime ( & rawtime ) ;
{
std : : ostringstream ss ;
ss < < ( 1900 + timeinfo - > tm_year ) ;
ss < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < ( 1 + timeinfo - > tm_mon ) ;
ss < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < timeinfo - > tm_mday ;
ss < < " - " ;
ss < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < timeinfo - > tm_hour ;
ss < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < timeinfo - > tm_min ;
ss < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < timeinfo - > tm_sec ;
config . set_key_value ( " timestamp " , new ConfigOptionString ( ss . str ( ) ) ) ;
}
config . set_key_value ( " year " , new ConfigOptionInt ( 1900 + timeinfo - > tm_year ) ) ;
config . set_key_value ( " month " , new ConfigOptionInt ( 1 + timeinfo - > tm_mon ) ) ;
config . set_key_value ( " day " , new ConfigOptionInt ( timeinfo - > tm_mday ) ) ;
config . set_key_value ( " hour " , new ConfigOptionInt ( timeinfo - > tm_hour ) ) ;
config . set_key_value ( " minute " , new ConfigOptionInt ( timeinfo - > tm_min ) ) ;
config . set_key_value ( " second " , new ConfigOptionInt ( timeinfo - > tm_sec ) ) ;
}
static inline bool opts_equal ( const DynamicConfig & config_old , const DynamicConfig & config_new , const std : : string & opt_key )
{
const ConfigOption * opt_old = config_old . option ( opt_key ) ;
const ConfigOption * opt_new = config_new . option ( opt_key ) ;
assert ( opt_new ! = nullptr ) ;
return opt_old ! = nullptr & & * opt_new = = * opt_old ;
}
std : : vector < std : : string > PlaceholderParser : : config_diff ( const DynamicPrintConfig & rhs )
{
std : : vector < std : : string > diff_keys ;
for ( const t_config_option_key & opt_key : rhs . keys ( ) )
if ( ! opts_equal ( m_config , rhs , opt_key ) )
diff_keys . emplace_back ( opt_key ) ;
return diff_keys ;
}
// Scalar configuration values are stored into m_single,
// vector configuration values are stored into m_multiple.
// All vector configuration values stored into the PlaceholderParser
// 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.
bool PlaceholderParser : : apply_config ( const DynamicPrintConfig & rhs )
{
bool modified = false ;
for ( const t_config_option_key & opt_key : rhs . keys ( ) ) {
if ( ! opts_equal ( m_config , rhs , opt_key ) ) {
this - > set ( opt_key , rhs . option ( opt_key ) - > clone ( ) ) ;
modified = true ;
}
}
return modified ;
}
void PlaceholderParser : : apply_only ( const DynamicPrintConfig & rhs , const std : : vector < std : : string > & keys )
{
for ( const t_config_option_key & opt_key : keys )
this - > set ( opt_key , rhs . option ( opt_key ) - > clone ( ) ) ;
}
void PlaceholderParser : : apply_config ( DynamicPrintConfig & & rhs )
{
m_config + = std : : move ( rhs ) ;
}
void PlaceholderParser : : apply_env_variables ( )
{
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 ) ;
}
}
}
namespace spirit = boost : : spirit ;
// Using an encoding, which accepts unsigned chars.
// Don't use boost::spirit::ascii, as it crashes internally due to indexing with negative char values for UTF8 characters into some 7bit character classification tables.
//namespace spirit_encoding = boost::spirit::ascii;
//FIXME iso8859_1 is just a workaround for the problem above. Replace it with UTF8 support!
namespace spirit_encoding = boost : : spirit : : iso8859_1 ;
namespace qi = boost : : spirit : : qi ;
namespace px = boost : : phoenix ;
namespace client
{
using Iterator = std : : string : : const_iterator ;
using IteratorRange = boost : : iterator_range < Iterator > ;
struct OptWithPos {
OptWithPos ( ) { }
OptWithPos ( ConfigOptionConstPtr opt , IteratorRange it_range , bool writable = false ) : opt ( opt ) , it_range ( it_range ) , writable ( writable ) { }
ConfigOptionConstPtr opt { nullptr } ;
bool writable { false } ;
// -1 means it is a scalar variable, or it is a vector variable and index was not assigned yet or the whole vector is considered.
int index { - 1 } ;
IteratorRange it_range ;
bool empty ( ) const { return opt = = nullptr ; }
bool has_index ( ) const { return index ! = - 1 ; }
} ;
std : : ostream & operator < < ( std : : ostream & os , OptWithPos const & opt )
{
os < < std : : string ( opt . it_range . begin ( ) , opt . it_range . end ( ) ) ;
return os ;
}
struct expr
{
expr ( ) { }
expr ( const expr & rhs ) : m_type ( rhs . type ( ) ) , it_range ( rhs . it_range )
{ if ( rhs . type ( ) = = TYPE_STRING ) m_data . s = new std : : string ( * rhs . m_data . s ) ; else m_data . set ( rhs . m_data ) ; }
expr ( expr & & rhs ) : expr ( std : : move ( rhs ) , rhs . it_range . begin ( ) , rhs . it_range . end ( ) ) { }
explicit expr ( bool b ) : m_type ( TYPE_BOOL ) { m_data . b = b ; }
explicit expr ( bool b , const Iterator & it_begin , const Iterator & it_end ) : m_type ( TYPE_BOOL ) , it_range ( it_begin , it_end ) { m_data . b = b ; }
explicit expr ( int i ) : m_type ( TYPE_INT ) { m_data . i = i ; }
explicit expr ( int i , const Iterator & it_begin , const Iterator & it_end ) : m_type ( TYPE_INT ) , it_range ( it_begin , it_end ) { m_data . i = i ; }
explicit expr ( double d ) : m_type ( TYPE_DOUBLE ) { m_data . d = d ; }
explicit expr ( double d , const Iterator & it_begin , const Iterator & it_end ) : m_type ( TYPE_DOUBLE ) , it_range ( it_begin , it_end ) { m_data . d = d ; }
explicit expr ( const char * s ) : m_type ( TYPE_STRING ) { m_data . s = new std : : string ( s ) ; }
explicit expr ( const std : : string & s ) : m_type ( TYPE_STRING ) { m_data . s = new std : : string ( s ) ; }
explicit expr ( std : : string & & s ) : m_type ( TYPE_STRING ) { m_data . s = new std : : string ( std : : move ( s ) ) ; }
explicit expr ( const std : : string & s , const Iterator & it_begin , const Iterator & it_end ) :
m_type ( TYPE_STRING ) , it_range ( it_begin , it_end ) { m_data . s = new std : : string ( s ) ; }
explicit expr ( expr & & rhs , const Iterator & it_begin , const Iterator & it_end ) : m_type ( rhs . type ( ) ) , it_range { it_begin , it_end }
{
m_data . set ( rhs . m_data ) ;
rhs . m_type = TYPE_EMPTY ;
}
expr & operator = ( const expr & rhs )
{
if ( rhs . type ( ) = = TYPE_STRING ) {
this - > set_s ( rhs . s ( ) ) ;
} else {
m_type = rhs . type ( ) ;
m_data . set ( rhs . m_data ) ;
}
this - > it_range = rhs . it_range ;
return * this ;
}
expr & operator = ( expr & & rhs )
{
if ( this ! = & rhs ) {
this - > reset ( ) ;
m_type = rhs . type ( ) ;
this - > it_range = rhs . it_range ;
m_data . set ( rhs . m_data ) ;
rhs . m_type = TYPE_EMPTY ;
}
return * this ;
}
void reset ( )
{
if ( this - > type ( ) = = TYPE_STRING )
delete m_data . s ;
m_type = TYPE_EMPTY ;
}
~ expr ( ) { reset ( ) ; }
enum Type {
TYPE_EMPTY = 0 ,
TYPE_BOOL ,
TYPE_INT ,
TYPE_DOUBLE ,
TYPE_STRING ,
} ;
Type type ( ) const { return m_type ; }
bool numeric_type ( ) const { return m_type = = TYPE_INT | | m_type = = TYPE_DOUBLE ; }
bool & b ( ) { return m_data . b ; }
bool b ( ) const { return m_data . b ; }
void set_b ( bool v ) { this - > reset ( ) ; this - > set_b_lite ( v ) ; }
void set_b_lite ( bool v ) { assert ( this - > type ( ) ! = TYPE_STRING ) ; Data tmp ; tmp . b = v ; m_data . set ( tmp ) ; m_type = TYPE_BOOL ; }
int & i ( ) { return m_data . i ; }
int i ( ) const { return m_data . i ; }
void set_i ( int v ) { this - > reset ( ) ; set_i_lite ( v ) ; }
void set_i_lite ( int v ) { assert ( this - > type ( ) ! = TYPE_STRING ) ; Data tmp ; tmp . i = v ; m_data . set ( tmp ) ; m_type = TYPE_INT ; }
int as_i ( ) const { return this - > type ( ) = = TYPE_INT ? this - > i ( ) : int ( this - > d ( ) ) ; }
int as_i_rounded ( ) const { return this - > type ( ) = = TYPE_INT ? this - > i ( ) : int ( std : : round ( this - > d ( ) ) ) ; }
double & d ( ) { return m_data . d ; }
double d ( ) const { return m_data . d ; }
void set_d ( double v ) { this - > reset ( ) ; this - > set_d_lite ( v ) ; }
void set_d_lite ( double v ) { assert ( this - > type ( ) ! = TYPE_STRING ) ; Data tmp ; tmp . d = v ; m_data . set ( tmp ) ; m_type = TYPE_DOUBLE ; }
double as_d ( ) const { return this - > type ( ) = = TYPE_DOUBLE ? this - > d ( ) : double ( this - > i ( ) ) ; }
std : : string & s ( ) { return * m_data . s ; }
const std : : string & s ( ) const { return * m_data . s ; }
void set_s ( const std : : string & s ) {
if ( this - > type ( ) = = TYPE_STRING )
* m_data . s = s ;
else
this - > set_s_take_ownership ( new std : : string ( s ) ) ;
}
void set_s ( std : : string & & s ) {
if ( this - > type ( ) = = TYPE_STRING )
* m_data . s = std : : move ( s ) ;
else
this - > set_s_take_ownership ( new std : : string ( std : : move ( s ) ) ) ;
}
void set_s ( const char * s ) {
if ( this - > type ( ) = = TYPE_STRING )
* m_data . s = s ;
else
this - > set_s_take_ownership ( new std : : string ( s ) ) ;
}
std : : string to_string ( ) const
{
std : : string out ;
switch ( this - > type ( ) ) {
case TYPE_EMPTY :
// Inside an if / else block to be skipped.
break ;
case TYPE_BOOL : out = this - > b ( ) ? " true " : " false " ; break ;
case TYPE_INT : out = std : : to_string ( this - > i ( ) ) ; break ;
case TYPE_DOUBLE :
#if 0
// The default converter produces trailing zeros after the decimal point.
out = std : : to_string ( data . d ) ;
# else
// ostringstream default converter produces no trailing zeros after the decimal point.
// It seems to be doing what the old boost::to_string() did.
{
std : : ostringstream ss ;
ss < < this - > d ( ) ;
out = ss . str ( ) ;
}
# endif
break ;
case TYPE_STRING : out = this - > s ( ) ; break ;
default : break ;
}
return out ;
}
// Range of input iterators covering this expression.
// Used for throwing parse exceptions.
IteratorRange it_range ;
expr unary_minus ( const Iterator start_pos ) const
{
switch ( this - > type ( ) ) {
case TYPE_EMPTY :
// Inside an if / else block to be skipped.
return expr ( ) ;
case TYPE_INT :
return expr ( - this - > i ( ) , start_pos , this - > it_range . end ( ) ) ;
case TYPE_DOUBLE :
return expr ( - this - > d ( ) , start_pos , this - > it_range . end ( ) ) ;
default :
this - > throw_exception ( " Cannot apply unary minus operator. " ) ;
}
assert ( false ) ;
// Suppress compiler warnings.
return expr ( ) ;
}
expr unary_integer ( const Iterator start_pos ) const
{
switch ( this - > type ( ) ) {
case TYPE_EMPTY :
// Inside an if / else block to be skipped.
return expr ( ) ;
case TYPE_INT :
return expr ( this - > i ( ) , start_pos , this - > it_range . end ( ) ) ;
case TYPE_DOUBLE :
return expr ( static_cast < int > ( this - > d ( ) ) , start_pos , this - > it_range . end ( ) ) ;
default :
this - > throw_exception ( " Cannot convert to integer. " ) ;
}
assert ( false ) ;
// Suppress compiler warnings.
return expr ( ) ;
}
expr round ( const Iterator start_pos ) const
{
switch ( this - > type ( ) ) {
case TYPE_EMPTY :
// Inside an if / else block to be skipped.
return expr ( ) ;
case TYPE_INT :
return expr ( this - > i ( ) , start_pos , this - > it_range . end ( ) ) ;
case TYPE_DOUBLE :
return expr ( static_cast < int > ( std : : round ( this - > d ( ) ) ) , start_pos , this - > it_range . end ( ) ) ;
default :
this - > throw_exception ( " Cannot round a non-numeric value. " ) ;
}
assert ( false ) ;
// Suppress compiler warnings.
return expr ( ) ;
}
expr unary_not ( const Iterator start_pos ) const
{
switch ( this - > type ( ) ) {
case TYPE_EMPTY :
// Inside an if / else block to be skipped.
return expr ( ) ;
case TYPE_BOOL :
return expr ( ! this - > b ( ) , start_pos , this - > it_range . end ( ) ) ;
default :
this - > throw_exception ( " Cannot apply a not operator. " ) ;
}
assert ( false ) ;
// Suppress compiler warnings.
return expr ( ) ;
}
expr & operator + = ( const expr & rhs )
{
if ( this - > type ( ) = = TYPE_EMPTY ) {
// Inside an if / else block to be skipped.
} else if ( this - > type ( ) = = TYPE_STRING ) {
// Convert the right hand side to string and append.
* m_data . s + = rhs . to_string ( ) ;
} else if ( rhs . type ( ) = = TYPE_STRING ) {
// Conver the left hand side to string, append rhs.
this - > set_s ( this - > to_string ( ) + rhs . s ( ) ) ;
} else {
const char * err_msg = " Cannot add non-numeric types. " ;
this - > throw_if_not_numeric ( err_msg ) ;
rhs . throw_if_not_numeric ( err_msg ) ;
if ( this - > type ( ) = = TYPE_DOUBLE | | rhs . type ( ) = = TYPE_DOUBLE )
this - > set_d_lite ( this - > as_d ( ) + rhs . as_d ( ) ) ;
else
m_data . i + = rhs . i ( ) ;
}
this - > it_range = IteratorRange ( this - > it_range . begin ( ) , rhs . it_range . end ( ) ) ;
return * this ;
}
expr & operator - = ( const expr & rhs )
{
if ( this - > type ( ) = = TYPE_EMPTY ) {
// Inside an if / else block to be skipped.
this - > reset ( ) ;
} else {
const char * err_msg = " Cannot subtract non-numeric types. " ;
this - > throw_if_not_numeric ( err_msg ) ;
rhs . throw_if_not_numeric ( err_msg ) ;
if ( this - > type ( ) = = TYPE_DOUBLE | | rhs . type ( ) = = TYPE_DOUBLE )
this - > set_d_lite ( this - > as_d ( ) - rhs . as_d ( ) ) ;
else
m_data . i - = rhs . i ( ) ;
this - > it_range = IteratorRange ( this - > it_range . begin ( ) , rhs . it_range . end ( ) ) ;
}
return * this ;
}
expr & operator * = ( const expr & rhs )
{
if ( this - > type ( ) = = TYPE_EMPTY ) {
// Inside an if / else block to be skipped.
this - > reset ( ) ;
} else {
const char * err_msg = " Cannot multiply with non-numeric type. " ;
this - > throw_if_not_numeric ( err_msg ) ;
rhs . throw_if_not_numeric ( err_msg ) ;
if ( this - > type ( ) = = TYPE_DOUBLE | | rhs . type ( ) = = TYPE_DOUBLE )
this - > set_d_lite ( this - > as_d ( ) * rhs . as_d ( ) ) ;
else
m_data . i * = rhs . i ( ) ;
this - > it_range = IteratorRange ( this - > it_range . begin ( ) , rhs . it_range . end ( ) ) ;
}
return * this ;
}
expr & operator / = ( const expr & rhs )
{
if ( this - > type ( ) = = TYPE_EMPTY ) {
// Inside an if / else block to be skipped.
this - > reset ( ) ;
} else {
this - > throw_if_not_numeric ( " Cannot divide a non-numeric type. " ) ;
rhs . throw_if_not_numeric ( " Cannot divide with a non-numeric type. " ) ;
if ( rhs . type ( ) = = TYPE_INT ? ( rhs . i ( ) = = 0 ) : ( rhs . d ( ) = = 0. ) )
rhs . throw_exception ( " Division by zero " ) ;
if ( this - > type ( ) = = TYPE_DOUBLE | | rhs . type ( ) = = TYPE_DOUBLE )
this - > set_d_lite ( this - > as_d ( ) / rhs . as_d ( ) ) ;
else
m_data . i / = rhs . i ( ) ;
this - > it_range = IteratorRange ( this - > it_range . begin ( ) , rhs . it_range . end ( ) ) ;
}
return * this ;
}
expr & operator % = ( const expr & rhs )
{
if ( this - > type ( ) = = TYPE_EMPTY ) {
// Inside an if / else block to be skipped.
this - > reset ( ) ;
} else {
this - > throw_if_not_numeric ( " Cannot divide a non-numeric type. " ) ;
rhs . throw_if_not_numeric ( " Cannot divide with a non-numeric type. " ) ;
if ( rhs . type ( ) = = TYPE_INT ? ( rhs . i ( ) = = 0 ) : ( rhs . d ( ) = = 0. ) )
rhs . throw_exception ( " Division by zero " ) ;
if ( this - > type ( ) = = TYPE_DOUBLE | | rhs . type ( ) = = TYPE_DOUBLE )
this - > set_d_lite ( std : : fmod ( this - > as_d ( ) , rhs . as_d ( ) ) ) ;
else
m_data . i % = rhs . i ( ) ;
this - > it_range = IteratorRange ( this - > it_range . begin ( ) , rhs . it_range . end ( ) ) ;
}
return * this ;
}
static void to_string2 ( expr & self , std : : string & out )
{
if ( self . type ( ) ! = TYPE_EMPTY )
// Not inside an if / else block to be skipped
out = self . to_string ( ) ;
}
static void evaluate_boolean ( expr & self , bool & out )
{
if ( self . type ( ) ! = TYPE_EMPTY ) {
// Not inside an if / else block to be skipped
if ( self . type ( ) ! = TYPE_BOOL )
self . throw_exception ( " Not a boolean expression " ) ;
out = self . b ( ) ;
}
}
static void evaluate_boolean_to_string ( expr & self , std : : string & out )
{
assert ( self . type ( ) ! = TYPE_EMPTY ) ;
if ( self . type ( ) ! = TYPE_BOOL )
self . throw_exception ( " Not a boolean expression " ) ;
out = self . b ( ) ? " true " : " false " ;
}
// Is lhs==rhs? Store the result into lhs.
static void compare_op ( expr & lhs , expr & rhs , char op , bool invert )
{
if ( lhs . type ( ) = = TYPE_EMPTY )
// Inside an if / else block to be skipped
return ;
bool value = false ;
if ( lhs . numeric_type ( ) & & rhs . numeric_type ( ) ) {
// Both types are numeric.
switch ( op ) {
case ' = ' :
value = ( lhs . type ( ) = = TYPE_DOUBLE | | rhs . type ( ) = = TYPE_DOUBLE ) ?
( std : : abs ( lhs . as_d ( ) - rhs . as_d ( ) ) < 1e-8 ) : ( lhs . i ( ) = = rhs . i ( ) ) ;
break ;
case ' < ' :
value = ( lhs . type ( ) = = TYPE_DOUBLE | | rhs . type ( ) = = TYPE_DOUBLE ) ?
( lhs . as_d ( ) < rhs . as_d ( ) ) : ( lhs . i ( ) < rhs . i ( ) ) ;
break ;
case ' > ' :
default :
value = ( lhs . type ( ) = = TYPE_DOUBLE | | rhs . type ( ) = = TYPE_DOUBLE ) ?
( lhs . as_d ( ) > rhs . as_d ( ) ) : ( lhs . i ( ) > rhs . i ( ) ) ;
break ;
}
} else if ( lhs . type ( ) = = TYPE_BOOL & & rhs . type ( ) = = TYPE_BOOL ) {
// Both type are bool.
if ( op ! = ' = ' )
boost : : throw_exception ( qi : : expectation_failure < Iterator > (
lhs . it_range . begin ( ) , rhs . it_range . end ( ) , spirit : : info ( " *Cannot compare the types. " ) ) ) ;
value = lhs . b ( ) = = rhs . b ( ) ;
} else if ( lhs . type ( ) = = TYPE_STRING | | rhs . type ( ) = = TYPE_STRING ) {
// One type is string, the other could be converted to string.
value = ( op = = ' = ' ) ? ( lhs . to_string ( ) = = rhs . to_string ( ) ) :
( op = = ' < ' ) ? ( lhs . to_string ( ) < rhs . to_string ( ) ) : ( lhs . to_string ( ) > rhs . to_string ( ) ) ;
} else {
boost : : throw_exception ( qi : : expectation_failure < Iterator > (
lhs . it_range . begin ( ) , rhs . it_range . end ( ) , spirit : : info ( " *Cannot compare the types. " ) ) ) ;
}
lhs . reset ( ) ;
lhs . set_b_lite ( invert ? ! value : value ) ;
}
// Compare operators, store the result into lhs.
static void equal ( expr & lhs , expr & rhs ) { compare_op ( lhs , rhs , ' = ' , false ) ; }
static void not_equal ( expr & lhs , expr & rhs ) { compare_op ( lhs , rhs , ' = ' , true ) ; }
static void lower ( expr & lhs , expr & rhs ) { compare_op ( lhs , rhs , ' < ' , false ) ; }
static void greater ( expr & lhs , expr & rhs ) { compare_op ( lhs , rhs , ' > ' , false ) ; }
static void leq ( expr & lhs , expr & rhs ) { compare_op ( lhs , rhs , ' > ' , true ) ; }
static void geq ( expr & lhs , expr & rhs ) { compare_op ( lhs , rhs , ' < ' , true ) ; }
static void throw_if_not_numeric ( const expr & param )
{
const char * err_msg = " Not a numeric type. " ;
param . throw_if_not_numeric ( err_msg ) ;
}
enum Function2ParamsType {
FUNCTION_MIN ,
FUNCTION_MAX ,
} ;
// Store the result into param1.
static void function_2params ( expr & param1 , expr & param2 , Function2ParamsType fun )
{
if ( param1 . type ( ) = = TYPE_EMPTY )
// Inside an if / else block to be skipped
return ;
throw_if_not_numeric ( param1 ) ;
throw_if_not_numeric ( param2 ) ;
if ( param1 . type ( ) = = TYPE_DOUBLE | | param2 . type ( ) = = TYPE_DOUBLE ) {
double d = 0. ;
switch ( fun ) {
case FUNCTION_MIN : d = std : : min ( param1 . as_d ( ) , param2 . as_d ( ) ) ; break ;
case FUNCTION_MAX : d = std : : max ( param1 . as_d ( ) , param2 . as_d ( ) ) ; break ;
default : param1 . throw_exception ( " Internal error: invalid function " ) ;
}
param1 . set_d_lite ( d ) ;
} else {
int i = 0 ;
switch ( fun ) {
case FUNCTION_MIN : i = std : : min ( param1 . as_i ( ) , param2 . as_i ( ) ) ; break ;
case FUNCTION_MAX : i = std : : max ( param1 . as_i ( ) , param2 . as_i ( ) ) ; break ;
default : param1 . throw_exception ( " Internal error: invalid function " ) ;
}
param1 . set_i_lite ( i ) ;
}
}
// Store the result into param1.
static void min ( expr & param1 , expr & param2 ) { function_2params ( param1 , param2 , FUNCTION_MIN ) ; }
static void max ( expr & param1 , expr & param2 ) { function_2params ( param1 , param2 , FUNCTION_MAX ) ; }
// Store the result into param1.
static void random ( expr & param1 , expr & param2 , std : : mt19937 & rng )
{
if ( param1 . type ( ) = = TYPE_EMPTY )
// Inside an if / else block to be skipped
return ;
throw_if_not_numeric ( param1 ) ;
throw_if_not_numeric ( param2 ) ;
if ( param1 . type ( ) = = TYPE_DOUBLE | | param2 . type ( ) = = TYPE_DOUBLE )
param1 . set_d_lite ( std : : uniform_real_distribution < > ( param1 . as_d ( ) , param2 . as_d ( ) ) ( rng ) ) ;
else
param1 . set_i_lite ( std : : uniform_int_distribution < > ( param1 . as_i ( ) , param2 . as_i ( ) ) ( rng ) ) ;
}
// Store the result into param1.
// param3 is optional
template < bool leading_zeros >
static void digits ( expr & param1 , expr & param2 , expr & param3 )
{
if ( param1 . type ( ) = = TYPE_EMPTY )
// Inside an if / else block to be skipped
return ;
throw_if_not_numeric ( param1 ) ;
if ( param2 . type ( ) ! = TYPE_INT )
param2 . throw_exception ( " digits: second parameter must be integer " ) ;
bool has_decimals = param3 . type ( ) ! = TYPE_EMPTY ;
if ( has_decimals & & param3 . type ( ) ! = TYPE_INT )
param3 . throw_exception ( " digits: third parameter must be integer " ) ;
char buf [ 256 ] ;
int ndigits = std : : clamp ( param2 . as_i ( ) , 0 , 64 ) ;
if ( has_decimals ) {
// Format as double.
int decimals = std : : clamp ( param3 . as_i ( ) , 0 , 64 ) ;
sprintf ( buf , leading_zeros ? " %0*.*lf " : " %*.*lf " , ndigits , decimals , param1 . as_d ( ) ) ;
} else
// Format as int.
sprintf ( buf , leading_zeros ? " %0*d " : " %*d " , ndigits , param1 . as_i_rounded ( ) ) ;
param1 . set_s ( buf ) ;
}
static void regex_op ( const expr & lhs , IteratorRange & rhs , char op , expr & out )
{
if ( lhs . type ( ) = = TYPE_EMPTY )
// Inside an if / else block to be skipped
return ;
const std : : string * subject = nullptr ;
if ( lhs . type ( ) = = TYPE_STRING ) {
// One type is string, the other could be converted to string.
subject = & lhs . s ( ) ;
} else {
lhs . throw_exception ( " Left hand side of a regex match must be a string. " ) ;
}
try {
std : : string pattern ( + + rhs . begin ( ) , - - rhs . end ( ) ) ;
bool result = SLIC3R_REGEX_NAMESPACE : : regex_match ( * subject , SLIC3R_REGEX_NAMESPACE : : regex ( pattern ) ) ;
if ( op = = ' ! ' )
result = ! result ;
out . set_b ( result ) ;
} catch ( SLIC3R_REGEX_NAMESPACE : : regex_error & ex ) {
// Syntax error in the regular expression
boost : : throw_exception ( qi : : expectation_failure < Iterator > (
rhs . begin ( ) , rhs . end ( ) , spirit : : info ( std : : string ( " *Regular expression compilation failed: " ) + ex . what ( ) ) ) ) ;
}
}
static void regex_matches ( expr & lhs , IteratorRange & rhs ) { return regex_op ( lhs , rhs , ' = ' , lhs ) ; }
static void regex_doesnt_match ( expr & lhs , IteratorRange & rhs ) { return regex_op ( lhs , rhs , ' ! ' , lhs ) ; }
static void one_of_test_init ( expr & out ) {
out . set_b ( false ) ;
}
template < bool RegEx >
static void one_of_test ( const expr & match , const expr & pattern , expr & out ) {
if ( match . type ( ) = = TYPE_EMPTY ) {
// Inside an if / else block to be skipped
out . reset ( ) ;
return ;
}
if ( ! out . b ( ) ) {
if ( match . type ( ) ! = TYPE_STRING )
match . throw_exception ( " one_of(): First parameter (the string to match against) has to be a string value " ) ;
if ( pattern . type ( ) ! = TYPE_STRING )
match . throw_exception ( " one_of(): Pattern has to be a string value " ) ;
if ( RegEx ) {
try {
out . set_b ( SLIC3R_REGEX_NAMESPACE : : regex_match ( match . s ( ) , SLIC3R_REGEX_NAMESPACE : : regex ( pattern . s ( ) ) ) ) ;
} catch ( SLIC3R_REGEX_NAMESPACE : : regex_error & ) {
// Syntax error in the regular expression
pattern . throw_exception ( " Regular expression compilation failed " ) ;
}
} else
out . set_b ( match . s ( ) = = pattern . s ( ) ) ;
}
}
static void one_of_test_regex ( const expr & match , IteratorRange & pattern , expr & out ) {
if ( match . type ( ) = = TYPE_EMPTY ) {
// Inside an if / else block to be skipped
out . reset ( ) ;
return ;
}
if ( ! out . b ( ) ) {
if ( match . type ( ) ! = TYPE_STRING )
match . throw_exception ( " one_of(): First parameter (the string to match against) has to be a string value " ) ;
regex_op ( match , pattern , ' = ' , out ) ;
}
}
static void logical_op ( expr & lhs , expr & rhs , char op )
{
if ( lhs . type ( ) = = TYPE_EMPTY )
// Inside an if / else block to be skipped
return ;
bool value = false ;
if ( lhs . type ( ) = = TYPE_BOOL & & rhs . type ( ) = = TYPE_BOOL ) {
value = ( op = = ' | ' ) ? ( lhs . b ( ) | | rhs . b ( ) ) : ( lhs . b ( ) & & rhs . b ( ) ) ;
} else {
boost : : throw_exception ( qi : : expectation_failure < Iterator > (
lhs . it_range . begin ( ) , rhs . it_range . end ( ) , spirit : : info ( " *Cannot apply logical operation to non-boolean operators. " ) ) ) ;
}
lhs . set_b_lite ( value ) ;
}
static void logical_or ( expr & lhs , expr & rhs ) { logical_op ( lhs , rhs , ' | ' ) ; }
static void logical_and ( expr & lhs , expr & rhs ) { logical_op ( lhs , rhs , ' & ' ) ; }
void throw_exception ( const char * message ) const
{
boost : : throw_exception ( qi : : expectation_failure < Iterator > (
this - > it_range . begin ( ) , this - > it_range . end ( ) , spirit : : info ( std : : string ( " * " ) + message ) ) ) ;
}
void throw_if_not_numeric ( const char * message ) const
{
if ( ! this - > numeric_type ( ) )
this - > throw_exception ( message ) ;
}
private :
// This object will take ownership of the parameter string object "s".
void set_s_take_ownership ( std : : string * s ) { assert ( this - > type ( ) ! = TYPE_STRING ) ; Data tmp ; tmp . s = s ; m_data . set ( tmp ) ; m_type = TYPE_STRING ; }
Type m_type = TYPE_EMPTY ;
union Data {
bool b ;
int i ;
double d ;
std : : string * s ;
// Copy the largest member variable through char*, which will alias with all other union members by default.
void set ( const Data & rhs ) { memcpy ( this , & rhs , sizeof ( rhs ) ) ; }
} m_data ;
} ;
std : : ostream & operator < < ( std : : ostream & os , const expr & expression )
{
typedef expr Expr ;
os < < std : : string ( expression . it_range . begin ( ) , expression . it_range . end ( ) ) < < " - " ;
switch ( expression . type ( ) ) {
case Expr : : TYPE_EMPTY : os < < " empty " ; break ;
case Expr : : TYPE_BOOL : os < < " bool ( " < < expression . b ( ) < < " ) " ; break ;
case Expr : : TYPE_INT : os < < " int ( " < < expression . i ( ) < < " ) " ; break ;
case Expr : : TYPE_DOUBLE : os < < " double ( " < < expression . d ( ) < < " ) " ; break ;
case Expr : : TYPE_STRING : os < < " string ( " < < expression . s ( ) < < " ) " ; break ;
default : os < < " unknown " ;
} ;
return os ;
}
struct MyContext : public ConfigOptionResolver {
// Config provided as a parameter to PlaceholderParser invocation, overriding PlaceholderParser stored config.
const DynamicConfig * external_config = nullptr ;
// Config stored inside PlaceholderParser.
const DynamicConfig * config = nullptr ;
// Config provided as a parameter to PlaceholderParser invocation, evaluated after the two configs above.
const DynamicConfig * config_override = nullptr ;
// Config provided as a parameter to PlaceholderParser invocation, containing variables that will be read out
// and processed by the PlaceholderParser callee.
mutable DynamicConfig * config_outputs = nullptr ;
// Local variables, read / write
mutable DynamicConfig config_local ;
size_t current_extruder_id = 0 ;
// Random number generator and optionally global variables.
PlaceholderParser : : ContextData * context_data = nullptr ;
// If false, the macro_processor will evaluate a full macro.
// If true, the macro processor will evaluate just a boolean condition using the full expressive power of the macro processor.
bool just_boolean_expression = false ;
std : : string error_message ;
// Table to translate symbol tag to a human readable error message.
static std : : map < std : : string , std : : string > tag_to_error_message ;
// Should the parser consider the parsed string to be a macro or a boolean expression?
static bool evaluate_full_macro ( const MyContext * ctx ) { return ! ctx - > just_boolean_expression ; }
// Entering a conditional block.
static void block_enter ( const MyContext * ctx , const bool condition )
{
if ( ctx - > skipping ( ) | | ! condition )
+ + ctx - > m_depth_suppressed ;
}
// Exiting a conditional block.
static void block_exit ( const MyContext * ctx , const bool condition , bool & not_yet_consumed , std : : string & data_in , std : : string & data_out )
{
if ( ctx - > skipping ( ) )
- - ctx - > m_depth_suppressed ;
else if ( condition & & not_yet_consumed ) {
data_out = std : : move ( data_in ) ;
not_yet_consumed = false ;
}
}
static void block_exit_ternary ( const MyContext * ctx , const bool condition , expr & data_in , expr & data_out )
{
if ( ctx - > skipping ( ) )
- - ctx - > m_depth_suppressed ;
else if ( condition )
data_out = std : : move ( data_in ) ;
}
// Inside a block, which is conditionally suppressed?
bool skipping ( ) const { return m_depth_suppressed > 0 ; }
const ConfigOption * optptr ( const t_config_option_key & opt_key ) const override
{
const ConfigOption * opt = nullptr ;
if ( config_override ! = nullptr )
opt = config_override - > option ( opt_key ) ;
if ( opt = = nullptr )
opt = config - > option ( opt_key ) ;
if ( opt = = nullptr & & external_config ! = nullptr )
opt = external_config - > option ( opt_key ) ;
return opt ;
}
const ConfigOption * resolve_symbol ( const std : : string & opt_key ) const { return this - > optptr ( opt_key ) ; }
ConfigOption * resolve_output_symbol ( const std : : string & opt_key ) const {
ConfigOption * out = nullptr ;
if ( this - > config_outputs )
out = this - > config_outputs - > optptr ( opt_key , false ) ;
if ( out = = nullptr & & this - > context_data ! = nullptr & & this - > context_data - > global_config )
out = this - > context_data - > global_config - > optptr ( opt_key ) ;
if ( out = = nullptr )
out = this - > config_local . optptr ( opt_key ) ;
return out ;
}
void store_new_variable ( const std : : string & opt_key , std : : unique_ptr < ConfigOption > & & opt , bool global_variable ) {
assert ( opt ) ;
if ( global_variable ) {
assert ( this - > context_data ! = nullptr & & this - > context_data - > global_config ) ;
this - > context_data - > global_config - > set_key_value ( opt_key , opt . release ( ) ) ;
} else
this - > config_local . set_key_value ( opt_key , opt . release ( ) ) ;
}
static void legacy_variable_expansion ( const MyContext * ctx , IteratorRange & opt_key , std : : string & output )
{
if ( ctx - > skipping ( ) )
return ;
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 ( ) )
ctx - > throw_exception ( " Trying to index a scalar variable " , opt_key ) ;
char * endptr = nullptr ;
idx = strtol ( opt_key_str . c_str ( ) + idx + 1 , & endptr , 10 ) ;
if ( endptr = = nullptr | | * endptr ! = 0 )
ctx - > throw_exception ( " Invalid vector index " , IteratorRange ( opt_key . begin ( ) + idx + 1 , opt_key . end ( ) ) ) ;
}
}
}
if ( opt = = nullptr )
ctx - > throw_exception ( " Variable does not exist " , opt_key ) ;
if ( opt - > is_scalar ( ) ) {
if ( opt - > is_nil ( ) )
ctx - > throw_exception ( " Trying to reference an undefined (nil) optional variable " , opt_key ) ;
output = opt - > serialize ( ) ;
} else {
const ConfigOptionVectorBase * vec = static_cast < const ConfigOptionVectorBase * > ( opt ) ;
if ( vec - > empty ( ) )
ctx - > throw_exception ( " Indexing an empty vector variable " , opt_key ) ;
if ( idx > = vec - > size ( ) )
idx = 0 ;
if ( vec - > is_nil ( idx ) )
ctx - > throw_exception ( " Trying to reference an undefined (nil) element of vector of optional values " , opt_key ) ;
output = vec - > vserialize ( ) [ idx ] ;
}
}
static void legacy_variable_expansion2 (
const MyContext * ctx ,
IteratorRange & opt_key ,
IteratorRange & opt_vector_index ,
std : : string & output )
{
if ( ctx - > skipping ( ) )
return ;
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 = = nullptr )
ctx - > throw_exception ( " Variable does not exist " , opt_key ) ;
}
if ( ! opt - > is_vector ( ) )
ctx - > throw_exception ( " Trying to index a scalar variable " , opt_key ) ;
const ConfigOptionVectorBase * vec = static_cast < const ConfigOptionVectorBase * > ( opt ) ;
if ( vec - > empty ( ) )
ctx - > throw_exception ( " Indexing an empty vector variable " , opt_key ) ;
const ConfigOption * opt_index = ctx - > resolve_symbol ( std : : string ( opt_vector_index . begin ( ) , opt_vector_index . end ( ) ) ) ;
if ( opt_index = = nullptr )
ctx - > throw_exception ( " Variable does not exist " , opt_key ) ;
if ( opt_index - > type ( ) ! = coInt )
ctx - > throw_exception ( " Indexing variable has to be integer " , opt_key ) ;
int idx = opt_index - > getInt ( ) ;
if ( idx < 0 )
ctx - > throw_exception ( " Negative vector index " , opt_key ) ;
if ( idx > = ( int ) vec - > size ( ) )
idx = 0 ;
if ( vec - > is_nil ( idx ) )
ctx - > throw_exception ( " Trying to reference an undefined (nil) element of vector of optional values " , opt_key ) ;
output = vec - > vserialize ( ) [ idx ] ;
}
static void resolve_variable (
const MyContext * ctx ,
IteratorRange & opt_key ,
OptWithPos & output )
{
if ( ! ctx - > skipping ( ) ) {
const std : : string key { opt_key . begin ( ) , opt_key . end ( ) } ;
const ConfigOption * opt = ctx - > resolve_symbol ( key ) ;
if ( opt = = nullptr ) {
opt = ctx - > resolve_output_symbol ( key ) ;
if ( opt = = nullptr )
ctx - > throw_exception ( " Not a variable name " , opt_key ) ;
output . writable = true ;
}
output . opt = opt ;
}
output . it_range = opt_key ;
}
static void store_variable_index (
const MyContext * ctx ,
OptWithPos & opt ,
int index ,
Iterator it_end ,
OptWithPos & output )
{
if ( ! ctx - > skipping ( ) ) {
if ( ! opt . opt - > is_vector ( ) )
ctx - > throw_exception ( " Cannot index a scalar variable " , opt . it_range ) ;
if ( index < 0 )
ctx - > throw_exception ( " Referencing a vector variable with a negative index " , opt . it_range ) ;
output = opt ;
output . index = index ;
} else
output = opt ;
output . it_range . end ( ) = it_end ;
}
// Evaluating a scalar variable into expr,
// all possible ConfigOption types are supported.
static void scalar_variable_to_expr ( const MyContext * ctx , OptWithPos & opt , expr & output )
{
if ( ctx - > skipping ( ) )
return ;
assert ( opt . opt - > is_scalar ( ) ) ;
if ( opt . opt - > is_nil ( ) )
ctx - > throw_exception ( " Trying to reference an undefined (nil) optional variable " , opt . it_range ) ;
switch ( opt . opt - > type ( ) ) {
case coFloat : output . set_d ( opt . opt - > getFloat ( ) ) ; break ;
case coInt : output . set_i ( opt . opt - > getInt ( ) ) ; break ;
case coString : output . set_s ( static_cast < const ConfigOptionString * > ( opt . opt ) - > value ) ; break ;
case coPercent : output . set_d ( opt . opt - > getFloat ( ) ) ; break ;
case coEnum :
case coPoint : output . set_s ( opt . opt - > serialize ( ) ) ; break ;
case coBool : output . set_b ( opt . opt - > getBool ( ) ) ; break ;
case coFloatOrPercent :
{
std : : string opt_key ( opt . it_range . begin ( ) , opt . it_range . end ( ) ) ;
if ( boost : : ends_with ( opt_key , " extrusion_width " ) ) {
// Extrusion width supports defaults and a complex graph of dependencies.
output . set_d ( Flow : : extrusion_width ( opt_key , * ctx , static_cast < unsigned int > ( ctx - > current_extruder_id ) ) ) ;
} else if ( ! static_cast < const ConfigOptionFloatOrPercent * > ( opt . opt ) - > percent ) {
// Not a percent, just return the value.
output . set_d ( opt . opt - > getFloat ( ) ) ;
} else {
// Resolve dependencies using the "ratio_over" link to a parent value.
const ConfigOptionDef * opt_def = print_config_def . get ( opt_key ) ;
assert ( opt_def ! = nullptr ) ;
double v = opt . opt - > getFloat ( ) * 0.01 ; // percent to ratio
for ( ; ; ) {
const ConfigOption * opt_parent = opt_def - > ratio_over . empty ( ) ? nullptr : ctx - > resolve_symbol ( opt_def - > ratio_over ) ;
if ( opt_parent = = nullptr )
ctx - > throw_exception ( " FloatOrPercent variable failed to resolve the \" ratio_over \" dependencies " , opt . it_range ) ;
if ( boost : : ends_with ( opt_def - > ratio_over , " extrusion_width " ) ) {
// Extrusion width supports defaults and a complex graph of dependencies.
assert ( opt_parent - > type ( ) = = coFloatOrPercent ) ;
v * = Flow : : extrusion_width ( opt_def - > ratio_over , static_cast < const ConfigOptionFloatOrPercent * > ( opt_parent ) , * ctx , static_cast < unsigned int > ( ctx - > current_extruder_id ) ) ;
break ;
}
if ( opt_parent - > type ( ) = = coFloat | | opt_parent - > type ( ) = = coFloatOrPercent ) {
v * = opt_parent - > getFloat ( ) ;
if ( opt_parent - > type ( ) = = coFloat | | ! static_cast < const ConfigOptionFloatOrPercent * > ( opt_parent ) - > percent )
break ;
v * = 0.01 ; // percent to ratio
}
// Continue one level up in the "ratio_over" hierarchy.
opt_def = print_config_def . get ( opt_def - > ratio_over ) ;
assert ( opt_def ! = nullptr ) ;
}
output . set_d ( v ) ;
}
break ;
}
default :
ctx - > throw_exception ( " Unsupported scalar variable type " , opt . it_range ) ;
}
}
// Evaluating one element of a vector variable.
// all possible ConfigOption types are supported.
static void vector_element_to_expr ( const MyContext * ctx , OptWithPos & opt , expr & output )
{
if ( ctx - > skipping ( ) )
return ;
assert ( opt . opt - > is_vector ( ) ) ;
if ( ! opt . has_index ( ) )
ctx - > throw_exception ( " Referencing a vector variable when scalar is expected " , opt . it_range ) ;
const ConfigOptionVectorBase * vec = static_cast < const ConfigOptionVectorBase * > ( opt . opt ) ;
if ( vec - > empty ( ) )
ctx - > throw_exception ( " Indexing an empty vector variable " , opt . it_range ) ;
size_t idx = ( opt . index < 0 ) ? 0 : ( opt . index > = int ( vec - > size ( ) ) ) ? 0 : size_t ( opt . index ) ;
if ( vec - > is_nil ( idx ) )
ctx - > throw_exception ( " Trying to reference an undefined (nil) element of vector of optional values " , opt . it_range ) ;
switch ( opt . opt - > type ( ) ) {
case coFloats : output . set_d ( static_cast < const ConfigOptionFloats * > ( opt . opt ) - > values [ idx ] ) ; break ;
case coInts : output . set_i ( static_cast < const ConfigOptionInts * > ( opt . opt ) - > values [ idx ] ) ; break ;
case coStrings : output . set_s ( static_cast < const ConfigOptionStrings * > ( opt . opt ) - > values [ idx ] ) ; break ;
case coPercents : output . set_d ( static_cast < const ConfigOptionPercents * > ( opt . opt ) - > values [ idx ] ) ; break ;
case coPoints : output . set_s ( to_string ( static_cast < const ConfigOptionPoints * > ( opt . opt ) - > values [ idx ] ) ) ; break ;
case coBools : output . set_b ( static_cast < const ConfigOptionBools * > ( opt . opt ) - > values [ idx ] ! = 0 ) ; break ;
//case coEnums: output.set_s(opt.opt->vserialize()[idx]); break;
default :
ctx - > throw_exception ( " Unsupported vector variable type " , opt . it_range ) ;
}
}
static void check_writable ( const MyContext * ctx , OptWithPos & opt ) {
if ( ! opt . writable )
ctx - > throw_exception ( " Cannot modify a read-only variable " , opt . it_range ) ;
}
static void check_numeric ( const expr & param ) {
if ( ! param . numeric_type ( ) )
param . throw_exception ( " Right side is not a numeric expression " ) ;
} ;
static size_t evaluate_count ( const expr & expr_count ) {
if ( expr_count . type ( ) ! = expr : : TYPE_INT )
expr_count . throw_exception ( " Expected number of elements to fill a vector with. " ) ;
int count = expr_count . i ( ) ;
if ( count < 0 )
expr_count . throw_exception ( " Negative number of elements specified. " ) ;
return size_t ( count ) ;
} ;
static void scalar_variable_assign_scalar ( const MyContext * ctx , OptWithPos & lhs , const expr & rhs )
{
2023-06-27 11:07:34 +08:00
assert ( ! ctx - > skipping ( ) ) ;
2023-06-10 10:14:12 +08:00
assert ( lhs . opt - > is_scalar ( ) ) ;
check_writable ( ctx , lhs ) ;
ConfigOption * wropt = const_cast < ConfigOption * > ( lhs . opt ) ;
switch ( wropt - > type ( ) ) {
case coFloat :
check_numeric ( rhs ) ;
static_cast < ConfigOptionFloat * > ( wropt ) - > value = rhs . as_d ( ) ;
break ;
case coInt :
check_numeric ( rhs ) ;
static_cast < ConfigOptionInt * > ( wropt ) - > value = rhs . as_i ( ) ;
break ;
case coString :
static_cast < ConfigOptionString * > ( wropt ) - > value = rhs . to_string ( ) ;
break ;
case coPercent :
check_numeric ( rhs ) ;
static_cast < ConfigOptionPercent * > ( wropt ) - > value = rhs . as_d ( ) ;
break ;
case coBool :
if ( rhs . type ( ) ! = expr : : TYPE_BOOL )
ctx - > throw_exception ( " Right side is not a boolean expression " , rhs . it_range ) ;
static_cast < ConfigOptionBool * > ( wropt ) - > value = rhs . b ( ) ;
break ;
default :
ctx - > throw_exception ( " Unsupported output scalar variable type " , lhs . it_range ) ;
}
}
static void vector_variable_element_assign_scalar ( const MyContext * ctx , OptWithPos & lhs , const expr & rhs )
{
2023-06-27 11:07:34 +08:00
assert ( ! ctx - > skipping ( ) ) ;
2023-06-10 10:14:12 +08:00
assert ( lhs . opt - > is_vector ( ) ) ;
check_writable ( ctx , lhs ) ;
if ( ! lhs . has_index ( ) )
ctx - > throw_exception ( " Referencing an output vector variable when scalar is expected " , lhs . it_range ) ;
ConfigOptionVectorBase * vec = const_cast < ConfigOptionVectorBase * > ( static_cast < const ConfigOptionVectorBase * > ( lhs . opt ) ) ;
if ( vec - > empty ( ) )
ctx - > throw_exception ( " Indexing an empty vector variable " , lhs . it_range ) ;
if ( lhs . index > = int ( vec - > size ( ) ) )
ctx - > throw_exception ( " Index out of range " , lhs . it_range ) ;
switch ( lhs . opt - > type ( ) ) {
case coFloats :
check_numeric ( rhs ) ;
static_cast < ConfigOptionFloats * > ( vec ) - > values [ lhs . index ] = rhs . as_d ( ) ;
break ;
case coInts :
check_numeric ( rhs ) ;
static_cast < ConfigOptionInts * > ( vec ) - > values [ lhs . index ] = rhs . as_i ( ) ;
break ;
case coStrings :
static_cast < ConfigOptionStrings * > ( vec ) - > values [ lhs . index ] = rhs . to_string ( ) ;
break ;
case coPercents :
check_numeric ( rhs ) ;
static_cast < ConfigOptionPercents * > ( vec ) - > values [ lhs . index ] = rhs . as_d ( ) ;
break ;
case coBools :
if ( rhs . type ( ) ! = expr : : TYPE_BOOL )
ctx - > throw_exception ( " Right side is not a boolean expression " , rhs . it_range ) ;
static_cast < ConfigOptionBools * > ( vec ) - > values [ lhs . index ] = rhs . b ( ) ;
break ;
default :
ctx - > throw_exception ( " Unsupported output vector variable type " , lhs . it_range ) ;
}
}
static void vector_variable_assign_expr_with_count ( const MyContext * ctx , OptWithPos & lhs , const expr & rhs_count , const expr & rhs_value )
{
2023-06-27 11:07:34 +08:00
assert ( ! ctx - > skipping ( ) ) ;
2023-06-10 10:14:12 +08:00
size_t count = evaluate_count ( rhs_count ) ;
auto * opt = const_cast < ConfigOption * > ( lhs . opt ) ;
switch ( lhs . opt - > type ( ) ) {
case coFloats :
check_numeric ( rhs_value ) ;
static_cast < ConfigOptionFloats * > ( opt ) - > values . assign ( count , rhs_value . as_d ( ) ) ;
break ;
case coInts :
check_numeric ( rhs_value ) ;
static_cast < ConfigOptionInts * > ( opt ) - > values . assign ( count , rhs_value . as_i ( ) ) ;
break ;
case coStrings :
static_cast < ConfigOptionStrings * > ( opt ) - > values . assign ( count , rhs_value . to_string ( ) ) ;
break ;
case coBools :
if ( rhs_value . type ( ) ! = expr : : TYPE_BOOL )
rhs_value . throw_exception ( " Right side is not a boolean expression " ) ;
static_cast < ConfigOptionBools * > ( opt ) - > values . assign ( count , rhs_value . b ( ) ) ;
break ;
default : assert ( false ) ;
}
}
static void variable_value ( const MyContext * ctx , OptWithPos & opt , expr & output )
{
if ( ! ctx - > skipping ( ) ) {
if ( opt . opt - > is_vector ( ) )
vector_element_to_expr ( ctx , opt , output ) ;
else
scalar_variable_to_expr ( ctx , opt , output ) ;
}
output . it_range = opt . it_range ;
}
// Return a boolean value, true if the scalar variable referenced by "opt" is nullable and it has a nil value.
// Return a boolean value, true if an element of a vector variable referenced by "opt[index]" is nullable and it has a nil value.
static void is_nil_test ( const MyContext * ctx , OptWithPos & opt , expr & output )
{
if ( ctx - > skipping ( ) ) {
} else if ( opt . opt - > is_vector ( ) ) {
if ( ! opt . has_index ( ) )
ctx - > throw_exception ( " Referencing a vector variable when scalar is expected " , opt . it_range ) ;
const ConfigOptionVectorBase * vec = static_cast < const ConfigOptionVectorBase * > ( opt . opt ) ;
if ( vec - > empty ( ) )
ctx - > throw_exception ( " Indexing an empty vector variable " , opt . it_range ) ;
output . set_b ( static_cast < const ConfigOptionVectorBase * > ( opt . opt ) - > is_nil ( opt . index > = int ( vec - > size ( ) ) ? 0 : size_t ( opt . index ) ) ) ;
} else {
assert ( opt . opt - > is_scalar ( ) ) ;
output . set_b ( opt . opt - > is_nil ( ) ) ;
}
output . it_range = opt . it_range ;
}
// Reference to an existing symbol, or a name of a new symbol.
struct NewOldVariable {
std : : string name ;
IteratorRange it_range ;
ConfigOption * opt { nullptr } ;
} ;
static void new_old_variable (
const MyContext * ctx ,
bool global_variable ,
const IteratorRange & it_range ,
NewOldVariable & out )
{
if ( ! ctx - > skipping ( ) ) {
t_config_option_key key ( std : : string ( it_range . begin ( ) , it_range . end ( ) ) ) ;
if ( const ConfigOption * opt = ctx - > resolve_symbol ( key ) ; opt )
ctx - > throw_exception ( " Symbol is already defined in read-only system dictionary " , it_range ) ;
if ( ctx - > config_outputs & & ctx - > config_outputs - > optptr ( key ) )
ctx - > throw_exception ( " Symbol is already defined as system output variable " , it_range ) ;
bool has_global_dictionary = ctx - > context_data ! = nullptr & & ctx - > context_data - > global_config ;
if ( global_variable ) {
if ( ! has_global_dictionary )
ctx - > throw_exception ( " Global variables are not available in this context " , it_range ) ;
if ( ctx - > config_local . optptr ( key ) )
ctx - > throw_exception ( " Variable name already defined in local scope " , it_range ) ;
out . opt = ctx - > context_data - > global_config - > optptr ( key ) ;
} else {
if ( has_global_dictionary & & ctx - > context_data - > global_config - > optptr ( key ) )
ctx - > throw_exception ( " Variable name already defined in global scope " , it_range ) ;
out . opt = ctx - > config_local . optptr ( key ) ;
}
out . name = std : : move ( key ) ;
}
out . it_range = it_range ;
}
// Decoding a scalar variable symbol "opt", assigning it a value of "param".
static void scalar_variable_assign_scalar_expression ( const MyContext * ctx , OptWithPos & opt , const expr & param )
{
if ( ! ctx - > skipping ( ) ) {
check_writable ( ctx , opt ) ;
if ( opt . opt - > is_vector ( ) )
vector_variable_element_assign_scalar ( ctx , opt , param ) ;
else
scalar_variable_assign_scalar ( ctx , opt , param ) ;
}
}
static void scalar_variable_new_from_scalar_expression (
const MyContext * ctx ,
bool global_variable ,
NewOldVariable & lhs ,
const expr & rhs )
{
if ( ctx - > skipping ( ) ) {
} else if ( lhs . opt ) {
if ( lhs . opt - > is_vector ( ) )
rhs . throw_exception ( " Cannot assign a scalar value to a vector variable. " ) ;
OptWithPos lhs_opt { lhs . opt , lhs . it_range , true } ;
scalar_variable_assign_scalar ( ctx , lhs_opt , rhs ) ;
} else {
std : : unique_ptr < ConfigOption > opt_new ;
switch ( rhs . type ( ) ) {
case expr : : TYPE_BOOL : opt_new = std : : make_unique < ConfigOptionBool > ( rhs . b ( ) ) ; break ;
case expr : : TYPE_INT : opt_new = std : : make_unique < ConfigOptionInt > ( rhs . i ( ) ) ; break ;
case expr : : TYPE_DOUBLE : opt_new = std : : make_unique < ConfigOptionFloat > ( rhs . d ( ) ) ; break ;
case expr : : TYPE_STRING : opt_new = std : : make_unique < ConfigOptionString > ( rhs . s ( ) ) ; break ;
default : assert ( false ) ;
}
const_cast < MyContext * > ( ctx ) - > store_new_variable ( lhs . name , std : : move ( opt_new ) , global_variable ) ;
}
}
static void vector_variable_new_from_array (
const MyContext * ctx ,
bool global_variable ,
NewOldVariable & lhs ,
const expr & rhs_count ,
const expr & rhs_value )
{
if ( ctx - > skipping ( ) ) {
} else if ( lhs . opt ) {
if ( lhs . opt - > is_scalar ( ) )
rhs_value . throw_exception ( " Cannot assign a vector value to a scalar variable. " ) ;
OptWithPos lhs_opt { lhs . opt , lhs . it_range , true } ;
vector_variable_assign_expr_with_count ( ctx , lhs_opt , rhs_count , rhs_value ) ;
} else {
size_t count = evaluate_count ( rhs_count ) ;
std : : unique_ptr < ConfigOption > opt_new ;
switch ( rhs_value . type ( ) ) {
case expr : : TYPE_BOOL : opt_new = std : : make_unique < ConfigOptionBools > ( count , rhs_value . b ( ) ) ; break ;
case expr : : TYPE_INT : opt_new = std : : make_unique < ConfigOptionInts > ( count , rhs_value . i ( ) ) ; break ;
case expr : : TYPE_DOUBLE : opt_new = std : : make_unique < ConfigOptionFloats > ( count , rhs_value . d ( ) ) ; break ;
case expr : : TYPE_STRING : opt_new = std : : make_unique < ConfigOptionStrings > ( count , rhs_value . s ( ) ) ; break ;
default : assert ( false ) ;
}
const_cast < MyContext * > ( ctx ) - > store_new_variable ( lhs . name , std : : move ( opt_new ) , global_variable ) ;
}
}
static void vector_variable_assign_array (
const MyContext * ctx ,
OptWithPos & lhs ,
const expr & rhs_count ,
const expr & rhs_value )
{
if ( ! ctx - > skipping ( ) ) {
check_writable ( ctx , lhs ) ;
if ( lhs . opt - > is_scalar ( ) )
rhs_value . throw_exception ( " Cannot assign a vector value to a scalar variable. " ) ;
vector_variable_assign_expr_with_count ( ctx , lhs , rhs_count , rhs_value ) ;
}
}
template < typename ConfigOptionType , typename RightValueEvaluate >
static void fill_vector_from_initializer_list ( ConfigOption * opt , const std : : vector < expr > & il , RightValueEvaluate rv_eval ) {
auto & out = static_cast < ConfigOptionType * > ( opt ) - > values ;
out . clear ( ) ;
out . reserve ( il . size ( ) ) ;
for ( const expr & i : il )
out . emplace_back ( rv_eval ( i ) ) ;
}
static void vector_variable_assign_initializer_list ( const MyContext * ctx , OptWithPos & lhs , const std : : vector < expr > & il )
{
if ( ctx - > skipping ( ) )
return ;
check_writable ( ctx , lhs ) ;
if ( lhs . opt - > is_scalar ( ) ) {
if ( il . size ( ) = = 1 )
// scalar_var = ( scalar )
scalar_variable_assign_scalar_expression ( ctx , lhs , il . front ( ) ) ;
else
// scalar_var = ()
// or
// scalar_var = ( scalar, scalar, ... )
ctx - > throw_exception ( " Cannot assign a vector value to a scalar variable. " , lhs . it_range ) ;
}
auto check_numeric_vector = [ ] ( const std : : vector < expr > & il ) {
for ( auto & i : il )
if ( ! i . numeric_type ( ) )
i . throw_exception ( " Right side is not a numeric expression " ) ;
} ;
ConfigOption * opt = const_cast < ConfigOption * > ( lhs . opt ) ;
switch ( lhs . opt - > type ( ) ) {
case coFloats :
check_numeric_vector ( il ) ;
fill_vector_from_initializer_list < ConfigOptionFloats > ( opt , il , [ ] ( auto & v ) { return v . as_d ( ) ; } ) ;
break ;
case coInts :
check_numeric_vector ( il ) ;
fill_vector_from_initializer_list < ConfigOptionInts > ( opt , il , [ ] ( auto & v ) { return v . as_i ( ) ; } ) ;
break ;
case coStrings :
fill_vector_from_initializer_list < ConfigOptionStrings > ( opt , il , [ ] ( auto & v ) { return v . to_string ( ) ; } ) ;
break ;
case coBools :
for ( auto & i : il )
if ( i . type ( ) ! = expr : : TYPE_BOOL )
i . throw_exception ( " Right side is not a boolean expression " ) ;
fill_vector_from_initializer_list < ConfigOptionBools > ( opt , il , [ ] ( auto & v ) { return v . b ( ) ; } ) ;
break ;
default : assert ( false ) ;
}
}
static void vector_variable_new_from_initializer_list (
const MyContext * ctx ,
bool global_variable ,
NewOldVariable & lhs ,
const std : : vector < expr > & il )
{
if ( ctx - > skipping ( ) )
return ;
if ( lhs . opt ) {
// Assign to an existing vector variable.
OptWithPos lhs_opt { lhs . opt , lhs . it_range , true } ;
vector_variable_assign_initializer_list ( ctx , lhs_opt , il ) ;
} else {
if ( il . empty ( ) )
ctx - > throw_exception ( " Cannot create vector variable from an empty initializer list, because its type cannot be deduced. " , lhs . it_range ) ;
// Allocate a new vector variable.
// First guesstimate type of the output vector.
size_t num_bool = 0 ;
size_t num_int = 0 ;
size_t num_double = 0 ;
size_t num_string = 0 ;
for ( auto & i : il )
switch ( i . type ( ) ) {
case expr : : TYPE_BOOL : + + num_bool ; break ;
case expr : : TYPE_INT : + + num_int ; break ;
case expr : : TYPE_DOUBLE : + + num_double ; break ;
case expr : : TYPE_STRING : + + num_string ; break ;
default : assert ( false ) ;
}
std : : unique_ptr < ConfigOption > opt_new ;
if ( num_string > 0 )
// Convert everything to strings.
opt_new = std : : make_unique < ConfigOptionStrings > ( ) ;
else if ( num_bool > 0 ) {
if ( num_double + num_int > 0 )
ctx - > throw_exception ( " Right side is not valid: Mixing numeric and boolean types. " , IteratorRange { il . front ( ) . it_range . begin ( ) , il . back ( ) . it_range . end ( ) } ) ;
opt_new = std : : make_unique < ConfigOptionBools > ( ) ;
} else {
// Output is numeric.
if ( num_double = = 0 )
opt_new = std : : make_unique < ConfigOptionInts > ( ) ;
else
opt_new = std : : make_unique < ConfigOptionFloats > ( ) ;
}
OptWithPos lhs_opt { opt_new . get ( ) , lhs . it_range , true } ;
vector_variable_assign_initializer_list ( ctx , lhs_opt , il ) ;
const_cast < MyContext * > ( ctx ) - > store_new_variable ( lhs . name , std : : move ( opt_new ) , global_variable ) ;
}
}
static bool is_vector_variable_reference ( const OptWithPos & var ) {
return ! var . empty ( ) & & ! var . has_index ( ) & & var . opt - > is_vector ( ) ;
}
// Called when checking whether the NewOldVariable could be assigned a vectir right hand side.
static bool could_be_vector_variable_reference ( const NewOldVariable & var ) {
return var . opt = = nullptr | | var . opt - > is_vector ( ) ;
}
static void copy_vector_variable_to_vector_variable ( const MyContext * ctx , OptWithPos & lhs , const OptWithPos & rhs )
{
if ( ctx - > skipping ( ) )
return ;
check_writable ( ctx , lhs ) ;
assert ( lhs . opt - > is_vector ( ) ) ;
if ( rhs . has_index ( ) | | ! rhs . opt - > is_vector ( ) )
ctx - > throw_exception ( " Cannot assign scalar to a vector " , lhs . it_range ) ;
if ( rhs . opt - > is_nil ( ) )
ctx - > throw_exception ( " Some elements of the right hand side vector variable of optional values are undefined (nil) " , rhs . it_range ) ;
if ( lhs . opt - > type ( ) ! = rhs . opt - > type ( ) ) {
// Vector types are not compatible.
switch ( lhs . opt - > type ( ) ) {
case coFloats :
ctx - > throw_exception ( " Left hand side is a float vector, while the right hand side is not. " , lhs . it_range ) ;
case coInts :
ctx - > throw_exception ( " Left hand side is an int vector, while the right hand side is not. " , lhs . it_range ) ;
case coStrings :
ctx - > throw_exception ( " Left hand side is a string vector, while the right hand side is not. " , lhs . it_range ) ;
case coBools :
ctx - > throw_exception ( " Left hand side is a bool vector, while the right hand side is not. " , lhs . it_range ) ;
default :
ctx - > throw_exception ( " Left hand side / right hand side vectors are not compatible. " , lhs . it_range ) ;
}
}
const_cast < ConfigOption * > ( lhs . opt ) - > set ( rhs . opt ) ;
}
static bool vector_variable_new_from_copy (
const MyContext * ctx ,
bool global_variable ,
NewOldVariable & lhs ,
const OptWithPos & rhs )
{
if ( ctx - > skipping ( ) )
// Skipping, continue parsing.
return true ;
if ( lhs . opt ) {
assert ( lhs . opt - > is_vector ( ) ) ;
OptWithPos lhs_opt { lhs . opt , lhs . it_range , true } ;
copy_vector_variable_to_vector_variable ( ctx , lhs_opt , rhs ) ;
} else {
if ( rhs . has_index ( ) | | ! rhs . opt - > is_vector ( ) )
// Stop parsing, let the other rules resolve this case.
return false ;
if ( rhs . opt - > is_nil ( ) )
ctx - > throw_exception ( " Some elements of the right hand side vector variable of optional values are undefined (nil) " , rhs . it_range ) ;
// Clone the vector variable.
std : : unique_ptr < ConfigOption > opt_new ;
if ( one_of ( rhs . opt - > type ( ) , { coFloats , coInts , coStrings , coBools } ) )
opt_new = std : : unique_ptr < ConfigOption > ( rhs . opt - > clone ( ) ) ;
else if ( rhs . opt - > type ( ) = = coPercents )
opt_new = std : : make_unique < ConfigOptionFloats > ( static_cast < const ConfigOptionPercents * > ( rhs . opt ) - > values ) ;
else
ctx - > throw_exception ( " Duplicating this type of vector variable is not supported " , rhs . it_range ) ;
const_cast < MyContext * > ( ctx ) - > store_new_variable ( lhs . name , std : : move ( opt_new ) , global_variable ) ;
}
// Continue parsing.
return true ;
}
static void initializer_list_append ( std : : vector < expr > & list , expr & param )
{
if ( param . type ( ) ! = expr : : TYPE_EMPTY )
// not skipping
list . emplace_back ( std : : move ( param ) ) ;
}
static void is_vector_empty ( const MyContext * ctx , OptWithPos & opt , expr & out )
{
if ( ! ctx - > skipping ( ) ) {
if ( opt . has_index ( ) | | ! opt . opt - > is_vector ( ) )
ctx - > throw_exception ( " parameter of empty() is not a vector variable " , opt . it_range ) ;
out . set_b ( static_cast < const ConfigOptionVectorBase * > ( opt . opt ) - > size ( ) = = 0 ) ;
}
out . it_range = opt . it_range ;
}
static void vector_size ( const MyContext * ctx , OptWithPos & opt , expr & out )
{
if ( ! ctx - > skipping ( ) ) {
if ( opt . has_index ( ) | | ! opt . opt - > is_vector ( ) )
ctx - > throw_exception ( " parameter of size() is not a vector variable " , opt . it_range ) ;
out . set_i ( int ( static_cast < const ConfigOptionVectorBase * > ( opt . opt ) - > size ( ) ) ) ;
}
out . it_range = opt . it_range ;
}
// Verify that the expression returns an integer, which may be used
// to address a vector.
static void evaluate_index ( expr & expr_index , int & output )
{
if ( expr_index . type ( ) ! = expr : : TYPE_EMPTY ) {
if ( expr_index . type ( ) ! = expr : : TYPE_INT )
expr_index . throw_exception ( " Non-integer index is not allowed to address a vector variable. " ) ;
output = expr_index . i ( ) ;
}
}
static void random ( const MyContext * ctx , expr & param1 , expr & param2 )
{
if ( ctx - > skipping ( ) )
return ;
if ( ctx - > context_data = = nullptr )
ctx - > throw_exception ( " Random number generator not available in this context. " ,
IteratorRange ( param1 . it_range . begin ( ) , param2 . it_range . end ( ) ) ) ;
expr : : random ( param1 , param2 , ctx - > context_data - > rng ) ;
}
static void throw_exception ( const std : : string & msg , const IteratorRange & it_range )
{
// An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content
// between the grammer terminal / non-terminal symbol name and a free-form error message.
boost : : throw_exception ( qi : : expectation_failure ( it_range . begin ( ) , it_range . end ( ) , spirit : : info ( std : : string ( " * " ) + msg ) ) ) ;
}
static void process_error_message ( const MyContext * context , const boost : : spirit : : info & info , const Iterator & it_begin , const Iterator & it_end , const Iterator & it_error )
{
std : : string & msg = const_cast < MyContext * > ( context ) - > error_message ;
std : : string first ( it_begin , it_error ) ;
std : : string last ( it_error , it_end ) ;
auto first_pos = first . rfind ( ' \n ' ) ;
auto last_pos = last . find ( ' \n ' ) ;
int line_nr = 1 ;
if ( first_pos = = std : : string : : npos )
first_pos = 0 ;
else {
// Calculate the current line number.
for ( size_t i = 0 ; i < = first_pos ; + + i )
if ( first [ i ] = = ' \n ' )
+ + line_nr ;
+ + first_pos ;
}
auto error_line = std : : string ( first , first_pos ) + std : : string ( last , 0 , last_pos ) ;
// Position of the it_error from the start of its line.
auto error_pos = ( it_error - it_begin ) - first_pos ;
msg + = " Parsing error at line " + std : : to_string ( line_nr ) ;
if ( ! info . tag . empty ( ) & & info . tag . front ( ) = = ' * ' ) {
// The gat contains an explanatory string.
msg + = " : " ;
msg + = info . tag . substr ( 1 ) ;
} else {
auto it = tag_to_error_message . find ( info . tag ) ;
if ( it = = tag_to_error_message . end ( ) ) {
// A generic error report based on the nonterminal or terminal symbol name.
msg + = " . Expecting tag " ;
msg + = info . tag ;
} else {
// Use the human readable error message.
msg + = " . " ;
msg + = it - > second ;
}
}
msg + = ' \n ' ;
// This hack removes all non-UTF8 characters from the source line, so that the upstream wxWidgets conversions
// from UTF8 to UTF16 don't bail out.
msg + = boost : : nowide : : narrow ( boost : : nowide : : widen ( error_line ) ) ;
msg + = ' \n ' ;
for ( size_t i = 0 ; i < error_pos ; + + i )
msg + = ' ' ;
msg + = " ^ \n " ;
}
private :
// For skipping execution of inactive conditional branches.
mutable int m_depth_suppressed { 0 } ;
} ;
struct InterpolateTableContext {
struct Item {
double x ;
IteratorRange it_range_x ;
double y ;
} ;
std : : vector < Item > table ;
static void init ( const expr & x ) {
if ( x . type ( ) ! = expr : : TYPE_EMPTY ) {
if ( ! x . numeric_type ( ) )
x . throw_exception ( " Interpolation value must be a number. " ) ;
}
}
static void add_pair ( const expr & x , const expr & y , InterpolateTableContext & table ) {
if ( x . type ( ) ! = expr : : TYPE_EMPTY ) {
if ( ! x . numeric_type ( ) )
x . throw_exception ( " X value of a table point must be a number. " ) ;
if ( ! y . numeric_type ( ) )
y . throw_exception ( " Y value of a table point must be a number. " ) ;
table . table . push_back ( { x . as_d ( ) , x . it_range , y . as_d ( ) } ) ;
}
}
static void evaluate ( const expr & expr_x , const InterpolateTableContext & table , expr & out ) {
if ( expr_x . type ( ) = = expr : : TYPE_EMPTY )
return ;
// Check whether the table X values are sorted.
double x = expr_x . as_d ( ) ;
2023-12-27 18:02:35 +08:00
assert ( ! std : : isnan ( x ) ) ;
2023-06-10 10:14:12 +08:00
bool evaluated = false ;
for ( size_t i = 1 ; i < table . table . size ( ) ; + + i ) {
double x0 = table . table [ i - 1 ] . x ;
double x1 = table . table [ i ] . x ;
if ( x0 > x1 )
boost : : throw_exception ( qi : : expectation_failure (
table . table [ i - 1 ] . it_range_x . begin ( ) , table . table [ i ] . it_range_x . end ( ) , spirit : : info ( " X coordinates of the table must be increasing " ) ) ) ;
if ( ! evaluated & & x > = x0 & & x < = x1 ) {
double y0 = table . table [ i - 1 ] . y ;
double y1 = table . table [ i ] . y ;
if ( x = = x0 )
out . set_d ( y0 ) ;
else if ( x = = x1 )
out . set_d ( y1 ) ;
else if ( is_approx ( x0 , x1 ) )
out . set_d ( 0.5 * ( y0 + y1 ) ) ;
else
out . set_d ( Slic3r : : lerp ( y0 , y1 , ( x - x0 ) / ( x1 - x0 ) ) ) ;
evaluated = true ;
}
}
if ( ! evaluated ) {
// Clamp x into the table range with EPSILON.
if ( double x0 = table . table . front ( ) . x ; x > x0 - EPSILON & & x < x0 )
out . set_d ( table . table . front ( ) . y ) ;
else if ( double x1 = table . table . back ( ) . x ; x > x1 & & x < x1 + EPSILON )
out . set_d ( table . table . back ( ) . y ) ;
else
// The value is really outside the table range.
expr_x . throw_exception ( " Interpolation value is outside the table range " ) ;
}
}
} ;
std : : ostream & operator < < ( std : : ostream & os , const InterpolateTableContext & table_context )
{
for ( const auto & item : table_context . table )
os < < " ( " < < item . x < < " , " < < item . y < < " ) " ;
return os ;
}
// Table to translate symbol tag to a human readable error message.
std : : map < std : : string , std : : string > MyContext : : tag_to_error_message = {
{ " eoi " , " Unknown syntax error " } ,
{ " start " , " Unknown syntax error " } ,
{ " text " , " Invalid text. " } ,
{ " text_block " , " Invalid text block. " } ,
{ " macro " , " Invalid macro. " } ,
{ " repeat " , " Unknown syntax error " } ,
{ " if_else_output " , " Not an {if}{else}{endif} macro. " } ,
{ " switch_output " , " Not a {switch} macro. " } ,
{ " legacy_variable_expansion " , " Expecting a legacy variable expansion format " } ,
{ " identifier " , " Expecting an identifier. " } ,
{ " conditional_expression " , " Expecting a conditional expression. " } ,
{ " logical_or_expression " , " Expecting a boolean expression. " } ,
{ " logical_and_expression " , " Expecting a boolean expression. " } ,
{ " equality_expression " , " Expecting an expression. " } ,
{ " bool_expr_eval " , " Expecting a boolean expression. " } ,
{ " relational_expression " , " Expecting an expression. " } ,
{ " additive_expression " , " Expecting an expression. " } ,
{ " multiplicative_expression " , " Expecting an expression. " } ,
{ " unary_expression " , " Expecting an expression. " } ,
{ " optional_parameter " , " Expecting a closing brace or an optional parameter. " } ,
{ " one_of_list " , " Expecting a list of string patterns (simple text or rexep) " } ,
{ " variable_reference " , " Expecting a variable reference. " } ,
{ " variable " , " Expecting a variable name. " } ,
{ " regular_expression " , " Expecting a regular expression. " }
} ;
// For debugging the boost::spirit parsers. Print out the string enclosed in it_range.
std : : ostream & operator < < ( std : : ostream & os , const IteratorRange & it_range )
{
os < < std : : string ( it_range . begin ( ) , it_range . end ( ) ) ;
return os ;
}
// Disable parsing int numbers (without decimals) and Inf/NaN symbols by the double parser.
struct strict_real_policies_without_nan_inf : public qi : : strict_real_policies < double >
{
template < typename It , typename Attr > static bool parse_nan ( It & , It const & , Attr & ) { return false ; }
template < typename It , typename Attr > static bool parse_inf ( It & , It const & , Attr & ) { return false ; }
} ;
// This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character.
// If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown.
struct utf8_char_parser : qi : : primitive_parser < utf8_char_parser >
{
// Define the attribute type exposed by this parser component
template < typename Context , typename Iterator >
struct attribute
{
typedef wchar_t type ;
} ;
// This function is called during the actual parsing process to skip whitespaces.
// Also it throws if it encounters valid or invalid UTF-8 sequence.
template < typename Iterator , typename Context , typename Skipper , typename Attribute >
bool parse ( Iterator & first , Iterator const & last , Context & context , Skipper const & skipper , Attribute & attr ) const
{
// The skipper shall always be empty, any white space will be accepted.
// skip_over(first, last, skipper);
if ( first = = last )
return false ;
// Iterator over the UTF-8 sequence.
auto it = first ;
// Read the first byte of the UTF-8 sequence.
unsigned char c = static_cast < boost : : uint8_t > ( * it + + ) ;
unsigned int cnt = 0 ;
// UTF-8 sequence must not start with a continuation character:
if ( ( c & 0xC0 ) = = 0x80 )
goto err ;
// Skip high surrogate first if there is one.
// If the most significant bit with a zero in it is in position
// 8-N then there are N bytes in this UTF-8 sequence:
{
unsigned char mask = 0x80u ;
unsigned int result = 0 ;
while ( c & mask ) {
+ + result ;
mask > > = 1 ;
}
cnt = ( result = = 0 ) ? 1 : ( ( result > 4 ) ? 4 : result ) ;
}
// Since we haven't read in a value, we need to validate the code points:
for ( - - cnt ; cnt > 0 ; - - cnt ) {
if ( it = = last )
goto err ;
c = static_cast < boost : : uint8_t > ( * it + + ) ;
// We must have a continuation byte:
if ( cnt > 1 & & ( c & 0xC0 ) ! = 0x80 )
goto err ;
}
first = it ;
return true ;
err :
MyContext : : throw_exception ( " Invalid utf8 sequence " , IteratorRange ( first , last ) ) ;
return false ;
}
// This function is called during error handling to create a human readable string for the error context.
template < typename Context >
spirit : : info what ( Context & ) const
{
return spirit : : info ( " unicode_char " ) ;
}
} ;
// This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character.
// If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown.
struct ascii_char_skipper_parser : public utf8_char_parser
{
// This function is called during the actual parsing process
template < typename Iterator , typename Context , typename Skipper , typename Attribute >
bool parse ( Iterator & first , Iterator const & last , Context & context , Skipper const & skipper , Attribute & attr ) const
{
Iterator it = first ;
// Let the UTF-8 parser throw if it encounters an invalid UTF-8 sequence.
if ( ! utf8_char_parser : : parse ( it , last , context , skipper , attr ) )
return false ;
char c = * first ;
if ( it - first > 1 | | c < 0 )
MyContext : : throw_exception ( " Non-ASCII7 characters are only allowed inside text blocks and string literals, not inside code blocks. " , IteratorRange ( first , it ) ) ;
if ( c = = ' \r ' | | c = = ' \n ' | | c = = ' \t ' | | c = = ' ' ) {
// Skip the whitespaces
+ + first ;
return true ;
} else
// Stop skipping, let this 7bit ASCII character be processed.
return false ;
}
// This function is called during error handling to create a human readable string for the error context.
template < typename Context >
spirit : : info what ( Context & ) const
{
return spirit : : info ( " ASCII7_char " ) ;
}
} ;
struct FactorActions {
static void set_start_pos ( Iterator & start_pos , expr & out )
{ out . it_range = IteratorRange ( start_pos , start_pos ) ; }
static void int_ ( const MyContext * ctx , int & value , Iterator & end_pos , expr & out ) {
if ( ctx - > skipping ( ) ) {
out . reset ( ) ;
out . it_range . end ( ) = end_pos ;
} else
out = expr ( value , out . it_range . begin ( ) , end_pos ) ;
}
static void double_ ( const MyContext * ctx , double & value , Iterator & end_pos , expr & out ) {
if ( ctx - > skipping ( ) ) {
out . reset ( ) ;
out . it_range . end ( ) = end_pos ;
} else
out = expr ( value , out . it_range . begin ( ) , end_pos ) ;
}
static void bool_ ( const MyContext * ctx , bool & value , Iterator & end_pos , expr & out ) {
if ( ctx - > skipping ( ) ) {
out . reset ( ) ;
out . it_range . end ( ) = end_pos ;
} else
out = expr ( value , out . it_range . begin ( ) , end_pos ) ;
}
static void string_ ( const MyContext * ctx , IteratorRange & it_range , expr & out ) {
if ( ctx - > skipping ( ) ) {
out . reset ( ) ;
out . it_range = it_range ;
} else {
// Unescape the string, UTF-8 safe.
std : : string s ;
auto begin = std : : next ( it_range . begin ( ) ) ;
auto end = std : : prev ( it_range . end ( ) ) ;
assert ( begin < = end ) ;
{
// 1) Get the size of the string after unescaping.
size_t len = 0 ;
for ( auto it = begin ; it ! = end ; ) {
if ( * it = = ' \\ ' ) {
if ( + + it = = end | |
( * it ! = ' r ' & & * it ! = ' n ' & & * it ! = ' " ' & & * it ! = ' \\ ' ) )
ctx - > throw_exception ( " Invalid escape sequence " , { std : : prev ( it ) , std : : next ( it ) } ) ;
+ + len ;
+ + it ;
} else {
size_t n = get_utf8_sequence_length ( & * it , end - it ) ;
len + = n ;
it + = n ;
}
}
// and reserve the string.
s . reserve ( len ) ;
}
// 2) Copy & unescape the string.
for ( auto it = begin ; it ! = end ; ) {
if ( * it = = ' \\ ' ) {
char c = * ( + + it ) ;
if ( c = = ' r ' )
c = ' \r ' ;
else if ( c = = ' n ' )
c = ' \n ' ;
s + = c ;
+ + it ;
} else {
size_t n = get_utf8_sequence_length ( & * it , end - it ) ;
s . append ( & * it , n ) ;
it + = n ;
}
}
out = expr ( std : : move ( s ) , it_range . begin ( ) , it_range . end ( ) ) ;
}
}
static void expr_ ( expr & value , Iterator & end_pos , expr & out )
{ auto begin_pos = out . it_range . begin ( ) ; out = expr ( std : : move ( value ) , begin_pos , end_pos ) ; }
static void minus_ ( expr & value , expr & out )
{ out = value . unary_minus ( out . it_range . begin ( ) ) ; }
static void not_ ( expr & value , expr & out )
{ out = value . unary_not ( out . it_range . begin ( ) ) ; }
static void to_int ( expr & value , expr & out )
{ out = value . unary_integer ( out . it_range . begin ( ) ) ; }
static void round ( expr & value , expr & out )
{ out = value . round ( out . it_range . begin ( ) ) ; }
// For indicating "no optional parameter".
static void noexpr ( expr & out ) { out . reset ( ) ; }
} ;
using skipper = ascii_char_skipper_parser ;
///////////////////////////////////////////////////////////////////////////
// Our macro_processor grammar
///////////////////////////////////////////////////////////////////////////
// Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html
struct macro_processor : qi : : grammar < Iterator , std : : string ( const MyContext * ) , qi : : locals < bool > , skipper >
{
macro_processor ( ) : macro_processor : : base_type ( start )
{
using namespace qi : : labels ;
qi : : alpha_type alpha ;
qi : : alnum_type alnum ;
qi : : eps_type eps ;
qi : : raw_type raw ;
qi : : lit_type lit ;
qi : : lexeme_type lexeme ;
qi : : no_skip_type no_skip ;
qi : : real_parser < double , strict_real_policies_without_nan_inf > strict_double ;
spirit_encoding : : char_type char_ ;
utf8_char_parser utf8char ;
spirit : : bool_type bool_ ;
spirit : : int_type int_ ;
spirit : : double_type double_ ;
spirit_encoding : : string_type string ;
spirit : : eoi_type eoi ;
spirit : : repository : : qi : : iter_pos_type iter_pos ;
auto kw = spirit : : repository : : qi : : distinct ( qi : : copy ( alnum | ' _ ' ) ) ;
qi : : _val_type _val ;
qi : : _1_type _1 ;
qi : : _2_type _2 ;
qi : : _3_type _3 ;
qi : : _4_type _4 ;
qi : : _a_type _a ;
qi : : _b_type _b ;
qi : : _r1_type _r1 ;
qi : : _r2_type _r2 ;
// 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.
// Also the start symbol switches between the "full macro syntax" and a "boolean expression only",
// depending on the context->just_boolean_expression flag. This way a single static expression parser
// could serve both purposes.
start =
( ( eps ( px : : bind ( & MyContext : : evaluate_full_macro , _r1 ) ) > text_block ( _r1 ) [ _val = _1 ] )
| conditional_expression ( _r1 ) [ px : : bind ( & expr : : evaluate_boolean_to_string , _1 , _val ) ]
) > eoi ;
start . name ( " start " ) ;
qi : : on_error < qi : : fail > ( start , px : : bind ( & MyContext : : process_error_message , _r1 , _4 , _1 , _2 , _3 ) ) ;
text_block = * (
text [ _val + = _1 ]
// Allow back tracking after '{' in case of a text_block embedded inside a condition.
// In that case the inner-most {else} wins and the {if}/{elsif}/{else} shall be paired.
// {elsif}/{else} without an {if} will be allowed to back track from the embedded text_block.
| ( lit ( ' { ' ) > > ( macros ( _r1 ) [ _val + = _1 ] > ' } ' ) | ' } ' )
| ( lit ( ' [ ' ) > legacy_variable_expansion ( _r1 ) [ _val + = _1 ] > ' ] ' )
) ;
text_block . name ( " text_block " ) ;
// 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 = no_skip [ raw [ + ( utf8char - char_ ( ' [ ' ) - char_ ( ' { ' ) ) ] ] ;
text . name ( " text " ) ;
// New style of macro expansion.
// The macro expansion may contain numeric or string expressions, ifs and cases.
macros =
+ ( block ( _r1 ) [ _val + = _1 ] | ( statement ( _r1 ) > ( + lit ( ' ; ' ) | & lit ( ' } ' ) ) ) [ _val + = _1 ] | + lit ( ' ; ' ) ) ;
macros . name ( " macro " ) ;
// if_macros and else_macros only differ by the look-ahead ending condition, which is to not have to repeat the last semicolon
// at the end of the block.
if_macros = kw [ " then " ] > * ( block ( _r1 ) [ _val + = _1 ] | ( statement ( _r1 ) > ( + lit ( ' ; ' ) | & ( kw [ " elsif " ] | kw [ " else " ] | kw [ " endif " ] ) ) ) [ _val + = _1 ] | + lit ( ' ; ' ) ) ;
if_macros . name ( " if_macros " ) ;
else_macros = * ( block ( _r1 ) [ _val + = _1 ] | ( statement ( _r1 ) > ( + lit ( ' ; ' ) | & kw [ " endif " ] ) ) [ _val + = _1 ] | + lit ( ' ; ' ) ) ;
else_macros . name ( " else_macros " ) ;
// Blocks do not require a separating semicolon.
block =
( kw [ " if " ] > if_else_output ( _r1 ) [ _val = _1 ] )
// (kw["switch"] ...
;
block . name ( " block " ) ;
// Statements require a separating semicolon.
statement =
( assignment_statement ( _r1 ) [ _val = _1 ] )
| ( new_variable_statement ( _r1 ) [ _val = _1 ] )
| ( conditional_expression ( _r1 ) [ px : : bind ( & expr : : to_string2 , _1 , _val ) ] )
;
// An if expression enclosed in {} (the outmost {} are already parsed by the caller).
// Also }{ could be replaced with ; to simplify writing of pure code.
if_else_output =
eps [ _a = true ] >
( bool_expr_eval ( _r1 ) [ px : : bind ( & MyContext : : block_enter , _r1 , _1 ) ] > ( if_text_block ( _r1 ) | if_macros ( _r1 ) ) )
[ px : : bind ( & MyContext : : block_exit , _r1 , _1 , _a , _2 , _val ) ] >
* ( ( kw [ " elsif " ] > bool_expr_eval ( _r1 ) [ px : : bind ( & MyContext : : block_enter , _r1 , _1 & & _a ) ] > ( if_text_block ( _r1 ) | if_macros ( _r1 ) ) )
[ px : : bind ( & MyContext : : block_exit , _r1 , _1 , _a , _2 , _val ) ] ) >
- ( kw [ " else " ] > eps [ px : : bind ( & MyContext : : block_enter , _r1 , _a ) ] > ( if_text_block ( _r1 ) | else_macros ( _r1 ) ) )
[ px : : bind ( & MyContext : : block_exit , _r1 , _a , _a , _1 , _val ) ] >
kw [ " endif " ] ;
if_else_output . name ( " if_else_output " ) ;
if_text_block = ( lit ( ' } ' ) > text_block ( _r1 ) > ' { ' ) ;
if_text_block . name ( " if_text_block " ) ;
// A switch expression enclosed in {} (the outmost {} are already parsed by the caller).
/*
switch_output =
eps [ _b = true ] >
omit [ expr ( _r1 ) [ _a = _1 ] ] > ' } ' > text_block ( _r1 ) [ px : : bind ( & expr : : set_if_equal , _a , _b , _1 , _val ) ] > ' { ' >
* ( " elsif " > omit [ bool_expr_eval ( _r1 ) [ _a = _1 ] ] > ' } ' > text_block ( _r1 ) [ px : : bind ( & expr : : set_if , _a , _b , _1 , _val ) ] ) > >
- ( " else " > ' } ' > > text_block ( _r1 ) [ px : : bind ( & expr : : set_if , _b , _b , _1 , _val ) ] ) >
" endif " ;
*/
// Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index].
legacy_variable_expansion =
( identifier > > & lit ( ' ] ' ) )
[ px : : bind ( & MyContext : : legacy_variable_expansion , _r1 , _1 , _val ) ]
| ( identifier > lit ( ' [ ' ) > identifier > ' ] ' )
[ px : : bind ( & MyContext : : legacy_variable_expansion2 , _r1 , _1 , _2 , _val ) ]
;
legacy_variable_expansion . name ( " legacy_variable_expansion " ) ;
identifier =
! kw [ keywords ] > >
raw [ lexeme [ ( alpha | ' _ ' ) > > * ( alnum | ' _ ' ) ] ] ;
identifier . name ( " identifier " ) ;
conditional_expression =
logical_or_expression ( _r1 ) [ _val = _1 ]
> > - ( ' ? ' > eps [ px : : bind ( & expr : : evaluate_boolean , _val , _a ) ] >
eps [ px : : bind ( & MyContext : : block_enter , _r1 , _a ) ] > conditional_expression ( _r1 ) [ px : : bind ( & MyContext : : block_exit_ternary , _r1 , _a , _1 , _val ) ]
> ' : ' >
eps [ px : : bind ( & MyContext : : block_enter , _r1 , ! _a ) ] > conditional_expression ( _r1 ) [ px : : bind ( & MyContext : : block_exit_ternary , _r1 , ! _a , _1 , _val ) ] ) ;
conditional_expression . name ( " conditional_expression " ) ;
logical_or_expression =
logical_and_expression ( _r1 ) [ _val = _1 ]
> > * ( ( ( kw [ " or " ] | " || " ) > logical_and_expression ( _r1 ) ) [ px : : bind ( & expr : : logical_or , _val , _1 ) ] ) ;
logical_or_expression . name ( " logical_or_expression " ) ;
logical_and_expression =
equality_expression ( _r1 ) [ _val = _1 ]
> > * ( ( ( kw [ " and " ] | " && " ) > equality_expression ( _r1 ) ) [ px : : bind ( & expr : : logical_and , _val , _1 ) ] ) ;
logical_and_expression . name ( " logical_and_expression " ) ;
equality_expression =
relational_expression ( _r1 ) [ _val = _1 ]
> > * ( ( " == " > relational_expression ( _r1 ) ) [ px : : bind ( & expr : : equal , _val , _1 ) ]
| ( " != " > relational_expression ( _r1 ) ) [ px : : bind ( & expr : : not_equal , _val , _1 ) ]
| ( " <> " > relational_expression ( _r1 ) ) [ px : : bind ( & expr : : not_equal , _val , _1 ) ]
| ( " =~ " > regular_expression ) [ px : : bind ( & expr : : regex_matches , _val , _1 ) ]
| ( " !~ " > regular_expression ) [ px : : bind ( & expr : : regex_doesnt_match , _val , _1 ) ]
) ;
equality_expression . name ( " bool expression " ) ;
// Evaluate a boolean expression stored as expr into a boolean value.
// Throw if the equality_expression does not produce a expr of boolean type.
bool_expr_eval = conditional_expression ( _r1 ) [ px : : bind ( & expr : : evaluate_boolean , _1 , _val ) ] ;
bool_expr_eval . name ( " bool_expr_eval " ) ;
relational_expression =
additive_expression ( _r1 ) [ _val = _1 ]
> > * ( ( " <= " > additive_expression ( _r1 ) ) [ px : : bind ( & expr : : leq , _val , _1 ) ]
| ( " >= " > additive_expression ( _r1 ) ) [ px : : bind ( & expr : : geq , _val , _1 ) ]
| ( lit ( ' < ' ) > additive_expression ( _r1 ) ) [ px : : bind ( & expr : : lower , _val , _1 ) ]
| ( lit ( ' > ' ) > additive_expression ( _r1 ) ) [ px : : bind ( & expr : : greater , _val , _1 ) ]
) ;
relational_expression . name ( " relational_expression " ) ;
additive_expression =
multiplicative_expression ( _r1 ) [ _val = _1 ]
> > * ( ( lit ( ' + ' ) > multiplicative_expression ( _r1 ) ) [ _val + = _1 ]
| ( lit ( ' - ' ) > multiplicative_expression ( _r1 ) ) [ _val - = _1 ]
) ;
additive_expression . name ( " additive_expression " ) ;
multiplicative_expression =
unary_expression ( _r1 ) [ _val = _1 ]
> > * ( ( lit ( ' * ' ) > unary_expression ( _r1 ) ) [ _val * = _1 ]
| ( lit ( ' / ' ) > unary_expression ( _r1 ) ) [ _val / = _1 ]
| ( lit ( ' % ' ) > unary_expression ( _r1 ) ) [ _val % = _1 ]
) ;
multiplicative_expression . name ( " multiplicative_expression " ) ;
assignment_statement =
2023-12-27 18:02:35 +08:00
( variable_reference ( _r1 ) [ _a = _1 ] > > ' = ' ) >
2023-06-10 10:14:12 +08:00
( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer.
initializer_list ( _r1 ) [ px : : bind ( & MyContext : : vector_variable_assign_initializer_list , _r1 , _a , _1 ) ]
// Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index.
// Only process such variable references, which return a naked vector variable.
| eps ( px : : bind ( & MyContext : : is_vector_variable_reference , _a ) ) > >
variable_reference ( _r1 ) [ px : : bind ( & MyContext : : copy_vector_variable_to_vector_variable , _r1 , _a , _1 ) ]
// Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above.
| conditional_expression ( _r1 )
[ px : : bind ( & MyContext : : scalar_variable_assign_scalar_expression , _r1 , _a , _1 ) ]
| ( kw [ " repeat " ] > " ( " > additive_expression ( _r1 ) > " , " > conditional_expression ( _r1 ) > " ) " )
[ px : : bind ( & MyContext : : vector_variable_assign_array , _r1 , _a , _1 , _2 ) ]
) ;
new_variable_statement =
( kw [ " local " ] [ _a = false ] | kw [ " global " ] [ _a = true ] ) > identifier [ px : : bind ( & MyContext : : new_old_variable , _r1 , _a , _1 , _b ) ] > lit ( ' = ' ) >
( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer.
initializer_list ( _r1 ) [ px : : bind ( & MyContext : : vector_variable_new_from_initializer_list , _r1 , _a , _b , _1 ) ]
// Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index.
// Only process such variable references, which return a naked vector variable.
| eps ( px : : bind ( & MyContext : : could_be_vector_variable_reference , _b ) ) > >
variable_reference ( _r1 ) [ px : : ref ( qi : : _pass ) = px : : bind ( & MyContext : : vector_variable_new_from_copy , _r1 , _a , _b , _1 ) ]
// Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above.
| conditional_expression ( _r1 )
[ px : : bind ( & MyContext : : scalar_variable_new_from_scalar_expression , _r1 , _a , _b , _1 ) ]
| ( kw [ " repeat " ] > " ( " > additive_expression ( _r1 ) > " , " > conditional_expression ( _r1 ) > " ) " )
[ px : : bind ( & MyContext : : vector_variable_new_from_array , _r1 , _a , _b , _1 , _2 ) ]
) ;
initializer_list = lit ( ' ( ' ) >
( lit ( ' ) ' ) |
( conditional_expression ( _r1 ) [ px : : bind ( & MyContext : : initializer_list_append , _val , _1 ) ] >
* ( lit ( ' , ' ) > conditional_expression ( _r1 ) [ px : : bind ( & MyContext : : initializer_list_append , _val , _1 ) ] ) >
lit ( ' ) ' )
)
) ;
unary_expression = iter_pos [ px : : bind ( & FactorActions : : set_start_pos , _1 , _val ) ] > > (
variable_reference ( _r1 ) [ px : : bind ( & MyContext : : variable_value , _r1 , _1 , _val ) ]
| ( lit ( ' ( ' ) > conditional_expression ( _r1 ) > ' ) ' > iter_pos ) [ px : : bind ( & FactorActions : : expr_ , _1 , _2 , _val ) ]
| ( lit ( ' - ' ) > unary_expression ( _r1 ) ) [ px : : bind ( & FactorActions : : minus_ , _1 , _val ) ]
| ( lit ( ' + ' ) > unary_expression ( _r1 ) > iter_pos ) [ px : : bind ( & FactorActions : : expr_ , _1 , _2 , _val ) ]
| ( ( kw [ " not " ] | ' ! ' ) > unary_expression ( _r1 ) > iter_pos ) [ px : : bind ( & FactorActions : : not_ , _1 , _val ) ]
| ( kw [ " min " ] > ' ( ' > conditional_expression ( _r1 ) [ _val = _1 ] > ' , ' > conditional_expression ( _r1 ) > ' ) ' )
[ px : : bind ( & expr : : min , _val , _2 ) ]
| ( kw [ " max " ] > ' ( ' > conditional_expression ( _r1 ) [ _val = _1 ] > ' , ' > conditional_expression ( _r1 ) > ' ) ' )
[ px : : bind ( & expr : : max , _val , _2 ) ]
| ( kw [ " random " ] > ' ( ' > conditional_expression ( _r1 ) [ _val = _1 ] > ' , ' > conditional_expression ( _r1 ) > ' ) ' )
[ px : : bind ( & MyContext : : random , _r1 , _val , _2 ) ]
| ( kw [ " digits " ] > ' ( ' > conditional_expression ( _r1 ) [ _val = _1 ] > ' , ' > conditional_expression ( _r1 ) > optional_parameter ( _r1 ) )
[ px : : bind ( & expr : : digits < false > , _val , _2 , _3 ) ]
| ( kw [ " zdigits " ] > ' ( ' > conditional_expression ( _r1 ) [ _val = _1 ] > ' , ' > conditional_expression ( _r1 ) > optional_parameter ( _r1 ) )
[ px : : bind ( & expr : : digits < true > , _val , _2 , _3 ) ]
| ( kw [ " int " ] > ' ( ' > conditional_expression ( _r1 ) > ' ) ' ) [ px : : bind ( & FactorActions : : to_int , _1 , _val ) ]
| ( kw [ " round " ] > ' ( ' > conditional_expression ( _r1 ) > ' ) ' ) [ px : : bind ( & FactorActions : : round , _1 , _val ) ]
| ( kw [ " is_nil " ] > ' ( ' > variable_reference ( _r1 ) > ' ) ' ) [ px : : bind ( & MyContext : : is_nil_test , _r1 , _1 , _val ) ]
| ( kw [ " one_of " ] > ' ( ' > one_of ( _r1 ) > ' ) ' ) [ _val = _1 ]
| ( kw [ " empty " ] > ' ( ' > variable_reference ( _r1 ) > ' ) ' ) [ px : : bind ( & MyContext : : is_vector_empty , _r1 , _1 , _val ) ]
| ( kw [ " size " ] > ' ( ' > variable_reference ( _r1 ) > ' ) ' ) [ px : : bind ( & MyContext : : vector_size , _r1 , _1 , _val ) ]
| ( kw [ " interpolate_table " ] > ' ( ' > interpolate_table ( _r1 ) > ' ) ' ) [ _val = _1 ]
| ( strict_double > iter_pos ) [ px : : bind ( & FactorActions : : double_ , _r1 , _1 , _2 , _val ) ]
| ( int_ > iter_pos ) [ px : : bind ( & FactorActions : : int_ , _r1 , _1 , _2 , _val ) ]
| ( kw [ bool_ ] > iter_pos ) [ px : : bind ( & FactorActions : : bool_ , _r1 , _1 , _2 , _val ) ]
| raw [ lexeme [ ' " ' > * ( ( utf8char - char_ ( ' \\ ' ) - char_ ( ' " ' ) ) | ( ' \\ ' > char_ ) ) > ' " ' ] ]
[ px : : bind ( & FactorActions : : string_ , _r1 , _1 , _val ) ]
) ;
unary_expression . name ( " unary_expression " ) ;
one_of = ( unary_expression ( _r1 ) [ _a = _1 ] > one_of_list ( _r1 , _a ) ) [ _val = _2 ] ;
one_of . name ( " one_of " ) ;
one_of_list =
eps [ px : : bind ( & expr : : one_of_test_init , _val ) ] >
( ( ' , ' > * (
(
unary_expression ( _r1 ) [ px : : bind ( & expr : : one_of_test < false > , _r2 , _1 , _val ) ]
| ( lit ( ' ~ ' ) > unary_expression ( _r1 ) ) [ px : : bind ( & expr : : one_of_test < true > , _r2 , _1 , _val ) ]
| regular_expression [ px : : bind ( & expr : : one_of_test_regex , _r2 , _1 , _val ) ]
) > > - lit ( ' , ' ) )
)
| eps
) ;
one_of_list . name ( " one_of_list " ) ;
interpolate_table = ( unary_expression ( _r1 ) [ _a = _1 ] > ' , ' > interpolate_table_list ( _r1 , _a ) )
[ px : : bind ( & InterpolateTableContext : : evaluate , _a , _2 , _val ) ] ;
interpolate_table . name ( " interpolate_table " ) ;
interpolate_table_list =
eps [ px : : bind ( & InterpolateTableContext : : init , _r2 ) ] >
( * ( ( lit ( ' ( ' ) > unary_expression ( _r1 ) > ' , ' > unary_expression ( _r1 ) > ' ) ' )
[ px : : bind ( & InterpolateTableContext : : add_pair , _1 , _2 , _val ) ] > > - lit ( ' , ' ) ) ) ;
interpolate_table . name ( " interpolate_table_list " ) ;
optional_parameter = iter_pos [ px : : bind ( & FactorActions : : set_start_pos , _1 , _val ) ] > > (
lit ( ' ) ' ) [ px : : bind ( & FactorActions : : noexpr , _val ) ]
| ( lit ( ' , ' ) > conditional_expression ( _r1 ) > ' ) ' ) [ _val = _1 ]
) ;
optional_parameter . name ( " optional_parameter " ) ;
variable_reference =
variable ( _r1 ) [ _a = _1 ] > >
(
( ' [ ' > additive_expression ( _r1 ) [ px : : bind ( & MyContext : : evaluate_index , _1 , _b ) ] > ' ] ' > iter_pos )
[ px : : bind ( & MyContext : : store_variable_index , _r1 , _a , _b , _2 , _val ) ]
| eps [ _val = _a ]
) ;
variable_reference . name ( " variable reference " ) ;
variable = identifier [ px : : bind ( & MyContext : : resolve_variable , _r1 , _1 , _val ) ] ;
variable . name ( " variable name " ) ;
regular_expression = raw [ lexeme [ ' / ' > * ( ( utf8char - char_ ( ' \\ ' ) - char_ ( ' / ' ) ) | ( ' \\ ' > char_ ) ) > ' / ' ] ] ;
regular_expression . name ( " regular_expression " ) ;
keywords . add
( " and " )
( " digits " )
( " zdigits " )
( " empty " )
( " if " )
( " int " )
( " is_nil " )
( " local " )
//("inf")
( " else " )
( " elsif " )
( " endif " )
( " false " )
( " global " )
( " interpolate_table " )
( " min " )
( " max " )
( " random " )
( " repeat " )
( " round " )
( " not " )
( " one_of " )
( " or " )
( " size " )
( " true " ) ;
if ( 0 ) {
debug ( start ) ;
debug ( text ) ;
debug ( text_block ) ;
debug ( macros ) ;
debug ( if_else_output ) ;
debug ( interpolate_table ) ;
// debug(switch_output);
debug ( legacy_variable_expansion ) ;
debug ( identifier ) ;
debug ( interpolate_table ) ;
debug ( interpolate_table_list ) ;
debug ( conditional_expression ) ;
debug ( logical_or_expression ) ;
debug ( logical_and_expression ) ;
debug ( equality_expression ) ;
debug ( bool_expr_eval ) ;
debug ( relational_expression ) ;
debug ( additive_expression ) ;
debug ( multiplicative_expression ) ;
debug ( unary_expression ) ;
debug ( one_of ) ;
debug ( one_of_list ) ;
debug ( optional_parameter ) ;
debug ( variable_reference ) ;
debug ( variable ) ;
debug ( regular_expression ) ;
}
}
// Generic expression over expr.
typedef qi : : rule < Iterator , expr ( const MyContext * ) , skipper > RuleExpression ;
// The start of the grammar.
qi : : rule < Iterator , std : : string ( const MyContext * ) , qi : : locals < bool > , skipper > start ;
// A free-form text.
qi : : rule < Iterator , std : : string ( ) , skipper > text ;
// A free-form text, possibly empty, possibly containing macro expansions.
qi : : rule < Iterator , std : : string ( const MyContext * ) , skipper > text_block ;
// Statements enclosed in curely braces {}
qi : : rule < Iterator , std : : string ( const MyContext * ) , skipper > block , statement , macros , if_text_block , if_macros , else_macros ;
// Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index].
qi : : rule < Iterator , std : : string ( const MyContext * ) , skipper > legacy_variable_expansion ;
// Parsed identifier name.
qi : : rule < Iterator , IteratorRange ( ) , skipper > identifier ;
// Ternary operator (?:) over logical_or_expression.
qi : : rule < Iterator , expr ( const MyContext * ) , qi : : locals < bool > , skipper > conditional_expression ;
// Logical or over logical_and_expressions.
RuleExpression logical_or_expression ;
// Logical and over relational_expressions.
RuleExpression logical_and_expression ;
// <, >, <=, >=
RuleExpression relational_expression ;
// Math expression consisting of +- operators over multiplicative_expressions.
RuleExpression additive_expression ;
// Boolean expressions over expressions.
RuleExpression equality_expression ;
// Math expression consisting of */ operators over factors.
RuleExpression multiplicative_expression ;
// Number literals, functions, braced expressions, variable references, variable indexing references.
RuleExpression unary_expression ;
// Accepting an optional parameter.
RuleExpression optional_parameter ;
// Rule to capture a regular expression enclosed in //.
qi : : rule < Iterator , IteratorRange ( ) , skipper > regular_expression ;
// Evaluate boolean expression into bool.
qi : : rule < Iterator , bool ( const MyContext * ) , skipper > bool_expr_eval ;
// Reference of a scalar variable, or reference to a field of a vector variable.
qi : : rule < Iterator , OptWithPos ( const MyContext * ) , qi : : locals < OptWithPos , int > , skipper > variable_reference ;
// Rule to translate an identifier to a ConfigOption, or to fail.
qi : : rule < Iterator , OptWithPos ( const MyContext * ) , skipper > variable ;
// Evaluating whether a nullable variable is nil.
qi : : rule < Iterator , expr ( const MyContext * ) , skipper > is_nil_test ;
// Evaluating "one of" list of patterns.
qi : : rule < Iterator , expr ( const MyContext * ) , qi : : locals < expr > , skipper > one_of ;
qi : : rule < Iterator , expr ( const MyContext * , const expr & param ) , skipper > one_of_list ;
// Evaluating the "interpolate_table" expression.
qi : : rule < Iterator , expr ( const MyContext * ) , qi : : locals < expr > , skipper > interpolate_table ;
qi : : rule < Iterator , InterpolateTableContext ( const MyContext * , const expr & param ) , skipper > interpolate_table_list ;
qi : : rule < Iterator , std : : string ( const MyContext * ) , qi : : locals < bool > , skipper > if_else_output ;
qi : : rule < Iterator , std : : string ( const MyContext * ) , qi : : locals < OptWithPos > , skipper > assignment_statement ;
// Allocating new local or global variables.
qi : : rule < Iterator , std : : string ( const MyContext * ) , qi : : locals < bool , MyContext : : NewOldVariable > , skipper > new_variable_statement ;
qi : : rule < Iterator , std : : vector < expr > ( const MyContext * ) , skipper > initializer_list ;
qi : : symbols < char > keywords ;
} ;
}
static const client : : macro_processor g_macro_processor_instance ;
static std : : string process_macro ( const std : : string & templ , client : : MyContext & context )
{
std : : string output ;
phrase_parse ( templ . begin ( ) , templ . end ( ) , g_macro_processor_instance ( & context ) , client : : skipper { } , output ) ;
if ( ! context . error_message . empty ( ) ) {
if ( context . error_message . back ( ) ! = ' \n ' & & context . error_message . back ( ) ! = ' \r ' )
context . error_message + = ' \n ' ;
throw Slic3r : : PlaceholderParserError ( context . error_message ) ;
}
return output ;
}
std : : string PlaceholderParser : : process ( const std : : string & templ , unsigned int current_extruder_id , const DynamicConfig * config_override , DynamicConfig * config_outputs , ContextData * context_data ) const
{
client : : MyContext context ;
context . external_config = this - > external_config ( ) ;
context . config = & this - > config ( ) ;
context . config_override = config_override ;
context . config_outputs = config_outputs ;
context . current_extruder_id = current_extruder_id ;
context . context_data = context_data ;
return process_macro ( templ , context ) ;
}
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
// Throws Slic3r::RuntimeError on syntax or runtime error.
bool PlaceholderParser : : evaluate_boolean_expression ( const std : : string & templ , const DynamicConfig & config , const DynamicConfig * config_override )
{
client : : MyContext context ;
context . config = & config ;
context . config_override = config_override ;
// Let the macro processor parse just a boolean expression, not the full macro language.
context . just_boolean_expression = true ;
return process_macro ( templ , context ) = = " true " ;
}
}