diff options
author | Lars Knoll <lars.knoll@qt.io> | 2018-02-09 13:33:35 +0100 |
---|---|---|
committer | Lars Knoll <lars.knoll@qt.io> | 2018-04-25 17:48:56 +0000 |
commit | dad5d1cc82a57a4f657aa3f37ad2f55b69d5b015 (patch) | |
tree | 1110fbbc2b15734533476c42ffcea2596f5f14f1 /src/qml | |
parent | 6d42c6fd3396ece5e74e602f7cdfc9c9299866bf (diff) |
Add support for ES6 template strings
This requires a bit of bookeeping in the lexer, as we can have
arbitrary expressions inside the ${...}. To make this work, keep
a stack of template states, in which we count the unclosed braces
to match up with the correct closing brace.
Implements support for `...`. Expressions of the type Foo`...`
and Foo()`...` will come in follow-up commits.
Change-Id: Ia332796cfb77895583d0093732e6f56c8b0662c9
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'src/qml')
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 43 | ||||
-rw-r--r-- | src/qml/compiler/qv4codegen_p.h | 1 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilerscanfunctions.cpp | 11 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilerscanfunctions_p.h | 1 | ||||
-rw-r--r-- | src/qml/parser/qqmljs.g | 51 | ||||
-rw-r--r-- | src/qml/parser/qqmljsast.cpp | 8 | ||||
-rw-r--r-- | src/qml/parser/qqmljsast_p.h | 24 | ||||
-rw-r--r-- | src/qml/parser/qqmljsastfwd_p.h | 1 | ||||
-rw-r--r-- | src/qml/parser/qqmljsastvisitor_p.h | 3 | ||||
-rw-r--r-- | src/qml/parser/qqmljslexer.cpp | 324 | ||||
-rw-r--r-- | src/qml/parser/qqmljslexer_p.h | 12 |
11 files changed, 341 insertions, 138 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 8ada1d505e..bf481f351f 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1933,6 +1933,49 @@ bool Codegen::visit(StringLiteral *ast) return false; } +bool Codegen::visit(TemplateLiteral *ast) +{ + if (hasError) + return false; + + Instruction::LoadRuntimeString instr; + instr.stringId = registerString(ast->value.toString()); + bytecodeGenerator->addInstruction(instr); + + if (ast->expression) { + RegisterScope scope(this); + int temp = bytecodeGenerator->newRegister(); + Instruction::StoreReg store; + store.reg = temp; + bytecodeGenerator->addInstruction(store); + + Reference expr = expression(ast->expression); + + if (ast->next) { + int temp2 = bytecodeGenerator->newRegister(); + expr.storeOnStack(temp2); + visit(ast->next); + + Instruction::Add instr; + instr.lhs = temp2; + bytecodeGenerator->addInstruction(instr); + } else { + expr.loadInAccumulator(); + } + + Instruction::Add instr; + instr.lhs = temp; + bytecodeGenerator->addInstruction(instr); + } + + auto r = Reference::fromAccumulator(this); + r.isReadonly = true; + + _expr.setResult(r); + return false; + +} + bool Codegen::visit(ThisExpression *) { if (hasError) diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index d51dc29517..61029644c3 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -576,6 +576,7 @@ protected: bool visit(AST::PreIncrementExpression *ast) override; bool visit(AST::RegExpLiteral *ast) override; bool visit(AST::StringLiteral *ast) override; + bool visit(AST::TemplateLiteral *ast) override; bool visit(AST::ThisExpression *ast) override; bool visit(AST::TildeExpression *ast) override; bool visit(AST::TrueLiteral *ast) override; diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 84ee452332..f985c74f9d 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -257,6 +257,17 @@ bool ScanFunctions::visit(FunctionExpression *ast) return true; } +bool ScanFunctions::visit(TemplateLiteral *ast) +{ + while (ast) { + if (ast->expression) + Node::accept(ast->expression, this); + ast = ast->next; + } + return true; + +} + void ScanFunctions::enterFunction(FunctionExpression *ast, bool enterName) { if (_context->isStrict && (ast->name == QLatin1String("eval") || ast->name == QLatin1String("arguments"))) diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index 87b7210879..0494a3248d 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -110,6 +110,7 @@ protected: bool visit(AST::IdentifierExpression *ast) override; bool visit(AST::ExpressionStatement *ast) override; bool visit(AST::FunctionExpression *ast) override; + bool visit(AST::TemplateLiteral *ast) override; void enterFunction(AST::FunctionExpression *ast, bool enterName); diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 60a10bee6b..5313d4d066 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -79,6 +79,12 @@ %token T_COMPATIBILITY_SEMICOLON %token T_ENUM "enum" +--- template strings +%token T_NO_SUBSTITUTION_TEMPLATE +%token T_TEMPLATE_HEAD +%token T_TEMPLATE_MIDDLE +%token T_TEMPLATE_TAIL + --- context keywords. %token T_PUBLIC "public" %token T_IMPORT "import" @@ -250,6 +256,7 @@ public: AST::ElementList *ElementList; AST::Elision *Elision; AST::ExpressionNode *Expression; + AST::TemplateLiteral *Template; AST::Finally *Finally; AST::FormalParameterList *FormalParameterList; AST::FunctionBody *FunctionBody; @@ -1342,6 +1349,37 @@ case $rule_number: { } break; ./ +PrimaryExpression: TemplateLiteral ; +/. +case $rule_number: + // nothing that needs to be done here + break; +./ + +TemplateLiteral: T_NO_SUBSTITUTION_TEMPLATE ; +/. case $rule_number: ./ + +TemplateSpans: T_TEMPLATE_TAIL ; +/. +case $rule_number: { + AST::TemplateLiteral *node = new (pool) AST::TemplateLiteral(stringRef(1), nullptr); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +TemplateSpans: T_TEMPLATE_MIDDLE Expression TemplateSpans; +/. case $rule_number: ./ + +TemplateLiteral: T_TEMPLATE_HEAD Expression TemplateSpans ; +/. case $rule_number: { + AST::TemplateLiteral *node = new (pool) AST::TemplateLiteral(stringRef(1), sym(2).Expression); + node->next = sym(3).Template; + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + PrimaryExpression: T_DIVIDE_ ; /: #define J_SCRIPT_REGEXPLITERAL_RULE1 $rule_number @@ -1721,6 +1759,13 @@ case $rule_number: { } break; ./ +MemberExpression: MemberExpression TemplateLiteral ; +/. +case $rule_number: { + qWarning() << "Template member expression implemented"; +} break; +./ + NewExpression: MemberExpression ; NewExpression: T_NEW NewExpression ; @@ -1772,6 +1817,12 @@ case $rule_number: { } break; ./ +CallExpression: CallExpression TemplateLiteral; +/. case $rule_number: { + qWarning() << "Template calling not implemented"; +} break; +./ + ArgumentListOpt: ; /. case $rule_number: { diff --git a/src/qml/parser/qqmljsast.cpp b/src/qml/parser/qqmljsast.cpp index 34657a7d48..6fdfd8279c 100644 --- a/src/qml/parser/qqmljsast.cpp +++ b/src/qml/parser/qqmljsast.cpp @@ -155,6 +155,14 @@ void StringLiteral::accept0(Visitor *visitor) visitor->endVisit(this); } +void TemplateLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + void NumericLiteral::accept0(Visitor *visitor) { if (visitor->visit(this)) { diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index ed3c83badf..5f094ad4f5 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -188,6 +188,7 @@ public: Kind_StringLiteral, Kind_StringLiteralPropertyName, Kind_SwitchStatement, + Kind_TemplateLiteral, Kind_ThisExpression, Kind_ThrowStatement, Kind_TildeExpression, @@ -428,6 +429,29 @@ public: SourceLocation literalToken; }; +class QML_PARSER_EXPORT TemplateLiteral : public ExpressionNode +{ +public: + QQMLJS_DECLARE_AST_NODE(TemplateLiteral) + + TemplateLiteral(const QStringRef &str, ExpressionNode *e) + : value(str), expression(e), next(nullptr) + { kind = K; } + + SourceLocation firstSourceLocation() const override + { return literalToken; } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : literalToken; } + + void accept0(Visitor *visitor) override; + + QStringRef value; + ExpressionNode *expression; + SourceLocation literalToken; + TemplateLiteral *next; +}; + class QML_PARSER_EXPORT RegExpLiteral: public ExpressionNode { public: diff --git a/src/qml/parser/qqmljsastfwd_p.h b/src/qml/parser/qqmljsastfwd_p.h index 140a757e51..3ebef5b128 100644 --- a/src/qml/parser/qqmljsastfwd_p.h +++ b/src/qml/parser/qqmljsastfwd_p.h @@ -91,6 +91,7 @@ class TrueLiteral; class FalseLiteral; class NumericLiteral; class StringLiteral; +class TemplateLiteral; class RegExpLiteral; class ArrayLiteral; class ObjectLiteral; diff --git a/src/qml/parser/qqmljsastvisitor_p.h b/src/qml/parser/qqmljsastvisitor_p.h index 13218f0e98..5a0767a697 100644 --- a/src/qml/parser/qqmljsastvisitor_p.h +++ b/src/qml/parser/qqmljsastvisitor_p.h @@ -125,6 +125,9 @@ public: virtual bool visit(StringLiteral *) { return true; } virtual void endVisit(StringLiteral *) {} + virtual bool visit(TemplateLiteral *) { return true; } + virtual void endVisit(TemplateLiteral *) {} + virtual bool visit(NumericLiteral *) { return true; } virtual void endVisit(NumericLiteral *) {} diff --git a/src/qml/parser/qqmljslexer.cpp b/src/qml/parser/qqmljslexer.cpp index 53a8d593da..d48766e9a8 100644 --- a/src/qml/parser/qqmljslexer.cpp +++ b/src/qml/parser/qqmljslexer.cpp @@ -242,6 +242,7 @@ int Lexer::lex() { const int previousTokenKind = _tokenKind; + again: _tokenSpell = QStringRef(); _tokenKind = scanToken(); _tokenLength = _codePtr - _tokenStartPtr - 1; @@ -253,6 +254,9 @@ int Lexer::lex() // update the flags switch (_tokenKind) { case T_LBRACE: + if (_bracesCount > 0) + ++_bracesCount; + Q_FALLTHROUGH(); case T_SEMICOLON: case T_QUESTION: case T_COLON: @@ -283,6 +287,11 @@ int Lexer::lex() case T_THROW: _restrictedKeyword = true; break; + case T_RBRACE: + if (_bracesCount > 0) + --_bracesCount; + if (_bracesCount == 0) + goto again; } // switch // update the parentheses state @@ -444,6 +453,12 @@ int Lexer::scanToken() return tk; } + if (_bracesCount == 0) { + // we're inside a Template string + return scanString(TemplateContinuation); + } + + _terminator = false; again: @@ -664,145 +679,12 @@ again: } return T_NOT; + case '`': + _outerTemplateBraceCount.push(_bracesCount); + Q_FALLTHROUGH(); case '\'': - case '"': { - const QChar quote = ch; - bool multilineStringLiteral = false; - - const QChar *startCode = _codePtr; - - if (_engine) { - while (_codePtr <= _endPtr) { - if (isLineTerminator()) { - if (qmlMode()) - break; - _errorCode = IllegalCharacter; - _errorMessage = QCoreApplication::translate("QQmlParser", "Stray newline in string literal"); - return T_ERROR; - } else if (_char == QLatin1Char('\\')) { - break; - } else if (_char == quote) { - _tokenSpell = _engine->midRef(startCode - _code.unicode() - 1, _codePtr - startCode); - scanChar(); - - return T_STRING_LITERAL; - } - scanChar(); - } - } - - _validTokenText = true; - _tokenText.resize(0); - startCode--; - while (startCode != _codePtr - 1) - _tokenText += *startCode++; - - while (_codePtr <= _endPtr) { - if (unsigned sequenceLength = isLineTerminatorSequence()) { - multilineStringLiteral = true; - _tokenText += _char; - if (sequenceLength == 2) - _tokenText += *_codePtr; - scanChar(); - } else if (_char == quote) { - scanChar(); - - if (_engine) - _tokenSpell = _engine->newStringRef(_tokenText); - - return multilineStringLiteral ? T_MULTILINE_STRING_LITERAL : T_STRING_LITERAL; - } else if (_char == QLatin1Char('\\')) { - scanChar(); - if (_codePtr > _endPtr) { - _errorCode = IllegalEscapeSequence; - _errorMessage = QCoreApplication::translate("QQmlParser", "End of file reached at escape sequence"); - return T_ERROR; - } - - QChar u; - - switch (_char.unicode()) { - // unicode escape sequence - case 'u': { - bool ok = false; - uint codePoint = decodeUnicodeEscapeCharacter(&ok); - if (!ok) - return T_ERROR; - if (QChar::requiresSurrogates(codePoint)) { - // need to use a surrogate pair - _tokenText += QChar(QChar::highSurrogate(codePoint)); - u = QChar::lowSurrogate(codePoint); - } else { - u = codePoint; - } - } break; - - // hex escape sequence - case 'x': { - bool ok = false; - u = decodeHexEscapeCharacter(&ok); - if (!ok) { - _errorCode = IllegalHexadecimalEscapeSequence; - _errorMessage = QCoreApplication::translate("QQmlParser", "Illegal hexadecimal escape sequence"); - return T_ERROR; - } - } break; - - // single character escape sequence - case '\\': u = QLatin1Char('\\'); scanChar(); break; - case '\'': u = QLatin1Char('\''); scanChar(); break; - case '\"': u = QLatin1Char('\"'); scanChar(); break; - case 'b': u = QLatin1Char('\b'); scanChar(); break; - case 'f': u = QLatin1Char('\f'); scanChar(); break; - case 'n': u = QLatin1Char('\n'); scanChar(); break; - case 'r': u = QLatin1Char('\r'); scanChar(); break; - case 't': u = QLatin1Char('\t'); scanChar(); break; - case 'v': u = QLatin1Char('\v'); scanChar(); break; - - case '0': - if (! _codePtr->isDigit()) { - scanChar(); - u = QLatin1Char('\0'); - break; - } - Q_FALLTHROUGH(); - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - _errorCode = IllegalEscapeSequence; - _errorMessage = QCoreApplication::translate("QQmlParser", "Octal escape sequences are not allowed"); - return T_ERROR; - - case '\r': - case '\n': - case 0x2028u: - case 0x2029u: - scanChar(); - continue; - - default: - // non escape character - u = _char; - scanChar(); - } - - _tokenText += u; - } else { - _tokenText += _char; - scanChar(); - } - } - - _errorCode = UnclosedStringLiteral; - _errorMessage = QCoreApplication::translate("QQmlParser", "Unclosed string at end of line"); - return T_ERROR; - } + case '"': + return scanString(ScanStringMode(ch.unicode())); case '0': case '1': case '2': @@ -910,6 +792,172 @@ again: return T_ERROR; } +int Lexer::scanString(ScanStringMode mode) +{ + QChar quote = (mode == TemplateContinuation) ? QChar(TemplateHead) : QChar(mode); + bool multilineStringLiteral = false; + + const QChar *startCode = _codePtr; + + if (_engine) { + while (_codePtr <= _endPtr) { + if (isLineTerminator()) { + if (qmlMode()) + break; + _errorCode = IllegalCharacter; + _errorMessage = QCoreApplication::translate("QQmlParser", "Stray newline in string literal"); + return T_ERROR; + } else if (_char == QLatin1Char('\\')) { + break; + } else if (_char == '$' && quote == QLatin1Char('`')) { + break; + } else if (_char == quote) { + _tokenSpell = _engine->midRef(startCode - _code.unicode() - 1, _codePtr - startCode); + scanChar(); + + if (quote == QLatin1Char('`')) + _bracesCount = _outerTemplateBraceCount.pop(); + + if (mode == TemplateHead) + return T_NO_SUBSTITUTION_TEMPLATE; + else if (mode == TemplateContinuation) + return T_TEMPLATE_TAIL; + else + return T_STRING_LITERAL; + } + scanChar(); + } + } + + _validTokenText = true; + _tokenText.resize(0); + startCode--; + while (startCode != _codePtr - 1) + _tokenText += *startCode++; + + while (_codePtr <= _endPtr) { + if (unsigned sequenceLength = isLineTerminatorSequence()) { + multilineStringLiteral = true; + _tokenText += _char; + if (sequenceLength == 2) + _tokenText += *_codePtr; + scanChar(); + } else if (_char == mode) { + scanChar(); + + if (_engine) + _tokenSpell = _engine->newStringRef(_tokenText); + + if (quote == QLatin1Char('`')) + _bracesCount = _outerTemplateBraceCount.pop(); + + if (mode == TemplateContinuation) + return T_TEMPLATE_TAIL; + else if (mode == TemplateHead) + return T_NO_SUBSTITUTION_TEMPLATE; + + return multilineStringLiteral ? T_MULTILINE_STRING_LITERAL : T_STRING_LITERAL; + } else if (quote == QLatin1Char('`') && _char == QLatin1Char('$') && *_codePtr == '{') { + scanChar(); + scanChar(); + _bracesCount = 1; + if (_engine) + _tokenSpell = _engine->newStringRef(_tokenText); + + return (mode == TemplateHead ? T_TEMPLATE_HEAD : T_TEMPLATE_MIDDLE); + } else if (_char == QLatin1Char('\\')) { + scanChar(); + if (_codePtr > _endPtr) { + _errorCode = IllegalEscapeSequence; + _errorMessage = QCoreApplication::translate("QQmlParser", "End of file reached at escape sequence"); + return T_ERROR; + } + + QChar u; + + switch (_char.unicode()) { + // unicode escape sequence + case 'u': { + bool ok = false; + uint codePoint = decodeUnicodeEscapeCharacter(&ok); + if (!ok) + return T_ERROR; + if (QChar::requiresSurrogates(codePoint)) { + // need to use a surrogate pair + _tokenText += QChar(QChar::highSurrogate(codePoint)); + u = QChar::lowSurrogate(codePoint); + } else { + u = codePoint; + } + } break; + + // hex escape sequence + case 'x': { + bool ok = false; + u = decodeHexEscapeCharacter(&ok); + if (!ok) { + _errorCode = IllegalHexadecimalEscapeSequence; + _errorMessage = QCoreApplication::translate("QQmlParser", "Illegal hexadecimal escape sequence"); + return T_ERROR; + } + } break; + + // single character escape sequence + case '\\': u = QLatin1Char('\\'); scanChar(); break; + case '\'': u = QLatin1Char('\''); scanChar(); break; + case '\"': u = QLatin1Char('\"'); scanChar(); break; + case 'b': u = QLatin1Char('\b'); scanChar(); break; + case 'f': u = QLatin1Char('\f'); scanChar(); break; + case 'n': u = QLatin1Char('\n'); scanChar(); break; + case 'r': u = QLatin1Char('\r'); scanChar(); break; + case 't': u = QLatin1Char('\t'); scanChar(); break; + case 'v': u = QLatin1Char('\v'); scanChar(); break; + + case '0': + if (! _codePtr->isDigit()) { + scanChar(); + u = QLatin1Char('\0'); + break; + } + Q_FALLTHROUGH(); + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + _errorCode = IllegalEscapeSequence; + _errorMessage = QCoreApplication::translate("QQmlParser", "Octal escape sequences are not allowed"); + return T_ERROR; + + case '\r': + case '\n': + case 0x2028u: + case 0x2029u: + scanChar(); + continue; + + default: + // non escape character + u = _char; + scanChar(); + } + + _tokenText += u; + } else { + _tokenText += _char; + scanChar(); + } + } + + _errorCode = UnclosedStringLiteral; + _errorMessage = QCoreApplication::translate("QQmlParser", "Unclosed string at end of line"); + return T_ERROR; +} + int Lexer::scanNumber(QChar ch) { if (ch == QLatin1Char('0')) { diff --git a/src/qml/parser/qqmljslexer_p.h b/src/qml/parser/qqmljslexer_p.h index 71a4a27dff..75afdf0e02 100644 --- a/src/qml/parser/qqmljslexer_p.h +++ b/src/qml/parser/qqmljslexer_p.h @@ -55,6 +55,7 @@ #include <private/qqmljsgrammar_p.h> #include <QtCore/qstring.h> +#include <QtCore/qstack.h> QT_QML_BEGIN_NAMESPACE @@ -167,6 +168,13 @@ private: inline void scanChar(); int scanToken(); int scanNumber(QChar ch); + enum ScanStringMode { + SingleQuote = '\'', + DoubleQuote = '"', + TemplateHead = '`', + TemplateContinuation = 0 + }; + int scanString(ScanStringMode mode); bool isLineTerminator() const; unsigned isLineTerminatorSequence() const; @@ -202,6 +210,10 @@ private: ParenthesesState _parenthesesState; int _parenthesesCount; + // template string stack + QStack<int> _outerTemplateBraceCount; + int _bracesCount = -1; + int _stackToken; int _patternFlags; |