/***************************************************************************
    File                 : MuParserScript.cpp
    Project              : SciDAVis
    --------------------------------------------------------------------
    Copyright            : (C) 2009 Knut Franke
    Email (use @ for *)  : Knut.Franke*gmx.net
    Description          : Evaluate mathematical expression using muParser

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  This program is free software; you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation; either version 2 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  This program is distributed in the hope that it will be useful,        *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/

// for NAN macro
#define _ISOC99_SOURCE

#include "MuParserScript.h"
#include "MuParserScripting.h"
#include "future/core/column/Column.h"
#include "Table.h"
#include "Matrix.h"
#include "Folder.h"
#include <math.h>
#include <QtCore/QByteArray>
#include <QtCore/QRegExp>

/**
 * \class MuParserScript
 * \brief Evaluate mathematical expressions using muParser
 *
 * Basically, this is an adapter between the standard mu::Parser and the scripting abstraction
 * layer. However, some additional functionality is also provided.
 *
 * The constants pi, Pi and PI as well as e and E are recognized in addition to _pi and _e,
 * respectively. This makes it possible to easily access them without referring to the
 * documentation. For functions, exploratory expression building is supported by interactive
 * lists of available mathematical functions; so redundant names are not needed for them.
 *
 * In addition to basic mathematical functions provided by mu::Parser, several special functions
 * implemented in the GSL are exported. For a complete list, see #muParserScripting::math_functions.
 *
 * Functionality for user-defined variables in expressions optionally supported by muParser is
 * enabled by providing a variable factory (see variableFactory()).
 *
 * Multiple statements separated by semicolon or newline are supported. Because muParser takes
 * newlines to mean "end of expression", they are translated to semicolons before handing the code
 * to muParser. Also, adjacent newlines/semicolons are simplified to a single semicolon, since
 * empty statements are not possible with muParser. Technically speaking, muParser doesn't have
 * statements at all; here, "statement" is referring to expressions which are only evaluated for
 * side effects (namely, assigning values to user-define variables) and whose results are discarded.
 * This is done by defining the semicolon as a low-precedence operator which always returns its
 * right-hand argument. Also, comments are implemented by throwing away any text between a hash
 * mark '#' and a newline during pre-processing in compile().
 *
 * For backwards-compatibility purposes, we need to support table column/cell access functions with
 * variable numbers of (string) arguments as well as overloaded functions accepting either a number
 * or a string; plus (from old QtiPlot versions) the ambiguous syntax col(name) (where name is an
 * unquoted column name, not a muParser expression as one would expect). All of this isn't supported
 * by muParser and has always required more or less extensive hacks on top of it. The approach taken
 * in this implementation is as follows:
 *
 *  - We define the new, muParser-compatible, functions column(), column_(), column__(), cell() and
 *  cell_(). Implemented in tableColumnFunction(), tableColumn_Function(), tableColumn__Function(),
 *  tableCellFunction() and tableCell_Function(), respectively.
 *
 *  - Backwards-compatible functions are implemented as a pre-processing step in compile(), where
 *  anything that looks like a call to a col(), tablecol() or cell() function is scanned for
 *  argument number and types and translated to an equivalent call of a new-style function. In the
 *  case of col(name), name is simply looked up in the current table; if a column of that name
 *  exists, the call is translated to column("name"); else, it's translated to column_(name)
 *  (so name can be a column number or an expression evaluating to a column number).
 *
 * %Note that contrary to previous versions, the internal cell() function takes a column name instead
 * of a column number argument (with legacy calls being translated to cell_() during
 * pre-processing). The idea here is that most of the time, column() and cell() (accepting column
 * names) should be used; usage of column_() and cell_() (accepting a column number) is generally
 * discouraged, since moving columns is now so easy that formulas with column numbers easily break
 * or, worse, silently produce wrong results. Finally, column__() (accepting a table name and a
 * column number) is provided for backwards-compatibility only and highly discouraged, since it
 * means moving columns in one table can mess up formulas in other tables all over the project.
 * While the difference between user-visible and internal signature of cell() may lead to confusion
 * for people reading the source code, this is deemed the lesser evil compared with inconsistent or
 * unclear naming conventions at the user interface.
 */

/**
 * \var MuParserScript::m_parser
 * \brief muParser object doing most of the expression evaluation work
 */

/**
 * \var MuParserScript::m_variables
 * \brief Stores user-visible muParser variables.
 *
 * Variables can be defined either from the C++ side by calling setDouble() or setInt(), or from the
 * muParser side by simply using the variable within an expression (causing muParser to call
 * variableFactory()). In the latter case, variables will evaluate to NaN until assigned a different
 * value within the user-specified expression.
 *
 * While using QHash would provide slightly faster lookups, re-hashing potentially invalidates
 * pointers to values in a hash; so we'd probably have to re-export all variables to muParser with
 * the new memory locations after inserting a new variable. But it's not clear a priori whether it's
 * safe to call mu::Parser::DefineVar() from within variableFactory() (which gets called from
 * muParser's variable handling code), and whether this may lead to muParser re-generating its
 * bytecode (potentially degrading performance). Or maybe a QHash<QByteArray,double*> with explicit
 * memory allocation would be the best solution. Until someone finds the time to do a benchmark and
 * think these issues through, we stick with QMap.
 */

/**
 * \brief MuParserScript instance currently executing eval()
 *
 * All functions exported to muParser need to be static. However, table and matrix access functions
 * need access to the current project and table/matrix (via #Context), so eval() sets this variable
 * before actually evaluating code for the benefit of column(), cell() etc. implementations.
 *
 * \sa tableColumnFunction(), tableColumn_Function(), tableColumn__Function(), tableCellFunction()
 * \sa tableCell_Function(), matrixCellFunction()
 */
MuParserScript *MuParserScript::s_currentInstance = 0;

MuParserScript::MuParserScript(ScriptingEnv *environment, const QString &code, QObject *context, const QString &name)
	: Script(environment, code, context, name) {
	m_parser.SetVarFactory(variableFactory, this);

	// redefine characters for operators to include ";"
	m_parser.DefineOprtChars(
			// standard operator chars as defined in mu::Parser::InitCharSets()
			"abcdefghijklmnopqrstuvwxyz"
			"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
			"+-*^/?<>=#!$%&|~'_"
			// our additions
			";");

	// statement separation needs lower precedence than everything else; assignment has precedence
	// -1, everything else defined in mu::Parser has non-negative precedence
	m_parser.DefineOprt(";", statementSeparator, -2);

	// aliases for _pi and _e
	m_parser.DefineConst("pi", M_PI);
	m_parser.DefineConst("Pi", M_PI);
	m_parser.DefineConst("PI", M_PI);
	m_parser.DefineConst("e", M_E);
	m_parser.DefineConst("E", M_E);

	// tell parser about mathematical functions
	for (const MuParserScripting::mathFunction *i=MuParserScripting::math_functions; i->name; i++)
		if (i->numargs == 1 && i->fun1 != NULL)
			m_parser.DefineFun(i->name, i->fun1);
		else if (i->numargs == 2 && i->fun2 != NULL)
			m_parser.DefineFun(i->name, i->fun2);
		else if (i->numargs == 3 && i->fun3 != NULL)
			m_parser.DefineFun(i->name, i->fun3);

	// tell parser about table/matrix access functions
	if (Context && Context->inherits("Table")) {
		m_parser.DefineFun("column", tableColumnFunction, false);
		m_parser.DefineFun("column_", tableColumn_Function, false);
		m_parser.DefineFun("column__", tableColumn__Function, false);
		m_parser.DefineFun("cell", tableCellFunction);
		m_parser.DefineFun("cell_", tableCell_Function);
	} else if (Context && Context->inherits("Matrix"))
		m_parser.DefineFun("cell", matrixCellFunction);
}

/**
 * \brief muParser callback for registering user-defined variables
 *
 * When encountering an unknown variable within an expression, this function gets called.
 * Memory for the variable is allocated in m_variables and a pointer to the memory location
 * is returned to muParser. New variables are initialized with NaN.
 */
double *MuParserScript::variableFactory(const char *name, void *self) {
	MuParserScript *me = static_cast<MuParserScript *>(self);
	return me->m_variables.insert(QByteArray(name), NAN).operator->();
}

/**
 * \brief Set a (local, double-valued) variable.
 *
 * Other kinds of variables are not supported (integers are converted to doubles).
 *
 * \sa m_variables
 */
bool MuParserScript::setDouble(double value, const char *name) {
	QByteArray baName(name);
	QMap<QByteArray, double>::iterator entry = m_variables.find(baName);
	if (entry == m_variables.end()) {
		// variable is not known yet
		entry = m_variables.insert(baName, value);
		try {
			m_parser.DefineVar(name, entry.operator->());
		} catch (mu::ParserError &e) {
			m_variables.erase(entry);
			emit_error(QString::fromLocal8Bit(e.GetMsg().c_str()), 0);
			return false;
		}
	} else
		// variable is known and only needs to be updated
		*entry = value;
	return true;
}

/**
 * \brief Implements a;b syntax, where a is evaluated only for side-effects and b is returned.
 *
 * Technically, muParser handles only expressions and doesn't have a "statement" concept. However,
 * it does support assignment to variables, which is an expression with side-effects and can be
 * thought of as a statement.
 */
double MuParserScript::statementSeparator(double a, double b) {
	Q_UNUSED(a);
	return b;
}

/**
 * \brief Implements column() function for tables.
 *
 * \arg \c columnPath Path to the column to read data from. See resolveColumnPath().
 *
 * The row to read from is determined by the muParser variable "i" set during iteration of a column
 * formula. For explicitly specifying the row, use cell() instead.
 *
 * \sa tableCellFunction()
 */
double MuParserScript::tableColumnFunction(const char *columnPath) {
	Column *column = s_currentInstance->resolveColumnPath(QString::fromUtf8(columnPath));
	if (!column) return NAN; // failsafe, shouldn't happen
	int row = qRound(s_currentInstance->m_variables["i"]) - 1;
	if (column->isInvalid(row))
		throw new EmptySourceError();
	return column->valueAt(row);
}

/**
 * \brief Implements column_() function for tables.
 *
 * \arg \c columnIndex 1-based index of the column to read data from.
 *
 * It is preferable to use column() instead of this function where possible, because referring to
 * columns via index silently breaks when moving columns.
 *
 * The row to read from is determined by the muParser variable "i" set during iteration of a column
 * formula. For explicitly specifying the row, use cell_() instead.
 *
 * \sa tableCell_Function()
 */
double MuParserScript::tableColumn_Function(double columnIndex) {
	Table *thisTable = qobject_cast<Table*>(s_currentInstance->Context);
	if (!thisTable)
		// improving the error message would break translations
		// TODO: change col() to column() for next minor release
		throw mu::Parser::exception_type(qPrintable(tr("col() works only on tables!")));
	Column *column = thisTable->d_future_table->column(qRound(columnIndex) - 1);
	if (!column)
		throw mu::Parser::exception_type(qPrintable(tr("There's no column %1 in table %2!")
					.arg(qRound(columnIndex)).arg(thisTable->objectName())));
	int row = qRound(s_currentInstance->m_variables["i"]) - 1;
	if (column->isInvalid(row))
		throw new EmptySourceError();
	return column->valueAt(row);
}

/**
 * \brief Implements column__() function for tables.
 *
 * \arg \c tableName Name of table to read data from, which can be anywhere in the project.
 * \arg \c columnIndex 1-based index of column to read data from.
 *
 * This function exists so that we can implement a fully backwards-compatible tablecol() function
 * in compile(). Using it in new projects is highly discouraged, because there's a high risk of
 * messing up column formulas all over the project by simply moving a column.
 *
 * The row to read from is determined by the muParser variable "i" set during iteration of a column
 * formula. It was never possible to explicitly specify the row when referencing external tables,
 * so this is not implemented for the discouraged version. However, see tableCellFunction() for how
 * to get a specific cell with the column specified by name.
 */
double MuParserScript::tableColumn__Function(const char *tableName, double columnIndex) {
	Table *thisTable = qobject_cast<Table*>(s_currentInstance->Context);
	if (!thisTable)
		// improving the error message would break translations
		// TODO: change tablecol() to column() for next minor release
		throw mu::Parser::exception_type(qPrintable(tr("tablecol() works only on tables!")));
	Table *targetTable = thisTable->folder()->rootFolder()->table(tableName, true);
	if (!targetTable)
		throw mu::Parser::exception_type(qPrintable(tr("Couldn't find a table named %1.")
					.arg(tableName)));
	Column *column = targetTable->d_future_table->column(qRound(columnIndex) - 1);
	if (!column)
		throw mu::Parser::exception_type(qPrintable(tr("There's no column %1 in table %2!")
					.arg(qRound(columnIndex)).arg(tableName)));
	int row = qRound(s_currentInstance->m_variables["i"]) - 1;
	if (column->isInvalid(row))
		throw new EmptySourceError();
	return column->valueAt(row);
}

/**
 * \brief Implements cell() function for tables.
 *
 * \arg \c columnPath Path to the column to read data from. See resolveColumnPath().
 * \arg \c rowIndex 1-based index of the row to read data from.
 *
 * \sa tableColumnFunction()
 */
double MuParserScript::tableCellFunction(const char *columnPath, double rowIndex) {
	Column *column = s_currentInstance->resolveColumnPath(QString::fromUtf8(columnPath));
	if (!column) return NAN; // failsafe, shouldn't happen
	int row = qRound(rowIndex) - 1;
	if (column->isInvalid(row))
		throw new EmptySourceError();
	return column->valueAt(row);
}

/**
 * \brief Implements cell_() function for tables.
 *
 * \arg \c columnIndex 1-based index of column to read data from.
 * \arg \c rowIndex 1-based index of row to read data from.
 *
 * It is preferable to use cell() instead of this function where possible, because referring to
 * columns via index silently breaks when moving columns.
 *
 * \sa tableColumn_Function()
 */
double MuParserScript::tableCell_Function(double columnIndex, double rowIndex) {
	Table *thisTable = qobject_cast<Table*>(s_currentInstance->Context);
	if (!thisTable)
		throw mu::Parser::exception_type(qPrintable(tr("cell() works only on tables and matrices!")));
	Column *column = thisTable->d_future_table->column(qRound(columnIndex) - 1);
	if (!column)
		throw mu::Parser::exception_type(qPrintable(tr("There's no column %1 in table %2!")
					.arg(qRound(columnIndex)).arg(thisTable->objectName())));
	int row = qRound(rowIndex) - 1;
	if (column->isInvalid(row))
		throw new EmptySourceError();
	return column->valueAt(row);
}

/**
 * \brief Implements cell() function for matrices.
 *
 * \arg \c rowIndex 1-based index of row to read data from.
 * \arg \c columnIndex 1-based index of column to read data from.
 */
double MuParserScript::matrixCellFunction(double rowIndex, double columnIndex) {
	Matrix *thisMatrix = qobject_cast<Matrix*>(s_currentInstance->Context);
	if (!thisMatrix)
		throw mu::Parser::exception_type(qPrintable(tr("cell() works only on tables and matrices!")));
	int row = qRound(rowIndex) - 1;
	int column = qRound(columnIndex) - 1;
	if (row < 0 || row >= thisMatrix->numRows())
		throw mu::Parser::exception_type(qPrintable(tr("There's no row %1 in matrix %2!").
				arg(qRound(rowIndex)).arg(thisMatrix->objectName())));
	if (column < 0 || column >= thisMatrix->numCols())
		throw mu::Parser::exception_type(qPrintable(tr("There's no column %1 in matrix %2!").
				arg(qRound(columnIndex)).arg(thisMatrix->objectName())));
	return thisMatrix->cell(row, column);
}

/**
 * \brief Look up the column specified by path.
 *
 * Path can be either relative to the current context (e.g., simply the name of a column in the
 * current table) or absolute (relative to the project root). For backwards compatibility, also
 * "table/column" is supported, where "table" is searched for anywhere in the project. This
 * shouldn't be used in new projects though, since it will be problematic once we drop the
 * requirement of project-wide unique table names.
 *
 * Also, it's possible to escape /'s in the path using \\. Column and folder names can already
 * contain slashes and table names will follow in a future release.
 */
Column *MuParserScript::resolveColumnPath(const QString &path) {
	Column *result = 0;

	// Split path into components.
	// While escape handling would be possible using a regular expression, it would require
	// lookbehind assertions, which are currently not supported by QRegExp. Thus, we can't simply
	// use QString::split() and have to explicitly loop over the characters in path.
	QStringList pathComponents;
	QString current;
	for (int i=0; i<path.size(); ++i)
		switch(path.at(i).toAscii()) {
			case '/':
				pathComponents << current;
				current.clear();
				break;
			case '\\':
				if (i+1 < path.size())
					current.append(path.at(++i));
				break;
			default:
				current.append(path.at(i));
				break;
		}
	QString columnName = current;

	Table *table = 0;
	if (pathComponents.isEmpty()) {
		// only column name specified, read from this table
		table = qobject_cast<Table *>(Context);
		if (!table)
			throw mu::Parser::exception_type(qPrintable(tr(
							"Accessing table values is not (yet) supported in this context.")));
	} else {
		// look up the table containing the column
		MyWidget *myContext = qobject_cast<MyWidget *>(Context);
		if (!myContext)
			throw mu::Parser::exception_type(qPrintable(tr(
							"Accessing table values is not (yet) supported in this context.")));
		QString tableName = pathComponents.takeLast();
		if (pathComponents.isEmpty())
			// needed for backwards compatibility, but will be problematic once we drop the requirement
			// of project-wide unique object names
			table = myContext->folder()->rootFolder()->table(tableName, true);
		else {
			Folder *folder;
			if (pathComponents.at(0).isEmpty())
				// absolute path
				folder = myContext->folder()->rootFolder();
			else if (pathComponents.at(0) == "..")
				// relative path
				folder = myContext->folder();
			else
				// invalid path
				throw mu::Parser::exception_type(qPrintable(tr("Couldn't find a table named %1.")
							.arg(pathComponents.join("/")+"/"+tableName)));
			pathComponents.removeFirst();
			foreach(QString f, pathComponents) {
				if (f == "..")
					folder = qobject_cast<Folder*>(folder->parent());
				else
					folder = folder->findSubfolder(f);
				if (!folder)
					throw mu::Parser::exception_type(qPrintable(tr("Couldn't find a table named %1.")
								.arg(pathComponents.join("/")+"/"+tableName)));
			}
			table = folder->table(tableName);
		}
		if (!table)
			throw mu::Parser::exception_type(qPrintable(tr("Couldn't find a table named %1.")
						.arg(pathComponents.join("/")+"/"+tableName)));
	}

	// finally, look up the column in the table
	result = table->d_future_table->column(columnName);
	if (!result)
		throw mu::Parser::exception_type(qPrintable(tr("There's no column named %1 in table %2!")
					.arg(columnName).arg(table->d_future_table->path())));

	return result;
}

/**
 * \brief Do in-place translation of overloaded functions.
 *
 * Recursively replaces:
 * - col("name") with column("name")
 * - col(arg) with column("arg") if the current table contains a column named "arg" and
 *   with column_(arg) otherwise
 * - col("name", row) with cell("name", row)
 * - col(arg, row) with cell("arg", row) if the current table contains a column named "arg" and
 *   with cell_(arg, row) otherwise
 * - tablecol("tableName", "columnName") with column("tableName/columnName")
 * - tablecol("tableName", columnIndex) with column__("tableName", columnIndex)
 * - cell(columnIndex, rowIndex) with cell_(columnIndex, rowIndex)
 *
 * Also escapes occurences of / in table/column names, so that legacy formulas work with the path
 * argument of the new column() and cell() functions (see resolveColumnPath()).
 */
bool MuParserScript::translateLegacyFunctions(QString &input) {
	QRegExp legacyFunction("(\\W||^)(col|tablecol|cell)\\s*\\(");

	int functionStart = legacyFunction.indexIn(input, 0);
	while (functionStart != -1) {
		QStringList arguments;
		int functionEnd = functionStart; // initialization is a failsafe
		QString replacement;

		// parse arguments of function
		QString currentArgument;
		for (int i = functionStart+legacyFunction.matchedLength(), parenthesisLevel = 1;
				parenthesisLevel > 0 && i < input.size();
				i++) {
			switch (input.at(i).toAscii()) {
				case '"':
					currentArgument += '"';
					for (i++; i < input.size() && input.at(i) != QChar('"'); i++)
						if (input.at(i) == QChar('\\')) {
							currentArgument += '\\';
							currentArgument += input.at(++i);
						} else
							currentArgument += input.at(i);
					currentArgument += '"';
					break;
				case '\\':
					currentArgument += '\\';
					currentArgument += input.at(++i);
					break;
				case '(':
					parenthesisLevel++;
					currentArgument += '(';
					break;
				case ')':
					parenthesisLevel--;
					if (parenthesisLevel > 0)
						currentArgument += ')';
					else
						functionEnd = i;
					break;
				case ',':
					if (parenthesisLevel == 1) {
						arguments << currentArgument;
						currentArgument.clear();
					} else
						currentArgument += ',';
					break;
				default:
					currentArgument += input.at(i);
					break;
			}
		}
		arguments << currentArgument;

		// select replacement function call
		Table *table = qobject_cast<Table *>(Context);
		if (legacyFunction.cap(2) == "col") {
			QString columnArgument;
			bool numericColumn = false;
			if (arguments.at(0).startsWith("\"")) {
				// col("name") -> column("name")
				columnArgument = arguments.at(0);
				// do escaping of path argument
				columnArgument.replace("\\","\\\\");
				columnArgument.replace("/", "\\/");
			} else if (table && table->d_future_table->column(arguments.at(0))) {
				// hack for ambiguous legacy syntax:
				// col(name) -> column("name"), if name is a column of the current table
				columnArgument = "\"" + arguments.at(0) + "\"";
				// do escaping of path argument
				columnArgument.replace("\\","\\\\");
				columnArgument.replace("/", "\\/");
			} else {
				// col(expression) -> column_(expression)
				columnArgument = arguments.at(0);
				if (!translateLegacyFunctions(columnArgument)) return false;
				numericColumn = true;
			}
			if (arguments.size() > 1) {
				QString rowArgument = arguments.at(1);
				if (!translateLegacyFunctions(rowArgument)) return false;
				replacement = QString("cell") + (numericColumn ? "_" : "") + "(" + columnArgument + ","
					+ rowArgument + ")";
			} else
				replacement = QString("column") + (numericColumn ? "_" : "") + "(" + columnArgument + ")";
		} else if (legacyFunction.cap(2) == "tablecol") {
			// assert number of arguments == 2
			if (arguments.size() != 2) {
				emit_error(tr("tablecol: wrong number of arguments (need 2, got %1)")
						.arg(arguments.size()), 0);
				compiled = Script::compileErr;
				return false;
			}
			if (arguments.at(1).startsWith("\"")) {
				// tablecol("table", "column") -> column("table/column")
				// do escaping of path argument
				QString tableArgument = arguments.at(0);
				tableArgument.replace("\\","\\\\");
				tableArgument.replace("/", "\\/");
				QString columnArgument = arguments.at(1);
				columnArgument.replace("\\","\\\\");
				columnArgument.replace("/", "\\/");
				// remove quotation marks
				tableArgument.remove(tableArgument.size()-1,1);
				columnArgument.remove(0,1);
				replacement = QString("column(") + tableArgument + "/" + columnArgument + ")";
			} else {
				// tablecol("table", column) -> column__("table", column)
				QString rowArgument = arguments.at(1);
				if (!translateLegacyFunctions(rowArgument)) return false;
				replacement = QString("column__(") + arguments.at(0) + "," + rowArgument + ")";
			}
		} else { // legacyFunction.cap(2) == "cell"
			// assert number of arguments == 2
			if (arguments.size() != 2) {
				emit_error(tr("cell: wrong number of arguments (need 2, got %1)")
						.arg(arguments.size()), 0);
				compiled = Script::compileErr;
				return false;
			}
			if (arguments.at(0).startsWith("\"")) {
				// keep cell("column",row) -- this is new-style syntax
				QString rowArgument = arguments.at(1);
				if (!translateLegacyFunctions(rowArgument)) return false;
				replacement = QString("cell(") + arguments.at(0) + "," + rowArgument + ")";
			} else {
				// cell(column,row) -> cell_(column,row)
				QString columnArgument = arguments.at(0);
				if (!translateLegacyFunctions(columnArgument)) return false;
				QString rowArgument = arguments.at(1);
				if (!translateLegacyFunctions(rowArgument)) return false;
				replacement = QString("cell_(") + columnArgument + "," + rowArgument + ")";
			}
		}

		// do replacement
		if (legacyFunction.cap(1).isEmpty())
			// matched with ^, not \W (lookbehind assertion would be darn handy...)
			input.replace(functionStart, functionEnd-functionStart+1, replacement);
		else
			// need to adjust for the additional matched character
			input.replace(functionStart+1, functionEnd-functionStart, replacement);
		// search for next match, starting at the end of the replaced text
		functionStart = legacyFunction.indexIn(input, functionStart + replacement.length());
	} // while (functionStart != -1)
	return true;
}

/**
 * \brief Pre-process #Code and hand it to #m_parser.
 *
 * This implements some functionality not directly supported by muParser, like overloaded functions
 * and multi-line expressions. See class documentation of MuParserScript for details.
 */
bool MuParserScript::compile(bool asFunction) {
	Q_UNUSED(asFunction); // only needed for Python

	QString intermediate = Code.trimmed(); // pre-processed version of #Code

	// remove comments
	bool inString = false;
	int commentStart = -1;
	for (int i=0; i<intermediate.size(); i++)
		switch (intermediate.at(i).toAscii()) {
			case '"':
				if (commentStart < 0) inString = !inString;
				break;
			case '#':
				if (!inString) commentStart = i;
				break;
			case '\n':
				if (commentStart >= 0) {
					intermediate.remove(commentStart, i-commentStart);
					i = commentStart;
					commentStart = -1;
				}
				break;
		}
	if (commentStart >= 0)
		intermediate.remove(commentStart, intermediate.size()-commentStart);

	// simplify statement separators
	intermediate.replace(QRegExp("([;\\n]\\s*)+"),"; ");

	// recursively translate legacy functions col(), tablecol() and cell()
	if (Context && Context->inherits("Table"))
		if (!translateLegacyFunctions(intermediate))
			return false;

	try {
		m_parser.SetExpr(qPrintable(intermediate));
	} catch (mu::ParserError &e) {
		emit_error(QString::fromLocal8Bit(e.GetMsg().c_str()), 0);
		return false;
	}

	compiled = isCompiled;
	return true;
}

QVariant MuParserScript::eval() {
	if (compiled != Script::isCompiled && !compile())
		return QVariant();
	try {
		// see documentation of s_currentInstance for explanation
		s_currentInstance = this;
		return m_parser.Eval();
	} catch (EmptySourceError *e) {
		// formula tried to access a table cell marked as invalid
		return "";
	} catch (mu::ParserError &e) {
		emit_error(QString::fromLocal8Bit(e.GetMsg().c_str()), 0);
		return QVariant();
	}
}
