diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 7654ba9b0..40be09997 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -38,6 +38,482 @@ namespace db { +// ------------------------------------------------------------------------------------------------------ + +SpiceExpressionParser::SpiceExpressionParser (const variables_type *vars) +{ + static variables_type empty_variables; + mp_variables = vars ? vars : &empty_variables; +} + +// expression syntax taken from ngspice: +// https://nmg.gitlab.io/ngspice-manual/circuitdescription/paramparametricnetlists/syntaxofexpressions.html + +static double sqrt_f (double v) { return sqrt (v); } +static double sin_f (double v) { return sin (v); } +static double cos_f (double v) { return cos (v); } +static double tan_f (double v) { return tan (v); } +static double sinh_f (double v) { return sinh (v); } +static double cosh_f (double v) { return cosh (v); } +static double tanh_f (double v) { return tanh (v); } +static double asin_f (double v) { return asin (v); } +static double acos_f (double v) { return acos (v); } +static double atan_f (double v) { return atan (v); } +static double asinh_f (double v) { return asinh (v); } +static double acosh_f (double v) { return acosh (v); } +static double atanh_f (double v) { return atanh (v); } +static double exp_f (double v) { return exp (v); } +static double ln_f (double v) { return log (v); } +static double log_f (double v) { return log10 (v); } +static double abs_f (double v) { return abs (v); } +static double nint_f (double v) { return round (v); } +static double floor_f (double v) { return floor (v); } +static double ceil_f (double v) { return ceil (v); } +static double sgn_f (double v) { return v == 0.0 ? 0.0 : (v < 0.0 ? -1.0 : 1.0); } +static double int_f (double v) { return sgn_f (v) * floor (sgn_f (v) * v); } + +tl::Variant +SpiceExpressionParser::eval_func (const std::string &name, const std::vector ¶ms, bool * /*status*/) const +{ + double (*f) (double) = 0; + + if (name == "sqrt") { f = sqrt_f; } else + if (name == "sin") { f = sin_f; } else + if (name == "cos") { f = cos_f; } else + if (name == "tan") { f = tan_f; } else + if (name == "sinh") { f = sinh_f; } else + if (name == "cosh") { f = cosh_f; } else + if (name == "tanh") { f = tanh_f; } else + if (name == "asin") { f = asin_f; } else + if (name == "acos") { f = acos_f; } else + if (name == "atan" || name == "arctan") { f = atan_f; } else + if (name == "asinh") { f = asinh_f; } else + if (name == "acosh") { f = acosh_f; } else + if (name == "atanh") { f = atanh_f; } else + if (name == "exp") { f = exp_f; } else + if (name == "ln") { f = ln_f; } else + if (name == "log") { f = log_f; } else + if (name == "abs") { f = abs_f; } else + if (name == "nint") { f = nint_f; } else + if (name == "floor") { f = floor_f; } else + if (name == "ceil") { f = ceil_f; } else + if (name == "sgn") { f = sgn_f; } else + if (name == "int") { f = int_f; } + + if (f != 0) { + + if (params.size () < 1 || ! params.front ().can_convert_to_double ()) { + return tl::Variant (); + } else { + return tl::Variant ((*f) (params.front ().to_double ())); + } + + } else if (name == "pwr" || name == "pow") { + + if (params.size () < 2 || ! params [0].can_convert_to_double () || ! params [1].can_convert_to_double ()) { + return tl::Variant (); + } else { + return tl::Variant (pow (params [0].to_double (), params [1].to_double ())); + } + + } else if (name == "ternary_fcn") { + + if (params.size () < 3) { + return tl::Variant (); + } else { + return params [0].to_bool () ? params [1] : params [2]; + } + + } else if (name == "min") { + + if (params.size () < 1) { + return tl::Variant (); + } + + tl::Variant v = params [0]; + for (size_t i = 1; i < params.size (); ++i) { + if (params [i] < v) { + v = params [i]; + } + } + return v; + + } else if (name == "max") { + + if (params.size () < 1) { + return tl::Variant (); + } + + tl::Variant v = params [0]; + for (size_t i = 1; i < params.size (); ++i) { + if (v < params [i]) { + v = params [i]; + } + } + return v; + + } else { + + return tl::Variant (); + + } +} + +tl::Variant +SpiceExpressionParser::read_atomic_value (tl::Extractor &ex, bool *status) const +{ + double vd = 0.0; + std::string var; + + if (ex.test ("-")) { + + tl::Variant v = read_atomic_value (ex, status); + if (v.can_convert_to_double ()) { + return tl::Variant (-v.to_double ()); + } else { + return tl::Variant (); + } + + } else if (ex.test ("!")) { + + tl::Variant v = read_atomic_value (ex, status); + return tl::Variant (! v.to_bool ()); + + } else if (ex.test ("(")) { + + tl::Variant v = read_tl_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (status) { + *status = ex.test (")"); + } else { + ex.expect (")"); + } + return v; + + } else if (ex.try_read (vd)) { + + if (status) { + *status = true; + } + + double f = 1.0; + if (*ex == 't' || *ex == 'T') { + f = 1e12; + } else if (*ex == 'g' || *ex == 'G') { + f = 1e9; + } else if (*ex == 'k' || *ex == 'K') { + f = 1e3; + } else if (*ex == 'm' || *ex == 'M') { + f = 1e-3; + if (ex.test_without_case ("meg")) { + f = 1e6; + } + } else if (*ex == 'u' || *ex == 'U') { + f = 1e-6; + } else if (*ex == 'n' || *ex == 'N') { + f = 1e-9; + } else if (*ex == 'p' || *ex == 'P') { + f = 1e-12; + } else if (*ex == 'f' || *ex == 'F') { + f = 1e-15; + } else if (*ex == 'a' || *ex == 'A') { + f = 1e-18; + } + while (*ex && isalpha (*ex)) { + ++ex; + } + + vd *= f; + return tl::Variant (vd); + + } else if (ex.try_read_word (var)) { + + if (ex.test ("(")) { + + // a function + + std::vector params; + if (! ex.test (")")) { + while (! ex.at_end ()) { + params.push_back (read_tl_expr (ex, status)); + if (status && !*status) { + return tl::Variant (); + } + if (! ex.test (",")) { + break; + } + } + if (status && ! ex.test (")")) { + *status = false; + return tl::Variant (); + } else { + ex.expect (")"); + } + } + + return eval_func (var, params, status); + + } else { + + auto vi = mp_variables->find (tl::to_upper_case (var)); + if (vi != mp_variables->end ()) { + return vi->second; + } else { + // keep word as string value + return tl::Variant (var); + } + + } + + } else { + + if (status) { + *status = false; + } else { + throw tl::Exception (tl::sprintf (tl::to_string (tr ("Expected number of variable name here: '...%s'")), ex.get ())); + } + + return tl::Variant (); + + } +} + +tl::Variant SpiceExpressionParser::read_pwr_expr (tl::Extractor &ex, bool *status) const +{ + tl::Variant v = read_atomic_value (ex, status); + if (status && !*status) { + return tl::Variant (); + } + while (true) { + if (ex.test ("**") || ex.test ("^")) { + tl::Variant vv = read_atomic_value (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) { + v = tl::Variant (); + } else { + v = tl::Variant (pow (v.to_double (), vv.to_double ())); + } + } else { + break; + } + } + return v; +} + +tl::Variant SpiceExpressionParser::read_dot_expr (tl::Extractor &ex, bool *status) const +{ + tl::Variant v = read_pwr_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + while (true) { + if (ex.test ("*")) { + tl::Variant vv = read_pwr_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) { + v = tl::Variant (); + } else { + v = v.to_double () * vv.to_double (); + } + } else if (ex.test ("/")) { + tl::Variant vv = read_pwr_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) { + v = tl::Variant (); + } else { + v = v.to_double () / vv.to_double (); + } + } else if (ex.test ("%")) { + tl::Variant vv = read_pwr_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) { + v = tl::Variant (); + } else { + v = tl::Variant ((long int) v.to_double () % (long int) vv.to_double ()); + } + } else { + break; + } + } + return v; +} + +tl::Variant SpiceExpressionParser::read_bar_expr (tl::Extractor &ex, bool *status) const +{ + tl::Variant v = read_dot_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + while (true) { + if (ex.test ("+")) { + tl::Variant vv = read_dot_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) { + v = tl::Variant (); + } else { + v = v.to_double () + vv.to_double (); + } + } else if (ex.test ("-")) { + tl::Variant vv = read_dot_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (! v.can_convert_to_double () || ! vv.can_convert_to_double ()) { + v = tl::Variant (); + } else { + v = v.to_double () - vv.to_double (); + } + } else { + break; + } + } + return v; +} + +tl::Variant SpiceExpressionParser::read_compare_expr (tl::Extractor &ex, bool *status) const +{ + tl::Variant v = read_bar_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + while (true) { + if (ex.test ("==")) { + tl::Variant vv = read_bar_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = tl::Variant (v == vv); + } else if (ex.test ("!=")) { + tl::Variant vv = read_bar_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = tl::Variant (!(v == vv)); + } else if (ex.test ("<=")) { + tl::Variant vv = read_bar_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = tl::Variant (v < vv || v == vv); + } else if (ex.test ("<")) { + tl::Variant vv = read_bar_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = tl::Variant (v < vv); + } else if (ex.test (">=")) { + tl::Variant vv = read_bar_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = tl::Variant (vv < v || v == vv); + } else if (ex.test (">")) { + tl::Variant vv = read_bar_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = tl::Variant (vv < v); + } else { + break; + } + } + return v; +} + +tl::Variant SpiceExpressionParser::read_logical_op (tl::Extractor &ex, bool *status) const +{ + tl::Variant v = read_compare_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + while (true) { + if (ex.test ("&&")) { + tl::Variant vv = read_compare_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = tl::Variant (v.to_bool () && vv.to_bool ()); + } else if (ex.test ("||")) { + tl::Variant vv = read_compare_expr (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = tl::Variant (v.to_bool () && vv.to_bool ()); + } else { + break; + } + } + return v; +} + +tl::Variant SpiceExpressionParser::read_ternary_op (tl::Extractor &ex, bool *status) const +{ + tl::Variant v = read_logical_op (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (ex.test ("?")) { + tl::Variant vv1 = read_logical_op (ex, status); + if (status && !*status) { + return tl::Variant (); + } + if (! ex.test (":")) { + if (status) { + *status = false; + } else { + ex.expect (":"); + } + } + tl::Variant vv2 = read_logical_op (ex, status); + if (status && !*status) { + return tl::Variant (); + } + v = v.to_bool () ? vv1 : vv2; + } + + return v; +} + +tl::Variant SpiceExpressionParser::read_tl_expr (tl::Extractor &ex, bool *status) const +{ + return read_ternary_op (ex, status); +} + +tl::Variant SpiceExpressionParser::read (tl::Extractor &ex) const +{ + try { + return read_tl_expr (ex, 0); + } catch (tl::Exception &error) { + throw NetlistSpiceReaderDelegateError (error.msg ()); + } +} + +bool SpiceExpressionParser::try_read (tl::Extractor &ex, tl::Variant &value) const +{ + tl::Extractor ex_saved = ex; + + bool status = false; + value = read_tl_expr (ex, &status); + if (! status) { + ex = ex_saved; + } + + return status; +} + + // ------------------------------------------------------------------------------------------------------ static const char *allowed_name_chars = "_.:,!+$/&\\#[]|<>"; @@ -133,7 +609,7 @@ std::string NetlistSpiceReaderDelegate::translate_net_name (const std::string &n void NetlistSpiceReaderDelegate::error (const std::string &msg) { - throw SpiceReaderDelegateException (msg); + throw NetlistSpiceReaderDelegateError (msg); } template @@ -218,156 +694,6 @@ void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, } } -double NetlistSpiceReaderDelegate::read_atomic_value (tl::Extractor &ex, const std::map &variables, bool *status) -{ - double v = 0.0; - std::string var; - - if (ex.test ("(")) { - - double v = read_dot_expr (ex, variables, status); - if (status && !*status) { - return 0.0; - } - if (status) { - *status = ex.test (")"); - } else { - ex.expect (")"); - } - return v; - - } else if (ex.try_read (v)) { - - if (status) { - *status = true; - } - - double f = 1.0; - if (*ex == 't' || *ex == 'T') { - f = 1e12; - } else if (*ex == 'g' || *ex == 'G') { - f = 1e9; - } else if (*ex == 'k' || *ex == 'K') { - f = 1e3; - } else if (*ex == 'm' || *ex == 'M') { - f = 1e-3; - if (ex.test_without_case ("meg")) { - f = 1e6; - } - } else if (*ex == 'u' || *ex == 'U') { - f = 1e-6; - } else if (*ex == 'n' || *ex == 'N') { - f = 1e-9; - } else if (*ex == 'p' || *ex == 'P') { - f = 1e-12; - } else if (*ex == 'f' || *ex == 'F') { - f = 1e-15; - } else if (*ex == 'a' || *ex == 'A') { - f = 1e-18; - } - while (*ex && isalpha (*ex)) { - ++ex; - } - - v *= f; - return v; - - } else if (ex.try_read_word (var)) { - - auto v = variables.find (tl::to_upper_case (var)); - if (v != variables.end ()) { - if (status) { - *status = true; - } - return v->second; - } else if (status) { - *status = false; - } else { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Undefined parameter '%s'")), var)); - } - - } else { - - if (status) { - *status = false; - } else { - throw tl::Exception (tl::sprintf (tl::to_string (tr ("Expected number of variable name here: '...%s'")), ex.get ())); - } - - return 0.0; - - } -} - -double NetlistSpiceReaderDelegate::read_bar_expr (tl::Extractor &ex, const std::map &variables, bool *status) -{ - double v = read_atomic_value (ex, variables, status); - if (status && !*status) { - return 0.0; - } - while (true) { - if (ex.test ("+")) { - double vv = read_atomic_value (ex, variables, status); - if (status && !*status) { - return 0.0; - } - v += vv; - } else if (ex.test ("-")) { - double vv = read_atomic_value (ex, variables, status); - if (status && !*status) { - return 0.0; - } - v -= vv; - } else { - break; - } - } - return v; -} - -double NetlistSpiceReaderDelegate::read_dot_expr (tl::Extractor &ex, const std::map &variables, bool *status) -{ - double v = read_bar_expr (ex, variables, status); - if (status && !*status) { - return 0.0; - } - while (true) { - if (ex.test ("*")) { - double vv = read_bar_expr (ex, variables, status); - if (status && !*status) { - return 0.0; - } - v *= vv; - } else if (ex.test ("/")) { - double vv = read_bar_expr (ex, variables, status); - if (status && !*status) { - return 0.0; - } - v /= vv; - } else { - break; - } - } - return v; -} - -double NetlistSpiceReaderDelegate::read_value (tl::Extractor &ex, const std::map &variables) -{ - try { - return read_dot_expr (ex, variables, 0); - } catch (tl::Exception &error) { - throw SpiceReaderDelegateException (error.msg ()); - } -} - -bool NetlistSpiceReaderDelegate::try_read_value (const std::string &s, double &value, const std::map &variables) -{ - bool status = false; - tl::Extractor ex (s.c_str ()); - value = read_dot_expr (ex, variables, &status); - return status; -} - void NetlistSpiceReaderDelegate::parse_element (const std::string &s, const std::string &element, std::string &model, double &value, std::vector &nn, std::map &pv, const std::map &variables) { parse_element_components (s, nn, pv, variables); @@ -685,6 +1011,39 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin return true; } +double +NetlistSpiceReaderDelegate::read_value (tl::Extractor &ex, const std::map &variables) +{ + std::map vvariables; + for (auto i = variables.begin (); i != variables.end (); ++i) { + vvariables.insert (std::make_pair (i->first, tl::Variant (i->second))); + } + + SpiceExpressionParser parser (&vvariables); + return parser.read (ex).to_double (); +} + +bool +NetlistSpiceReaderDelegate::try_read_value (const std::string &s, double &v, const std::map &variables) +{ + std::map vvariables; + for (auto i = variables.begin (); i != variables.end (); ++i) { + vvariables.insert (std::make_pair (i->first, tl::Variant (i->second))); + } + + SpiceExpressionParser parser (&vvariables); + + tl::Variant vv; + tl::Extractor ex (s.c_str ()); + bool res = parser.try_read (ex, vv); + + if (res) { + v = vv.to_double (); + } + + return res; +} + // ------------------------------------------------------------------------------------------------------ class SpiceReaderStream @@ -1121,7 +1480,7 @@ SpiceCircuitDict::read (tl::InputStream &stream) read_card (); } - } catch (SpiceReaderDelegateException &ex) { + } catch (NetlistSpiceReaderDelegateError &ex) { // Translate the exception and add a location error (ex.msg ()); @@ -1490,7 +1849,7 @@ SpiceNetlistBuilder::build () build_global_nets (); mp_delegate->finish (mp_netlist); - } catch (SpiceReaderDelegateException &ex) { + } catch (NetlistSpiceReaderDelegateError &ex) { // translate the error and add a source location error (ex.msg ()); diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index 6bb7d39d7..3d33728a9 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -43,15 +43,32 @@ class DeviceClass; class Device; /** - * @brief A specific SPICE reader exception that allows attaching location information + * @brief A class implementing the expression parser + * + * This class is exposed mainly for testing purposes. */ -class SpiceReaderDelegateException - : public tl::Exception +class DB_PUBLIC SpiceExpressionParser { public: - SpiceReaderDelegateException (const std::string &msg) - : tl::Exception (msg) - { } + typedef std::map variables_type; + + SpiceExpressionParser (const variables_type *vars); + + tl::Variant read (tl::Extractor &ex) const; + bool try_read (tl::Extractor &ex, tl::Variant &v) const; + +private: + const variables_type *mp_variables; + + tl::Variant read_atomic_value (tl::Extractor &ex, bool *status) const; + tl::Variant read_dot_expr (tl::Extractor &ex, bool *status) const; + tl::Variant read_bar_expr (tl::Extractor &ex, bool *status) const; + tl::Variant read_pwr_expr (tl::Extractor &ex, bool *status) const; + tl::Variant read_compare_expr (tl::Extractor &ex, bool *status) const; + tl::Variant read_logical_op (tl::Extractor &ex, bool *status) const; + tl::Variant read_ternary_op (tl::Extractor &ex, bool *status) const; + tl::Variant read_tl_expr (tl::Extractor &ex, bool *status) const; + tl::Variant eval_func (const std::string &name, const std::vector ¶ms, bool *status) const; }; /** @@ -161,11 +178,6 @@ public: * @brief Tries to read a value from the extractor (with formula evaluation) */ static bool try_read_value (const std::string &s, double &v, const std::map &variables); - -private: - static double read_atomic_value (tl::Extractor &ex, const std::map &variables, bool *status); - static double read_dot_expr (tl::Extractor &ex, const std::map &variables, bool *status); - static double read_bar_expr (tl::Extractor &ex, const std::map &variables, bool *status); }; /**