scidavis  2.3.0
About: SciDAVis is a free application for Scientific Data Analysis and Visualization (a fork off of QtiPlot).
  Fossies Dox: scidavis-2.3.0.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

MuParserScript.cpp
Go to the documentation of this file.
1 
11 
30 // for NAN macro
31 #define _ISOC99_SOURCE
32 
33 #include "MuParserScript.h"
34 #include "MuParserScripting.h"
35 #include "QStringStdString.h"
37 #include "Table.h"
38 #include "Matrix.h"
39 #include "Folder.h"
40 #include <math.h>
41 #include <QtCore/QByteArray>
42 #include <QtCore/QRegExp>
43 
138 
139 MuParserScript::MuParserScript(ScriptingEnv *environment, const QString &code, QObject *context, const QString &name)
140  : Script(environment, code, context, name) {
141  m_parser.SetVarFactory(variableFactory, this);
142 
143  // redefine characters for operators to include ";"
144  static const auto opChars=
145  // standard operator chars as defined in mu::Parser::InitCharSets()
146  _T("abcdefghijklmnopqrstuvwxyz")
147  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
148  "+-*^/?<>=#!$%&|~'_"
149  // our additions
150  ";";
151 
152  m_parser.DefineOprtChars(opChars);
153  // work around muparser bug number 6 https://code.google.com/p/muparser/issues/detail?id=6
154  m_parser.DefineInfixOprtChars(opChars);
155 
156  // statement separation needs lower precedence than everything else; assignment has precedence
157  // -1, everything else defined in mu::Parser has non-negative precedence
158  m_parser.DefineOprt(_T(";"), statementSeparator, -2);
159 
160  // aliases for _pi and _e
161  m_parser.DefineConst(_T("pi"), M_PI);
162  m_parser.DefineConst(_T("Pi"), M_PI);
163  m_parser.DefineConst(_T("PI"), M_PI);
164  m_parser.DefineConst(_T("e"), M_E);
165  m_parser.DefineConst(_T("E"), M_E);
166 
167  // tell parser about mathematical functions
169  if (i->numargs == 1 && i->fun1 != NULL)
170  m_parser.DefineFun(i->name, i->fun1);
171  else if (i->numargs == 2 && i->fun2 != NULL)
172  m_parser.DefineFun(i->name, i->fun2);
173  else if (i->numargs == 3 && i->fun3 != NULL)
174  m_parser.DefineFun(i->name, i->fun3);
175 
176  // tell parser about table/matrix access functions
177  if (Context && Context->inherits("Table")) {
178  m_parser.DefineFun(_T("column"), tableColumnFunction, false);
179  m_parser.DefineFun(_T("column_"), tableColumn_Function, false);
180  m_parser.DefineFun(_T("column__"), tableColumn__Function, false);
181  m_parser.DefineFun(_T("cell"), tableCellFunction);
182  m_parser.DefineFun(_T("cell_"), tableCell_Function);
183  } else if (Context && Context->inherits("Matrix"))
184  m_parser.DefineFun(_T("cell"), matrixCellFunction);
185 }
186 
194 double *MuParserScript::variableFactory(const mu::string_type::value_type *name, void *self) {
195  MuParserScript *me = static_cast<MuParserScript *>(self);
196  return me->m_variables.insert(QStringFromString(name), NAN).operator->();
197 }
198 
206 bool MuParserScript::setDouble(double value, const char *name) {
207  QString baName(name);
208  auto entry = m_variables.find(baName);
209  if (entry == m_variables.end()) {
210  // variable is not known yet
211  entry = m_variables.insert(baName, value);
212  try {
213  m_parser.DefineVar(toString<mu::string_type>(baName).c_str(), entry.operator->());
214  } catch (mu::ParserError &e) {
215  m_variables.erase(entry);
216  emit_error(QStringFromString(e.GetMsg()),0);
217  return false;
218  }
219  } else
220  // variable is known and only needs to be updated
221  *entry = value;
222  return true;
223 }
224 
232 double MuParserScript::statementSeparator(double a, double b) {
233  Q_UNUSED(a);
234  return b;
235 }
236 
247 double MuParserScript::tableColumnFunction(const mu::string_type::value_type *columnPath) {
249  if (!column) return NAN; // failsafe, shouldn't happen
250  int row = qRound(s_currentInstance->m_variables["i"]) - 1;
251  if (column->isInvalid(row))
252  throw new EmptySourceError();
253  return column->valueAt(row);
254 }
255 
269 double MuParserScript::tableColumn_Function(double columnIndex) {
270  Table *thisTable = qobject_cast<Table*>(s_currentInstance->Context);
271  if (!thisTable)
272  // improving the error message would break translations
273  // TODO: change col() to column() for next minor release
274  throw MuException(tr("col() works only on tables!"));
275  Column *column = thisTable->d_future_table->column(qRound(columnIndex) - 1);
276  if (!column)
277  throw MuException(tr("There's no column %1 in table %2!")
278  .arg(qRound(columnIndex)).arg(thisTable->objectName()));
279  int row = qRound(s_currentInstance->m_variables["i"]) - 1;
280  if (column->isInvalid(row))
281  throw new EmptySourceError();
282  return column->valueAt(row);
283 }
284 
300 double MuParserScript::tableColumn__Function(const mu::string_type::value_type *tableName, double columnIndex) {
301  Table *thisTable = qobject_cast<Table*>(s_currentInstance->Context);
302  if (!thisTable)
303  // improving the error message would break translations
304  // TODO: change tablecol() to column() for next minor release
305  throw MuException(tr("tablecol() works only on tables!"));
306  Table *targetTable = thisTable->folder()->rootFolder()->table(QStringFromString(tableName), true);
307  if (!targetTable)
308  throw MuException(tr("Couldn't find a table named %1.").arg(tableName));
309  Column *column = targetTable->d_future_table->column(qRound(columnIndex) - 1);
310  if (!column)
311  throw MuException(tr("There's no column %1 in table %2!")
312  .arg(qRound(columnIndex)).arg(tableName));
313  int row = qRound(s_currentInstance->m_variables["i"]) - 1;
314  if (column->isInvalid(row))
315  throw new EmptySourceError();
316  return column->valueAt(row);
317 }
318 
327 double MuParserScript::tableCellFunction(const mu::string_type::value_type *columnPath, double rowIndex) {
329  if (!column) return NAN; // failsafe, shouldn't happen
330  int row = qRound(rowIndex) - 1;
331  if (column->isInvalid(row))
332  throw new EmptySourceError();
333  return column->valueAt(row);
334 }
335 
347 double MuParserScript::tableCell_Function(double columnIndex, double rowIndex) {
348  Table *thisTable = qobject_cast<Table*>(s_currentInstance->Context);
349  if (!thisTable)
350  throw MuException(tr("cell() works only on tables and matrices!"));
351  Column *column = thisTable->d_future_table->column(qRound(columnIndex) - 1);
352  if (!column)
353  throw MuException(tr("There's no column %1 in table %2!")
354  .arg(qRound(columnIndex)).arg(thisTable->objectName()));
355  int row = qRound(rowIndex) - 1;
356  if (column->isInvalid(row))
357  throw new EmptySourceError();
358  return column->valueAt(row);
359 }
360 
367 double MuParserScript::matrixCellFunction(double rowIndex, double columnIndex) {
368  Matrix *thisMatrix = qobject_cast<Matrix*>(s_currentInstance->Context);
369  if (!thisMatrix)
370  throw MuException(tr("cell() works only on tables and matrices!"));
371  int row = qRound(rowIndex) - 1;
372  int column = qRound(columnIndex) - 1;
373  if (row < 0 || row >= thisMatrix->numRows())
374  throw MuException(tr("There's no row %1 in matrix %2!").
375  arg(qRound(rowIndex)).arg(thisMatrix->objectName()));
376  if (column < 0 || column >= thisMatrix->numCols())
377  throw MuException(tr("There's no column %1 in matrix %2!").
378  arg(qRound(columnIndex)).arg(thisMatrix->objectName()));
379  return thisMatrix->cell(row, column);
380 }
381 
395  Column *result = 0;
396 
397  // Split path into components.
398  // While escape handling would be possible using a regular expression, it would require
399  // lookbehind assertions, which are currently not supported by QRegExp. Thus, we can't simply
400  // use QString::split() and have to explicitly loop over the characters in path.
401  QStringList pathComponents;
402  QString current;
403  for (int i=0; i<path.size(); ++i)
404  switch(path.at(i).toLatin1()) {
405  case '/':
406  pathComponents << current;
407  current.clear();
408  break;
409  case '\\':
410  if (i+1 < path.size())
411  current.append(path.at(++i));
412  break;
413  default:
414  current.append(path.at(i));
415  break;
416  }
417  QString columnName = current;
418 
419  Table *table = 0;
420  if (pathComponents.isEmpty()) {
421  // only column name specified, read from this table
422  table = qobject_cast<Table *>(Context);
423  if (!table)
424  throw MuException(tr("Accessing table values is not (yet) supported in this context."));
425  } else {
426  // look up the table containing the column
427  MyWidget *myContext = qobject_cast<MyWidget *>(Context);
428  if (!myContext)
429  throw MuException(tr("Accessing table values is not (yet) supported in this context."));
430  QString tableName = pathComponents.takeLast();
431  if (pathComponents.isEmpty())
432  // needed for backwards compatibility, but will be problematic once we drop the requirement
433  // of project-wide unique object names
434  table = myContext->folder()->rootFolder()->table(tableName, true);
435  else {
436  Folder *folder;
437  if (pathComponents.at(0).isEmpty())
438  // absolute path
439  folder = myContext->folder()->rootFolder();
440  else if (pathComponents.at(0) == "..")
441  // relative path
442  folder = myContext->folder();
443  else
444  // invalid path
445  throw MuException(tr("Couldn't find a table named %1.")
446  .arg(pathComponents.join("/")+"/"+tableName));
447  pathComponents.removeFirst();
448  foreach(QString f, pathComponents) {
449  if (f == "..")
450  folder = qobject_cast<Folder*>(folder->parent());
451  else
452  folder = folder->findSubfolder(f);
453  if (!folder)
454  throw MuException(tr("Couldn't find a table named %1.")
455  .arg(pathComponents.join("/")+"/"+tableName));
456  }
457  table = folder->table(tableName);
458  }
459  if (!table)
460  throw MuException(tr("Couldn't find a table named %1.")
461  .arg(pathComponents.join("/")+"/"+tableName));
462  }
463 
464  // finally, look up the column in the table
465  result = table->d_future_table->column(columnName, false);
466  if (!result)
467  throw MuException(tr("There's no column named %1 in table %2!")
468  .arg(columnName).arg(table->d_future_table->path()));
469 
470  return result;
471 }
472 
491  QRegExp legacyFunction("(\\W||^)(col|tablecol|cell)\\s*\\(");
492 
493  int functionStart = legacyFunction.indexIn(input, 0);
494  while (functionStart != -1) {
495  QStringList arguments;
496  int functionEnd = functionStart; // initialization is a failsafe
497  QString replacement;
498 
499  // parse arguments of function
500  QString currentArgument;
501  for (int i = functionStart+legacyFunction.matchedLength(), parenthesisLevel = 1;
502  parenthesisLevel > 0 && i < input.size();
503  i++) {
504  switch (input.at(i).toLatin1()) {
505  case '"':
506  currentArgument += '"';
507  for (i++; i < input.size() && input.at(i) != QChar('"'); i++)
508  if (input.at(i) == QChar('\\')) {
509  currentArgument += '\\';
510  currentArgument += input.at(++i);
511  } else
512  currentArgument += input.at(i);
513  currentArgument += '"';
514  break;
515  case '\\':
516  currentArgument += '\\';
517  currentArgument += input.at(++i);
518  break;
519  case '(':
520  parenthesisLevel++;
521  currentArgument += '(';
522  break;
523  case ')':
524  parenthesisLevel--;
525  if (parenthesisLevel > 0)
526  currentArgument += ')';
527  else
528  functionEnd = i;
529  break;
530  case ',':
531  if (parenthesisLevel == 1) {
532  arguments << currentArgument;
533  currentArgument.clear();
534  } else
535  currentArgument += ',';
536  break;
537  default:
538  currentArgument += input.at(i);
539  break;
540  }
541  }
542  arguments << currentArgument;
543 
544  // select replacement function call
545  Table *table = qobject_cast<Table *>(Context);
546  if (legacyFunction.cap(2) == "col") {
547  QString columnArgument;
548  bool numericColumn = false;
549  if (arguments.at(0).startsWith("\"")) {
550  // col("name") -> column("name")
551  columnArgument = arguments.at(0);
552  // do escaping of path argument
553  columnArgument.replace("\\","\\\\");
554  columnArgument.replace("/", "\\/");
555  } else if (table && table->d_future_table->column(arguments.at(0), false)) {
556  // hack for ambiguous legacy syntax:
557  // col(name) -> column("name"), if name is a column of the current table
558  columnArgument = "\"" + arguments.at(0) + "\"";
559  // do escaping of path argument
560  columnArgument.replace("\\","\\\\");
561  columnArgument.replace("/", "\\/");
562  } else {
563  // col(expression) -> column_(expression)
564  columnArgument = arguments.at(0);
565  if (!translateLegacyFunctions(columnArgument)) return false;
566  numericColumn = true;
567  }
568  if (arguments.size() > 1) {
569  QString rowArgument = arguments.at(1);
570  if (!translateLegacyFunctions(rowArgument)) return false;
571  replacement = QString("cell") + (numericColumn ? "_" : "") + "(" + columnArgument + ","
572  + rowArgument + ")";
573  } else
574  replacement = QString("column") + (numericColumn ? "_" : "") + "(" + columnArgument + ")";
575  } else if (legacyFunction.cap(2) == "tablecol") {
576  // assert number of arguments == 2
577  if (arguments.size() != 2) {
578  emit_error(tr("tablecol: wrong number of arguments (need 2, got %1)")
579  .arg(arguments.size()), 0);
581  return false;
582  }
583  if (arguments.at(1).startsWith("\"")) {
584  // tablecol("table", "column") -> column("table/column")
585  // do escaping of path argument
586  QString tableArgument = arguments.at(0);
587  tableArgument.replace("\\","\\\\");
588  tableArgument.replace("/", "\\/");
589  QString columnArgument = arguments.at(1);
590  columnArgument.replace("\\","\\\\");
591  columnArgument.replace("/", "\\/");
592  // remove quotation marks
593  tableArgument.remove(tableArgument.size()-1,1);
594  columnArgument.remove(0,1);
595  replacement = QString("column(") + tableArgument + "/" + columnArgument + ")";
596  } else {
597  // tablecol("table", column) -> column__("table", column)
598  QString rowArgument = arguments.at(1);
599  if (!translateLegacyFunctions(rowArgument)) return false;
600  replacement = QString("column__(") + arguments.at(0) + "," + rowArgument + ")";
601  }
602  } else { // legacyFunction.cap(2) == "cell"
603  // assert number of arguments == 2
604  if (arguments.size() != 2) {
605  emit_error(tr("cell: wrong number of arguments (need 2, got %1)")
606  .arg(arguments.size()), 0);
608  return false;
609  }
610  if (arguments.at(0).startsWith("\"")) {
611  // keep cell("column",row) -- this is new-style syntax
612  QString rowArgument = arguments.at(1);
613  if (!translateLegacyFunctions(rowArgument)) return false;
614  replacement = QString("cell(") + arguments.at(0) + "," + rowArgument + ")";
615  } else {
616  // cell(column,row) -> cell_(column,row)
617  QString columnArgument = arguments.at(0);
618  if (!translateLegacyFunctions(columnArgument)) return false;
619  QString rowArgument = arguments.at(1);
620  if (!translateLegacyFunctions(rowArgument)) return false;
621  replacement = QString("cell_(") + columnArgument + "," + rowArgument + ")";
622  }
623  }
624 
625  // do replacement
626  if (legacyFunction.cap(1).isEmpty())
627  // matched with ^, not \W (lookbehind assertion would be darn handy...)
628  input.replace(functionStart, functionEnd-functionStart+1, replacement);
629  else
630  // need to adjust for the additional matched character
631  input.replace(functionStart+1, functionEnd-functionStart, replacement);
632  // search for next match, starting at the end of the replaced text
633  functionStart = legacyFunction.indexIn(input, functionStart + replacement.length());
634  } // while (functionStart != -1)
635  return true;
636 }
637 
644 bool MuParserScript::compile(bool asFunction) {
645  Q_UNUSED(asFunction); // only needed for Python
646 
647  QString intermediate = Code.trimmed(); // pre-processed version of #Code
648 
649  // remove comments
650  bool inString = false;
651  int commentStart = -1;
652  for (int i=0; i<intermediate.size(); i++)
653  switch (intermediate.at(i).toLatin1()) {
654  case '"':
655  if (commentStart < 0) inString = !inString;
656  break;
657  case '#':
658  if (!inString) commentStart = i;
659  break;
660  case '\n':
661  if (commentStart >= 0) {
662  intermediate.remove(commentStart, i-commentStart);
663  i = commentStart;
664  commentStart = -1;
665  }
666  break;
667  }
668  if (commentStart >= 0)
669  intermediate.remove(commentStart, intermediate.size()-commentStart);
670 
671  // simplify statement separators
672  intermediate.replace(QRegExp("([;\\n]\\s*)+"),"; ");
673 
674  // recursively translate legacy functions col(), tablecol() and cell()
675  if (Context && Context->inherits("Table"))
676  if (!translateLegacyFunctions(intermediate))
677  return false;
678 
679  try {
680  m_parser.SetExpr(toString<mu::string_type>(intermediate));
681  } catch (mu::ParserError &e) {
682  emit_error(QStringFromString(e.GetMsg()), 0);
683  return false;
684  }
685 
687  return true;
688 }
689 
691  if (compiled != Script::isCompiled && !compile())
692  return QVariant();
693  try {
694  // see documentation of s_currentInstance for explanation
695  s_currentInstance = this;
696  return m_parser.Eval();
697  } catch (EmptySourceError *e) {
698  // formula tried to access a table cell marked as invalid
699  return "";
700  } catch (mu::ParserError &e) {
701  emit_error(QStringFromString(e.GetMsg()), 0);
702  return QVariant();
703  }
704 }
Matrix
Matrix worksheet class.
Definition: Matrix.h:52
MuParserScripting::mathFunction::name
const mu::string_type::value_type * name
Definition: MuParserScripting.h:74
MyWidget::folder
Folder * folder()
Returns the pointer to the parent folder of the window.
Definition: MyWidget.h:128
Script::compiled
enum Script::compileStatus compiled
Column.h
QStringFromString
QString QStringFromString(const std::string &x)
Definition: QStringStdString.h:15
Folder::table
Table * table(const QString &name, bool recursive=false)
Return table named name or NULL.
Definition: Folder.h:109
MuParserScript.h
Matrix.h
Column
Aspect that manages a column.
Definition: Column.h:59
MuParserScript::MuException
Definition: MuParserScript.h:46
MyWidget
Base class of all MDI client windows.
Definition: MyWidget.h:52
MuParserScript::tableColumn__Function
static double tableColumn__Function(const mu::string_type::value_type *tableName, double columnIndex)
Implements column__() function for tables.
Definition: MuParserScript.cpp:300
Script::Context
QObject * Context
Definition: Script.h:108
Table::d_future_table
QPointer< future::Table > d_future_table
Definition: Table.h:55
MuParserScript::s_currentInstance
static MuParserScript * s_currentInstance
MuParserScript instance currently executing eval()
Definition: MuParserScript.h:80
MuParserScript::matrixCellFunction
static double matrixCellFunction(double rowIndex, double columnIndex)
Implements cell() function for matrices.
Definition: MuParserScript.cpp:367
Table
MDI window providing a spreadsheet table with column logic.
Definition: Table.h:51
Script::compileErr
@ compileErr
Definition: Script.h:109
Column::isInvalid
bool isInvalid(int row) const override
Return whether a certain row contains an invalid value
Definition: Column.cpp:598
MuParserScript::resolveColumnPath
Column * resolveColumnPath(const QString &path)
Look up the column specified by path.
Definition: MuParserScript.cpp:394
MuParserScripting.h
Folder.h
Folder::rootFolder
Folder * rootFolder()
The root of the hierarchy this folder belongs to.
Definition: Folder.cpp:173
MuParserScript::tableColumn_Function
static double tableColumn_Function(double columnIndex)
Implements column_() function for tables.
Definition: MuParserScript.cpp:269
Matrix::numCols
int numCols()
Return the number of columns.
Definition: Matrix.cpp:293
MuParserScripting::mathFunction
Definition: MuParserScripting.h:73
MuParserScript::tableCellFunction
static double tableCellFunction(const mu::string_type::value_type *columnPath, double rowIndex)
Implements cell() function for tables.
Definition: MuParserScript.cpp:327
QStringStdString.h
MuParserScript::variableFactory
static double * variableFactory(const mu::string_type::value_type *name, void *self)
muParser callback for registering user-defined variables
Definition: MuParserScript.cpp:194
ScriptingEnv
An interpreter for evaluating scripting code. Abstract.
Definition: ScriptingEnv.h:53
Column::clear
void clear() override
Clear the whole column.
Definition: Column.cpp:142
MuParserScript::m_parser
mu::Parser m_parser
muParser object doing most of the expression evaluation work
Definition: MuParserScript.h:77
Folder
Folder for the project explorer.
Definition: Folder.h:62
M_PI
#define M_PI
Definition: ArrowMarker.cpp:41
Script
A chunk of scripting code. Abstract.
Definition: Script.h:53
MuParserScript::m_variables
QMap< QString, double > m_variables
Stores user-visible muParser variables.
Definition: MuParserScript.h:78
MuParserScript::translateLegacyFunctions
bool translateLegacyFunctions(QString &input)
Do in-place translation of overloaded functions.
Definition: MuParserScript.cpp:490
Script::emit_error
void emit_error(const QString &message, int lineNumber)
Definition: Script.h:112
name
char * name()
Definition: exp_saturation.c:45
EmptySourceError
Definition: MuParserScripting.h:131
Folder::findSubfolder
Folder * findSubfolder(const QString &s, bool caseSensitive=true, bool partialMatch=false)
Pointer to the subfolder called s.
Definition: Folder.cpp:91
Script::isCompiled
@ isCompiled
Definition: Script.h:109
MuParserScript::tableCell_Function
static double tableCell_Function(double columnIndex, double rowIndex)
Implements cell_() function for tables.
Definition: MuParserScript.cpp:347
MuParserScript
Evaluate mathematical expressions using muParser.
Definition: MuParserScript.h:42
MuParserScript::MuParserScript
MuParserScript(ScriptingEnv *environment, const QString &code, QObject *context=0, const QString &name="<input>")
Definition: MuParserScript.cpp:139
MuParserScript::tableColumnFunction
static double tableColumnFunction(const mu::string_type::value_type *columnPath)
Implements column() function for tables.
Definition: MuParserScript.cpp:247
MuParserScript::eval
QVariant eval() override
Definition: MuParserScript.cpp:690
Script::name
const QString name() const
Like QObject::name, but with unicode support.
Definition: Script.h:67
MuParserScript::compile
bool compile(bool asFunction=true) override
Pre-process Code and hand it to m_parser.
Definition: MuParserScript.cpp:644
Column::valueAt
double valueAt(int row) const override
Return the double value in row 'row'.
Definition: Column.cpp:260
MuParserScript::statementSeparator
static double statementSeparator(double a, double b)
Implements a;b syntax, where a is evaluated only for side-effects and b is returned.
Definition: MuParserScript.cpp:232
Matrix::numRows
int numRows()
Return the number of rows.
Definition: Matrix.cpp:283
Table.h
Matrix::cell
double cell(int row, int col)
Return the value of the cell as a double.
Definition: Matrix.cpp:134
MuParserScript::setDouble
bool setDouble(double value, const char *name) override
Set a (local, double-valued) variable.
Definition: MuParserScript.cpp:206
MuParserScripting::math_functions
static const mathFunction math_functions[]
Definition: MuParserScripting.h:81
Script::Code
QString Code
Definition: Script.h:107