diff options
author | Fabian Kosmale <fabian.kosmale@qt.io> | 2019-06-17 16:02:23 +0200 |
---|---|---|
committer | Fabian Kosmale <fabian.kosmale@qt.io> | 2019-07-04 12:41:09 +0200 |
commit | 9b1f2a7d0efa42f0f07afd398794a3e6fd20db31 (patch) | |
tree | 14296326c52c48d6553f9ae4c84bf42f33610f48 /src/qml/parser | |
parent | 1289bd6045818249915028fb345ec29c4ead52e5 (diff) |
extend grammar for better version parsing support
Be more strict in parsing version numbers
This also makes it easier to access the version number in other places
using the Visitor interface, like (soon) the linter and avoids reparsing
the text twice.
Potential disadvantages: previously allowed import statements will
rejected at parse time, e.g.
import QtQuick 0b10
Potential further advantage: Weird import statements like
import QtQuick 0b10
will be rejected earlier
Change-Id: Ifcd187b79a90952bc964c688afa4ea9b158e5109
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'src/qml/parser')
-rw-r--r-- | src/qml/parser/qqmljs.g | 39 | ||||
-rw-r--r-- | src/qml/parser/qqmljsast.cpp | 6 | ||||
-rw-r--r-- | src/qml/parser/qqmljsast_p.h | 30 | ||||
-rw-r--r-- | src/qml/parser/qqmljsastfwd_p.h | 4 | ||||
-rw-r--r-- | src/qml/parser/qqmljsastvisitor_p.h | 2 | ||||
-rw-r--r-- | src/qml/parser/qqmljslexer.cpp | 60 | ||||
-rw-r--r-- | src/qml/parser/qqmljslexer_p.h | 8 |
7 files changed, 136 insertions, 13 deletions
diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index aada928dea..daaa402ef1 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -58,6 +58,7 @@ %token T_MINUS "-" T_MINUS_EQ "-=" T_MINUS_MINUS "--" %token T_NEW "new" T_NOT "!" T_NOT_EQ "!=" %token T_NOT_EQ_EQ "!==" T_NUMERIC_LITERAL "numeric literal" T_OR "|" +%token T_VERSION_NUMBER "version number" %token T_OR_EQ "|=" T_OR_OR "||" T_PLUS "+" %token T_PLUS_EQ "+=" T_PLUS_PLUS "++" T_QUESTION "?" %token T_RBRACE "}" T_RBRACKET "]" T_REMAINDER "%" @@ -315,6 +316,7 @@ public: AST::UiArrayMemberList *UiArrayMemberList; AST::UiQualifiedId *UiQualifiedId; AST::UiEnumMemberList *UiEnumMemberList; + AST::UiVersionSpecifier *UiVersionSpecifier; }; public: @@ -802,20 +804,47 @@ UiImport: UiImportHead T_SEMICOLON; } break; ./ -UiImport: UiImportHead T_NUMERIC_LITERAL T_AUTOMATIC_SEMICOLON; -UiImport: UiImportHead T_NUMERIC_LITERAL T_SEMICOLON; +UiVersionSpecifier: T_VERSION_NUMBER T_DOT T_VERSION_NUMBER; /. case $rule_number: { - sym(1).UiImport->versionToken = loc(2); + auto version = new (pool) AST::UiVersionSpecifier(sym(1).dval, sym(3).dval); + version->majorToken = loc(1); + version->minorToken = loc(3); + sym(1).UiVersionSpecifier = version; + } break; +./ + + +UiVersionSpecifier: T_VERSION_NUMBER; +/. + case $rule_number: { + auto version = new (pool) AST::UiVersionSpecifier(sym(1).dval, 0); + version->majorToken = loc(1); + sym(1).UiVersionSpecifier = version; + } break; +./ + +UiImport: UiImportHead UiVersionSpecifier T_AUTOMATIC_SEMICOLON; +UiImport: UiImportHead UiVersionSpecifier T_SEMICOLON; +/. + case $rule_number: { + auto versionToken = loc(2); + auto version = sym(2).UiVersionSpecifier; + sym(1).UiImport->version = version; + if (version->minorToken.isValid()) { + versionToken.length += version->minorToken.length + (version->minorToken.offset - versionToken.offset - versionToken.length); + } + sym(1).UiImport->versionToken = versionToken; sym(1).UiImport->semicolonToken = loc(3); } break; ./ -UiImport: UiImportHead T_NUMERIC_LITERAL T_AS QmlIdentifier T_AUTOMATIC_SEMICOLON; -UiImport: UiImportHead T_NUMERIC_LITERAL T_AS QmlIdentifier T_SEMICOLON; +UiImport: UiImportHead UiVersionSpecifier T_AS QmlIdentifier T_AUTOMATIC_SEMICOLON; +UiImport: UiImportHead UiVersionSpecifier T_AS QmlIdentifier T_SEMICOLON; /. case $rule_number: { sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->version = sym(2).UiVersionSpecifier; sym(1).UiImport->asToken = loc(3); sym(1).UiImport->importIdToken = loc(4); sym(1).UiImport->importId = stringRef(4); diff --git a/src/qml/parser/qqmljsast.cpp b/src/qml/parser/qqmljsast.cpp index e5817ab763..1bc0e6e364 100644 --- a/src/qml/parser/qqmljsast.cpp +++ b/src/qml/parser/qqmljsast.cpp @@ -1472,6 +1472,12 @@ LeftHandSideExpression *LeftHandSideExpression::leftHandSideExpressionCast() return this; } +void UiVersionSpecifier::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + visitor->endVisit(this); +} } } // namespace QQmlJS::AST QT_END_NAMESPACE diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index b81553776d..606137b67d 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -234,7 +234,6 @@ public: Kind_PatternProperty, Kind_PatternPropertyList, - Kind_UiArrayBinding, Kind_UiImport, Kind_UiObjectBinding, @@ -251,7 +250,8 @@ public: Kind_UiSourceElement, Kind_UiHeaderItemList, Kind_UiEnumDeclaration, - Kind_UiEnumMemberList + Kind_UiEnumMemberList, + Kind_UiVersionSpecifier }; inline Node() {} @@ -492,7 +492,30 @@ public: SourceLocation literalToken; }; -class QML_PARSER_EXPORT StringLiteral: public LeftHandSideExpression +class QML_PARSER_EXPORT UiVersionSpecifier : public Node +{ +public: + QQMLJS_DECLARE_AST_NODE(UiVersionSpecifier) + + UiVersionSpecifier(int majorum, int minorum) : majorVersion(majorum), minorVersion(minorum) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override { return majorToken; } + + SourceLocation lastSourceLocation() const override + { + return minorToken.isValid() ? minorToken : majorToken; + } + + // attributes: + int majorVersion; + int minorVersion; + SourceLocation majorToken; + SourceLocation minorToken; +}; + +class QML_PARSER_EXPORT StringLiteral : public LeftHandSideExpression { public: QQMLJS_DECLARE_AST_NODE(StringLiteral) @@ -2855,6 +2878,7 @@ public: SourceLocation asToken; SourceLocation importIdToken; SourceLocation semicolonToken; + UiVersionSpecifier *version = nullptr; }; class QML_PARSER_EXPORT UiObjectMember: public Node diff --git a/src/qml/parser/qqmljsastfwd_p.h b/src/qml/parser/qqmljsastfwd_p.h index e9caa918d5..6fe108e425 100644 --- a/src/qml/parser/qqmljsastfwd_p.h +++ b/src/qml/parser/qqmljsastfwd_p.h @@ -178,8 +178,10 @@ class UiQualifiedId; class UiHeaderItemList; class UiEnumDeclaration; class UiEnumMemberList; +class UiVersionSpecifier; -} } // namespace AST +} // namespace AST +} // namespace QQmlJS QT_END_NAMESPACE diff --git a/src/qml/parser/qqmljsastvisitor_p.h b/src/qml/parser/qqmljsastvisitor_p.h index 9115449a46..f3732cbba8 100644 --- a/src/qml/parser/qqmljsastvisitor_p.h +++ b/src/qml/parser/qqmljsastvisitor_p.h @@ -111,6 +111,7 @@ public: virtual bool visit(UiQualifiedId *) { return true; } virtual bool visit(UiEnumDeclaration *) { return true; } virtual bool visit(UiEnumMemberList *) { return true; } + virtual bool visit(UiVersionSpecifier *) { return true; } virtual void endVisit(UiProgram *) {} virtual void endVisit(UiImport *) {} @@ -129,6 +130,7 @@ public: virtual void endVisit(UiQualifiedId *) {} virtual void endVisit(UiEnumDeclaration *) {} virtual void endVisit(UiEnumMemberList *) { } + virtual void endVisit(UiVersionSpecifier *) {} // QQmlJS virtual bool visit(ThisExpression *) { return true; } diff --git a/src/qml/parser/qqmljslexer.cpp b/src/qml/parser/qqmljslexer.cpp index 1b3b129c6a..165925d2a2 100644 --- a/src/qml/parser/qqmljslexer.cpp +++ b/src/qml/parser/qqmljslexer.cpp @@ -47,6 +47,7 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qvarlengtharray.h> #include <QtCore/qdebug.h> +#include <QtCore/QScopedValueRollback> QT_BEGIN_NAMESPACE Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); @@ -271,16 +272,29 @@ int Lexer::lex() ++_bracesCount; Q_FALLTHROUGH(); case T_SEMICOLON: + _importState = ImportState::NoQmlImport; + Q_FALLTHROUGH(); case T_QUESTION: case T_COLON: case T_TILDE: _delimited = true; break; + case T_AUTOMATIC_SEMICOLON: + case T_AS: + _importState = ImportState::NoQmlImport; + Q_FALLTHROUGH(); default: if (isBinop(_tokenKind)) _delimited = true; break; + case T_IMPORT: + if (qmlMode() || (_handlingDirectives && previousTokenKind == T_DOT)) + _importState = ImportState::SawImport; + if (isBinop(_tokenKind)) + _delimited = true; + break; + case T_IF: case T_FOR: case T_WHILE: @@ -620,6 +634,8 @@ again: return T_DIVIDE_; case '.': + if (_importState == ImportState::SawImport) + return T_DOT; if (isDecimalDigit(_char.unicode())) return scanNumber(ch); if (_char == QLatin1Char('.')) { @@ -730,7 +746,10 @@ again: case '7': case '8': case '9': - return scanNumber(ch); + if (_importState == ImportState::SawImport) + return scanVersionNumber(ch); + else + return scanNumber(ch); default: { uint c = ch.unicode(); @@ -1148,6 +1167,26 @@ int Lexer::scanNumber(QChar ch) return T_NUMERIC_LITERAL; } +int Lexer::scanVersionNumber(QChar ch) +{ + if (ch == QLatin1Char('0')) { + _tokenValue = 0; + return T_VERSION_NUMBER; + } + + int acc = 0; + acc += ch.digitValue(); + + while (_char.isDigit()) { + acc *= 10; + acc += _char.digitValue(); + scanChar(); // consume the digit + } + + _tokenValue = acc; + return T_VERSION_NUMBER; +} + bool Lexer::scanRegExp(RegExpBodyPrefix prefix) { _tokenText.resize(0); @@ -1410,6 +1449,7 @@ bool Lexer::scanDirectives(Directives *directives, DiagnosticMessage *error) error->column = tokenStartColumn(); }; + QScopedValueRollback<bool> directivesGuard(_handlingDirectives, true); Q_ASSERT(!_qmlMode); lex(); // fetch the first token @@ -1465,8 +1505,7 @@ bool Lexer::scanDirectives(Directives *directives, DiagnosticMessage *error) } } else if (_tokenKind == T_IDENTIFIER) { - // .import T_IDENTIFIER (. T_IDENTIFIER)* T_NUMERIC_LITERAL as T_IDENTIFIER - + // .import T_IDENTIFIER (. T_IDENTIFIER)* T_VERSION_NUMBER . T_VERSION_NUMBER as T_IDENTIFIER while (true) { if (!isUriToken(_tokenKind)) { setError(QCoreApplication::translate("QQmlParser","Invalid module URI")); @@ -1492,12 +1531,25 @@ bool Lexer::scanDirectives(Directives *directives, DiagnosticMessage *error) } } - if (_tokenKind != T_NUMERIC_LITERAL) { + if (_tokenKind != T_VERSION_NUMBER) { setError(QCoreApplication::translate("QQmlParser","Module import requires a version")); return false; // expected the module version number } version = tokenText(); + lex(); + if (_tokenKind != T_DOT) { + setError(QCoreApplication::translate( "QQmlParser", "Module import requires a minor version (missing dot)")); + return false; // expected the module version number + } + version += QLatin1Char('.'); + + lex(); + if (_tokenKind != T_VERSION_NUMBER) { + setError(QCoreApplication::translate( "QQmlParser", "Module import requires a minor version (missing number)")); + return false; // expected the module version number + } + version += tokenText(); } // diff --git a/src/qml/parser/qqmljslexer_p.h b/src/qml/parser/qqmljslexer_p.h index 4fe40948e3..e2ee4ae351 100644 --- a/src/qml/parser/qqmljslexer_p.h +++ b/src/qml/parser/qqmljslexer_p.h @@ -124,6 +124,11 @@ public: StaticIsKeyword = 0x4 }; + enum class ImportState { + SawImport, + NoQmlImport + }; + public: Lexer(Engine *engine); @@ -188,6 +193,7 @@ private: inline void scanChar(); int scanToken(); int scanNumber(QChar ch); + int scanVersionNumber(QChar ch); enum ScanStringMode { SingleQuote = '\'', DoubleQuote = '"', @@ -242,6 +248,7 @@ private: int _tokenLength; int _tokenLine; int _tokenColumn; + ImportState _importState = ImportState::NoQmlImport; bool _validTokenText; bool _prohibitAutomaticSemicolon; @@ -253,6 +260,7 @@ private: bool _skipLinefeed = false; int _generatorLevel = 0; bool _staticIsKeyword = false; + bool _handlingDirectives = false; }; } // end of namespace QQmlJS |