WIP: rewriting Spice expression parser to support more functions

This commit is contained in:
Matthias Koefferlein 2023-02-21 18:04:50 +01:00
parent b421f1e499
commit 715c7ed282
2 changed files with 535 additions and 164 deletions

View File

@ -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<tl::Variant> &params, 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<tl::Variant> 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 <class Cls>
@ -218,156 +694,6 @@ void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s,
}
}
double NetlistSpiceReaderDelegate::read_atomic_value (tl::Extractor &ex, const std::map<std::string, double> &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<std::string, double> &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<std::string, double> &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<std::string, double> &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<std::string, double> &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<std::string> &nn, std::map<std::string, double> &pv, const std::map<std::string, double> &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<std::string, double> &variables)
{
std::map<std::string, tl::Variant> 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<std::string, double> &variables)
{
std::map<std::string, tl::Variant> 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 ());

View File

@ -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<std::string, tl::Variant> 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<tl::Variant> &params, 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<std::string, double> &variables);
private:
static double read_atomic_value (tl::Extractor &ex, const std::map<std::string, double> &variables, bool *status);
static double read_dot_expr (tl::Extractor &ex, const std::map<std::string, double> &variables, bool *status);
static double read_bar_expr (tl::Extractor &ex, const std::map<std::string, double> &variables, bool *status);
};
/**