From 4058b501914216bf28ab62c02b78abcbf7f5a3c9 Mon Sep 17 00:00:00 2001 From: Aaron Kennedy Date: Tue, 26 May 2009 11:17:20 +1000 Subject: roberto: Added support for CSS like numeric literals e.g. 10p --- src/declarative/qml/parser/javascript.g | 6 +- src/declarative/qml/parser/javascriptast.cpp | 38 +++++ src/declarative/qml/parser/javascriptast_p.h | 37 ++++- src/declarative/qml/parser/javascriptlexer.cpp | 57 +++++++- src/declarative/qml/parser/javascriptlexer_p.h | 19 +++ src/declarative/qml/parser/javascriptparser.cpp | 6 +- src/declarative/qml/parser/parser.pri | 2 + src/declarative/qml/qml.pri | 13 +- src/declarative/qml/qmlscriptparser.cpp | 74 ++++++++-- src/declarative/qml/rewriter/rewriter.cpp | 55 ++++++++ src/declarative/qml/rewriter/rewriter.pri | 4 + src/declarative/qml/rewriter/rewriter_p.h | 112 +++++++++++++++ src/declarative/qml/rewriter/textwriter.cpp | 176 ++++++++++++++++++++++++ src/declarative/qml/rewriter/textwriter_p.h | 60 ++++++++ 14 files changed, 624 insertions(+), 35 deletions(-) create mode 100644 src/declarative/qml/rewriter/rewriter.cpp create mode 100644 src/declarative/qml/rewriter/rewriter.pri create mode 100644 src/declarative/qml/rewriter/rewriter_p.h create mode 100644 src/declarative/qml/rewriter/textwriter.cpp create mode 100644 src/declarative/qml/rewriter/textwriter_p.h diff --git a/src/declarative/qml/parser/javascript.g b/src/declarative/qml/parser/javascript.g index 155630b..8cabeea 100644 --- a/src/declarative/qml/parser/javascript.g +++ b/src/declarative/qml/parser/javascript.g @@ -875,7 +875,7 @@ case $rule_number: { PrimaryExpression: T_NUMERIC_LITERAL ; /. case $rule_number: { - AST::NumericLiteral *node = makeAstNode (driver->nodePool(), sym(1).dval); + AST::NumericLiteral *node = makeAstNode (driver->nodePool(), sym(1).dval, lexer->flags); node->literalToken = loc(1); sym(1).Node = node; } break; @@ -2830,7 +2830,7 @@ PropertyNameAndValueListOpt: PropertyNameAndValueList ; yytoken = *tk; yylval = 0; yylloc = token_buffer[0].loc; - yylloc.length = 0; + yylloc.length = 0; first_token = &token_buffer[0]; last_token = &token_buffer[2]; @@ -2852,7 +2852,7 @@ PropertyNameAndValueListOpt: PropertyNameAndValueList ; yytoken = tk; yylval = 0; yylloc = token_buffer[0].loc; - yylloc.length = 0; + yylloc.length = 0; action = errorState; goto _Lcheck_token; diff --git a/src/declarative/qml/parser/javascriptast.cpp b/src/declarative/qml/parser/javascriptast.cpp index 083dd28..130229b 100644 --- a/src/declarative/qml/parser/javascriptast.cpp +++ b/src/declarative/qml/parser/javascriptast.cpp @@ -49,6 +49,44 @@ QT_BEGIN_NAMESPACE namespace JavaScript { namespace AST { +int NumericLiteral::suffixLength[] = { + 0, // noSuffix + 2, // emSuffix + 2, // exSuffix + 2, // pxSuffix + 2, // cmSuffix + 2, // mmSuffix + 2, // inSuffix + 2, // ptSuffix + 2, // pcSuffix + 3, // degSuffix + 3, // radSuffix + 4, // gradSuffix + 2, // msSuffix + 1, // sSuffix + 2, // hzSuffix + 3 // khzSuffix +}; + +const char *const NumericLiteral::suffixSpell[] = { + "", + "em", + "ex", + "px", + "cm", + "mm", + "in", + "pt", + "pc", + "deg", + "rad", + "grad", + "ms", + "s", + "hz", + "khz" +}; + ExpressionNode *Node::expressionCast() { return 0; diff --git a/src/declarative/qml/parser/javascriptast_p.h b/src/declarative/qml/parser/javascriptast_p.h index 134f3cc..b5fd922 100644 --- a/src/declarative/qml/parser/javascriptast_p.h +++ b/src/declarative/qml/parser/javascriptast_p.h @@ -398,8 +398,30 @@ class NumericLiteral: public ExpressionNode public: JAVASCRIPT_DECLARE_AST_NODE(NumericLiteral) - NumericLiteral(double v): - value (v) { kind = K; } + enum Suffix { // ### keep it in sync with the Suffix enum in javascriptlexer_p.h + noSuffix, + emSuffix, + exSuffix, + pxSuffix, + cmSuffix, + mmSuffix, + inSuffix, + ptSuffix, + pcSuffix, + degSuffix, + radSuffix, + gradSuffix, + msSuffix, + sSuffix, + hzSuffix, + khzSuffix + }; + + static int suffixLength[]; + static const char *const suffixSpell[]; + + NumericLiteral(double v, int suffix): + value(v), suffix(suffix) { kind = K; } virtual ~NumericLiteral() {} virtual void accept0(Visitor *visitor); @@ -412,6 +434,7 @@ public: // attributes: double value; + int suffix; SourceLocation literalToken; }; @@ -2314,7 +2337,7 @@ public: virtual SourceLocation firstSourceLocation() const { if (defaultToken.isValid()) - return defaultToken; + return defaultToken; return propertyToken; } @@ -2375,9 +2398,9 @@ public: virtual SourceLocation firstSourceLocation() const { if (FunctionDeclaration *funDecl = cast(sourceElement)) - return funDecl->firstSourceLocation(); + return funDecl->firstSourceLocation(); else if (VariableStatement *varStmt = cast(sourceElement)) - return varStmt->firstSourceLocation(); + return varStmt->firstSourceLocation(); return SourceLocation(); } @@ -2385,9 +2408,9 @@ public: virtual SourceLocation lastSourceLocation() const { if (FunctionDeclaration *funDecl = cast(sourceElement)) - return funDecl->lastSourceLocation(); + return funDecl->lastSourceLocation(); else if (VariableStatement *varStmt = cast(sourceElement)) - return varStmt->lastSourceLocation(); + return varStmt->lastSourceLocation(); return SourceLocation(); } diff --git a/src/declarative/qml/parser/javascriptlexer.cpp b/src/declarative/qml/parser/javascriptlexer.cpp index 7455b87..fda6ad2 100644 --- a/src/declarative/qml/parser/javascriptlexer.cpp +++ b/src/declarative/qml/parser/javascriptlexer.cpp @@ -377,7 +377,7 @@ int Lexer::findReservedWord(const QChar *c, int size) const else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('r') && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('p') && c[4] == QLatin1Char('e') && c[5] == QLatin1Char('r') - && c[6] == QLatin1Char('t') && c[7] == QLatin1Char('y')) + && c[6] == QLatin1Char('t') && c[7] == QLatin1Char('y')) return JavaScriptGrammar::T_PROPERTY; else if (check_reserved) { if (c[0] == QLatin1Char('a') && c[1] == QLatin1Char('b') @@ -753,12 +753,65 @@ int Lexer::lex() bol = false; } + if (state == Number) { + // CSS-style suffix for numeric literals + + flags = noSuffix; + + if (current == 'e' && next1 == 'm') { + flags = emSuffix; + shift(2); + } else if (current == 'e' && next1 == 'x') { + flags = exSuffix; + shift(2); + } else if (current == 'p' && next1 == 'x') { + flags = pxSuffix; + shift(2); + } else if (current == 'c' && next1 == 'm') { + flags = cmSuffix; + shift(2); + } else if (current == 'm' && next1 == 'm') { + flags = mmSuffix; + shift(2); + } else if (current == 'i' && next1 == 'n') { + flags = inSuffix; + shift(2); + } else if (current == 'p' && next1 == 't') { + flags = ptSuffix; + shift(2); + } else if (current == 'p' && next1 == 'c') { + flags = pcSuffix; + shift(1); + } else if (current == 'd' && next1 == 'e' && next2 == 'g') { + flags = degSuffix; + shift(3); + } else if (current == 'r' && next1 == 'a' && next2 == 'd') { + flags = radSuffix; + shift(3); + } else if (current == 'g' && next1 == 'r' && next2 == 'a' && next3 == 'd') { + flags = gradSuffix; + shift(4); + } else if (current == 'm' && next1 == 's') { + flags = msSuffix; + shift(2); + } else if (current == 's') { + flags = sSuffix; + shift(1); + } else if (current == 'h' && next1 == 'z') { + flags = hzSuffix; + shift(2); + } else if (current == 'k' && next1 == 'h' && next2 == 'z') { + flags = khzSuffix; + shift(3); + } + } + // no identifiers allowed directly after numeric literal, e.g. "3in" is bad if ((state == Number || state == Octal || state == Hex) && isIdentLetter(current)) { state = Bad; err = IllegalIdentifier; - errmsg = QLatin1String("Identifier cannot start with numeric literal"); + errmsg = QLatin1String("Identifier cannot start with numeric literal `%1'"); } // terminate string diff --git a/src/declarative/qml/parser/javascriptlexer_p.h b/src/declarative/qml/parser/javascriptlexer_p.h index 092609c..a47c1ae 100644 --- a/src/declarative/qml/parser/javascriptlexer_p.h +++ b/src/declarative/qml/parser/javascriptlexer_p.h @@ -112,6 +112,25 @@ public: Other, Bad }; + enum Suffix { + noSuffix, + emSuffix, + exSuffix, + pxSuffix, + cmSuffix, + mmSuffix, + inSuffix, + ptSuffix, + pcSuffix, + degSuffix, + radSuffix, + gradSuffix, + msSuffix, + sSuffix, + hzSuffix, + khzSuffix + }; + enum Error { NoError, IllegalCharacter, diff --git a/src/declarative/qml/parser/javascriptparser.cpp b/src/declarative/qml/parser/javascriptparser.cpp index 7ff438e..34ecd0e 100644 --- a/src/declarative/qml/parser/javascriptparser.cpp +++ b/src/declarative/qml/parser/javascriptparser.cpp @@ -430,7 +430,7 @@ case 51: { } break; case 52: { - AST::NumericLiteral *node = makeAstNode (driver->nodePool(), sym(1).dval); + AST::NumericLiteral *node = makeAstNode (driver->nodePool(), sym(1).dval, lexer->flags); node->literalToken = loc(1); sym(1).Node = node; } break; @@ -1675,7 +1675,7 @@ case 320: { yytoken = *tk; yylval = 0; yylloc = token_buffer[0].loc; - yylloc.length = 0; + yylloc.length = 0; first_token = &token_buffer[0]; last_token = &token_buffer[2]; @@ -1697,7 +1697,7 @@ case 320: { yytoken = tk; yylval = 0; yylloc = token_buffer[0].loc; - yylloc.length = 0; + yylloc.length = 0; action = errorState; goto _Lcheck_token; diff --git a/src/declarative/qml/parser/parser.pri b/src/declarative/qml/parser/parser.pri index b4d226a..72bd46c 100644 --- a/src/declarative/qml/parser/parser.pri +++ b/src/declarative/qml/parser/parser.pri @@ -1,4 +1,6 @@ +INCLUDEPATH += $$PWD + HEADERS += $$PWD/javascriptast_p.h \ $$PWD/javascriptastfwd_p.h \ $$PWD/javascriptastvisitor_p.h \ diff --git a/src/declarative/qml/qml.pri b/src/declarative/qml/qml.pri index 99e30e4..b6ac30e 100644 --- a/src/declarative/qml/qml.pri +++ b/src/declarative/qml/qml.pri @@ -22,7 +22,8 @@ SOURCES += qml/qmlparser.cpp \ qml/qmlparserstatus.cpp \ qml/qmlcompositetypemanager.cpp \ qml/qmlinfo.cpp \ - qml/qmlerror.cpp + qml/qmlerror.cpp \ + qml/qmlscriptparser.cpp HEADERS += qml/qmlparser_p.h \ qml/qmlinstruction_p.h \ @@ -59,15 +60,13 @@ HEADERS += qml/qmlparser_p.h \ qml/qmlcompositetypemanager_p.h \ qml/qmllist.h \ qml/qmldeclarativedata_p.h \ - qml/qmlerror.h + qml/qmlerror.h \ + qml/qmlscriptparser_p.h # for qtscript debugger QT += scripttools -include(script/script.pri) -# new language front-end +include(script/script.pri) include(parser/parser.pri) +include(rewriter/rewriter.pri) -HEADERS += qml/qmlscriptparser_p.h - -SOURCES += qml/qmlscriptparser.cpp diff --git a/src/declarative/qml/qmlscriptparser.cpp b/src/declarative/qml/qmlscriptparser.cpp index 5b3564f..07f6b17 100644 --- a/src/declarative/qml/qmlscriptparser.cpp +++ b/src/declarative/qml/qmlscriptparser.cpp @@ -9,6 +9,8 @@ #include "parser/javascriptastvisitor_p.h" #include "parser/javascriptast_p.h" +#include "rewriter/textwriter_p.h" + #include #include #include @@ -22,6 +24,46 @@ using namespace QmlParser; namespace { +class RewriteNumericLiterals: protected AST::Visitor +{ + unsigned _position; + TextWriter _writer; + +public: + QString operator()(QString code, unsigned position, AST::Node *node) + { + _position = position; + + AST::Node::acceptChild(node, this); + + _writer.write(&code); + + return code; + } + +protected: + using AST::Visitor::visit; + + virtual bool visit(AST::NumericLiteral *node) + { + if (node->suffix != AST::NumericLiteral::noSuffix) { + const int suffixLength = AST::NumericLiteral::suffixLength[node->suffix]; + const char *suffixSpell = AST::NumericLiteral::suffixSpell[node->suffix]; + QString pre; + pre += QLatin1String("qmlNumberFrom"); + pre += QChar(QLatin1Char(suffixSpell[0])).toUpper(); + pre += QLatin1String(&suffixSpell[1]); + pre += QLatin1Char('('); + _writer.replace(node->literalToken.begin() - _position, 0, pre); + _writer.replace(node->literalToken.end() - _position - suffixLength, + suffixLength, + QLatin1String(")")); + } + + return false; + } +}; + class ProcessAST: protected AST::Visitor { struct State { @@ -107,24 +149,30 @@ protected: QString textAt(const AST::SourceLocation &loc) const { return _contents.mid(loc.offset, loc.length); } + QString textAt(const AST::SourceLocation &first, const AST::SourceLocation &last) const { return _contents.mid(first.offset, last.offset + last.length - first.offset); } - QString asString(AST::ExpressionNode *expr) const + RewriteNumericLiterals rewriteNumericLiterals; + + QString asString(AST::ExpressionNode *expr) { if (! expr) return QString(); - return textAt(expr->firstSourceLocation(), expr->lastSourceLocation()); + return rewriteNumericLiterals(textAt(expr->firstSourceLocation(), expr->lastSourceLocation()), + expr->firstSourceLocation().offset, expr); } - QString asString(AST::Statement *stmt) const + QString asString(AST::Statement *stmt) { if (! stmt) return QString(); - QString s = textAt(stmt->firstSourceLocation(), stmt->lastSourceLocation()); + QString s = rewriteNumericLiterals(textAt(stmt->firstSourceLocation(), stmt->lastSourceLocation()), + stmt->firstSourceLocation().offset, stmt); + s += QLatin1Char('\n'); return s; } @@ -214,7 +262,7 @@ ProcessAST::defineObjectBinding_helper(AST::UiQualifiedId *propertyName, int propertyCount = 0; for (; propertyName; propertyName = propertyName->next){ ++propertyCount; - _stateStack.pushProperty(propertyName->name->asString(), + _stateStack.pushProperty(propertyName->name->asString(), this->location(propertyName)); } @@ -322,7 +370,7 @@ Object *ProcessAST::defineObjectBinding(AST::UiQualifiedId *qualifiedId, script = asString(scriptBinding->statement); } - LocationSpan l = this->location(scriptBinding->statement->firstSourceLocation(), + LocationSpan l = this->location(scriptBinding->statement->firstSourceLocation(), scriptBinding->statement->lastSourceLocation()); _stateStack.pushProperty(QLatin1String("script"), l); @@ -414,7 +462,7 @@ bool ProcessAST::visit(AST::UiPublicMember *node) { "var", Object::DynamicProperty::Variant }, { "variant", Object::DynamicProperty::Variant } }; - const int propTypeNameToTypesCount = sizeof(propTypeNameToTypes) / + const int propTypeNameToTypesCount = sizeof(propTypeNameToTypes) / sizeof(propTypeNameToTypes[0]); bool typeFound = false; @@ -425,7 +473,7 @@ bool ProcessAST::visit(AST::UiPublicMember *node) typeFound = true; } } - + if(!typeFound) { QmlError error; error.setDescription(QCoreApplication::translate("QmlParser","Expected property type")); @@ -515,7 +563,7 @@ bool ProcessAST::visit(AST::UiScriptBinding *node) AST::UiQualifiedId *propertyName = node->qualifiedId; for (; propertyName; propertyName = propertyName->next){ ++propertyCount; - _stateStack.pushProperty(propertyName->name->asString(), + _stateStack.pushProperty(propertyName->name->asString(), location(propertyName)); } @@ -526,7 +574,7 @@ bool ProcessAST::visit(AST::UiScriptBinding *node) if (AST::ExpressionStatement *stmt = AST::cast(node->statement)) { primitive = getVariant(stmt->expression); } else { // do binding - primitive = QmlParser::Variant(asString(node->statement), + primitive = QmlParser::Variant(asString(node->statement), QmlParser::Variant::Script); } @@ -550,7 +598,7 @@ bool ProcessAST::visit(AST::UiArrayBinding *node) AST::UiQualifiedId *propertyName = node->qualifiedId; for (; propertyName; propertyName = propertyName->next){ ++propertyCount; - _stateStack.pushProperty(propertyName->name->asString(), + _stateStack.pushProperty(propertyName->name->asString(), location(propertyName)); } @@ -608,7 +656,7 @@ bool ProcessAST::visit(AST::UiSourceElement *node) } Value *value = new Value; - value->location = location(node->firstSourceLocation(), + value->location = location(node->firstSourceLocation(), node->lastSourceLocation()); value->value = QmlParser::Variant(source); @@ -673,7 +721,7 @@ bool QmlScriptParser::parse(const QByteArray &data, const QUrl &url) process(code, parser.ast()); // Set the url for process errors - for(int ii = 0; ii < _errors.count(); ++ii) + for(int ii = 0; ii < _errors.count(); ++ii) _errors[ii].setUrl(url); } diff --git a/src/declarative/qml/rewriter/rewriter.cpp b/src/declarative/qml/rewriter/rewriter.cpp new file mode 100644 index 0000000..ec504fa --- /dev/null +++ b/src/declarative/qml/rewriter/rewriter.cpp @@ -0,0 +1,55 @@ +#include "rewriter_p.h" +#include "javascriptast_p.h" + +QT_BEGIN_NAMESPACE + +using namespace JavaScript; + +void Rewriter::replace(const AST::SourceLocation &loc, const QString &text) +{ replace(loc.offset, loc.length, text); } + +void Rewriter::remove(const AST::SourceLocation &loc) +{ return replace(loc.offset, loc.length, QString()); } + +void Rewriter::remove(const AST::SourceLocation &firstLoc, const AST::SourceLocation &lastLoc) +{ return replace(firstLoc.offset, lastLoc.offset + lastLoc.length - firstLoc.offset, QString()); } + +void Rewriter::insertTextBefore(const AST::SourceLocation &loc, const QString &text) +{ replace(loc.offset, 0, text); } + +void Rewriter::insertTextAfter(const AST::SourceLocation &loc, const QString &text) +{ replace(loc.offset + loc.length, 0, text); } + +void Rewriter::replace(int offset, int length, const QString &text) +{ textWriter.replace(offset, length, text); } + +void Rewriter::insertText(int offset, const QString &text) +{ replace(offset, 0, text); } + +void Rewriter::removeText(int offset, int length) +{ replace(offset, length, QString()); } + +QString Rewriter::textAt(const AST::SourceLocation &loc) const +{ return _code.mid(loc.offset, loc.length); } + +QString Rewriter::textAt(const AST::SourceLocation &firstLoc, const AST::SourceLocation &lastLoc) const +{ return _code.mid(firstLoc.offset, lastLoc.offset + lastLoc.length - firstLoc.offset); } + +void Rewriter::accept(JavaScript::AST::Node *node) +{ JavaScript::AST::Node::acceptChild(node, this); } + +void Rewriter::moveTextBefore(const AST::SourceLocation &firstLoc, + const AST::SourceLocation &lastLoc, + const AST::SourceLocation &loc) +{ + textWriter.move(firstLoc.offset, lastLoc.offset + lastLoc.length - firstLoc.offset, loc.offset); +} + +void Rewriter::moveTextAfter(const AST::SourceLocation &firstLoc, + const AST::SourceLocation &lastLoc, + const AST::SourceLocation &loc) +{ + textWriter.move(firstLoc.offset, lastLoc.offset + lastLoc.length - firstLoc.offset, loc.offset + loc.length); +} + +QT_END_NAMESPACE diff --git a/src/declarative/qml/rewriter/rewriter.pri b/src/declarative/qml/rewriter/rewriter.pri new file mode 100644 index 0000000..987e26d --- /dev/null +++ b/src/declarative/qml/rewriter/rewriter.pri @@ -0,0 +1,4 @@ + +INCLUDEPATH += $$PWD +HEADERS += $$PWD/rewriter_p.h $$PWD/textwriter_p.h +SOURCES += $$PWD/rewriter.cpp $$PWD/textwriter.cpp diff --git a/src/declarative/qml/rewriter/rewriter_p.h b/src/declarative/qml/rewriter/rewriter_p.h new file mode 100644 index 0000000..892c006 --- /dev/null +++ b/src/declarative/qml/rewriter/rewriter_p.h @@ -0,0 +1,112 @@ +#ifndef REWRITER_H +#define REWRITER_H + +#include +#include + +#include "textwriter_p.h" +#include "javascriptastvisitor_p.h" + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +namespace JavaScript { + +//////////////////////////////////////////////////////////////////////////////// +// Replacement +//////////////////////////////////////////////////////////////////////////////// +class Replacement +{ + int _offset; + int _length; + QString _text; + +public: + Replacement(int offset = 0, int length = 0, const QString &text = QString()) + : _offset(offset), _length(length), _text(text) + { } + + bool isNull() const { return _offset == _length; } + operator bool() const { return ! isNull(); } + + int offset() const { return _offset; } + int length() const { return _length; } + QString text() const { return _text; } +}; + + + +//////////////////////////////////////////////////////////////////////////////// +// Rewriter +//////////////////////////////////////////////////////////////////////////////// +class Rewriter: public AST::Visitor +{ +protected: + TextWriter textWriter; +public: + // + // Token based API + // + + /// Returns the text of the token at the given \a location. + QString textAt(const AST::SourceLocation &location) const; + + QString textAt(const AST::SourceLocation &firstLoc, + const AST::SourceLocation &lastLoc) const; + + /// Replace the token at \a loc with the given \a text. + void replace(const AST::SourceLocation &loc, const QString &text); + + /// Remove the token at the given \a location. + void remove(const AST::SourceLocation &location); + + /// Remove all tokens in the range [\a firstLoc, \a lastLoc]. + void remove(const AST::SourceLocation &firstLoc, const AST::SourceLocation &lastLoc); + + /// Insert \a text before the token at the given \a location. + void insertTextBefore(const AST::SourceLocation &location, const QString &text); + + /// Insert \a text after the token at the given \a location. + void insertTextAfter(const AST::SourceLocation &loc, const QString &text); + + void moveTextBefore(const AST::SourceLocation &firstLoc, + const AST::SourceLocation &lastLoc, + const AST::SourceLocation &loc); + + void moveTextAfter(const AST::SourceLocation &firstLoc, + const AST::SourceLocation &lastLoc, + const AST::SourceLocation &loc); + + // + // low-level offset based API + // + void replace(int offset, int length, const QString &text); + void insertText(int offset, const QString &text); + void removeText(int offset, int length); + + /// Visit the given \a node. + void accept(AST::Node *node); + + /// Returns the original unchanged source code. + QString code() const { return _code; } + + /// Returns the list of replacements. + QList replacementList() const { return _replacementList; } + +protected: + /// \internal + void setCode(const QString &code) { _code = code; } + +private: + QString _code; + QList _replacementList; +}; + +} // end of namespace JavaScript + +QT_END_NAMESPACE +QT_END_HEADER + +#endif // REWRITER_H diff --git a/src/declarative/qml/rewriter/textwriter.cpp b/src/declarative/qml/rewriter/textwriter.cpp new file mode 100644 index 0000000..d56c9a1 --- /dev/null +++ b/src/declarative/qml/rewriter/textwriter.cpp @@ -0,0 +1,176 @@ +#include "textwriter_p.h" + +QT_BEGIN_NAMESPACE + +using namespace JavaScript; + +TextWriter::TextWriter() + :string(0), cursor(0) +{ +} + +static bool overlaps(int posA, int lengthA, int posB, int lengthB) { + return (posA < posB + lengthB && posA + lengthA > posB + lengthB) + || (posA < posB && posA + lengthA > posB); +} + +bool TextWriter::hasOverlap(int pos, int length) +{ + { + QListIterator i(replaceList); + while (i.hasNext()) { + const Replace &cmd = i.next(); + if (overlaps(pos, length, cmd.pos, cmd.length)) + return true; + } + } + { + QListIterator i(moveList); + while (i.hasNext()) { + const Move &cmd = i.next(); + if (overlaps(pos, length, cmd.pos, cmd.length)) + return true; + } + return false; + } +} + +bool TextWriter::hasMoveInto(int pos, int length) +{ + QListIterator i(moveList); + while (i.hasNext()) { + const Move &cmd = i.next(); + if (cmd.to >= pos && cmd.to < pos + length) + return true; + } + return false; +} + +void TextWriter::replace(int pos, int length, const QString &replacement) +{ + Q_ASSERT(!hasOverlap(pos, length)); + Q_ASSERT(!hasMoveInto(pos, length)); + + Replace cmd; + cmd.pos = pos; + cmd.length = length; + cmd.replacement = replacement; + replaceList += cmd; +} + +void TextWriter::move(int pos, int length, int to) +{ + Q_ASSERT(!hasOverlap(pos, length)); + + Move cmd; + cmd.pos = pos; + cmd.length = length; + cmd.to = to; + moveList += cmd; +} + +void TextWriter::doReplace(const Replace &replace) +{ + int diff = replace.replacement.size() - replace.length; + { + QMutableListIterator i(replaceList); + while (i.hasNext()) { + Replace &c = i.next(); + if (replace.pos < c.pos) + c.pos += diff; + else if (replace.pos + replace.length < c.pos + c.length) + c.length += diff; + } + } + { + QMutableListIterator i(moveList); + while (i.hasNext()) { + Move &c = i.next(); + if (replace.pos < c.pos) + c.pos += diff; + else if (replace.pos + replace.length < c.pos + c.length) + c.length += diff; + + if (replace.pos < c.to) + c.to += diff; + } + } + + if (string) { + string->replace(replace.pos, replace.length, replace.replacement); + } else if (cursor) { + cursor->setPosition(replace.pos); + cursor->setPosition(replace.pos + replace.length, QTextCursor::KeepAnchor); + cursor->insertText(replace.replacement); + } +} + +void TextWriter::doMove(const Move &move) +{ + QString text; + if (string) { + text = string->mid(move.pos, move.length); + } else if (cursor) { + cursor->setPosition(move.pos); + cursor->setPosition(move.pos + move.length, QTextCursor::KeepAnchor); + text = cursor->selectedText(); + } + + Replace cut; + cut.pos = move.pos; + cut.length = move.length; + Replace paste; + paste.pos = move.to; + paste.length = 0; + paste.replacement = text; + + replaceList.append(cut); + replaceList.append(paste); + + Replace cmd; + while (!replaceList.isEmpty()) { + cmd = replaceList.first(); + replaceList.removeFirst(); + doReplace(cmd); + } +} + +void TextWriter::write(QString *s) +{ + string = s; + write_helper(); + string = 0; +} + +void TextWriter::write(QTextCursor *textCursor) +{ + cursor = textCursor; + write_helper(); + cursor = 0; +} + +void TextWriter::write_helper() +{ + if (cursor) + cursor->beginEditBlock(); + { + Replace cmd; + while (!replaceList.isEmpty()) { + cmd = replaceList.first(); + replaceList.removeFirst(); + doReplace(cmd); + } + } + { + Move cmd; + while (!moveList.isEmpty()) { + cmd = moveList.first(); + moveList.removeFirst(); + doMove(cmd); + } + } + if (cursor) + cursor->endEditBlock(); +} + +QT_END_NAMESPACE diff --git a/src/declarative/qml/rewriter/textwriter_p.h b/src/declarative/qml/rewriter/textwriter_p.h new file mode 100644 index 0000000..52d18d3 --- /dev/null +++ b/src/declarative/qml/rewriter/textwriter_p.h @@ -0,0 +1,60 @@ +#ifndef TEXTWRITER_H +#define TEXTWRITER_H + +#include +#include +#include + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +namespace JavaScript { + +class TextWriter +{ + QString *string; + QTextCursor *cursor; + + struct Replace { + int pos; + int length; + QString replacement; + }; + + QList replaceList; + + struct Move { + int pos; + int length; + int to; + }; + + QList moveList; + + bool hasOverlap(int pos, int length); + bool hasMoveInto(int pos, int length); + + void doReplace(const Replace &replace); + void doMove(const Move &move); + + void write_helper(); + +public: + TextWriter(); + + void replace(int pos, int length, const QString &replacement); + void move(int pos, int length, int to); + + void write(QString *s); + void write(QTextCursor *textCursor); + +}; + +} // end of namespace JavaScript + +QT_END_NAMESPACE +QT_END_HEADER + +#endif // TEXTWRITER_H -- cgit v0.12