1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-23 17:05:26 +02:00

Bug 480238 - Parse QML Header Statements

The acorn parser is now able to parse QML import and pragma statements.

Change-Id: Iaeebaa21f8b013935f8cdf2c2a2ff511038e1069
Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
This commit is contained in:
Matthew Bastien 2015-10-21 10:29:36 -04:00 committed by Gerrit Code Review @ Eclipse.org
parent e18311d2a2
commit 89f0a08d77
2 changed files with 603 additions and 52 deletions

View file

@ -41,75 +41,158 @@ module.exports = function(acorn) {
// QML parser methods
var pp = acorn.Parser.prototype;
/*
* Parses a set of QML Header Statements which can either be of
* the type import or pragma
*/
pp.qml_parseHeaderStatements = function() {
var node = this.startNode()
node.statements = [];
loop: {
var loop = true;
while (loop) {
switch (this.type) {
case tt._import:
var qmlImport = this.qml_parseImportStatement();
node.statements.push(qmlImport);
break loop;
break;
case tt._pragma:
// TODO: parse QML pragma statement
var qmlPragma = this.qml_parsePragmaStatement();
node.statements.push(qmlPragma);
break;
default:
loop = false;
}
}
return this.finishNode(node, "QMLHeaderStatements")
return this.finishNode(node, "QMLHeaderStatements");
}
/*
* Parses a QML Pragma statement of the form:
* 'pragma' <Identifier>
*/
pp.qml_parsePragmaStatement = function() {
var node = this.startNode();
this.next();
node.identifier = this.parseIdent(false);
this.semicolon();
return this.finishNode(node, "QMLPragmaStatement");
}
/*
* Parses a QML Import statement of the form:
* 'import' <ModuleIdentifier> <Version.Number> [as <Qualifier>]
* 'import' <DirectoryPath> [as <Qualifier>]
*
* as specified by http://doc.qt.io/qt-5/qtqml-syntax-imports.html
*/
pp.qml_parseImportStatement = function() {
var node = this.startNode();
// Advance to the next token since this method should only be called
// when the current token is 'import'
this.next();
node.module = this.qml_parseModuleIdentifier();
// TODO: parse the 'as Identifier' portion of an import statement
// The type of import varies solely on the next token
switch(this.type) {
case tt.name:
node.module = this.qml_parseModule();
break;
case tt.string:
node.directoryPath = this.parseLiteral(this.value);
break;
default:
this.unexpected();
break;
}
// Parse the qualifier, if any
if (this.type === tt._as) {
node.qualifier = this.qml_parseQualifier();
}
this.semicolon();
return this.finishNode(node, "QMLImportStatement");
};
pp.qml_parseModuleIdentifier = function() {
/*
* Parses a QML Module of the form:
* <QualifiedId> <VersionLiteral>
*/
pp.qml_parseModule = function() {
var node = this.startNode();
// Parse the qualified id/string
if (this.type == tt.name)
node.qualifiedId = this.qml_parseQualifiedId();
else if (this.type == tt.string) {
node.file = this.value;
this.next();
} else
node.qualifiedId = this.qml_parseQualifiedId();
if (this.type === tt.num) {
node.version = this.qml_parseVersionLiteral();
} else {
this.unexpected();
}
// Parse the version number
if (this.type == tt.num) {
node.version = this.parseLiteral(this.value);
// TODO: check that version number has major and minor
} else
this.unexpected();
return this.finishNode(node, "QMLModuleIdentifier");
return this.finishNode(node, "QMLModule");
};
pp.qml_parseQualifiedId = function() {
var id = this.value;
/*
* Parses a QML Version Literal which consists of a major and minor
* version separated by a '.'
*/
pp.qml_parseVersionLiteral = function() {
var node = this.startNode();
node.raw = this.input.slice(this.start, this.end);
node.value = this.value;
var matches;
if (matches = /(\d+)\.(\d+)/.exec(node.raw)) {
node.major = parseInt(matches[1]);
node.minor = parseInt(matches[2]);
} else {
this.raise(this.start, "QML module must specify major and minor version");
}
this.next();
while(this.type == tt.dot) {
return this.finishNode(node, "QMLVersionLiteral");
}
/*
* Parses a QML Qualifier of the form:
* 'as' <Identifier>
*/
pp.qml_parseQualifier = function() {
var node = this.startNode();
this.next();
node.identifier = this.parseIdent(false);
return this.finishNode(node, "QMLQualifier");
}
/*
* Parses a Qualified ID of the form:
* <Identifier> ('.' <Identifier>)*
*/
pp.qml_parseQualifiedId = function() {
var node = this.startNode();
node.parts = [];
var id = this.value;
node.parts.push(this.value);
this.next();
while(this.type === tt.dot) {
id += '.';
this.next();
if (this.type == tt.name)
if (this.type === tt.name) {
id += this.value;
else
node.parts.push(this.value);
} else {
this.unexpected();
}
this.next();
}
return id;
node.raw = id;
return this.finishNode(node, "QMLQualifiedID");
}
/*
* Returns a TokenType that matches the given word or undefined if
* no such TokenType could be found.
* no such TokenType could be found. This method only matches
* QML-specific keywords.
*
* Uses contextual information to determine whether or not a keyword
* such as 'color' is being used as an identifier. If this is found
@ -125,7 +208,7 @@ module.exports = function(acorn) {
return tt._readonly;
case "import":
// Make sure that 'import' is recognized as a keyword
// regardless of ecma version set in acorn.
// regardless of the ecma version set in acorn.
return tt._import;
case "color":
return tt._color;
@ -150,7 +233,7 @@ module.exports = function(acorn) {
node.body = [];
var headerStmts = this.qml_parseHeaderStatements();
node.body.push(headerStmts)
node.body.push(headerStmts);
// TODO: Parse QML object root
@ -166,8 +249,9 @@ module.exports = function(acorn) {
var word = this.readWord1();
var type = this.qml_getTokenType(word);
if (type !== undefined)
if (type !== undefined) {
return this.finishToken(type, word);
}
// If we were unable to find a QML keyword, call acorn's implementation
// of the readWord method. Since we don't have access to _tokentype, and
@ -181,5 +265,5 @@ module.exports = function(acorn) {
});
}
return acorn
return acorn;
};

View file

@ -9,6 +9,10 @@
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
var test = require("./driver.js").test;
var testFail = require("./driver.js").testFail;
var tokTypes = require("../").tokTypes;
var testFixture = {
'QML': {
'import QtQuick 2.2': {
@ -27,46 +31,509 @@ var testFixture = {
end: { line: 1, column: 18 }
},
module: {
type: "QMLModuleIdentifier",
qualifiedId: "QtQuick",
type: "QMLModule",
range: [7, 18],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 18 }
},
qualifiedId: {
type: "QMLQualifiedID",
range: [7, 14],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 14 }
},
parts: [ "QtQuick" ],
raw: "QtQuick"
},
version: {
type: "Literal",
type: "QMLVersionLiteral",
range: [15, 18],
loc: {
start: { line: 1, column: 15 },
end: { line: 1, column: 18 }
},
value: 2.2,
major: 2,
minor: 2,
raw: "2.2"
}
}
}
]
},
'import "./file.js"': {
type: "QMLHeaderStatements",
range: [0, 18],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 18 }
},
statements: [
{
type: "QMLImportStatement",
range: [0, 18],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 18 }
},
directoryPath: {
type:"Literal",
range: [7, 18],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 18 }
},
value: "./file.js",
raw: "\"./file.js\""
}
}
]
},
'import "./file.js" as MyModule': {
type: "QMLHeaderStatements",
range: [0, 30],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 30 }
},
statements: [
{
type: "QMLImportStatement",
range: [0, 30],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 30 }
},
directoryPath: {
type:"Literal",
range: [7, 18],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 18 }
},
value: "./file.js",
raw: "\"./file.js\""
},
qualifier: {
type: "QMLQualifier",
range: [19, 30],
loc: {
start: { line: 1, column: 19 },
end: { line: 1, column: 30 }
},
identifier: {
type:"Identifier",
range: [22, 30],
loc: {
start: { line: 1, column: 22 },
end: { line: 1, column: 30 }
},
name: "MyModule"
}
}
}
]
},
'import QtQuick ver': "Unexpected token (1:15)",
'import QtQuick 0x01': "QML module must specify major and minor version (1:15)",
'import QtQuick 1': "QML module must specify major and minor version (1:15)",
'import QtQuick 2.2\nimport "./file.js"': {
type: "QMLHeaderStatements",
range: [0, 37],
loc: {
start: { line: 1, column: 0 },
end: { line: 2, column: 18 }
},
statements: [
{
type: "QMLImportStatement",
range: [0, 18],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 18 }
},
module: {
type: "QMLModule",
range: [7, 18],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 18 }
},
qualifiedId: {
type: "QMLQualifiedID",
range: [7, 14],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 14 }
},
parts: [ "QtQuick" ],
raw: "QtQuick"
},
version: {
type: "QMLVersionLiteral",
range: [15, 18],
loc: {
start: { line: 1, column: 15 },
end: { line: 1, column: 18 }
},
value: 2.2,
major: 2,
minor: 2,
raw: "2.2"
}
}
},
{
type: "QMLImportStatement",
range: [19, 37],
loc: {
start: { line: 2, column: 0 },
end: { line: 2, column: 18 }
},
directoryPath: {
type:"Literal",
range: [26, 37],
loc: {
start: { line: 2, column: 7 },
end: { line: 2, column: 18 }
},
value: "./file.js",
raw: "\"./file.js\""
}
}
]
},
'import QtQuick 2.2;import "./file.js"': {
type: "QMLHeaderStatements",
range: [0, 37],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 37 }
},
statements: [
{
type: "QMLImportStatement",
range: [0, 19],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 19 }
},
module: {
type: "QMLModule",
range: [7, 18],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 18 }
},
qualifiedId: {
type: "QMLQualifiedID",
range: [7, 14],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 14 }
},
parts: [ "QtQuick" ],
raw: "QtQuick"
},
version: {
type: "QMLVersionLiteral",
range: [15, 18],
loc: {
start: { line: 1, column: 15 },
end: { line: 1, column: 18 }
},
value: 2.2,
major: 2,
minor: 2,
raw: "2.2"
}
}
},
{
type: "QMLImportStatement",
range: [19, 37],
loc: {
start: { line: 1, column: 19 },
end: { line: 1, column: 37 }
},
directoryPath: {
type:"Literal",
range: [26, 37],
loc: {
start: { line: 1, column: 26 },
end: { line: 1, column: 37 }
},
value: "./file.js",
raw: "\"./file.js\""
}
}
]
},
'import Module 1.0 as MyModule': {
type: "QMLHeaderStatements",
range: [0, 29],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 29 }
},
statements: [
{
type: "QMLImportStatement",
range: [0, 29],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 29 }
},
module: {
type: "QMLModule",
range: [7, 17],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 17 }
},
qualifiedId: {
type: "QMLQualifiedID",
range: [7, 13],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 13 }
},
parts: [ "Module" ],
raw: "Module"
},
version: {
type: "QMLVersionLiteral",
range: [14, 17],
loc: {
start: { line: 1, column: 14 },
end: { line: 1, column: 17 }
},
value: 1,
major: 1,
minor: 0,
raw: "1.0"
}
},
qualifier: {
type: "QMLQualifier",
range: [18, 29],
loc: {
start: { line: 1, column: 18 },
end: { line: 1, column: 29 }
},
identifier: {
type:"Identifier",
range: [21, 29],
loc: {
start: { line: 1, column: 21 },
end: { line: 1, column: 29 }
},
name: "MyModule"
}
}
}
]
},
'import Qualified.Id.Test 1.0': {
type: "QMLHeaderStatements",
range: [0, 28],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 28 }
},
statements: [
{
type: "QMLImportStatement",
range: [0, 28],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 28 }
},
module: {
type: "QMLModule",
range: [7, 28],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 28 }
},
qualifiedId: {
type: "QMLQualifiedID",
range: [7, 24],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 24 }
},
parts: [
"Qualified",
"Id",
"Test"
],
raw: "Qualified.Id.Test"
},
version: {
type: "QMLVersionLiteral",
range: [25, 28],
loc: {
start: { line: 1, column: 25 },
end: { line: 1, column: 28 }
},
value: 1,
major: 1,
minor: 0,
raw: "1.0"
}
}
}
]
},
'pragma Singleton': {
type: "QMLHeaderStatements",
range: [0, 16],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 16 }
},
statements: [
{
type: "QMLPragmaStatement",
range: [0, 16],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 16 }
},
identifier: {
type: "Identifier",
range: [7, 16],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 16 }
},
name: "Singleton"
}
}
]
},
'pragma Singleton\npragma Other': {
type: "QMLHeaderStatements",
range: [0, 29],
loc: {
start: { line: 1, column: 0 },
end: { line: 2, column: 12 }
},
statements: [
{
type: "QMLPragmaStatement",
range: [0, 16],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 16 }
},
identifier: {
type: "Identifier",
range: [7, 16],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 16 }
},
name: "Singleton"
}
},
{
type: "QMLPragmaStatement",
range: [17, 29],
loc: {
start: { line: 2, column: 0 },
end: { line: 2, column: 12 }
},
identifier: {
type: "Identifier",
range: [24, 29],
loc: {
start: { line: 2, column: 7 },
end: { line: 2, column: 12 }
},
name: "Other"
}
}
]
},
'pragma Singleton;pragma Other': {
type: "QMLHeaderStatements",
range: [0, 29],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 29 }
},
statements: [
{
type: "QMLPragmaStatement",
range: [0, 17],
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column: 17 }
},
identifier: {
type: "Identifier",
range: [7, 16],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 16 }
},
name: "Singleton"
}
},
{
type: "QMLPragmaStatement",
range: [17, 29],
loc: {
start: { line: 1, column: 17 },
end: { line: 1, column: 29 }
},
identifier: {
type: "Identifier",
range: [24, 29],
loc: {
start: { line: 1, column: 24 },
end: { line: 1, column: 29 }
},
name: "Other"
}
}
]
}
}
};
if (typeof exports !== "undefined") {
var test = require("./driver.js").test;
var testFail = require("./driver.js").testFail;
var tokTypes = require("../").tokTypes;
}
for (var ns in testFixture) {
ns = testFixture[ns];
for (var code in ns) {
test(code, {
type: 'Program',
body: [ns[code]]
}, {
ecmaVersion: 6,
locations: true,
ranges: true
});
if (typeof ns[code] === "string") {
// Expected test result holds an error message
testFail(code, ns[code], { ecmaVersion: 6 });
} else {
// Expected test result holds an AST
test(code, {
type: "Program",
body: [ns[code]]
}, {
ecmaVersion: 6,
locations: true,
ranges: true,
});
}
}
}