1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-06-07 01:36:01 +02:00

Bug 480238 - Clean Up Acorn QML Parser

Fixed the AST elements so that they more closely match the format used
by acorn (i.e. using 'id' instead of 'identifier' and 'name' instead of
'raw' for the identifier name).  Resolved one of the ambiguities dealing
with using 'signal', 'property', and 'readonly' as identifiers for
properties and QML Objects.   Added a bunch of new tests and fixed the
old ones to match the new AST.

Change-Id: I5a8bbd14b3e6f8531268740dd9e57cb48a0e22b3
Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
This commit is contained in:
Matthew Bastien 2015-11-03 16:07:42 -05:00
parent 5eaf1129a5
commit 19cd53ec10
6 changed files with 2528 additions and 1917 deletions

View file

@ -14,9 +14,9 @@
var injectQML;
(function (mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
if (typeof exports === "object" && typeof module === "object") // CommonJS
return module.exports = mod();
if (typeof define == "function" && define.amd) // AMD
if (typeof define === "function" && define.amd) // AMD
return define([], mod);
injectQML = mod(); // Plain browser env
})(function () {
@ -103,11 +103,8 @@ var injectQML;
}
}
if (node.statements.length > 0) {
return this.finishNode(node, "QMLHeaderStatements");
}
return undefined;
}
/*
* Parses a QML Pragma statement of the form:
@ -115,8 +112,8 @@ var injectQML;
*/
pp.qml_parsePragmaStatement = function () {
var node = this.startNode();
this.next();
node.identifier = this.parseIdent(false);
this.expectContextual(qtt._pragma);
node.id = this.parseIdent(false);
this.semicolon();
return this.finishNode(node, "QMLPragmaStatement");
}
@ -130,38 +127,41 @@ var injectQML;
*/
pp.qml_parseImportStatement = function () {
var node = this.startNode();
this.next();
// The type of import varies solely on the next token
if (!this.eat(tt._import) && !this.eatContextual(qtt._import)) {
this.unexpected();
}
switch (this.type) {
case tt.name:
node.module = this.qml_parseModule();
node.directory = null;
break;
case tt.string:
node.directoryPath = this.parseLiteral(this.value);
node.module = null;
node.directory = this.parseLiteral(this.value);
break;
default:
this.unexpected();
break;
}
// Parse the qualifier, if any
if (this.isContextual(qtt._as)) {
node.qualifier = this.qml_parseQualifier();
}
this.semicolon();
return this.finishNode(node, "QMLImportStatement");
};
/*
* Parses a QML Module of the form:
* <QualifiedId> <VersionLiteral>
* <QMLQualifiedId> <QMLVersionLiteral> ['as' <QMLQualifier>]?
*/
pp.qml_parseModule = function () {
var node = this.startNode();
node.qualifiedId = this.qml_parseQualifiedId();
node.id = this.qml_parseQualifiedId(false);
if (this.type === tt.num) {
node.version = this.qml_parseVersionLiteral();
} else {
@ -184,11 +184,11 @@ var injectQML;
if (matches = /(\d+)\.(\d+)/.exec(node.raw)) {
node.major = parseInt(matches[1]);
node.minor = parseInt(matches[2]);
this.next();
} else {
this.raise(this.start, "QML module must specify major and minor version");
}
this.next();
return this.finishNode(node, "QMLVersionLiteral");
}
@ -198,8 +198,8 @@ var injectQML;
*/
pp.qml_parseQualifier = function () {
var node = this.startNode();
this.next();
node.identifier = this.qml_parseIdent(false);
this.expectContextual(qtt._as);
node.id = this.qml_parseIdent(false);
return this.finishNode(node, "QMLQualifier");
}
@ -213,8 +213,8 @@ var injectQML;
if (!node) {
node = this.startNode();
}
if (!node.qualifiedId) {
node.qualifiedId = this.qml_parseQualifiedId();
if (!node.id) {
node.id = this.qml_parseQualifiedId(false);
}
node.block = this.qml_parseMemberBlock();
return this.finishNode(node, "QMLObjectLiteral");
@ -243,27 +243,32 @@ var injectQML;
* - a Signal Definition
*/
pp.qml_parseMember = function () {
var node = this.startNode();
if (this.type === tt._default
|| this.isContextual(qtt._default)
|| this.isContextual(qtt._readonly)
|| this.isContextual(qtt._property)) {
return this.qml_parsePropertyDeclaration(node);
if (this.type === tt._default || this.isContextual(qtt._default) || this.isContextual(qtt._readonly) || this.isContextual(qtt._property)) {
return this.qml_parsePropertyDeclaration();
} else if (this.isContextual(qtt._signal)) {
return this.qml_parseSignalDefinition(node);
return this.qml_parseSignalDefinition();
} else if (this.type === tt._function) {
return this.parseFunctionStatement(node);
return this.parseFunctionStatement();
}
return this.qml_parseObjectLiteralOrPropertyBinding();
}
node.qualifiedId = this.qml_parseQualifiedId();
/*
* Parses a QML Object Literal or Property Binding depending on the tokens found.
*/
pp.qml_parseObjectLiteralOrPropertyBinding = function (node) {
if (!node) {
node = this.startNode();
}
if (!node.id) {
node.id = this.qml_parseQualifiedId(false);
}
switch (this.type) {
case tt.braceL:
return this.qml_parseObjectLiteral(node);
case tt.colon:
return this.qml_parseProperty(node);
return this.qml_parsePropertyBinding(node);
}
this.unexpected();
}
@ -271,84 +276,138 @@ var injectQML;
* Parses a QML Property of the form:
* <QMLQualifiedID> <QMLBinding>
*/
pp.qml_parseProperty = function(node) {
pp.qml_parsePropertyBinding = function (node) {
if (!node) {
node = this.startNode();
}
if (!node.qualifiedId) {
node.qualifiedId = this.qml_parseQualifiedId();
if (!node.id) {
node.id = this.qml_parseQualifiedId(false);
}
node.binding = this.qml_parseBinding();
return this.finishNode(node, "QMLProperty");
this.expect(tt.colon);
node.expr = this.qml_parsePropertyAssignment();
return this.finishNode(node, "QMLPropertyBinding");
}
/*
* Parses a QML Signal Definition of the form:
* 'signal' <Identifier> [(<Type> <Identifier> [',' <Type> <Identifier>]* )]?
*/
pp.qml_parseSignalDefinition = function(node) {
if (!node) {
node = this.startNode();
}
this.next();
node.identifier = this.qml_parseIdent(false);
node.parameters = [];
if (this.type === tt.parenL) {
this.next();
if (this.type !== tt.parenR) {
do {
var paramNode = this.startNode();
paramNode.type = this.qml_parseIdent(false);
paramNode.identifier = this.qml_parseIdent(false);
node.parameters.push(paramNode);
} while(this.eat(tt.comma));
}
if (!this.eat(tt.parenR)) {
pp.qml_parseSignalDefinition = function () {
var node = this.startNode();
// Parse as a qualified id in case this is not a signal definition
var signal = this.qml_parseQualifiedId(true);
if (signal.parts.length === 1) {
if (signal.name !== qtt._signal) {
this.unexpected();
}
if (this.type === tt.colon || this.type === tt.braceL) {
// This is a property binding or object literal
node.id = signal;
return this.qml_parseObjectLiteralOrPropertyBinding(node);
}
} else {
// Signal keyword is a qualified ID. This is not a signal definition
node.id = signal;
return this.qml_parseObjectLiteralOrPropertyBinding(node);
}
node.id = this.qml_parseIdent(false);
this.qml_parseSignalParams(node);
this.semicolon();
return this.finishNode(node, "QMLSignalDefinition");
}
/*
* Parses QML Signal Parameters of the form:
* [(<Type> <Identifier> [',' <Type> <Identifier>]* )]?
*/
pp.qml_parseSignalParams = function (node) {
node.params = [];
if (this.eat(tt.parenL)) {
if (!this.eat(tt.parenR)) {
do {
var param = this.startNode();
param.kind = this.qml_parseIdent(false);
param.id = this.qml_parseIdent(false);
node.params.push(this.finishNode(param, "QMLParameter"));
} while (this.eat(tt.comma));
this.expect(tt.parenR);
}
}
}
/*
* Parses a QML Property Declaration (or Alias) of the form:
* ['default'|'readonly'] 'property' <QMLType> <Identifier> [<QMLBinding>]
*/
pp.qml_parsePropertyDeclaration = function(node) {
pp.qml_parsePropertyDeclaration = function () {
var node = this.startNode();
// Parse 'default' or 'readonly'
node["default"] = false;
node["readonly"] = false;
if (this.type === tt._default || this.isContextual(qtt._default)) {
if (this.eat(tt._default) || this.eatContextual(qtt._default)) {
node["default"] = true;
this.next();
} else if (this.eatContextual(qtt._readonly)) {
node["readonly"] = true;
} else if (this.isContextual(qtt._readonly)) {
// Parse as a qualified id in case this is not a property declaration
var readonly = this.qml_parseQualifiedId(true);
if (readonly.parts.length === 1) {
if (this.type === tt.colon || this.type === tt.braceL) {
// This is a property binding or object literal.
node.id = readonly;
return this.qml_parseObjectLiteralOrPropertyBinding(node);
}
this.expectContextual(qtt._property);
node.typeInfo = this.qml_parseType();
node.identifier = this.qml_parseIdent(false);
if (this.type !== tt.colon) {
node["readonly"] = true;
} else {
// Readonly keyword is a qualified ID. This is not a property declaration.
node.id = readonly;
return this.qml_parseObjectLiteralOrPropertyBinding(node);
}
}
// Parse as a qualified id in case this is not a property declaration
var property = this.qml_parseQualifiedId(true);
if (property.parts.length === 1 || node["default"] || node["readonly"]) {
if (property.name !== qtt._property) {
this.unexpected();
}
if (this.type === tt.colon || this.type === tt.braceL) {
// This is a property binding or object literal.
node["default"] = undefined;
node["readonly"] = undefined;
node.id = property;
return this.qml_parseObjectLiteralOrPropertyBinding(node);
}
} else {
// Property keyword is a qualified ID. This is not a property declaration.
node["default"] = undefined;
node["readonly"] = undefined;
node.id = property;
return this.qml_parseObjectLiteralOrPropertyBinding(node);
}
node.kind = this.qml_parseKind();
node.id = this.qml_parseIdent(false);
if (!this.eat(tt.colon)) {
node.init = null;
this.semicolon();
} else {
node.binding = this.qml_parseBinding();
node.init = this.qml_parsePropertyAssignment();
}
if (node.typeInfo.type === qtt._alias) {
node.typeInfo = undefined;
return this.finishNode(node, "QMLPropertyAlias");
}
return this.finishNode(node, "QMLPropertyDeclaration");
}
/*
* Parses a QML Binding of the form:
* ':' (<Expression>|<QMLStatementBlock>)
* Parses one of the following possibilities for a QML Property assignment:
* - JavaScript Expression
* - QML JavaScript Statement Block
* - QML Object Literal
*/
pp.qml_parseBinding = function() {
var node = this.startNode();
this.expect(tt.colon);
pp.qml_parsePropertyAssignment = function () {
// TODO: solve ambiguity where a QML Object Literal starts with a
// Qualified Id that looks very similar to a MemberExpression in
// JavaScript. For now, we just won't parse statements like:
@ -356,12 +415,12 @@ var injectQML;
// test: QMLObject.QualifiedId { }
if (this.type === tt.braceL) {
node.block = this.qml_parseStatementBlock();
return this.finishNode(node, "QMLBinding");
}
node.expr = this.parseExpression(false);
return this.qml_parseStatementBlock();
} else {
var node = this.parseExpression(false);
this.semicolon();
return this.finishNode(node, "QMLBinding");
return node;
}
}
/*
@ -382,52 +441,42 @@ var injectQML;
* Parses a QML Type which can be either a Qualified ID or a primitive type keyword.
* Returns a node of type qtt._alias if the type keyword parsed was "alias".
*/
pp.qml_parseType = function() {
var node = this.startNode();
pp.qml_parseKind = function () {
if (this.type === tt.name || this.type === tt._var) {
var value = this.value;
if (this.qml_eatPrimitiveType(value)) {
node.isPrimitive = true;
node.primitive = value;
return value;
} else if (this.eatContextual(qtt._alias)) {
return this.finishNode(node, qtt._alias);
return qtt._alias;
} else {
node.isPrimitive = false;
node.qualifiedId = this.qml_parseQualifiedId();
return this.qml_parseQualifiedId(false);
}
}
} else {
this.unexpected();
}
return this.finishNode(node, "QMLType");
}
/*
* Parses a Qualified ID of the form:
* <Identifier> ('.' <Identifier>)*
*
* If 'liberal' is true then this method will allow non-contextual QML keywords as
* identifiers.
*/
pp.qml_parseQualifiedId = function() {
pp.qml_parseQualifiedId = function (liberal) {
var node = this.startNode();
node.parts = [];
if (!this.qml_isIdent(this.type, this.value)) {
this.unexpected();
node.parts.push(this.qml_parseIdent(liberal));
while (this.eat(tt.dot)) {
node.parts.push(this.qml_parseIdent(liberal));
}
var id = this.value;
this.next();
node.parts.push(id);
while(this.type === tt.dot) {
id += '.';
this.next();
if (!this.qml_isIdent(this.type, this.value)) {
this.unexpected();
node.name = "";
for (var i = 0; i < node.parts.length; i++) {
node.name += node.parts[i].name;
if (i < node.parts.length - 1) {
node.name += ".";
}
id += this.value;
node.parts.push(this.value);
this.next();
}
node.raw = id;
return this.finishNode(node, "QMLQualifiedID");
}
@ -435,16 +484,21 @@ var injectQML;
/*
* Parses an Identifier in a QML Context. That is, this method uses 'isQMLContextual'
* to throw an error if a non-contextual QML keyword is found.
*
* If 'liberal' is true then this method will allow non-contextual QML keywords as
* identifiers.
*/
pp.qml_parseIdent = function (liberal) {
// Check for non-contextual QML keywords
if (this.type === tt.name) {
if (!liberal) {
for (var key in keywords) {
if (!keywords[key].isQMLContextual && this.isContextual(key)) {
this.unexpected();
}
}
}
}
return this.parseIdent(liberal);
}
@ -499,20 +553,10 @@ var injectQML;
// replacing JavaScripts top-level. Here we are parsing such things
// as the root object literal and header statements of QML. Eventually,
// these rules will delegate down to JavaScript expressions.
if (!node.body) {
node.body = [];
}
var headerStmts = this.qml_parseHeaderStatements();
if (headerStmts !== undefined) {
node.body.push(headerStmts);
}
node.headerStatements = this.qml_parseHeaderStatements();
node.rootObject = null;
if (this.type !== tt.eof) {
var objRoot = this.qml_parseObjectLiteral();
if (objRoot !== undefined) {
node.body.push(objRoot);
}
node.rootObject = this.qml_parseObjectLiteral();
}
if (!this.eat(tt.eof)) {

View file

@ -6,6 +6,6 @@
"test": "node test/run.js"
},
"dependencies": {
"acorn": "^2.4.0"
"acorn": "^2.5.2"
}
}

File diff suppressed because it is too large Load diff

View file

@ -17,60 +17,45 @@
return define(["acorn/dist/walk"], mod);
mod(acorn.walk); // Plain browser env
})(function (walk) {
function skipThrough(node, st, c) { c(node, st) }
function skipThrough(node, st, c) {
c(node, st)
}
function ignore(node, st, c) {}
var base = walk.base;
base["Program"] = function (node, st, c) {
for (var i = 0; i < node.body.length; i++) {
var nodeBody = node.body[i];
if (node.body[i].type === "QMLObjectLiteral") {
c(node.body[i], st, "QMLRootObject");
} else {
c(node.body[i], st);
}
}
}
c(node.headerStatements, st);
c(node.rootObject, st, "QMLRootObject");
};
base["QMLHeaderStatements"] = function (node, st, c) {
for (var i = 0; i < node.statements.length; i++) {
c(node.statements[i], st, "QMLHeaderStatement");
}
}
};
base["QMLHeaderStatement"] = skipThrough;
base["QMLImportStatement"] = function(node, st, c) {
c(node.module, st);
}
base["QMLModule"] = ignore;
base["QMLImportStatement"] = ignore;
base["QMLPragmaStatement"] = ignore;
base["QMLRootObject"] = skipThrough;
base["QMLObjectLiteral"] = function (node, st, c) {
c(node.block, st);
}
};
base["QMLMemberBlock"] = function (node, st, c) {
for (var i = 0; i < node.members.length; i++) {
c(node.members[i], st, "QMLMember");
}
}
};
base["QMLMember"] = skipThrough;
base["QMLPropertyDeclaration"] = function (node, st, c) {
c(node.identifier, st, "Pattern");
c(node.binding, st);
}
};
base["QMLSignalDefinition"] = ignore;
base["QMLProperty"] = function(node, st, c) {
// c(node.qualifiedId, st)
base["QMLPropertyBinding"] = function (node, st, c) {
c(node.binding, st);
}
base["QMLBinding"] = function(node, st, c) {
if (node.block) {
c(node.block, st);
} else {
c(node.expr, st, "Expression");
}
}
};
base["QMLStatementBlock"] = function (node, st, c) {
for (var i = 0; i < node.statements.length; i++) {
c(node.statements[i], st, "Statement");
}
}
};
})