1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-24 09:25:31 +02:00

Bug 480238 - QML Parser based on Acorn

Added a new plugin 'org.eclipse.cdt.qt.core.acorn' which houses the new
acorn-qml parser.

Change-Id: I7b456ecec97d44e10ca7e259523b5262c67c538d
Signed-off-by: Matthew Bastien <mbastien@blackberry.com>
This commit is contained in:
Matthew Bastien 2015-10-20 12:19:57 -04:00 committed by Gerrit Code Review @ Eclipse.org
parent 508ee9fd44
commit f8b769b2b3
16 changed files with 717 additions and 0 deletions

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.cdt.qt.core.acorn</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View file

@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

View file

@ -0,0 +1,10 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.cdt.qt.core.acorn
Bundle-Version: 2.0.0.qualifier
Bundle-Activator: org.eclipse.cdt.qt.core.acorn.Activator
Bundle-Vendor: %providerName
Require-Bundle: org.eclipse.core.runtime
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy

View file

@ -0,0 +1 @@
/node_modules

View file

@ -0,0 +1,81 @@
#!/usr/bin/env node
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
var path = require("path");
var fs = require("fs");
var acorn = require("..");
var infile, parsed, tokens, options = {}, silent = false, compact = false, tokenize = false;
function help(status) {
var print = (status == 0) ? console.log : console.error;
print("usage: " + path.basename(process.argv[1]) + "[--ecma3|--ecma5|--ecma6]");
print(" [--tokenize] [--locations] [---allow-hash-bang] [--compact] [--silent] [--module] [--help] [--] infile");
process.exit(status);
}
for (var i = 2; i < process.argv.length; ++i) {
var arg = process.argv[i];
if (arg[0] != "-" && !infile) infile = arg;
else if (arg == "--" && !infile && i + 2 == process.argv.length)
infile = process.argv[++i];
else if (arg == "--ecma3")
options.ecmaVersion = 3;
else if (arg == "--ecma5")
options.ecmaVersion = 5;
else if (arg == "--ecma6")
options.ecmaVersion = 6;
else if (arg == "--ecma7")
options.ecmaVersion = 7;
else if (arg == "--locations")
options.locations = true;
else if (arg == "--allow-hash-bang")
options.allowHashBang = true;
else if (arg == "--silent")
silent = true;
else if (arg == "--compact")
compact = true;
else if (arg == "--help")
help(0);
else if (arg == "--tokenize")
tokenize = true;
else if (arg == "--module")
options.sourceType = 'module';
else
help(1);
}
// Enable qml parser
options.plugins = { qml: true }
try {
var code = fs.readFileSync(infile, "utf8");
if (!tokenize)
parsed = acorn.parse(code, options);
else {
var get = acorn.tokenize(code, options);
tokens = [];
while (true) {
var token = get();
tokens.push(token);
if (token.type.type == "eof")
break;
}
}
} catch(e) {
console.log(e.message);
process.exit(1);
}
if (!silent)
console.log(JSON.stringify(tokenize ? tokens : parsed, null, compact ? null : 2));

View file

@ -0,0 +1,13 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
'use strict';
module.exports = require('./inject')(require('acorn'));

View file

@ -0,0 +1,185 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
'use strict';
module.exports = function(acorn) {
// Define QML token types
var tt = acorn.tokTypes;
/*
* Shorthand for defining keywords in tt (acorn.tokTypes). Creates a new key in
* tt with the label _<keywordName>.
*/
function kw(name, options) {
if (options === undefined)
options = {};
options.keyword = name;
tt["_" + name] = new acorn.TokenType(name, options);
}
// Define QML keywords
kw("property");
kw("readonly");
kw("color");
kw("pragma");
kw("as");
// Define QML token contexts
var tc = acorn.tokContexts;
// TODO: Add QML contexts (one such example is so we can parse keywords as identifiers)
// QML parser methods
var pp = acorn.Parser.prototype;
pp.qml_parseHeaderStatements = function() {
var node = this.startNode()
node.statements = [];
loop: {
switch (this.type) {
case tt._import:
var qmlImport = this.qml_parseImportStatement();
node.statements.push(qmlImport);
break loop;
case tt._pragma:
// TODO: parse QML pragma statement
}
}
return this.finishNode(node, "QMLHeaderStatements")
}
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
this.semicolon();
return this.finishNode(node, "QMLImportStatement");
};
pp.qml_parseModuleIdentifier = 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
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");
};
pp.qml_parseQualifiedId = function() {
var id = this.value;
this.next();
while(this.type == tt.dot) {
id += '.';
this.next();
if (this.type == tt.name)
id += this.value;
else
this.unexpected();
this.next();
}
return id;
}
/*
* Returns a TokenType that matches the given word or undefined if
* no such TokenType could be found.
*
* Uses contextual information to determine whether or not a keyword
* such as 'color' is being used as an identifier. If this is found
* to be the case, tt.name is returned.
*/
pp.qml_getTokenType = function(word) {
// TODO: use context to determine if this is an identifier or
// a keyword (color, real, etc. can be used as identifiers)
switch(word) {
case "property":
return tt._property;
case "readonly":
return tt._readonly;
case "import":
// Make sure that 'import' is recognized as a keyword
// regardless of ecma version set in acorn.
return tt._import;
case "color":
return tt._color;
case "pragma":
return tt._pragma;
case "as":
return tt._as;
}
return undefined;
}
acorn.plugins.qml = function(instance) {
// Extend acorn's 'parseTopLevel' method
instance.extend("parseTopLevel", function(nextMethod) {
return function(node) {
// Most of QML's constructs sit at the top-level of the parse tree,
// 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();
node.body.push(headerStmts)
// TODO: Parse QML object root
// TODO: don't call acorn's parseTopLevel method once the above are working
return nextMethod.call(this, node);
};
});
// Extend acorn's 'readWord' method
instance.extend("readWord", function(nextMethod) {
return function() {
// Parse a word and attempt to match it to a QML keyword
var word = this.readWord1();
var type = this.qml_getTokenType(word);
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
// subsequently _tokentype.keywords, we can't look for JavaScript keyword
// matches ourselves. This is unfortunate because we have to move the parser
// backwards and let readWord call readWord1 a second time for every word
// that is not a QML keyword.
this.pos -= word.length;
return nextMethod.call(this);
};
});
}
return acorn
};

View file

@ -0,0 +1,11 @@
{
"name": "acorn-qml",
"description": "QML Parser",
"version": "2.4.0",
"scripts": {
"test": "node test/run.js"
},
"dependencies": {
"acorn": "^2.4.0"
}
}

View file

@ -0,0 +1,118 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
var tests = [];
exports.test = function(code, ast, options) {
tests.push({code: code, ast: ast, options: options});
};
exports.testFail = function(code, message, options) {
tests.push({code: code, error: message, options: options});
};
exports.testAssert = function(code, assert, options) {
tests.push({code: code, assert: assert, options: options});
};
exports.runTests = function(config, callback) {
var parse = config.parse;
for (var i = 0; i < tests.length; ++i) {
var test = tests[i];
if (config.filter && !config.filter(test)) continue;
try {
var testOpts = test.options || {locations: true};
var expected = {};
if (expected.onComment = testOpts.onComment) {
testOpts.onComment = []
}
if (expected.onToken = testOpts.onToken) {
testOpts.onToken = [];
}
testOpts.plugins = { qml: true };
var ast = parse(test.code, testOpts);
if (test.error) {
if (config.loose) {
callback("ok", test.code);
} else {
callback("fail", test.code, "Expected error message: " + test.error + "\nBut parsing succeeded.");
}
}
else if (test.assert) {
var error = test.assert(ast);
if (error) callback("fail", test.code,
"\n Assertion failed:\n " + error);
else callback("ok", test.code);
} else {
var mis = misMatch(test.ast, ast);
for (var name in expected) {
if (mis) break;
if (expected[name]) {
mis = misMatch(expected[name], testOpts[name]);
testOpts[name] = expected[name];
}
}
if (mis) callback("fail", test.code, mis);
else callback("ok", test.code);
}
} catch(e) {
if (!(e instanceof SyntaxError)) {
throw e;
}
if (test.error) {
if (e.message == test.error) callback("ok", test.code);
else callback("fail", test.code,
"Expected error message: " + test.error + "\nGot error message: " + e.message);
} else {
callback("error", test.code, e.stack || e.toString());
}
}
}
};
function ppJSON(v) { return v instanceof RegExp ? v.toString() : JSON.stringify(v, null, 2); }
function addPath(str, pt) {
if (str.charAt(str.length-1) == ")")
return str.slice(0, str.length-1) + "/" + pt + ")";
return str + " (" + pt + ")";
}
var misMatch = exports.misMatch = function(exp, act) {
if (!exp || !act || (typeof exp != "object") || (typeof act != "object")) {
if (exp !== act) return ppJSON(exp) + " !== " + ppJSON(act);
} else if (exp instanceof RegExp || act instanceof RegExp) {
var left = ppJSON(exp), right = ppJSON(act);
if (left !== right) return left + " !== " + right;
} else if (exp.splice) {
if (!act.slice) return ppJSON(exp) + " != " + ppJSON(act);
if (act.length != exp.length) return "array length mismatch " + exp.length + " != " + act.length;
for (var i = 0; i < act.length; ++i) {
var mis = misMatch(exp[i], act[i]);
if (mis) return addPath(mis, i);
}
} else {
for (var prop in exp) {
var mis = misMatch(exp[prop], act[prop]);
if (mis) return addPath(mis, prop);
}
}
};
function mangle(ast) {
if (typeof ast != "object" || !ast) return;
if (ast.slice) {
for (var i = 0; i < ast.length; ++i) mangle(ast[i]);
} else {
var loc = ast.start && ast.end && {start: ast.start, end: ast.end};
if (loc) { delete ast.start; delete ast.end; }
for (var name in ast) if (ast.hasOwnProperty(name)) mangle(ast[name]);
if (loc) ast.loc = loc;
}
}

View file

@ -0,0 +1,81 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
var driver = require("./driver.js");
require("./tests-qml.js");
function group(name) {
if (typeof console === "object" && console.group) {
console.group(name);
}
}
function groupEnd() {
if (typeof console === "object" && console.groupEnd) {
console.groupEnd(name);
}
}
function log(title, message) {
if (typeof console === "object") console.log(title, message);
}
var stats, modes = {
Normal: {
config: {
parse: require("..").parse
}
}
};
function report(state, code, message) {
if (state != "ok") {++stats.failed; log(code, message);}
++stats.testsRun;
}
group("Errors");
for (var name in modes) {
group(name);
var mode = modes[name];
stats = mode.stats = {testsRun: 0, failed: 0};
var t0 = +new Date;
driver.runTests(mode.config, report);
mode.stats.duration = +new Date - t0;
groupEnd();
}
groupEnd();
function outputStats(name, stats) {
log(name + ":", stats.testsRun + " tests run in " + stats.duration + "ms; " +
(stats.failed ? stats.failed + " failures." : "all passed."));
}
var total = {testsRun: 0, failed: 0, duration: 0};
group("Stats");
for (var name in modes) {
var stats = modes[name].stats;
outputStats(name + " parser", stats);
for (var key in stats) total[key] += stats[key];
}
outputStats("Total", total);
groupEnd();
if (total.failed && typeof process === "object") {
process.stdout.write("", function() {
process.exit(1);
});
}

View file

@ -0,0 +1,72 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
var testFixture = {
'QML': {
'import QtQuick 2.2': {
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 }
},
module: {
type: "QMLModuleIdentifier",
qualifiedId: "QtQuick",
range: [7, 18],
loc: {
start: { line: 1, column: 7 },
end: { line: 1, column: 18 }
},
version: {
type: "Literal",
range: [15, 18],
loc: {
start: { line: 1, column: 15 },
end: { line: 1, column: 18 }
},
value: 2.2,
raw: "2.2"
}
}
}
]
}
}
};
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
});
}
}

View file

@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.

View file

@ -0,0 +1,8 @@
# Copyright (c) 2013 QNX Software Systems and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
pluginName=C/C++ Qt Acorn QML Parser
providerName=Eclipse CDT

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.cdt</groupId>
<artifactId>cdt-parent</artifactId>
<version>8.8.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<version>2.0.0-SNAPSHOT</version>
<artifactId>org.eclipse.cdt.qt.core.acorn</artifactId>
<packaging>eclipse-plugin</packaging>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>generate-parsers</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<ant antfile="${basedir}/build.xml" target="build"/>
</target>
</configuration>
</execution>
<execution>
<id>clean-parsers</id>
<phase>clean</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<ant antfile="${basedir}/build.xml" target="clean"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.qt.core.acorn;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
private static BundleContext context;
static BundleContext getContext() {
return context;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext bundleContext) throws Exception {
Activator.context = bundleContext;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext bundleContext) throws Exception {
Activator.context = null;
}
}