diff --git a/src/backends/qalculate/qalculateexpression.h b/src/backends/qalculate/qalculateexpression.h --- a/src/backends/qalculate/qalculateexpression.h +++ b/src/backends/qalculate/qalculateexpression.h @@ -37,18 +37,15 @@ QTemporaryFile *m_tempFile; QString m_message; - enum MsgType { MSG_NONE=0, MSG_INFO=1, MSG_WARN=2, MSG_ERR=4 }; - + enum MsgType { MSG_NONE=0, MSG_INFO=1, MSG_WARN=2, MSG_ERR=4 }; + void evaluatePlotCommand(); - void evaluateLoadVariablesCommand(); - void evaluateSaveVariablesCommand(); - QString parseForFilename(QString argument, QString usage); bool stringToBool(const QString&, bool*); void deletePlotDataParameters(const std::vector&); void showMessage(QString msg, MessageType mtype); int checkForCalculatorMessages(); - void updateVariables(MathStructure); + void updateVariables(); QSharedPointer printOptions(); EvaluationOptions evaluationOptions(); ParseOptions parseOptions(); @@ -59,7 +56,9 @@ ~QalculateExpression(); void evaluate(); - void interrupt() {} + void interrupt(); + void parseOutput(QString& output); + void parseError(QString& error); }; #endif diff --git a/src/backends/qalculate/qalculateexpression.cpp b/src/backends/qalculate/qalculateexpression.cpp --- a/src/backends/qalculate/qalculateexpression.cpp +++ b/src/backends/qalculate/qalculateexpression.cpp @@ -66,16 +66,20 @@ void QalculateExpression::evaluate() { + /* + Use Api for: + * help + * plot + Use qalc for any other command + */ setStatus(Cantor::Expression::Computing); - m_message = QLatin1String(""); - if (command().isEmpty()) { + setStatus(Cantor::Expression::Done); return; } - QStringList commands=command().split(QLatin1Char('\n')); - QString resultString; + QStringList commands = command().split(QLatin1Char('\n')); foreach(const QString& command, commands) { if (command.contains(QLatin1String("help"))) { @@ -90,156 +94,50 @@ evaluatePlotCommand(); return; } - else if (command.trimmed().startsWith(QLatin1String("saveVariables")) && - (command.indexOf(QLatin1String("saveVariables"))+13 == command.size() || - command[command.indexOf(QLatin1String("saveVariables"))+13].isSpace())) { - evaluateSaveVariablesCommand(); - return; - } - else if (command.trimmed().startsWith(QLatin1String("loadVariables")) && - (command.indexOf(QLatin1String("loadVariables"))+13 == command.size() || - command[command.indexOf(QLatin1String("loadVariables"))+13].isSpace())) { - evaluateLoadVariablesCommand(); - return; - } - - string expression = unlocalizeExpression(command); - - qDebug() << "EXPR: " << QLatin1String(expression.c_str()); - - EvaluationOptions eo = evaluationOptions(); - - MathStructure result = CALCULATOR->calculate(expression, eo); - - // update the answer variables - static_cast(session())->setLastResult(result); - - // error handling - if (checkForCalculatorMessages() & (MSG_WARN | MSG_WARN)) - return; - - updateVariables(CALCULATOR->parse(expression, eo.parse_options)); - - QSharedPointer po = printOptions(); - - result.format(*po); - - resultString+=QLatin1String(result.print(*po).c_str()) + QLatin1Char('\n'); - } + // we are here because the commands entered by user are regular commands. We would have returned by now otherwise + QalculateSession* currentSession = dynamic_cast(session()); + currentSession->runExpression(); - setResult(new Cantor::TextResult(resultString)); - setStatus(Done); } -void QalculateExpression::evaluateSaveVariablesCommand() +void QalculateExpression::parseOutput(QString& output) { - QString argString = command().mid(command().indexOf(QLatin1String("saveVariables"))+13); - argString = argString.trimmed(); - - QString usage = i18n("Usage: saveVariables file"); - - QString fileName = parseForFilename(argString, usage); - if (fileName.isNull()) - return; - - // We want to save Temporary variables, but Qalculate does not. - std::vector variables = CALCULATOR->variables; - // If somebody saves his variables in Cantor_Internal_Temporary - // he deserves unexpected behavior. - std::string tmpCategory = "Temporary"; - std::string newCategory = "Cantor_Internal_Temporary"; - for (size_t i = 0; i < variables.size(); ++i) { - if (variables[i]->category() == tmpCategory) - variables[i]->setCategory(newCategory); - } - - int res = CALCULATOR->saveVariables(fileName.toLatin1().data()); - - for (size_t i = 0; i < variables.size(); ++i) { - if (variables[i]->category() == newCategory) - variables[i]->setCategory(tmpCategory); - } + output.remove(QLatin1String(">")); + output = output.trimmed(); + + qDebug() << "output from qalc for command: " << command() << " " << output << endl; + setResult(new Cantor::TextResult(output)); + // update the variable model + updateVariables(); + setStatus(Cantor::Expression::Done); +} - if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { - return; - } - if (res < 0) { - showMessage(i18n("Saving failed."), MESSAGE_ERROR); - return; +void QalculateExpression::updateVariables() +{ + QalculateSession* currentSession = dynamic_cast(session()); + QMap &variables = currentSession->variables; + Cantor::DefaultVariableModel* model = static_cast(currentSession->variableModel()); + QMap::const_iterator it = variables.constBegin(); + while (it != variables.constEnd()) { + model->addVariable(it.key(), it.value()); + ++it; } - - setStatus(Done); } -void QalculateExpression::evaluateLoadVariablesCommand() +void QalculateExpression::parseError(QString& error) { - QString argString = command().mid(command().indexOf(QLatin1String("loadVariables"))+13); - argString = argString.trimmed(); - - QString usage = i18n("Usage: loadVariables file"); - - QString fileName = parseForFilename(argString, usage); - if (fileName.isNull()) - return; - - int res = CALCULATOR->loadDefinitions(fileName.toLatin1().data()); - if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { - return; - } - if (res < 0) { - showMessage(i18n("Loading failed."), MESSAGE_ERROR); - return; - } - - // We have to store temporary variables in a different category - // (see parseSaveVariablesCommand()) - std::vector variables = CALCULATOR->variables; - std::string tmpCategory = "Temporary"; - std::string newCategory = "Cantor_Internal_Temporary"; - - for (size_t i = 0; i < variables.size(); ++i) { - if (variables[i]->category() == newCategory) - variables[i]->setCategory(tmpCategory); - } - - setStatus(Done); + error.remove(QLatin1String(">")); + error = error.trimmed(); + qDebug() << "Error from qalc for command: " << command() << " " << error << endl; + setErrorMessage(error); + setStatus(Cantor::Expression::Error); } -QString QalculateExpression::parseForFilename(QString argument, QString usage) +void QalculateExpression::interrupt() { - if (argument.isEmpty()) { - showMessage(usage, MESSAGE_ERROR); - return QString(); - } - - QString fileName = QLatin1String(""); - QChar sep = QLatin1Char('\0'); - int i = 0; - if (argument[0] == QLatin1Char('\'') || argument[0] == QLatin1Char('"')) { - sep = argument[0]; - i = 1; - } - while (i < argument.size() && !argument[i].isSpace() && - argument[i] != sep) { - if (argument[i] == QLatin1Char('\\') && i < argument.size()-1) - ++i; - fileName += argument[i]; - ++i; - } - - if (sep != QLatin1Char('\0') && i == argument.size()) { - showMessage(i18n("missing %1", sep), MESSAGE_ERROR); - return QString(); - } - - if (i < argument.size() - 1) { - showMessage(usage, MESSAGE_ERROR); - return QString(); - } - - return fileName; + setStatus(Cantor::Expression::Interrupted); } void QalculateExpression::evaluatePlotCommand() @@ -937,31 +835,7 @@ ); } -void QalculateExpression::updateVariables(MathStructure command) -{ - Cantor::DefaultVariableModel* model = - static_cast(session()->variableModel()); - QStack stack; - stack.push(&command); - QSharedPointer po = printOptions(); - while (!stack.isEmpty()) { - MathStructure* current = stack.pop(); - if (current->isFunction() && current->function()->name() == "save" && - current->countChildren() >= 2 && current->getChild(2)->isSymbolic()) - { - // I'd like to use CALCULATOR->getVariable and KnownVariable::get, - // but that doesn't work for built in variables, as it keeps - // returning the old value - std::string name = current->getChild(2)->symbol(); - MathStructure m = CALCULATOR->calculate(name, evaluationOptions()); - m.format(*po); - model->addVariable(QLatin1String(name.c_str()), QLatin1String(m.print(*po).c_str())); - } - for (size_t i = 0; i < current->countChildren(); ++i) { - stack.push(current->getChild(i+1)); - } - } -} + QSharedPointer QalculateExpression::printOptions() { diff --git a/src/backends/qalculate/qalculatesession.h b/src/backends/qalculate/qalculatesession.h --- a/src/backends/qalculate/qalculatesession.h +++ b/src/backends/qalculate/qalculatesession.h @@ -20,8 +20,11 @@ #define QALCULATE_SESSION_H #include "session.h" +#include "qalculateexpression.h" #include +#include +#include #include #include @@ -31,14 +34,31 @@ } class QalculateEngine; +class QProcess; + class QalculateSession : public Cantor::Session { Q_OBJECT private: - QList m_ansVariables; Cantor::DefaultVariableModel* m_variableModel; + QProcess* m_process; + QalculateExpression* m_currentExpression; + QString m_output; + QString m_finalOutput; + QString m_currentCommand; + QString m_saveError; + QQueue m_expressionQueue; + QQueue m_commandQueue; + bool m_isSaveCommand; + + +private: + void runExpressionQueue(); + void runCommandQueue(); + QString parseSaveCommand(QString& currentCmd); + void storeVariables(QString& currentCmd, QString output); public: QalculateSession( Cantor::Backend* backend); @@ -54,8 +74,17 @@ virtual Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd); virtual QSyntaxHighlighter* syntaxHighlighter(QObject* parent); - void setLastResult(MathStructure); + void runExpression(); QAbstractItemModel* variableModel(); + +public: + QMap variables; + +public Q_SLOTS: + void readOutput(); + void readError(); + void processStarted(); + void currentExpressionStatusChanged(Cantor::Expression::Status status); }; #endif diff --git a/src/backends/qalculate/qalculatesession.cpp b/src/backends/qalculate/qalculatesession.cpp --- a/src/backends/qalculate/qalculatesession.cpp +++ b/src/backends/qalculate/qalculatesession.cpp @@ -20,12 +20,14 @@ #include "settings.h" #include "qalculatesession.h" -#include "qalculateexpression.h" #include "qalculatecompletionobject.h" #include "qalculatehighlighter.h" #include "defaultvariablemodel.h" #include +#include +#include +#include #include #include @@ -38,66 +40,370 @@ QalculateSession::QalculateSession( Cantor::Backend* backend) : Session(backend), - m_variableModel(new Cantor::DefaultVariableModel(this)) + m_variableModel(new Cantor::DefaultVariableModel(this)), + m_process(0), + m_currentExpression(0), + m_isSaveCommand(false) { + /* + qalc does all of this by default but we still need the CALCULATOR instance for plotting + graphs + */ + if ( !CALCULATOR ) { new Calculator(); CALCULATOR->loadGlobalDefinitions(); CALCULATOR->loadLocalDefinitions(); CALCULATOR->loadExchangeRates(); } - // from qalc.cc in libqalculate - std::string ansName = "ans"; - // m_undefined is not a variable in this class, but is defined in - // libqalculate/includes.h - m_ansVariables.append(static_cast(CALCULATOR->addVariable(new KnownVariable("Temporary", ansName, m_undefined, "Last Answer", false)))); - m_ansVariables[0]->addName("answer"); - m_ansVariables[0]->addName(ansName + "1"); - m_ansVariables.append(static_cast(CALCULATOR->addVariable(new KnownVariable("Temporary", ansName+"2", m_undefined, "Answer 2", false)))); - m_ansVariables.append(static_cast(CALCULATOR->addVariable(new KnownVariable("Temporary", ansName+"3", m_undefined, "Answer 3", false)))); - m_ansVariables.append(static_cast(CALCULATOR->addVariable(new KnownVariable("Temporary", ansName+"4", m_undefined, "Answer 4", false)))); - m_ansVariables.append(static_cast(CALCULATOR->addVariable(new KnownVariable("Temporary", ansName+"5", m_undefined, "Answer 5", false)))); } QalculateSession::~QalculateSession() { CALCULATOR->abort(); + if(m_process) + m_process->kill(); } void QalculateSession::login() { - if(!QalculateSettings::autorunScripts().isEmpty()){ - QString autorunScripts = QalculateSettings::self()->autorunScripts().join(QLatin1String("\n")); - evaluateExpression(autorunScripts, QalculateExpression::DeleteOnFinish); + /* we will , most probably, use autoscripts for setting the mode , evaulate options, print options etc */ + + // if(!QalculateSettings::autorunScripts().isEmpty()){ + // QString autorunScripts = QalculateSettings::self()->autorunScripts().join(QLatin1String("\n")); + // + // evaluateExpression(autorunScripts, QalculateExpression::DeleteOnFinish); + // } + + /* + set up the process here. The program path , arguments(if any),channel modes , and connections should all be set up here. + once the setup is complete, start the process and inform the worksheet that we are ready + */ + m_process = new QProcess(this); + + m_process->setProgram(QStandardPaths::findExecutable(QLatin1String("qalc"))); + m_process->setProcessChannelMode(QProcess::SeparateChannels); + + connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput())); + connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readError())); + connect(m_process, SIGNAL(started()), this, SLOT(processStarted())); + + m_process->start(); +} + +void QalculateSession::readOutput() +{ + while(m_process->bytesAvailable()) { + m_output.append(QString::fromLocal8Bit(m_process->readLine())); + qDebug() << m_output << endl; + } + + if(m_currentExpression && !m_output.isEmpty() && m_output.trimmed().endsWith(QLatin1String(">"))) { + + // check if the commandQueue is empty or not . if it's not empty run the "runCommandQueue" function. + // store the output in finalOutput and clear m_output + + if(m_currentCommand.trimmed().isEmpty()) + m_output.clear(); + + if(!m_output.toLower().contains(QLatin1String("error")) && m_isSaveCommand) { + storeVariables(m_currentCommand, m_output); + m_isSaveCommand = false; + } + + m_output = m_output.trimmed(); + m_output.remove(m_currentCommand); + if (!m_output.isEmpty()) + m_finalOutput.append(m_output); + + // we tried to perform a save operation but failed(see parseSaveCommand()).In such a case + // m_output will be empty but m_saveError will contain the error message. + if(!m_saveError.isEmpty()) { + m_finalOutput.append(m_saveError); + m_saveError.clear(); + } + + m_finalOutput.append(QLatin1String("\n")); + m_output.clear(); + + + + if (!m_commandQueue.isEmpty()) + runCommandQueue(); + else { + qDebug () << "parsing output: " << m_finalOutput << endl; + m_currentExpression->parseOutput(m_finalOutput); + m_finalOutput.clear(); + } + + + } +} + +void QalculateSession::storeVariables(QString& currentCmd, QString output) +{ + + // internally we pass save(value,variable) command to qlac to save the variables. see parseSaveCommand() + // TODO: if the user if trying to override a default variable(constansts etc) or an existing variable, ask the user if he/she wants to override it or not. + + qDebug() << "save command " << currentCmd << endl; + + /** + if we have reached here, we expect our variable model to be updated with new variables. + In case the variable model is not updated, it most probably because we were not able to successfully parse the + current command and ouput to extract variable and value + + This is probably not the best way to get the variable and value. + But since qalc does not provide a way to get the list of variables, we will have to stick to parsing + **/ + QString value; + QString var; + QRegExp regex; + // find the value + regex.setPattern(QLatin1String("[\\s\\w\\W]+=\\s*([\\w\\W]+)")); + if(regex.exactMatch(output)) { + int pos = regex.indexIn(output); + if (pos > -1) { + value = regex.cap(1); + value = value.trimmed(); + value.replace(QLatin1String("\n"), QLatin1String("")); + value.remove(QLatin1String(">")); + } } - changeStatus(Cantor::Session::Done); + //find the varaiable. + // ex1: currentCmd = save(10, var_1,category, title): var_1 = variable + // ex2: currentCmd = save(planet(jupter,mass), jupiter_mass, category, title): jupiter_mass = variable + // Not the best regex. Cab be improved + regex.setPattern(QLatin1String("\\s*save\\s*\\(\\s*[\\s\\w]+\\s*,([\\s\\w]+),*[\\w\\W]*\\)\\s*;*$|\\s*save\\s*\\(\\s*[\\s\\w\\W]+\\)\\s*,([\\s\\w]+),*[\\w\\W]*\\)\\s*;*$")); + if(regex.exactMatch(currentCmd)) { + int pos = regex.indexIn(currentCmd); + if (pos > -1) { + if(!regex.cap(1).trimmed().isEmpty()) + var = regex.cap(1).trimmed(); + else + var = regex.cap(2).trimmed(); + + var = var.trimmed(); + var.replace(QLatin1String("\n"), QLatin1String("")); + var.remove(QLatin1String(">")); + } + } + if(!value.isEmpty() && !var.isEmpty()) + variables.insert(var, value); + +} + +void QalculateSession::readError() +{ + + QString error = QLatin1String(m_process->readAllStandardError()); + if(m_currentExpression) { + m_currentExpression->parseError(error); + } +} + +void QalculateSession::processStarted() +{ + qDebug() << "process started " << m_process->program() << m_process->processId() << endl; emit ready(); } void QalculateSession::logout() { + qDebug () << "logging out " << endl; + if(m_process) { + m_process->write("quit\n"); + if(!m_process->waitForFinished(1000)) + m_process->kill(); + } } void QalculateSession::interrupt() { - changeStatus(Cantor::Session::Done); + qDebug () << "interrupting .... " << endl; + if(m_currentExpression) + m_currentExpression->interrupt(); + + m_commandQueue.clear(); + m_expressionQueue.clear(); + m_output.clear(); + m_finalOutput.clear(); + m_currentCommand.clear(); + m_currentExpression = 0; + +} + +void QalculateSession::runExpression() +{ + + const QString& command = m_currentExpression->command(); + foreach(const QString& cmd, command.split(QLatin1Char('\n'))) { + m_commandQueue.enqueue(cmd); + } + runCommandQueue(); + +} + + +void QalculateSession::runCommandQueue() +{ + if (!m_commandQueue.isEmpty()) { + m_currentCommand = m_commandQueue.dequeue(); + // parse the current command if it's a save/load/store command + if( m_currentCommand.toLower().trimmed().startsWith(QLatin1String("save")) || + m_currentCommand.toLower().trimmed().startsWith(QLatin1String("store")) || + m_currentCommand.trimmed().startsWith(QLatin1String("saveVariables"))) { + + m_currentCommand = parseSaveCommand(m_currentCommand); + } + + + m_currentCommand = m_currentCommand.trimmed(); + m_currentCommand += QLatin1String("\n"); + m_process->write(m_currentCommand.toLocal8Bit()); + + } +} + +QString QalculateSession::parseSaveCommand(QString& currentCmd) +{ + /* + make sure the command is: + * fomatted correctly. e.g if the command is save(value,variable), we have to make sure that there is no space between save and '(', otherwise qalc + waits for user input which is not supported by us as of now + * supported save commands: save(value,variable,[category],[title]), save definitions, save mode, save var, store var, + saveVariables filename + */ + + QRegExp regex; + regex.setCaseSensitivity(Qt::CaseInsensitive); + + regex.setPattern(QLatin1String("\\s*save\\s*definitions\\s*")); + if(regex.exactMatch(currentCmd)) { + // save the variables in ~/.cantor/backends/qalculate/definitions + currentCmd.clear(); + return currentCmd; + } + + regex.setPattern(QLatin1String("\\s*save\\s*mode\\s*")); + if(regex.exactMatch(currentCmd)) { + // save the mode in ~/.cantor/backends/qalculate/cantor_qalc.cfg + currentCmd.clear(); + return currentCmd; + } + + regex.setPattern(QLatin1String("\\s*saveVariables\\s*[\\w\\W]+")); + if(regex.exactMatch(currentCmd)) { + // save the variables in a file + currentCmd.clear(); + return currentCmd; + } + + + regex.setPattern(QLatin1String("\\s*store\\s*([a-zA-Z_]+[\\w]*)|\\s*save\\s*([a-zA-Z_]+[\\w]*)")); + if(regex.exactMatch(currentCmd)) { + m_isSaveCommand = true; + int pos = regex.indexIn(currentCmd); + if(pos > -1) { + if(!regex.cap(1).trimmed().isEmpty()) + currentCmd = QStringLiteral("save(%1, %2)").arg(QStringLiteral("ans")).arg(regex.cap(1).trimmed()); + else + currentCmd = QStringLiteral("save(%1, %2)").arg(QStringLiteral("ans")).arg(regex.cap(2).trimmed()); + + return currentCmd; + } + } + + regex.setPattern(QLatin1String("\\s*save\\s*(\\([\\w\\W]+\\))\\s*;*$")); + if(regex.exactMatch(currentCmd)) { + m_isSaveCommand = true; + int pos = regex.indexIn(currentCmd); + if (pos > -1) { + currentCmd = QStringLiteral("save%1").arg(regex.cap(1).trimmed()); + return currentCmd; + } + } + + /* + If we have not returned by this point, it's because: + * we did not parse the save command properly. This might be due to malformed regular expressions. + * or the commnad given by the user is malformed. More likely to happen + In both these cases we will simply return an empty string because we don't want qalc to run malformed queries, + else it would wait for user input and hence Qprocess would never return a complete output and the expression will remain in + 'calculating' state + */ + m_saveError = currentCmd + QLatin1String("\nError: Could not save.\n"); + return QLatin1String(""); + +} + +void QalculateSession::currentExpressionStatusChanged(Cantor::Expression::Status status) +{ + // depending on the status of the expression change the status of the session; + switch (status) { + + case Cantor::Expression::Computing: + break; + case Cantor::Expression::Interrupted: + changeStatus(Cantor::Session::Done); + break; + case Cantor::Expression::Done: + case Cantor::Expression::Error: + qDebug() << " ****** STATUS " << status; + changeStatus(Cantor::Session::Done); + if(m_expressionQueue.size() > 0) + m_expressionQueue.dequeue(); + if(!m_expressionQueue.isEmpty()) + runExpressionQueue(); + } } Cantor::Expression* QalculateSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) { - QalculateExpression* expr = new QalculateExpression(this); - expr->setFinishingBehavior(behave); + + qDebug() << " ** evaluating expression: " << cmd << endl; + qDebug() << " size of expression queue: " << m_expressionQueue.size() << endl; changeStatus(Cantor::Session::Running); + + QalculateExpression* expr = new QalculateExpression(this); + expr->setFinishingBehavior(behave); expr->setCommand(cmd); - expr->evaluate(); - changeStatus(Cantor::Session::Done); + + m_expressionQueue.enqueue(expr); + runExpressionQueue(); return expr; + } +void QalculateSession::runExpressionQueue() +{ + if(!m_expressionQueue.isEmpty()) { + + if(!m_currentExpression) + m_currentExpression = m_expressionQueue.head(); + + else { + /* there was some expression that was being executed by cantor. We run the new expression only + if the current expression's status is 'Done' or 'Error', if not , we simply return + */ + Cantor::Expression::Status expr_status = m_currentExpression->status(); + if(expr_status != Cantor::Expression::Done && expr_status != Cantor::Expression::Error) + return; + } + + m_currentExpression = m_expressionQueue.head(); + connect(m_currentExpression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); + // start processing the expression + m_currentExpression->evaluate(); + + } +} + + Cantor::CompletionObject* QalculateSession::completionFor(const QString& command, int index) { return new QalculateCompletionObject(command, index, this); @@ -113,13 +419,6 @@ return new QalculateHighlighter(parent); } -void QalculateSession::setLastResult(MathStructure result) -{ - for (int i = m_ansVariables.size()-1; i >0 ; --i) { - m_ansVariables[i]->set(m_ansVariables[i-1]->get()); - } - m_ansVariables[0]->set(result); -} QAbstractItemModel* QalculateSession::variableModel() {