summaryrefslogtreecommitdiffstats
path: root/src/declarative/qml/qmlscriptparser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/declarative/qml/qmlscriptparser.cpp')
-rw-r--r--src/declarative/qml/qmlscriptparser.cpp632
1 files changed, 632 insertions, 0 deletions
diff --git a/src/declarative/qml/qmlscriptparser.cpp b/src/declarative/qml/qmlscriptparser.cpp
new file mode 100644
index 0000000..317a3bf
--- /dev/null
+++ b/src/declarative/qml/qmlscriptparser.cpp
@@ -0,0 +1,632 @@
+
+#include "qmlscriptparser_p.h"
+#include "qmlxmlparser_p.h"
+#include "qmlparser_p.h"
+
+#include "parser/javascriptengine_p.h"
+#include "parser/javascriptparser_p.h"
+#include "parser/javascriptlexer_p.h"
+#include "parser/javascriptnodepool_p.h"
+#include "parser/javascriptastvisitor_p.h"
+#include "parser/javascriptast_p.h"
+
+#include <QStack>
+#include <QtDebug>
+
+QT_BEGIN_NAMESPACE
+
+using namespace JavaScript;
+using namespace QmlParser;
+
+namespace {
+
+class ProcessAST: protected AST::Visitor
+{
+ struct State {
+ State() : object(0), property(0) {}
+ State(Object *o) : object(o), property(0) {}
+ State(Object *o, Property *p) : object(o), property(p) {}
+
+ Object *object;
+ Property *property;
+ };
+
+ struct StateStack : public QStack<State>
+ {
+ void pushObject(Object *obj)
+ {
+ push(State(obj));
+ }
+
+ void pushProperty(const QString &name, int lineNumber)
+ {
+ const State &state = top();
+ if (state.property) {
+ State s(state.property->getValue(),
+ state.property->getValue()->getProperty(name.toLatin1()));
+ s.property->line = lineNumber;
+ push(s);
+ } else {
+ State s(state.object,
+ state.object->getProperty(name.toLatin1()));
+ s.property->line = lineNumber;
+ push(s);
+ }
+ }
+ };
+
+public:
+ ProcessAST(QmlScriptParser *parser);
+ virtual ~ProcessAST();
+
+ void operator()(const QString &code, AST::Node *node);
+
+protected:
+ Object *defineObjectBinding(int line,
+ AST::UiQualifiedId *propertyName,
+ const QString &objectType,
+ AST::UiObjectInitializer *initializer = 0);
+ Object *defineObjectBinding_helper(int line,
+ AST::UiQualifiedId *propertyName,
+ const QString &objectType,
+ AST::UiObjectInitializer *initializer = 0);
+ QString getPrimitive(const QByteArray &propertyName, AST::ExpressionNode *expr);
+ void defineProperty(const QString &propertyName, int line, const QString &primitive);
+
+ using AST::Visitor::visit;
+ using AST::Visitor::endVisit;
+
+ virtual bool visit(AST::UiProgram *node);
+ virtual bool visit(AST::UiImport *node);
+ virtual bool visit(AST::UiObjectDefinition *node);
+ virtual bool visit(AST::UiPublicMember *node);
+ virtual bool visit(AST::UiObjectBinding *node);
+
+ virtual bool visit(AST::UiScriptBinding *node);
+ virtual bool visit(AST::UiArrayBinding *node);
+ virtual bool visit(AST::UiSourceElement *node);
+
+ void accept(AST::Node *node);
+
+ QString asString(AST::UiQualifiedId *node) const;
+
+ const State state() const;
+ Object *currentObject() const;
+ Property *currentProperty() const;
+
+ QString qualifiedNameId() const;
+
+ QString textAt(const AST::SourceLocation &loc) const
+ { return _contents.mid(loc.offset, loc.length); }
+
+ QString textAt(const AST::SourceLocation &first,
+ const AST::SourceLocation &last) const
+ { return _contents.mid(first.offset, last.offset + last.length - first.offset); }
+
+ QString asString(AST::ExpressionNode *expr) const
+ {
+ if (! expr)
+ return QString();
+
+ return textAt(expr->firstSourceLocation(), expr->lastSourceLocation());
+ }
+
+ QString asString(AST::Statement *stmt) const
+ {
+ if (! stmt)
+ return QString();
+
+ QString s = textAt(stmt->firstSourceLocation(), stmt->lastSourceLocation());
+ s += QLatin1Char('\n');
+ return s;
+ }
+
+private:
+ QmlScriptParser *_parser;
+ StateStack _stateStack;
+ QStringList _scope;
+ QString _contents;
+
+ inline bool isSignalProperty(const QByteArray &propertyName) const {
+ return (propertyName.length() >= 3 && propertyName.startsWith("on") &&
+ ('A' <= propertyName.at(2) && 'Z' >= propertyName.at(2)));
+ }
+
+};
+
+ProcessAST::ProcessAST(QmlScriptParser *parser)
+ : _parser(parser)
+{
+}
+
+ProcessAST::~ProcessAST()
+{
+}
+
+void ProcessAST::operator()(const QString &code, AST::Node *node)
+{
+ _contents = code;
+ accept(node);
+}
+
+void ProcessAST::accept(AST::Node *node)
+{
+ AST::Node::acceptChild(node, this);
+}
+
+const ProcessAST::State ProcessAST::state() const
+{
+ if (_stateStack.isEmpty())
+ return State();
+
+ return _stateStack.back();
+}
+
+Object *ProcessAST::currentObject() const
+{
+ return state().object;
+}
+
+Property *ProcessAST::currentProperty() const
+{
+ return state().property;
+}
+
+QString ProcessAST::qualifiedNameId() const
+{
+ return _scope.join(QLatin1String("/"));
+}
+
+QString ProcessAST::asString(AST::UiQualifiedId *node) const
+{
+ QString s;
+
+ for (AST::UiQualifiedId *it = node; it; it = it->next) {
+ s.append(it->name->asString());
+
+ if (it->next)
+ s.append(QLatin1Char('.'));
+ }
+
+ return s;
+}
+
+Object *ProcessAST::defineObjectBinding_helper(int line,
+ AST::UiQualifiedId *propertyName,
+ const QString &objectType,
+ AST::UiObjectInitializer *initializer)
+{
+ bool isType = !objectType.isEmpty() && objectType.at(0).isUpper() && !objectType.contains(QLatin1Char('.'));
+ if (!isType) {
+ qWarning() << "bad name for a class"; // ### FIXME
+ return false;
+ }
+
+ int propertyCount = 0;
+ for (; propertyName; propertyName = propertyName->next){
+ ++propertyCount;
+ _stateStack.pushProperty(propertyName->name->asString(), propertyName->identifierToken.startLine);
+ }
+
+ // Class
+ const int typeId = _parser->findOrCreateTypeId(objectType);
+
+ Object *obj = new Object;
+ obj->type = typeId;
+ _scope.append(objectType);
+ obj->typeName = qualifiedNameId().toLatin1();
+ _scope.removeLast();
+ obj->line = line;
+
+ if (propertyCount) {
+ Property *prop = currentProperty();
+ Value *v = new Value;
+ v->object = obj;
+ v->line = line;
+ prop->addValue(v);
+
+ while (propertyCount--)
+ _stateStack.pop();
+
+ } else {
+
+ if (! _parser->tree()) {
+ _parser->setTree(obj);
+
+ if (!_parser->scriptFile().isEmpty()) {
+ _stateStack.pushObject(obj);
+ Object *scriptObject= defineObjectBinding(line, 0, QLatin1String("Script"));
+ _stateStack.pushObject(scriptObject);
+ defineProperty(QLatin1String("src"), line, _parser->scriptFile());
+ _stateStack.pop(); // scriptObject
+ _stateStack.pop(); // object
+ }
+
+ } else {
+ const State state = _stateStack.top();
+ Value *v = new Value;
+ v->object = obj;
+ v->line = line;
+ if (state.property)
+ state.property->addValue(v);
+ else
+ state.object->getDefaultProperty()->addValue(v);
+ }
+ }
+
+ _stateStack.pushObject(obj);
+ accept(initializer);
+ _stateStack.pop();
+
+ return obj;
+}
+
+Object *ProcessAST::defineObjectBinding(int line,
+ AST::UiQualifiedId *qualifiedId,
+ const QString &objectType,
+ AST::UiObjectInitializer *initializer)
+{
+ if (objectType == QLatin1String("Connection")) {
+
+ Object *obj = defineObjectBinding_helper(line, 0, QLatin1String("Connection"));
+
+ _stateStack.pushObject(obj);
+
+ AST::UiObjectMemberList *it = initializer->members;
+ for (; it; it = it->next) {
+ AST::UiScriptBinding *scriptBinding = AST::cast<AST::UiScriptBinding *>(it->member);
+ if (! scriptBinding)
+ continue;
+
+ QString propertyName = asString(scriptBinding->qualifiedId);
+ if (propertyName == QLatin1String("script")) {
+ QString script;
+ if (AST::ExpressionStatement *stmt = AST::cast<AST::ExpressionStatement *>(scriptBinding->statement)) {
+ script = getPrimitive("script", stmt->expression);
+ } else {
+ script = asString(scriptBinding->statement);
+ }
+ defineProperty(QLatin1String("script"), line, script);
+ } else {
+ accept(it->member);
+ }
+ }
+
+ _stateStack.pop(); // object
+
+ return obj;
+ }
+
+ return defineObjectBinding_helper(line, qualifiedId, objectType, initializer);
+}
+
+void ProcessAST::defineProperty(const QString &propertyName, int line, const QString &primitive)
+{
+ _stateStack.pushProperty(propertyName, line);
+ Value *value = new Value;
+ value->primitive = primitive;
+ value->line = line;
+ currentProperty()->addValue(value);
+ _stateStack.pop();
+}
+
+// UiProgram: UiImportListOpt UiObjectMemberList ;
+bool ProcessAST::visit(AST::UiProgram *node)
+{
+ accept(node->imports);
+ accept(node->members->member);
+ return false;
+}
+
+// UiImport: T_IMPORT T_STRING_LITERAL ;
+bool ProcessAST::visit(AST::UiImport *node)
+{
+ QString fileName = node->fileName->asString();
+ _parser->addNamespacePath(fileName);
+ return false;
+}
+
+// UiObjectMember: T_PUBLIC UiMemberType T_IDENTIFIER T_COLON Expression
+// UiObjectMember: T_PUBLIC UiMemberType T_IDENTIFIER
+//
+// UiMemberType: "property" | "signal"
+bool ProcessAST::visit(AST::UiPublicMember *node)
+{
+ const QString memberType = node->memberType->asString();
+ const QString name = node->name->asString();
+
+ if (memberType == QLatin1String("property")) {
+ _stateStack.pushProperty(QLatin1String("properties"), node->publicToken.startLine);
+
+ Object *obj = defineObjectBinding(node->identifierToken.startLine,
+ 0,
+ QLatin1String("Property"));
+
+ _stateStack.pushObject(obj);
+
+ defineProperty(QLatin1String("name"), node->identifierToken.startLine, name);
+ if (node->expression) // default value
+ defineProperty(QLatin1String("value"), node->identifierToken.startLine, getPrimitive("value", node->expression));
+
+ _stateStack.pop(); // object
+ _stateStack.pop(); // properties
+
+ } else if (memberType == QLatin1String("signal")) {
+ _stateStack.pushProperty(QLatin1String("signals"), node->publicToken.startLine);
+
+ Object *obj = defineObjectBinding(node->identifierToken.startLine,
+ 0,
+ QLatin1String("Signal"));
+
+ _stateStack.pushObject(obj);
+
+ defineProperty(QLatin1String("name"), node->identifierToken.startLine, name);
+
+ _stateStack.pop(); // object
+ _stateStack.pop(); // signals
+ } else {
+ qWarning() << "bad public identifier" << memberType; // ### FIXME
+ }
+
+
+ // ### TODO drop initializer (unless some example needs differnet properties than name and type and value.
+
+ return false;
+}
+
+
+// UiObjectMember: T_IDENTIFIER UiObjectInitializer ;
+bool ProcessAST::visit(AST::UiObjectDefinition *node)
+{
+
+ defineObjectBinding(node->identifierToken.startLine,
+ 0,
+ node->name->asString(),
+ node->initializer);
+
+ return false;
+}
+
+
+// UiObjectMember: UiQualifiedId T_COLON T_IDENTIFIER UiObjectInitializer ;
+bool ProcessAST::visit(AST::UiObjectBinding *node)
+{
+ defineObjectBinding(node->identifierToken.startLine,
+ node->qualifiedId,
+ node->name->asString(),
+ node->initializer);
+
+ return false;
+}
+
+QString ProcessAST::getPrimitive(const QByteArray &propertyName, AST::ExpressionNode *expr)
+{
+ QString primitive;
+
+ if (isSignalProperty(propertyName)) {
+ primitive = asString(expr);
+ } else if (propertyName == "id" && expr && expr->kind == AST::Node::Kind_IdentifierExpression) {
+ primitive = asString(expr);
+ } else if (AST::StringLiteral *lit = AST::cast<AST::StringLiteral *>(expr)) {
+ // hack: emulate weird XML feature that string literals are not quoted.
+ //This needs to be fixed in the qmlcompiler once xml goes away.
+ primitive = lit->value->asString();
+ } else if (expr->kind == AST::Node::Kind_TrueLiteral
+ || expr->kind == AST::Node::Kind_FalseLiteral
+ || expr->kind == AST::Node::Kind_NumericLiteral
+ ) {
+ primitive = asString(expr);
+ } else {
+ // create a binding
+ primitive += QLatin1Char('{');
+ primitive += asString(expr);
+ primitive += QLatin1Char('}');
+ }
+ return primitive;
+}
+
+
+// UiObjectMember: UiQualifiedId T_COLON Statement ;
+bool ProcessAST::visit(AST::UiScriptBinding *node)
+{
+ int propertyCount = 0;
+ AST::UiQualifiedId *propertyName = node->qualifiedId;
+ for (; propertyName; propertyName = propertyName->next){
+ ++propertyCount;
+ _stateStack.pushProperty(propertyName->name->asString(), propertyName->identifierToken.startLine);
+ }
+
+ Property *prop = currentProperty();
+ QString primitive;
+
+ if (AST::ExpressionStatement *stmt = AST::cast<AST::ExpressionStatement *>(node->statement)) {
+ primitive = getPrimitive(prop->name, stmt->expression);
+ } else if (isSignalProperty(prop->name)) {
+ if (AST::Block *block = AST::cast<AST::Block *>(node->statement)) {
+ const int start = block->lbraceToken.offset + block->rbraceToken.length;
+ primitive += _contents.mid(start, block->rbraceToken.offset - start);
+ } else {
+ primitive += asString(node->statement);
+ }
+ } else { // do binding
+ primitive += QLatin1Char('{');
+ primitive += asString(node->statement);
+ primitive += QLatin1Char('}');
+ }
+
+ Value *v = new Value;
+ v->primitive = primitive;
+ v->line = node->colonToken.startLine;
+ prop->addValue(v);
+
+ while (propertyCount--)
+ _stateStack.pop();
+
+ return true;
+}
+
+// UiObjectMember: UiQualifiedId T_COLON T_LBRACKET UiObjectMemberList T_RBRACKET ;
+bool ProcessAST::visit(AST::UiArrayBinding *node)
+{
+ int propertyCount = 0;
+ AST::UiQualifiedId *propertyName = node->qualifiedId;
+ for (; propertyName; propertyName = propertyName->next){
+ ++propertyCount;
+ _stateStack.pushProperty(propertyName->name->asString(), propertyName->identifierToken.startLine);
+ }
+
+ accept(node->members);
+
+ while (propertyCount--)
+ _stateStack.pop();
+
+ return false;
+}
+
+bool ProcessAST::visit(AST::UiSourceElement *node)
+{
+ QmlParser::Object *obj = currentObject();
+ if (! (obj && obj->typeName == "Script")) {
+ // ### warning
+ return false;
+ }
+
+ QString source;
+
+ int line = 0;
+ if (AST::FunctionDeclaration *funDecl = AST::cast<AST::FunctionDeclaration *>(node->sourceElement)) {
+ line = funDecl->functionToken.startLine;
+ source = asString(funDecl);
+ } else if (AST::VariableStatement *varStmt = AST::cast<AST::VariableStatement *>(node->sourceElement)) {
+ // ignore variable declarations
+ line = varStmt->declarationKindToken.startLine;
+ }
+
+ Value *value = new Value;
+ value->primitive = source;
+ value->line = line;
+
+ obj->getDefaultProperty()->addValue(value);
+
+ return false;
+}
+
+} // end of anonymous namespace
+
+
+QmlScriptParser::QmlScriptParser()
+ : root(0), _errorLine(-1)
+{
+
+}
+
+QmlScriptParser::~QmlScriptParser()
+{
+}
+
+bool QmlScriptParser::parse(const QByteArray &data, const QUrl &url)
+{
+ if (QmlComponentPrivate::isXml(data)) {
+ // parse using the XML parser.
+ QmlXmlParser xmlParser;
+ if (xmlParser.parse(data, url)) {
+ _nameSpacePaths = xmlParser.nameSpacePaths();
+ root = xmlParser.takeTree();
+ _typeNames = xmlParser.types();
+ return true;
+ }
+
+ _error = xmlParser.errorDescription();
+ _errorLine = 0; // ### FIXME
+ return false;
+ }
+
+ const QString fileName = url.toString();
+ const QString code = QString::fromUtf8(data); // ### FIXME
+
+ JavaScriptParser parser;
+ JavaScriptEnginePrivate driver;
+
+ NodePool nodePool(fileName, &driver);
+ driver.setNodePool(&nodePool);
+
+ Lexer lexer(&driver);
+ lexer.setCode(code, /*line = */ 1);
+ driver.setLexer(&lexer);
+
+ if (! parser.parse(&driver)) {
+ _error = parser.errorMessage();
+ _errorLine = parser.errorLineNumber();
+ return false;
+ }
+
+ ProcessAST process(this);
+ process(code, parser.ast());
+
+ return true;
+}
+
+QString QmlScriptParser::errorDescription() const
+{
+ return _error;
+}
+
+int QmlScriptParser::errorLine() const
+{
+ return _errorLine;
+}
+
+QMap<QString,QString> QmlScriptParser::nameSpacePaths() const
+{
+ return _nameSpacePaths;
+}
+
+QStringList QmlScriptParser::types() const
+{
+ return _typeNames;
+}
+
+Object *QmlScriptParser::tree() const
+{
+ return root;
+}
+
+void QmlScriptParser::clear()
+{
+ if (root) {
+ root->release();
+ root = 0;
+ }
+ _nameSpacePaths.clear();
+ _typeNames.clear();
+ _error.clear();
+ _scriptFile.clear();
+ _errorLine = 0;
+}
+
+int QmlScriptParser::findOrCreateTypeId(const QString &name)
+{
+ int index = _typeNames.indexOf(name);
+
+ if (index == -1) {
+ index = _typeNames.size();
+ _typeNames.append(name);
+ }
+
+ return index;
+}
+
+void QmlScriptParser::setTree(Object *tree)
+{
+ Q_ASSERT(! root);
+
+ root = tree;
+}
+
+void QmlScriptParser::addNamespacePath(const QString &path)
+{
+ _nameSpacePaths.insertMulti(QString(), path);
+}
+
+
+QT_END_NAMESPACE