diff --git a/src/backends/R/CMakeLists.txt b/src/backends/R/CMakeLists.txt --- a/src/backends/R/CMakeLists.txt +++ b/src/backends/R/CMakeLists.txt @@ -11,6 +11,7 @@ rcompletionobject.cpp rhighlighter.cpp rsettingswidget.cpp + rkeywords.cpp ) kconfig_add_kcfg_files(RBackend_SRCS settings.kcfgc) diff --git a/src/backends/R/rexpression.cpp b/src/backends/R/rexpression.cpp --- a/src/backends/R/rexpression.cpp +++ b/src/backends/R/rexpression.cpp @@ -100,14 +100,17 @@ void RExpression::parseError(QString error) { - error.replace(command(), QLatin1String("")); - error.replace(QLatin1String(">"), QLatin1String("")); - error = error.trimmed(); + if(!error.isEmpty()) { - setResult(new Cantor::TextResult(error)); + error.replace(command(), QLatin1String("")); + error.replace(QLatin1String(">"), QLatin1String("")); + error = error.trimmed(); - setStatus(Cantor::Expression::Error); - setErrorMessage(error); + setResult(new Cantor::TextResult(error)); + + setStatus(Cantor::Expression::Error); + setErrorMessage(error); + } } void RExpression::interrupt() diff --git a/src/backends/R/rhighlighter.h b/src/backends/R/rhighlighter.h --- a/src/backends/R/rhighlighter.h +++ b/src/backends/R/rhighlighter.h @@ -31,27 +31,9 @@ RHighlighter( QObject* parent); ~RHighlighter(); - protected: - void highlightBlock(const QString &text); - public Q_SLOTS: - void refreshSyntaxRegExps(); - - Q_SIGNALS: - void syntaxRegExps(QVector&,QVector&); - - private: - inline void formatRule(const QRegExp &p, const QTextCharFormat &fmt, const QString& text,bool shift=false); - inline void massFormat(const QVector& rules, const QTextCharFormat &fmt, const QString& text,bool shift=false); - - static const QStringList keywords_list; - static const QStringList operators_list; - static const QStringList specials_list; - QVector keywords; - QVector operators; - QVector specials; - QVector functions; - QVector variables; + void updateHighlighter(); + }; #endif /* _RHIGHLIGHTER_H */ diff --git a/src/backends/R/rhighlighter.cpp b/src/backends/R/rhighlighter.cpp --- a/src/backends/R/rhighlighter.cpp +++ b/src/backends/R/rhighlighter.cpp @@ -20,77 +20,28 @@ */ #include "rhighlighter.h" - +#include "rkeywords.h" #include #include -const QStringList RHighlighter::keywords_list=QStringList() - << QLatin1String("if") << QLatin1String("else") << QLatin1String("switch") << QLatin1String("while") << QLatin1String("for") << QLatin1String("repeat") << QLatin1String("function") << QLatin1String("in") - << QLatin1String("next") << QLatin1String("break") << QLatin1String("TRUE") << QLatin1String("FALSE") << QLatin1String("NULL") << QLatin1String("NA") << QLatin1String("NA_integer_") << QLatin1String("NA_real_") - << QLatin1String("NA_complex_") << QLatin1String("NA_character_") << QLatin1String("Inf") << QLatin1String("NaN"); - -const QStringList RHighlighter::operators_list=QStringList() - << QLatin1String("(\\+|\\-|\\*|/|<-|->|<=|>=|={1,2}|\\!=|\\|{1,2}|&{1,2}|:{1,3}|\\^|@|\\$|~)((?!(\\+|\\-|\\*|/|<-|->|<=|>=|=|\\!=|\\||&|:|\\^|@|\\$|~))|$)") - << QLatin1String("%[^%]*%"); // Taken in Kate highlighter - -const QStringList RHighlighter::specials_list=QStringList() - << QLatin1String("BUG") << QLatin1String("TODO") << QLatin1String("FIXME") << QLatin1String("NB") << QLatin1String("WARNING") << QLatin1String("ERROR"); - RHighlighter::RHighlighter(QObject* parent) : Cantor::DefaultHighlighter(parent) { - foreach (const QString& s, keywords_list) - keywords.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); - foreach (const QString& s, operators_list) - operators.append(QRegExp(s)); - foreach (const QString& s, specials_list) - specials.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); -} - -RHighlighter::~RHighlighter() -{ -} + RKeywords* instance = RKeywords::getInstance(); -void RHighlighter::refreshSyntaxRegExps() -{ - emit syntaxRegExps(variables,functions); -} + addKeywords(instance->getKeywords()); + addFunctions(instance->getFunctions()); + addRules(instance->getOperators(), operatorFormat()); + addRules(instance->getSpecials(), commentFormat()); -// FIXME: due to lack of lookbehinds in QRegExp here we use a flag showing if we need to shift the boundary of formating -// to make up for the accidently matched character -void RHighlighter::formatRule(const QRegExp &p, const QTextCharFormat &fmt, const QString& text,bool shift) -{ - int index = p.indexIn(text); - while (index >= 0) { - int length = p.matchedLength(); - setFormat(index+(shift?1:0), length-(shift?1:0), fmt); - index = p.indexIn(text, index + length); - } } -void RHighlighter::massFormat(const QVector &p, const QTextCharFormat &fmt, const QString& text,bool shift) +RHighlighter::~RHighlighter() { - foreach (const QRegExp &rule, p) - formatRule(rule,fmt,text,shift); } - -void RHighlighter::highlightBlock(const QString& text) +void RHighlighter::updateHighlighter() { - if(text.isEmpty()) - return; - - //Do some backend independent highlighting (brackets etc.) - DefaultHighlighter::highlightBlock(text); - - //Let's mark every functionlike call as an error, then paint right ones in their respective format - // TODO: find more elegant solution not involving double formatting - formatRule(QRegExp(QLatin1String("\\b[A-Za-z0-9_]+(?=\\()")),errorFormat(),text); + qDebug () << RKeywords::getInstance()->getFunctions().size() << endl; + addFunctions(RKeywords::getInstance()->getFunctions()); - //formatRule(QRegExp("[^A-Za-z_]-?([0-9]+)?(((e|i)?-?)|\\.)[0-9]*L?"),numberFormat(),text,true); // TODO: errorneous number formats, refine - massFormat(keywords,keywordFormat(),text); - massFormat(operators,operatorFormat(),text); - massFormat(specials,commentFormat(),text); // FIXME must be distinc - massFormat(functions,functionFormat(),text); - massFormat(variables,variableFormat(),text); - formatRule(QRegExp(QLatin1String("\"[^\"]+\"")),stringFormat(),text); // WARNING a bit redundant } diff --git a/src/backends/R/rkeywords.h b/src/backends/R/rkeywords.h new file mode 100644 --- /dev/null +++ b/src/backends/R/rkeywords.h @@ -0,0 +1,57 @@ +/* + 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. + + --- + Copyright (C) 2017 Rishabh Gupta + */ + +#include +#include + +/** + * RKeywords is used to maintain a list of all the built-ins available in R language + * Built-ins are: keywords(if,else,etc), functions(min(), max(),etc), operators(+,-, etc) + * List of keywords and operators can be found at https://cran.r-project.org/doc/manuals/R-lang.pdf + * Other than built-ins, it's also used to maintain a list of specials(TODO,WARNING etc) + * + */ + +class RKeywords +{ +private: + RKeywords(); + static RKeywords* _instance; + QString m_builtIns; + QStringList m_keywordsList; + QStringList m_functionsList; + QStringList m_specialsList; + QStringList m_operatorsList; + + bool isKeyword(QString& key); + bool isOperator(QString& key); + + +public: + + static RKeywords* getInstance(); + void setupBuiltIns(QString& builtIns); + void parseBuiltIns(); + QStringList& getKeywords(); + QStringList& getFunctions(); + QStringList& getSpecials(); + QStringList& getOperators(); + +}; diff --git a/src/backends/R/rkeywords.cpp b/src/backends/R/rkeywords.cpp new file mode 100644 --- /dev/null +++ b/src/backends/R/rkeywords.cpp @@ -0,0 +1,138 @@ +/* + 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. + + --- + Copyright (C) 2017 Rishabh Gupta + */ + + +#include "rkeywords.h" +#include +#include +#include + +RKeywords* RKeywords::_instance = 0; + +RKeywords::RKeywords() +{ + m_keywordsList << QLatin1String("if") << QLatin1String("else") << QLatin1String("switch") + << QLatin1String("while") << QLatin1String("for") << QLatin1String("repeat") << QLatin1String("function") + << QLatin1String("in") << QLatin1String("next") << QLatin1String("break") << QLatin1String("TRUE") + << QLatin1String("FALSE") << QLatin1String("NULL") << QLatin1String("NA") << QLatin1String("NA_integer_") + << QLatin1String("NA_real_") << QLatin1String("NA_complex_") << QLatin1String("NA_character_") + << QLatin1String("Inf") << QLatin1String("NaN"); + + m_operatorsList << QLatin1String("+") << QLatin1String("-") << QLatin1String("*") << QLatin1String("/") + << QLatin1String("%%") << QLatin1String("^") << QLatin1String(">") << QLatin1String("<") + << QLatin1String(">=") << QLatin1String("<=") << QLatin1String("==") << QLatin1String("!=") + << QLatin1String("!") << QLatin1String("&") << QLatin1String("|") << QLatin1String("~") + << QLatin1String("<-") << QLatin1String("->") << QLatin1String("$") << QLatin1String("$") + << QLatin1String(":"); + + m_specialsList << QLatin1String("BUG") << QLatin1String("TODO") << QLatin1String("FIXME") << QLatin1String("NB") << QLatin1String("WARNING") << QLatin1String("ERROR"); + + +} + + +RKeywords* RKeywords::getInstance() +{ + if(!_instance) { + _instance = new RKeywords(); + } + + return _instance; +} + +void RKeywords::setupBuiltIns(QString& builtIns) +{ + /** + * builtIns is a string containg R's built in functions, keywords and operators. + * This is generated using the command 'apropos('')'. + * @see RSession::loadBuiltIns + */ + + m_builtIns = builtIns; + parseBuiltIns(); + +} + + +QStringList& RKeywords::getKeywords() +{ + return m_keywordsList; +} + +QStringList& RKeywords::getFunctions() +{ + return m_functionsList; +} + +QStringList& RKeywords::getSpecials() +{ + return m_specialsList; +} + +QStringList& RKeywords::getOperators() +{ + return m_operatorsList; +} + + +void RKeywords::parseBuiltIns() +{ + /** + * m_builtIns is a string containg R's built in functions, keywords and operators + * we need to parse the string to separate out functions. we already have keywords and operators + * Algo: check if the current string is a part of m_keywordsList/m_operatorsList, if no, push the current + * word to m_functionsList + */ + + // remove white space, prompt, command(apropos) + m_builtIns.replace(QLatin1String(" "), QLatin1String("")); + m_builtIns.replace(QLatin1String("apropos('')"), QLatin1String("")); + m_builtIns.replace(QLatin1String(">"), QLatin1String("")); + m_builtIns.replace(QRegExp(QLatin1String("\"")),QLatin1String("")); + m_builtIns.replace(QRegExp(QLatin1String("\\[[0-9]+\\]")), QLatin1String("")); + + QStringList builtInsList = m_builtIns.split(QLatin1String("\n")); + + + for(QString& key: builtInsList) { + if(!key.isEmpty()) { + + if(!isKeyword(key) && !isOperator(key)) { + // push back to functions list + m_functionsList << key; + } + } + + } + qDebug() << "size of functions list " << m_functionsList.size() << endl; + +} + +bool RKeywords::isKeyword(QString& key) +{ + return m_keywordsList.contains(key); +} + +bool RKeywords::isOperator(QString& key) +{ + return m_operatorsList.contains(key); +} + + diff --git a/src/backends/R/rsession.h b/src/backends/R/rsession.h --- a/src/backends/R/rsession.h +++ b/src/backends/R/rsession.h @@ -48,28 +48,26 @@ void runExpression(RExpression* expr); -protected Q_SLOTS: - void receiveSymbols(const QStringList& v, const QStringList & f); - void fillSyntaxRegExps(QVector& v, QVector& f); +private: + void loadBuiltIns(); Q_SIGNALS: - void symbolsChanged(); + void updateHighlighter(); public Q_SLOTS: void readOutput(); void readError(); void processStarted(); void currentExpressionStatusChanged(Cantor::Expression::Status status); + void readBuiltInsOutput(); private: QProcess* m_Process; RExpression* m_CurrentExpression; QString m_Output; QString m_Error; - /* Available variables and functions, TODO make full classes and type info */ - QStringList m_variables; - QStringList m_functions; + }; #endif /* _RSESSION_H */ diff --git a/src/backends/R/rsession.cpp b/src/backends/R/rsession.cpp --- a/src/backends/R/rsession.cpp +++ b/src/backends/R/rsession.cpp @@ -22,6 +22,7 @@ #include "rexpression.h" #include "rcompletionobject.h" #include "rhighlighter.h" +#include "rkeywords.h" #include #include @@ -64,22 +65,55 @@ /* * interactive - forcing an interactive session * quiet - don't print the startup message - * no-save - don't save the workspace after the end of session(when users quits). Should the user be allowed to save the session? + * no-save - don't save the workspace after the end of session(when users quits). Should the user be + * allowed to save the session ? */ args.append(QLatin1String("--interactive")); args.append(QLatin1String("--quiet")); args.append(QLatin1String("--no-save")); m_Process->setArguments(args); - connect(m_Process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput())); - connect(m_Process, SIGNAL(readyReadStandardError()), this, SLOT(readError())); connect(m_Process, SIGNAL(started()), this, SLOT(processStarted())); - + connect(m_Process, SIGNAL(readyReadStandardOutput()), this, SLOT(readBuiltInsOutput())); m_Process->start(); } +void RSession::loadBuiltIns() +{ + /* + * ‘apropos()’ returns a character vector giving the names of objects + * in the search list matching (as a regular expression) ‘what’ + * For apropos(''), a complete list of names of objects in the symbol table will be returned. + * The list contains functions, keywords, operators + */ + QString query = QLatin1String("apropos('')"); + query += QLatin1String("\n"); + + m_Output.clear(); + + m_Process->write(query.toLocal8Bit()); + +} + +void RSession::readBuiltInsOutput() +{ + while(m_Process->bytesAvailable()) { + m_Output.append(QString::fromLocal8Bit(m_Process->readLine())); + } + + if(!m_Output.isEmpty() && m_Output.trimmed().endsWith(QLatin1String(">"))){ + RKeywords::getInstance()->setupBuiltIns(m_Output); + qDebug() << RKeywords::getInstance()->getFunctions().size(); + m_Output.clear(); + disconnect(m_Process, SIGNAL(readyReadStandardOutput()), this, SLOT(readBuiltInsOutput())); + connect(m_Process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput())); + connect(m_Process, SIGNAL(readyReadStandardOutput()), this, SLOT(readError())); + + emit updateHighlighter(); + } +} void RSession::readOutput() { @@ -103,7 +137,14 @@ void RSession::processStarted() { qDebug() << m_Process->program() << " with pid " << m_Process->processId() << " started successfully " << endl; + + m_Process->readAllStandardOutput().clear(); + m_Process->readAllStandardError().clear(); + + loadBuiltIns(); + emit ready(); + } void RSession::logout() @@ -129,7 +170,7 @@ Cantor::Expression* RSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) { qDebug()<<"evaluating: "<setFinishingBehavior(behave); expr->setCommand(cmd); @@ -193,32 +234,12 @@ QSyntaxHighlighter* RSession::syntaxHighlighter(QObject* parent) { RHighlighter *h=new RHighlighter(parent); - connect(h,SIGNAL(syntaxRegExps(QVector&,QVector&)),this,SLOT(fillSyntaxRegExps(QVector&,QVector&))); - connect(this,SIGNAL(symbolsChanged()),h,SLOT(refreshSyntaxRegExps())); - return h; -} -void RSession::fillSyntaxRegExps(QVector& v, QVector& f) -{ - // WARNING: current implementation as-in-maxima is a performance hit - // think about grouping expressions together or only fetching needed ones - v.clear(); f.clear(); - - foreach (const QString s, m_variables) - if (!s.contains(QRegExp(QLatin1String("[^A-Za-z0-9_.]")))) - v.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); - foreach (const QString s, m_functions) - if (!s.contains(QRegExp(QLatin1String("[^A-Za-z0-9_.]")))) - f.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); -} + connect(this, SIGNAL(updateHighlighter()), h, SLOT(updateHighlighter())); -void RSession::receiveSymbols(const QStringList& v, const QStringList & f) -{ - m_variables=v; - m_functions=f; - - emit symbolsChanged(); + return h; } + #include "rsession.moc"