diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp index 1d78fb7..be7b956 100644 --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -1,845 +1,864 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "expressionvisitor.h" #include "parsesession.h" #include "editorintegrator.h" #include "helper.h" #include "declarations/variabledeclaration.h" #include "declarations/classdeclaration.h" #include #include #include #include #include #include #include #include #include "duchaindebug.h" #define ifDebug(x) using namespace KDevelop; namespace Php { ExpressionVisitor::ExpressionVisitor(EditorIntegrator* editor) : m_editor(editor), m_createProblems(false), m_offset(CursorInRevision::invalid()), m_currentContext(nullptr), m_isAssignmentExpressionEqual(false), m_inDefine(false) { } DeclarationPointer ExpressionVisitor::processVariable(VariableIdentifierAst* variable) { Q_ASSERT(m_currentContext); CursorInRevision position = m_editor->findPosition(variable->variable, EditorIntegrator::BackEdge); if ( m_offset.isValid() ) { position.line += m_offset.line; position.column += m_offset.column; } DeclarationPointer ret; Identifier identifier = identifierForNode(variable).last(); ifDebug(qCDebug(DUCHAIN) << "processing variable" << identifier.toString() << position.castToSimpleCursor();) DUChainReadLocker lock; if (identifier.nameEquals(Identifier(QStringLiteral("this")))) { if (m_currentContext->parentContext() && m_currentContext->parentContext()->type() == DUContext::Class && m_currentContext->parentContext()->owner()) { ret = m_currentContext->parentContext()->owner(); } } else { //DontSearchInParent-flag because (1) in Php global variables aren't available in function //context and (2) a function body consists of a single context (so this is no problem) ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::DontSearchInParent); } if (!ret && m_currentContext->type() == DUContext::Namespace) { ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::NoSearchFlags); } if (!ret) { //look for a function argument ///TODO: why doesn't m_currentContext->findDeclarations() work? /// evaluate if the stuff below is fast enough (faster?) than findDeclarations() ///see r1028306 foreach(const DUContext::Import &import, m_currentContext->importedParentContexts() ) { if ( !import.isDirect() || import.position > position ) { continue; } DUContext* ctx = import.context(m_currentContext->topContext()); if ( ctx->type() == DUContext::Function ) { QList args = ctx->findLocalDeclarations(identifier); if ( !args.isEmpty() ) { ret = args.first(); break; } } } } if (!ret) { //look for a superglobal variable foreach(Declaration* dec, m_currentContext->topContext()->findDeclarations(identifier, position)) { VariableDeclaration* varDec = dynamic_cast(dec); if (varDec && varDec->isSuperglobal()) { ret = dec; break; } } } lock.unlock(); if ( !m_isAssignmentExpressionEqual || identifier.nameEquals( Identifier(QStringLiteral("this")) ) // might be something like $s = $s . $s; in which case we have to add a use for the first $s || (ret && ret->range().end < position) ) { // also don't report uses for the place of declaration if (!ret || ret->range().end != position) { usingDeclaration(variable, ret); } } ifDebug(qCDebug(DUCHAIN) << "found declaration:" << (ret ? ret->toString() : QString("no declaration found"));) return ret; } void ExpressionVisitor::visitNode(AstNode *node) { if (node && node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitNode(node); } void ExpressionVisitor::visitAssignmentExpression(AssignmentExpressionAst *node) { if (node->assignmentExpressionEqual) { m_isAssignmentExpressionEqual = true; } visitNode(node->expression); m_isAssignmentExpressionEqual = false; visitNode(node->assignmentExpressionEqual); visitNode(node->assignmentExpression); if (node->operation == OperationPlus || node->operation == OperationMinus || node->operation == OperationMul || node->operation == OperationDiv || node->operation == OperationExp) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitArrayIndexSpecifier(ArrayIndexSpecifierAst* node) { DefaultVisitor::visitArrayIndexSpecifier(node); // it's an array item but we don't support it really, so just assign type mixed and be done m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } void ExpressionVisitor::visitCompoundVariableWithSimpleIndirectReference(CompoundVariableWithSimpleIndirectReferenceAst *node) { if (node->variable) { m_result.setDeclaration(processVariable(node->variable)); } DefaultVisitor::visitCompoundVariableWithSimpleIndirectReference(node); } void ExpressionVisitor::visitVariable(VariableAst* node) { if ( node->variablePropertiesSequence && node->variablePropertiesSequence->front() && node->variablePropertiesSequence->front()->element && node->variablePropertiesSequence->front()->element->variableProperty && node->variablePropertiesSequence->front()->element->variableProperty->objectProperty ) { // make sure we mark $foo as a use in $foo->... bool isAssignmentExpressionEqual = m_isAssignmentExpressionEqual; m_isAssignmentExpressionEqual = false; DefaultVisitor::visitVariable(node); m_isAssignmentExpressionEqual = isAssignmentExpressionEqual; } else { DefaultVisitor::visitVariable(node); } } void ExpressionVisitor::visitVarExpressionNewObject(VarExpressionNewObjectAst *node) { DefaultVisitor::visitVarExpressionNewObject(node); if (node->className->staticIdentifier != -1) { static const QualifiedIdentifier id(QStringLiteral("static")); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->className, dec); m_result.setDeclaration(dec); } else if (node->className->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->className->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->className->identifier, id); m_result.setDeclaration(dec); } } void ExpressionVisitor::visitVarExpressionArray(VarExpressionArrayAst *node) { DefaultVisitor::visitVarExpressionArray(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeArray))); } void ExpressionVisitor::visitClosure(ClosureAst* node) { auto* closureType = new FunctionType; m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); if (node->functionBody) { visitInnerStatementList(node->functionBody); } if (node->returnType && node->returnType->typehint && isClassTypehint(node->returnType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = node->returnType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } //First try return typehint or phpdoc return typehint AbstractType::Ptr type = returnType(node->returnType, {}, m_editor, m_currentContext); if (!type) { //If failed, use the inferred type from return statements type = m_result.type(); } closureType->setReturnType(type); if (node->parameters->parametersSequence) { const KDevPG::ListNode< ParameterAst* >* it = node->parameters->parametersSequence->front(); forever { AbstractType::Ptr type = parameterType(it->element, {}, m_editor, m_currentContext); closureType->addArgument(type); if (it->element->parameterType && it->element->parameterType->typehint && isClassTypehint(it->element->parameterType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = it->element->parameterType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } if (it->element->defaultValue) { visitExpr(it->element->defaultValue); } if ( it->hasNext() ) { it = it->next; } else { break; } } } if (node->lexicalVars && node->lexicalVars->lexicalVarsSequence) { const KDevPG::ListNode< LexicalVarAst* >* it = node->lexicalVars->lexicalVarsSequence->front(); DUChainWriteLocker lock; forever { DeclarationPointer found; foreach(Declaration* dec, m_currentContext->findDeclarations(identifierForNode(it->element->variable))) { if (dec->kind() == Declaration::Instance) { found = dec; break; } } usingDeclaration(it->element->variable, found); if ( it->hasNext() ) { it = it->next; } else { break; } } } m_result.setType(AbstractType::Ptr(closureType)); } void ExpressionVisitor::visitFunctionCallParameterList( FunctionCallParameterListAst* node ) { QList decs = m_result.allDeclarations(); AbstractType::Ptr type = m_result.type(); DefaultVisitor::visitFunctionCallParameterList( node ); m_result.setDeclarations(decs); m_result.setType(type); } void ExpressionVisitor::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) { DefaultVisitor::visitFunctionCallParameterListElement(node); if (m_inDefine) m_inDefine = false; //reset after first parameter passed, the second argument can be a class name } void ExpressionVisitor::visitFunctionCall(FunctionCallAst* node) { if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); if (id.toString(RemoveExplicitlyGlobalPrefix) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //in a define() call the first argument is the constant name. we don't want to look for a class name to build uses m_inDefine = true; } } DefaultVisitor::visitFunctionCall(node); m_inDefine = false; if (node->stringFunctionNameOrClass) { if (node->stringFunctionName) { //static function call foo::bar() DUContext* context = findClassContext(node->stringFunctionNameOrClass); if (context) { DUChainReadLocker lock(DUChain::lock()); QualifiedIdentifier methodName(stringForNode(node->stringFunctionName).toLower()); m_result.setDeclarations(context->findDeclarations(methodName)); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->stringFunctionName, m_result.allDeclarations().last()); FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { m_result.setHadUnresolvedIdentifiers(true); usingDeclaration(node->stringFunctionName, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } } else if (node->varFunctionName) { //static function call foo::$bar() } else if (node->expr) { //static function call foo::{expr}() const QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); m_result.setDeclaration(dec); } else { //global function call foo(); const QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(FunctionDeclarationType, id); ifDebug(qCDebug(DUCHAIN) << "function call of" << (dec ? dec->toString() : QString("function not found"));) m_result.setDeclaration(dec); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); if (dec) { FunctionType::Ptr function = dec->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setHadUnresolvedIdentifiers(true); } } } } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(IdentifierAst* className) { DUContext* context = nullptr; DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, className); usingDeclaration(className, declaration); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(NamespacedIdentifierAst* className) { DUContext* context = nullptr; const QualifiedIdentifier id = identifierForNamespace(className, m_editor); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(className->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(className, id); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } void ExpressionVisitor::visitConstantOrClassConst(ConstantOrClassConstAst *node) { DefaultVisitor::visitConstantOrClassConst(node); if (node->classConstant) { //class constant Foo::BAR DUContext* context = findClassContext(node->constant); if (context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(Identifier(m_editor->parseSession()->symbol(node->classConstant)))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->classConstant, m_result.allDeclarations().last()); } else { usingDeclaration(node->classConstant, DeclarationPointer()); } if (!stringForNode(node->classConstant).compare(QLatin1String("class"), Qt::CaseInsensitive)) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } else { m_result.setType(AbstractType::Ptr()); } } else { QString str(stringForNode(node->constant).toLower()); if (str == QLatin1String("true") || str == QLatin1String("false")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } else if (str == QLatin1String("null")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } else { //constant (created with declare('foo', 'bar')) or const Foo = 1; QualifiedIdentifier id = identifierForNamespace(node->constant, m_editor, true); DeclarationPointer declaration = findDeclarationImport(ConstantDeclarationType, id); if (!declaration) { ///TODO: is this really wanted? //it could also be a global function call, without () declaration = findDeclarationImport(FunctionDeclarationType, id); } m_result.setDeclaration(declaration); usingDeclaration(node->constant->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(node->constant, id); } } } void ExpressionVisitor::visitScalar(ScalarAst *node) { DefaultVisitor::visitScalar(node); if (node->commonScalar) { uint type = IntegralType::TypeVoid; switch (node->commonScalar->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } m_result.setType(AbstractType::Ptr(new IntegralType(type))); } else if (node->varname != -1) { //STRING_VARNAME-Token, probably the type of varname should be used m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } else if (node->encapsList) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } if (!m_inDefine && node->commonScalar && node->commonScalar->scalarType == ScalarTypeString) { QString str = m_editor->parseSession()->symbol(node->commonScalar); QRegExp exp("^['\"]([A-Za-z0-9_]+)['\"]$"); if (exp.exactMatch(str)) { //that *could* be a class name QualifiedIdentifier id(exp.cap(1).toLower()); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); if (declaration) { usingDeclaration(node->commonScalar, declaration); } else { m_result.setHadUnresolvedIdentifiers(true); } } } } void ExpressionVisitor::visitStaticScalar(StaticScalarAst *node) { if (node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitStaticScalar(node); uint type = IntegralType::TypeVoid; if (node->value) { switch (node->value->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } } else if (node->plusValue || node->minusValue) { type = IntegralType::TypeInt; } else if (node->array != -1) { type = IntegralType::TypeArray; } if (type != IntegralType::TypeVoid) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } void ExpressionVisitor::visitEncapsVar(EncapsVarAst *node) { DefaultVisitor::visitEncapsVar(node); if (node->variable) { // handle $foo DeclarationPointer dec = processVariable(node->variable); if (dec && node->propertyIdentifier) { // handle property in $foo->bar DeclarationPointer foundDec; DUChainReadLocker lock(DUChain::lock()); if ( StructureType::Ptr structType = dec->type() ) { if ( ClassDeclaration* cdec = dynamic_cast(structType->declaration(m_currentContext->topContext())) ) { ///TODO: share code with visitVariableProperty DUContext* ctx = cdec->internalContext(); if (!ctx && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == cdec->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) ctx = m_currentContext->parentContext(); } } if (ctx) { foreach( Declaration* pdec, ctx->findDeclarations(identifierForNode(node->propertyIdentifier)) ) { if ( !pdec->isFunctionDeclaration() ) { foundDec = pdec; break; } } } } } lock.unlock(); usingDeclaration(node->propertyIdentifier, foundDec); } } } void ExpressionVisitor::visitVariableProperty(VariablePropertyAst *node) { ifDebug(qCDebug(DUCHAIN) << "node:" << m_editor->parseSession()->symbol(node) << (node->isFunctionCall != -1 ? QString("is function call") : QString("is no function call"));) if (node->objectProperty && node->objectProperty->objectDimList) { //handle $foo->bar() and $foo->baz, $foo is m_result.type() AbstractType::Ptr type = m_result.type(); //If the variable type is unsure, try to see if it contains a StructureType. If so, use that // (since the other types do not allow accessing properties) if (type && type.cast()) { UnsureType::Ptr unsureType = type.cast(); int numStructureType = 0; StructureType::Ptr structureType; for (unsigned int i = 0; itypesSize(); ++i) { StructureType::Ptr subType = unsureType->types()[i].type(); if (subType) { structureType = subType; ++numStructureType; } } //Only use the found structureType if there's exactly *one* such type if (numStructureType == 1) { Q_ASSERT(structureType); type = AbstractType::Ptr(structureType); } } if (type && StructureType::Ptr::dynamicCast(type)) { DUChainReadLocker lock(DUChain::lock()); Declaration* declaration = StructureType::Ptr::staticCast(type)->declaration(m_currentContext->topContext()); if (declaration) { ifDebug(qCDebug(DUCHAIN) << "parent:" << declaration->toString();) DUContext* context = declaration->internalContext(); if (!context && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } if (context) { QualifiedIdentifier propertyId; if ( node->isFunctionCall != -1 ) { propertyId = QualifiedIdentifier(stringForNode(node->objectProperty->objectDimList->variableName->name).toLower()); } else { propertyId = identifierForNode(node->objectProperty->objectDimList->variableName->name); } ifDebug(qCDebug(DUCHAIN) << "property id:" << propertyId.toString();) QList decs; foreach ( Declaration* dec, context->findDeclarations(propertyId) ) { if ( node->isFunctionCall != -1 ) { if ( dec->isFunctionDeclaration() ) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } else { if ( !dec->isFunctionDeclaration() ) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } } m_result.setDeclarations(decs); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, m_result.allDeclarations().last()); } if (node->isFunctionCall != -1) { FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, DeclarationPointer()); } m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } } DefaultVisitor::visitVariableProperty(node); } void ExpressionVisitor::visitStaticMember(StaticMemberAst* node) { //don't call DefaultVisitor::visitStaticMember(node); //because we would end up in visitCompoundVariableWithSimpleIndirectReference if (node->variable->variable->variable) { DUContext* context = findClassContext(node->className); if (context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(identifierForNode(node->variable->variable->variable))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->variable->variable->variable, m_result.allDeclarations().last()); } else { usingDeclaration(node->variable->variable->variable, DeclarationPointer()); } } else { usingDeclaration(node->className, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } if (node->variable->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* it = node->variable->offsetItemsSequence->front(); do { visitDimListItem(it->element); } while(it->hasNext() && (it = it->next)); } } } void ExpressionVisitor::visitUnaryExpression(UnaryExpressionAst* node) { DefaultVisitor::visitUnaryExpression(node); if (node->castType) { uint type = 0; switch (node->castType) { case CastInt: type = IntegralType::TypeInt; break; case CastDouble: type = IntegralType::TypeFloat; break; case CastString: type = IntegralType::TypeString; break; case CastArray: type = IntegralType::TypeArray; break; case CastObject: { /// Qualified identifier for 'stdclass' static const QualifiedIdentifier stdclassQId(QStringLiteral("stdclass")); DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(m_currentContext->findDeclarations(stdclassQId)); break; } case CastBool: type = IntegralType::TypeBoolean; break; case CastUnset: //TODO break; } if (type) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } } void ExpressionVisitor::visitAdditiveExpressionRest(AdditiveExpressionRestAst* node) { DefaultVisitor::visitAdditiveExpressionRest(node); if (node->operation == OperationPlus || node->operation == OperationMinus) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitRelationalExpression(RelationalExpressionAst *node) { DefaultVisitor::visitRelationalExpression(node); if (node->instanceofType && node->instanceofType->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->instanceofType->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->instanceofType->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->instanceofType->identifier, id); - m_result.setDeclaration(dec); + + m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); + } +} + +void ExpressionVisitor::visitRelationalExpressionRest(RelationalExpressionRestAst *node) +{ + DefaultVisitor::visitRelationalExpressionRest(node); + + m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); +} + +void ExpressionVisitor::visitEqualityExpressionRest(EqualityExpressionRestAst *node) +{ + DefaultVisitor::visitEqualityExpressionRest(node); + + if (node->operation && node->operation == OperationSpaceship) { + m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); + } else { + m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } QString ExpressionVisitor::stringForNode(AstNode* id) { if (!id) return QString(); return m_editor->parseSession()->symbol(id); } QualifiedIdentifier ExpressionVisitor::identifierForNode(IdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } QString ExpressionVisitor::stringForNode(VariableIdentifierAst* id) { if (!id) return QString(); QString ret(m_editor->parseSession()->symbol(id->variable)); ret = ret.mid(1); //cut off $ return ret; } QualifiedIdentifier ExpressionVisitor::identifierForNode(VariableIdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } void ExpressionVisitor::setCreateProblems(bool v) { m_createProblems = v; } void ExpressionVisitor::setOffset(const CursorInRevision& offset) { m_offset = offset; } void ExpressionVisitor::buildNamespaceUses(NamespacedIdentifierAst* namespaces, const QualifiedIdentifier& identifier) { QualifiedIdentifier curId; curId.setExplicitlyGlobal(identifier.explicitlyGlobal()); Q_ASSERT(identifier.count() == namespaces->namespaceNameSequence->count()); for ( int i = 0; i < identifier.count() - 1; ++i ) { curId.push(identifier.at(i)); AstNode* node = namespaces->namespaceNameSequence->at(i)->element; DeclarationPointer dec = findDeclarationImport(NamespaceDeclarationType, curId); usingDeclaration(node, dec); } } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, IdentifierAst* node) { // methods and class names are case insensitive QualifiedIdentifier id; if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { id = QualifiedIdentifier(stringForNode(node).toLower()); } else { id = identifierForNode(node); } return findDeclarationImport(declarationType, id); } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node) { return findDeclarationImport(declarationType, identifierForNode(node)); } DeclarationPointer ExpressionVisitor::findDeclarationImport( DeclarationType declarationType, const QualifiedIdentifier& identifier) { return findDeclarationImportHelper(m_currentContext, identifier, declarationType); } Declaration* ExpressionVisitor::findVariableDeclaration(DUContext* context, Identifier identifier, CursorInRevision position, DUContext::SearchFlag flag) { QList decls = context->findDeclarations(identifier, position, nullptr, flag); for (int i = decls.count() - 1; i >= 0; i--) { Declaration *dec = decls.at(i); if (dec->kind() == Declaration::Instance && dynamic_cast(dec)) { return dec; } } return nullptr; } } diff --git a/duchain/expressionvisitor.h b/duchain/expressionvisitor.h index 8b590d8..bcc7575 100644 --- a/duchain/expressionvisitor.h +++ b/duchain/expressionvisitor.h @@ -1,111 +1,113 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef EXPRESSIONVISITOR_H #define EXPRESSIONVISITOR_H #include "phpdefaultvisitor.h" #include "phpduchainexport.h" #include "expressionevaluationresult.h" #include "helper.h" #include #include namespace KDevelop { class TopDUContext; class Declaration; } namespace Php { class EditorIntegrator; class KDEVPHPDUCHAIN_EXPORT ExpressionVisitor : public DefaultVisitor { public: ExpressionVisitor(EditorIntegrator* editor); ExpressionEvaluationResult result() { return m_result; } void setCreateProblems(bool v); void setOffset(const KDevelop::CursorInRevision& offset); void visitNode(AstNode *node) override; protected: KDevelop::DeclarationPointer processVariable( VariableIdentifierAst* variable); void visitAssignmentExpression(AssignmentExpressionAst *node) override; void visitArrayIndexSpecifier(ArrayIndexSpecifierAst* node) override; void visitCompoundVariableWithSimpleIndirectReference(CompoundVariableWithSimpleIndirectReferenceAst *node) override; void visitVarExpressionNewObject(VarExpressionNewObjectAst *node) override; void visitVarExpressionArray(VarExpressionArrayAst *node) override; void visitClosure(ClosureAst* node) override; void visitFunctionCall(FunctionCallAst* node) override; void visitConstantOrClassConst(ConstantOrClassConstAst *node) override; void visitScalar(ScalarAst *node) override; void visitStaticScalar(StaticScalarAst *node) override; void visitEncapsVar(EncapsVarAst *node) override; void visitVariableProperty(VariablePropertyAst *node) override; void visitStaticMember(StaticMemberAst* node) override; void visitUnaryExpression(UnaryExpressionAst* node) override; void visitAdditiveExpressionRest(AdditiveExpressionRestAst* node) override; void visitVariable(VariableAst* node) override; void visitFunctionCallParameterList( FunctionCallParameterListAst* node ) override; void visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) override; void visitRelationalExpression(RelationalExpressionAst* node) override; + void visitRelationalExpressionRest(RelationalExpressionRestAst* node) override; + void visitEqualityExpressionRest(EqualityExpressionRestAst* node) override; QString stringForNode(AstNode* id); KDevelop::QualifiedIdentifier identifierForNode(IdentifierAst* id); QString stringForNode(VariableIdentifierAst* id); KDevelop::QualifiedIdentifier identifierForNode(VariableIdentifierAst* id); virtual void usingDeclaration(AstNode* node, const KDevelop::DeclarationPointer& decl) { Q_UNUSED(node) Q_UNUSED(decl) } KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, IdentifierAst* node); KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node); KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, const KDevelop::QualifiedIdentifier& identifier); KDevelop::Declaration* findVariableDeclaration(KDevelop::DUContext* context, KDevelop::Identifier identifier, KDevelop::CursorInRevision position, KDevelop::DUContext::SearchFlag flag); protected: EditorIntegrator* m_editor; private: KDevelop::DUContext* findClassContext(NamespacedIdentifierAst* className); KDevelop::DUContext* findClassContext(IdentifierAst* className); void buildNamespaceUses(NamespacedIdentifierAst* namespaces, const KDevelop::QualifiedIdentifier& identifier); bool m_createProblems; KDevelop::CursorInRevision m_offset; KDevelop::DUContext* m_currentContext; ExpressionEvaluationResult m_result; bool m_isAssignmentExpressionEqual; bool m_inDefine; }; } #endif diff --git a/duchain/tests/duchain_multiplefiles.cpp b/duchain/tests/duchain_multiplefiles.cpp index 09237dd..2856521 100644 --- a/duchain/tests/duchain_multiplefiles.cpp +++ b/duchain/tests/duchain_multiplefiles.cpp @@ -1,324 +1,320 @@ /* This file is part of KDevelop Copyright 2010 Niko Sams Copyright 2011 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain_multiplefiles.h" #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(Php::TestDUChainMultipleFiles) using namespace KDevelop; using namespace Php; void TestDUChainMultipleFiles::initTestCase() { DUChainTestBase::initTestCase(); TestCore* core = dynamic_cast(ICore::self()); Q_ASSERT(core); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChainMultipleFiles::testImportsGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f1(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testImportsBaseClassNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testNonExistingBaseClass() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f1(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsGlobalFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); - } void TestDUChainMultipleFiles::testNonExistingGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsStaticFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testNonExistingStaticFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testForeachImportedIdentifier() { // see https://bugs.kde.org/show_bug.cgi?id=269369 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); // build dependency TestFile f1(QStringLiteral("bar(); foreach($i as $a => $b) {} } \n" " public function bar() { $a = new SomeIterator(); return $a; }\n" " }\n"), QStringLiteral("php"), project); for(int i = 0; i < 2; ++i) { if (i > 0) { features = static_cast(features | TopDUContext::ForceUpdate); } f2.parse(features); QVERIFY(f2.waitForParsed()); QTest::qWait(100); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(f2.topContext()->childContexts().size(), 1); DUContext* ACtx = f2.topContext()->childContexts().first(); QVERIFY(ACtx); QCOMPARE(ACtx->childContexts().size(), 4); Declaration* iDec = ACtx->childContexts().at(1)->localDeclarations().first(); QVERIFY(iDec); Declaration* SomeIteratorDec = f1.topContext()->localDeclarations().first(); QVERIFY(SomeIteratorDec); if (i == 0) { QEXPECT_FAIL("", "needs a full two-pass (i.e. get rid of PreDeclarationBuilder)", Continue); } QVERIFY(iDec->abstractType()->equals(SomeIteratorDec->abstractType().constData())); QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0))); } } void TestDUChainMultipleFiles::testUpdateForeach() { TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f(QStringLiteral(" $k) {}\n"), QStringLiteral("php"), project); f.parse(features); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 0); QCOMPARE(kDec->uses().count(), 1); QCOMPARE(kDec->uses().begin()->count(), 1); QCOMPARE(kDec->uses().begin()->begin()->start.line, 2); } // delete $k = null; line f.setFileContents(QStringLiteral(" $k) {}\n")); f.parse(static_cast(features | TopDUContext::ForceUpdate)); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 25); QCOMPARE(kDec->uses().count(), 0); } } void TestDUChainMultipleFiles::testTodoExtractorReparse() { TestFile file(QStringLiteral("baz();"), QStringLiteral("php")); QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("TODO")); auto features = TopDUContext::AllDeclarationsContextsAndUses; for (int i = 0; i < 2; ++i) { if (i == 1) { file.setFileContents(QStringLiteral("asdf();")); features = static_cast(features | TopDUContext::ForceUpdate); } file.parse(features); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QCOMPARE(top->problems().size(), 1); QCOMPARE(top->problems().at(0)->description(), QString("TODO")); QCOMPARE(top->problems().at(0)->range(), RangeInRevision(2, 3, 2, 7)); } } void TestDUChainMultipleFiles::testIteratorForeachReparse() { TestFile file(QStringLiteral("(features | TopDUContext::ForceUpdate); } file.parse(features); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->localDeclarations().size() == 2); QCOMPARE(top->localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("a")); IntegralType::Ptr type = top->localDeclarations().at(0)->type(); QVERIFY(type); //Should actually parse as an TypeInt, but this does not work. QVERIFY(type->dataType() == IntegralType::TypeMixed); } } diff --git a/duchain/tests/expressionparser.cpp b/duchain/tests/expressionparser.cpp index eb316dd..c76fc11 100644 --- a/duchain/tests/expressionparser.cpp +++ b/duchain/tests/expressionparser.cpp @@ -1,706 +1,722 @@ /* This file is part of KDevelop Copyright 2008 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "expressionparser.h" #include #include #include #include #include #include #include #include #include "../types/structuretype.h" #include "../expressionparser.h" using namespace KDevelop; QTEST_MAIN(Php::TestExpressionParser) namespace Php { TestExpressionParser::TestExpressionParser() { } void TestExpressionParser::newClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestExpressionParser::newSelf() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->childContexts().last()), CursorInRevision(0, 30)); QVERIFY(res.type()); QCOMPARE(StructureType::Ptr::staticCast(res.type())->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestExpressionParser::newStatic() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->childContexts().last()), CursorInRevision(0, 30)); QVERIFY(res.type().cast()); QCOMPARE(res.type().cast()->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestExpressionParser::memberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(res.type()); QCOMPARE(res.allDeclarations().count(), 1); QCOMPARE(res.allDeclarations().first().data(), top->childContexts().first()->localDeclarations().first()); QCOMPARE(StructureType::Ptr::staticCast(res.type())->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestExpressionParser::memberFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo()"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(res.type()); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())->dataType() == IntegralType::TypeVoid); QCOMPARE(res.allDeclarations().size(), 1); QCOMPARE(res.allDeclarations().first().data(), top->childContexts().first()->localDeclarations().first()); } void TestExpressionParser::newTrait() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("problems().isEmpty()); QCOMPARE(top->childContexts().size(), 1); QVERIFY(top->childContexts().at(0)->type() == DUContext::Class); } void TestExpressionParser::newTraitWithAbstractMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("problems().isEmpty()); QCOMPARE(top->childContexts().size(), 1); QVERIFY(top->childContexts().at(0)->type() == DUContext::Class); } void TestExpressionParser::invalidTrait_data() { QTest::addColumn("code"); QTest::newRow("constant") << "problems().isEmpty()); } void TestExpressionParser::invalidTraitUse_data() { QTest::addColumn("code"); QTest::newRow("abstractModifier") << "problems().isEmpty()); } void TestExpressionParser::namespaceUseNameConflict() { QByteArray alias("problems().isEmpty()); } void TestExpressionParser::globalFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()); } void TestExpressionParser::chainCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first()->type(); QVERIFY(fn); QVERIFY(fn->returnType()->equals(top->localDeclarations().first()->abstractType().data())); ExpressionParser p(true); ExpressionEvaluationResult res = p.evaluateType(QByteArray("$a->foo()"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(res.type()); QVERIFY(res.type()->equals(top->localDeclarations().first()->abstractType().data())); res = p.evaluateType(QByteArray("$a->foo()->foo()->foo()"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(res.type()); QVERIFY(res.type()->equals(top->localDeclarations().first()->abstractType().data())); } void TestExpressionParser::thisObject() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first()->internalContext(); ExpressionParser p(true); ExpressionEvaluationResult res = p.evaluateType(QByteArray("$this"), DUContextPointer(funContext), CursorInRevision(1, 0)); QCOMPARE(res.allDeclarations().count(), 1); QCOMPARE(res.allDeclarations().first().data(), top->localDeclarations().first()); QVERIFY(res.type()); QVERIFY(StructureType::Ptr::dynamicCast(res.type())); QCOMPARE(StructureType::Ptr::dynamicCast(res.type())->declaration(top), top->localDeclarations().first()); } void TestExpressionParser::integralTypes() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("dataType(), static_cast(IntegralType::TypeInt)); res = p.evaluateType(QByteArray("123.1"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeFloat)); res = p.evaluateType(QByteArray("\"asdf\""), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeString)); res = p.evaluateType(QByteArray("\"as $foo df\""), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeString)); res = p.evaluateType(QByteArray("'asdf'"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeString)); res = p.evaluateType(QByteArray("true"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeBoolean)); res = p.evaluateType(QByteArray("TRUE"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeBoolean)); res = p.evaluateType(QByteArray("null"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeNull)); res = p.evaluateType(QByteArray("NULL"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeNull)); } void TestExpressionParser::newObject() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("declaration(top), top->localDeclarations().first()); } void TestExpressionParser::cast() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("dataType() == IntegralType::TypeString); res = p.evaluateType(QByteArray("(int)$foo"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); res = p.evaluateType(QByteArray("(double)$foo"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeFloat); res = p.evaluateType(QByteArray("(bool)$foo"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeBoolean); res = p.evaluateType(QByteArray("(array)$foo"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeArray); res = p.evaluateType(QByteArray("(object)$foo"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(StructureType::Ptr::dynamicCast(res.type())); QVERIFY(StructureType::Ptr::staticCast(res.type())->qualifiedIdentifier() == QualifiedIdentifier("stdclass")); } void TestExpressionParser::operations() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("dataType() == IntegralType::TypeString); res = p.evaluateType(QByteArray("1 . 1"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeString); res = p.evaluateType(QByteArray("1 + 1"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); res = p.evaluateType(QByteArray("1 ** 1"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); res = p.evaluateType(QByteArray("'1' + '1'"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); res = p.evaluateType(QByteArray("$foo .= '1'"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeString); res = p.evaluateType(QByteArray("$foo .= 1"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeString); res = p.evaluateType(QByteArray("$foo += 1"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); res = p.evaluateType(QByteArray("$foo += '1'"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); res = p.evaluateType(QByteArray("$foo *= 1"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); res = p.evaluateType(QByteArray("$foo *= '1'"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); res = p.evaluateType(QByteArray("$foo **= 1"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); + + res = p.evaluateType(QByteArray("$foo = 3 > 1"), DUContextPointer(top), CursorInRevision(1, 0)); + QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); + QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeBoolean); + + res = p.evaluateType(QByteArray("$foo = 3 != 1"), DUContextPointer(top), CursorInRevision(1, 0)); + QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); + QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeBoolean); + + res = p.evaluateType(QByteArray("$foo = $a instanceof A"), DUContextPointer(top), CursorInRevision(1, 0)); + QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); + QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeBoolean); + + res = p.evaluateType(QByteArray("$foo = 3 <=> 2"), DUContextPointer(top), CursorInRevision(1, 0)); + QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); + QVERIFY(IntegralType::Ptr::staticCast(res.type())->dataType() == IntegralType::TypeInt); } void TestExpressionParser::findArg() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().size(), 3); QVERIFY(top->childContexts().at(0)->type() == DUContext::Class); QVERIFY(top->childContexts().at(1)->type() == DUContext::Function); QVERIFY(top->childContexts().at(2)->type() != DUContext::Function); ExpressionEvaluationResult res = p.evaluateType(QByteArray("$arg"), DUContextPointer(top->childContexts().last()), CursorInRevision(0, 47)); QVERIFY(IntegralType::Ptr::dynamicCast(res.type())); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeMixed)); res = p.evaluateType(QByteArray("$bar"), DUContextPointer(top->childContexts().last()), CursorInRevision(0, 47)); ReferenceType::Ptr type = ReferenceType::Ptr::dynamicCast(res.type()); QVERIFY(type); QVERIFY(IntegralType::Ptr::dynamicCast(type->baseType())); QCOMPARE(IntegralType::Ptr::staticCast(type->baseType())->dataType(), static_cast(IntegralType::TypeMixed)); res = p.evaluateType(QByteArray("$a"), DUContextPointer(top->childContexts().last()), CursorInRevision(0, 47)); type = ReferenceType::Ptr::dynamicCast(res.type()); QVERIFY(type); QVERIFY(StructureType::Ptr::dynamicCast(type->baseType())); QCOMPARE(StructureType::Ptr::staticCast(type->baseType())->declaration(top), top->localDeclarations().first()); } void TestExpressionParser::array_data() { QTest::addColumn("code"); QTest::newRow("normalSyntax") << "problems().isEmpty()); ExpressionParser p(true); QCOMPARE(top->localDeclarations().first()->abstractType().cast()->dataType(), static_cast(IntegralType::TypeArray)); ExpressionEvaluationResult res = p.evaluateType("$b = $a[0]", DUContextPointer(top), CursorInRevision(0, 22)); QVERIFY(res.type().cast()); QEXPECT_FAIL("", "we'd need advanced array support to know that [0] returns a string...", Continue); QCOMPARE(res.type().cast()->dataType(), static_cast(IntegralType::TypeString)); // fallback QCOMPARE(res.type().cast()->dataType(), static_cast(IntegralType::TypeMixed)); } void TestExpressionParser::arrayFunctionDereferencing_data() { QTest::addColumn("code"); QTest::newRow("globalFunction") << "bar()[0];\n"; QTest::newRow("staticClassMethod") << "bar()[0];\n" "$c = obj::bar()[0];\n");*/ TopDUContext* top = parse(code.toUtf8(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); Declaration* decl = top->localDeclarations().last(); IntegralType::Ptr type = decl->abstractType().cast(); QVERIFY(type); QEXPECT_FAIL("", "we'd need advanced array support to know that [0] returns a int...", Continue); QCOMPARE(type->dataType(), static_cast(IntegralType::TypeInt)); // fallback QCOMPARE(type->dataType(), static_cast(IntegralType::TypeMixed)); } void TestExpressionParser::arrayLiteralDereferencing_data() { QTest::addColumn("code"); QTest::newRow("normalSyntax") << "problems().isEmpty()); Declaration* decl = top->localDeclarations().last(); IntegralType::Ptr type = decl->abstractType().cast(); QVERIFY(type); QEXPECT_FAIL("", "we'd need advanced array support to know that [0] returns a int...", Continue); QCOMPARE(type->dataType(), static_cast(IntegralType::TypeInt)); // fallback QCOMPARE(type->dataType(), static_cast(IntegralType::TypeMixed)); } void TestExpressionParser::stringAsArray_data() { QTest::addColumn("code"); QTest::newRow("constantEncapsedString") << "problems().isEmpty()); Declaration* decl = top->localDeclarations().last(); IntegralType::Ptr type = decl->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), static_cast(IntegralType::TypeString)); } void TestExpressionParser::classMemberOnInstantiation() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); ExpressionParser p(true); ExpressionEvaluationResult res = p.evaluateType(QByteArray("$a"), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(res.type()); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeString)); } void TestExpressionParser::classNameConstant_data() { QTest::addColumn("NSconst"); QTest::newRow("fullNamespace") << "\\NS\\ClassName::class"; QTest::newRow("normalNamespace") << "NS\\ClassName::class"; QTest::newRow("inNamespace") << "$n"; } void TestExpressionParser::classNameConstant() { QFETCH(QString, NSconst); // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("problems().isEmpty()); ExpressionParser p(true); ExpressionEvaluationResult res = p.evaluateType(NSconst.toUtf8(), DUContextPointer(top), CursorInRevision(1, 0)); QVERIFY(res.type()); QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeString)); } void TestExpressionParser::invalidVariadicFunction_data() { QTest::addColumn("code"); QTest::newRow("defaultValue") << "problems().isEmpty()); } void TestExpressionParser::invalidArgumentUnpacking() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("problems().isEmpty()); } } diff --git a/org.kde.kdev-php.metainfo.xml b/org.kde.kdev-php.metainfo.xml index 698be8d..6a5e775 100644 --- a/org.kde.kdev-php.metainfo.xml +++ b/org.kde.kdev-php.metainfo.xml @@ -1,47 +1,47 @@ org.kde.kdev-php org.kde.kdevelop.desktop KDevelop PHP Support Implementació de PHP al KDevelop Implementació de PHP al KDevelop Podpora PHP v KDevelop KDevelop – PHP-Unterstützung KDevelop PHP Support Implementación de PHP para KDevelop Prise en charge de PHP pour KDevelop Compatibilidade con PHP para KDevelop Supporto PHP per KDevelop PHP ondersteuning in KDevelop Obsługa PHP w KDevelop Suporte para PHP do KDevelop PHP podpora pre KDevelop KDevelop PHP-stöd Підтримка PHP KDevelop xxKDevelop PHP Supportxx PHP language support for KDevelop Implementació del llenguatge PHP al KDevelop Implementació del llenguatge PHP al KDevelop Podpora jazyka PHP pro KDevelop PHP-Sprachunterstützung für KDevelop PHP language support for KDevelop Implementación del lenguaje PHP para KDevelop Prise en charge du langage PHP pour KDevelop Compatibilidade coa linguaxe PHP para KDevelop Supporto al linguaggio PHP per KDevelop Ondersteuning van PHP-taal voor KDevelop Obsługa języka PHP dla KDevelop Suporte à linguagem PHP do KDevelop Podpora jazyka PHP pre KDevelop Stöd för språket PHP i KDevelop Підтримка мови PHP у KDevelop xxPHP language support for KDevelopxx GPL-2.0+ CC0-1.0 https://kdevelop.org - https://bugs.kde.org/enter_bug.cgi?format=guided&product=kdevelop&component=Language Support%3A PHP + https://bugs.kde.org/enter_bug.cgi?format=guided&product=kdevelop&component=Language%20Support%3A%20PHP https://docs.kde.org/index.php?application=kdevelop https://www.kde.org/community/donations/?app=kdevelop KDE kdevelop-devel@kde.org diff --git a/parser/php.g b/parser/php.g index 88f022b..ca1c899 100644 --- a/parser/php.g +++ b/parser/php.g @@ -1,1293 +1,1296 @@ ------------------------------------------------------------------------------- -- Copyright (c) 2008 Niko Sams -- -- This grammar is free software; you can redistribute it and/or -- modify it under the terms of the GNU Library General Public -- License as published by the Free Software Foundation; either -- version 2 of the License, or (at your option) any later version. -- -- This grammar is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -- Lesser General Public License for more details. -- -- You should have received a copy of the GNU Library General Public License -- along with this library; see the file COPYING.LIB. If not, write to -- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -- Boston, MA 02110-1301, USA. ----------------------------------------------------------- ----------------------------------------------------------- -- Grammar for PHP 5.2 -- Modelled after the Zend Grammar shipped with PHP5.2 -- source, the PHP Language Reference documentation, -- and parts taken from KDevelop Java Grammar ----------------------------------------------------------- -- 4 first/first conflicts: -- - var_expression: variable vs. varExpressionNormal -- no problem because of ifs that allow always just one rule -- - classNameReference: STRING vs. staticMember (foo vs. foo::$bar) -- resolved by LA() -- - encapsVar: STRING_VARNAME LBRACKET vs. expr (expr allows STRING_VARNAME too - but not LBRACKET) -- resolved by LA() -- - constantOrClassConst: constant vs. class constant (FOO v.s Cls::FOO) -- resolved by LA() (could be avoided, but the Ast is much cleaner that way) -- 1 first/follow conflicts: -- - elseifList: dangling-else conflict - should be ok -- TODO: (post 1.0.0 release) -- 1) decrease memory consumption -- 1.1) use quint32 instead of qint64 for end/start tokens -- 1.2) investigate whether using a map/hash for the ducontext member of all -- ast nodes gives a significant memory decrease while not hampering performance -- 1.3) investigate how unions could be used for exclusive AST node members -- 1.4) see whether we can always use the expression lists instead of both -- single member pointer and list of members, esp. in expressions -- 2) better cope with invalid code, have at least a partial AST -- 3) investigate whether expanding the visitor lookup to a -- (albeit huge) switch() in KDev-PG-Qt gives a significant performance gain -- I have the gut feeling that the current lookup takes unnecessary much time -- ------------------------------------------------------------ -- Forward declaration in phpast.h ------------------------------------------------------------ [: #include namespace KDevelop { class DUContext; } :] ------------------------------------------------------------ -- Additional includes for the parser ------------------------------------------------------------ %parser_declaration_header "parser/tokenstream.h" %parser_declaration_header "QtCore/QString" %parser_declaration_header "language/duchain/problem.h" %parser_declaration_header "parser/phplexer.h" %parser_bits_header "parserdebug.h" ------------------------------------------------------------ -- Export macro to use the parser in a shared lib ------------------------------------------------------------ %export_macro "KDEVPHPPARSER_EXPORT" %export_macro_header "parserexport.h" ------------------------------------------------------------ -- Enumeration types for additional AST members, -- in the global "Php" namespace ------------------------------------------------------------ %namespace [: class Lexer; enum ModifierFlags { ModifierPrivate = 1, ModifierPublic = 1 << 1, ModifierProtected = 1 << 2, ModifierStatic = 1 << 3, ModifierFinal = 1 << 4, ModifierAbstract = 1 << 5 }; enum ClassModifier { NormalClass, AbstractClass, FinalClass }; enum ScalarTypes { ScalarTypeInt, ScalarTypeFloat, ScalarTypeString }; enum CastType { CastInt, CastDouble, CastString, CastArray, CastObject, CastBool, CastUnset }; enum OperationType { OperationPlus = 1, OperationMinus, OperationConcat, OperationMul, OperationDiv, OperationExp, OperationMod, OperationAnd, OperationOr, OperationXor, OperationSl, - OperationSr + OperationSr, + OperationSpaceship, }; :] ------------------------------------------------------------ -- Ast Node class members ------------------------------------------------------------ %ast_extra_members [: KDevelop::DUContext* ducontext; :] ------------------------------------------------------------ -- Parser class members ------------------------------------------------------------ %parserclass (public declaration) [: /** * Transform the raw input into tokens. * When this method returns, the parser's token stream has been filled * and any parse*() method can be called. */ void tokenize(const QString& contents, int initialState = Lexer::HtmlState); enum ProblemType { Error, Warning, Info, Todo }; KDevelop::ProblemPointer reportProblem( Parser::ProblemType type, const QString& message, int tokenOffset = -1 ); QList problems() { return m_problems; } QString tokenText(qint64 begin, qint64 end); void setDebug(bool debug); void setCurrentDocument(KDevelop::IndexedString url); void setTodoMarkers(const QStringList& markers); void extractTodosFromComment(const QString& comment, qint64 offset); enum InitialLexerState { HtmlState = 0, DefaultState = 1 }; :] %parserclass (private declaration) [: enum VarExpressionState { Normal, OnlyVariable, OnlyNewObject }; QString m_contents; bool m_debug; KDevelop::IndexedString m_currentDocument; QList m_problems; struct ParserState { VarExpressionState varExpressionState; bool varExpressionIsVariable; }; ParserState m_state; QRegularExpression m_todoMarkers; :] %parserclass (constructor) [: m_state.varExpressionState = Normal; m_state.varExpressionIsVariable = false; :] %token_stream TokenStream ;; ----------------------------------------------------------- -- List of defined tokens ----------------------------------------------------------- -- keywords: %token ABSTRACT ("abstract"), BREAK ("break"), CASE ("case"), CATCH ("catch"), CLASS ("class"), CONST ("const"), CONTINUE ("continue"), DEFAULT ("default"), DO ("do"), ELSE ("else"), EXTENDS ("extends"), FINAL ("final"), FOR ("for"), IF ("if"), IMPLEMENTS ("implements"), INSTANCEOF ("instanceof"), INTERFACE ("interface"), NEW ("new"), PRIVATE ("private"), PROTECTED ("protected"), PUBLIC ("public"), RETURN ("return"), STATIC ("static"), SWITCH ("switch"), THROW ("throw"), TRY ("try"), WHILE ("while"), ECHO ("echo"), PRINT ("print"), FINALLY ("finally"), CLONE ("clone"), EXIT ("exit"), ELSEIF ("elseif"), ENDIF ("endif"), ENDWHILE ("endwhile"), ENDFOR ("endfor"), FOREACH ("foreach"), ENDFOREACH ("endforeach"), DECLARE ("declare"), ENDDECLARE ("enddeclare"), AS ("as"), ENDSWITCH ("endswitch"), FUNCTION ("function"), USE ("use"), GLOBAL ("global"), VAR ("var "), UNSET ("unset"), ISSET ("isset"), EMPTY ("empty"), HALT_COMPILER ("halt compiler"), DOUBLE_ARROW ("=>"), LIST ("list"), ARRAY ("array"), CLASS_C ("__CLASS__"), METHOD_C ("__METHOD__"), FUNC_C ("__FUNCTION__"), LINE ("__LINE__"), FILE ("__FILE__"), COMMENT ("comment"), DOC_COMMENT ("doc comment"), PAAMAYIM_NEKUDOTAYIM ("::"), INCLUDE ("include"), INCLUDE_ONCE ("include_once"), EVAL ("eval"), REQUIRE ("require"), REQUIRE_ONCE ("require_once"), NAMESPACE ("namespace"), NAMESPACE_C("__NAMESPACE__"), USE("use"), GOTO ("goto"), TRAIT ("trait"), INSTEADOF ("insteadof"), CALLABLE ("callable"), VOID ("void"), DIR ("__DIR__"), TRAIT_C ("__TRAIT__"), YIELD ("yield") ;; -- casts: %token INT_CAST ("int cast"), DOUBLE_CAST ("double cast"), STRING_CAST ("string cast"), ARRAY_CAST ("array cast"), OBJECT_CAST ("object cast"), BOOL_CAST ("bool cast"), UNSET_CAST ("unset cast") ;; -- seperators: %token SEMICOLON (";"), DOUBLE_QUOTE ("\""), LBRACKET ("["), RBRACKET ("]"), LPAREN ("("), RPAREN (")"), LBRACE ("{"), RBRACE ("}"), COMMA (","), AT ("@"), CURLY_OPEN ("curly open"), -- { in "{$foo}"; not the same as LBRACE DOLLAR_OPEN_CURLY_BRACES ("${"), START_HEREDOC ("start heredoc"), END_HEREDOC ("end heredoc"), BACKTICK ("`"), BACKSLASH ("\\"), START_NOWDOC("start nowdoc"), END_NOWDOC("end nowdoc") ;; -- operators: %token IS_EQUAL ("=="), IS_NOT_EQUAL ("!="), IS_IDENTICAL ("==="), IS_NOT_IDENTICAL ("!=="), IS_SMALLER ("<"), IS_GREATER (">"), IS_SMALLER_OR_EQUAL ("<="), IS_GREATER_OR_EQUAL (">="), BOOLEAN_OR ("||"), BOOLEAN_AND ("&&"), ASSIGN ("="), EXP_ASSIGN("**="), PLUS_ASSIGN ("+="), MINUS_ASSIGN ("-="), MUL_ASSIGN ("*="), DIV_ASSIGN ("/="), CONCAT_ASSIGN (".="), MOD_ASSIGN ("%="), AND_ASSIGN ("&="), OR_ASSIGN ("|="), XOR_ASSIGN ("^="), SL_ASSIGN ("<<="), SR_ASSIGN (">>="), OBJECT_OPERATOR ("->"), PLUS ("+"), MINUS("-"), CONCAT("."), INC ("++"), DEC ("--"), BANG ("!"), QUESTION ("?"), COLON (":"), BIT_AND ("&"), BIT_OR("|"), BIT_XOR ("^"), SL ("<<"), SR (">>"), MUL("*"), DIV("/"), MOD ("%"), TILDE ("~"), DOLLAR ("$"), EXP ("**"), ELLIPSIS ("..."), NULL_COALESCE ("??"), SPACESHIP ("<=>"), LOGICAL_OR ("logical or"), LOGICAL_AND ("logical and"), LOGICAL_XOR ("logical xor") ;; -- literals and identifiers: %token INLINE_HTML ("inline html"), WHITESPACE ("whitespace"), CONSTANT_ENCAPSED_STRING ("constant encapsed string"), VARIABLE ("variable"), ENCAPSED_AND_WHITESPACE ("encapsed and whitespace"), DNUMBER ("double number"), LNUMBER ("long number"), NUM_STRING ("num string"), STRING ("string"), STRING_VARNAME ("string varname") ;; -- when in "${varname}" -- open/close tags %token OPEN_TAG (""), OPEN_TAG_WITH_ECHO (" start ;; namespaceDeclaration=namespaceDeclarationStatement | statement=topStatement -> outerTopStatement ;; -- first/first conflict for FUNCTION (?[: (LA(1).kind == Token_FUNCTION && ((LA(2).kind == Token_BIT_AND && LA(3).kind == Token_LPAREN) || LA(2).kind == Token_LPAREN)) || LA(1).kind != Token_FUNCTION :] statement=statement ) | functionDeclaration=functionDeclarationStatement | classDeclaration=classDeclarationStatement | traitDeclaration=traitDeclarationStatement | interfaceDeclaration=interfaceDeclarationStatement | HALT_COMPILER LPAREN RPAREN SEMICOLON -- Lexer stops allready -> topStatement ;; [: bool reported = false; while ( true ) { :] try/recover(#statements=topStatement)* [: if (yytoken != Token_RBRACE && yytoken != Token_EOF && yytoken != Token_CLOSE_TAG && yytoken != Token_ELSEIF && yytoken != Token_ELSE && yytoken != Token_ENDIF && yytoken != Token_ENDFOREACH && yytoken != Token_ENDFOR && yytoken != Token_ENDWHILE && yytoken != Token_ENDSWITCH && yytoken != Token_ENDDECLARE && yytoken != Token_CASE && yytoken != Token_DEFAULT) { if (!reported) { qint64 index = tokenStream->index() - 1; Token &token = tokenStream->at(index); QString tokenValue = token.kind != 0 ? tokenText(token.begin, token.end) : QStringLiteral("EOF"); reportProblem(Error, QStringLiteral("Unexpected token \"%1\".").arg(tokenValue)); reported = true; } yylex(); } else { break; } } :] -> innerStatementList ;; --Operator Precedence, from PHP Manual --left or --left xor --left and --right print --right = += -= *= /= .= %= &= |= ^= <<= >>= assignment --left ? : ternary --right ?? comparison --left || logical --left && logical --left | bitwise --left ^ bitwise --left & bitwise and references --non-associative == != === !== <=> comparison --non-associative < <= > >= comparison --left << >> bitwise --left + - . arithmetic and string --left * / % arithmetic --non-associative ! ~ - (int) (float) (string) (array) (object) @ types --non-associative ++ -- increment/decrement --left [ array() --non-associative new new expression=logicalOrExpression -> expr ;; #expression=logicalXorExpression @ LOGICAL_OR -> logicalOrExpression ;; #expression=logicalAndExpression @ LOGICAL_XOR -> logicalXorExpression ;; #expression=printExpression @ LOGICAL_AND -> logicalAndExpression ;; (print=PRINT*) expression=assignmentExpression -> printExpression ;; -- leftside must me a variable, we check afterwards if it was a variable and -- if not we report an error 0 --needed for line below [: m_state.varExpressionIsVariable = false; :] --reset flag expression=conditionalExpression ( assignmentExpressionEqual=assignmentExpressionEqual | ( ( PLUS_ASSIGN [: (*yynode)->operation = OperationPlus; :] | MINUS_ASSIGN [: (*yynode)->operation = OperationMinus; :] | MUL_ASSIGN [: (*yynode)->operation = OperationMul; :] | EXP_ASSIGN [: (*yynode)->operation = OperationExp; :] | DIV_ASSIGN [: (*yynode)->operation = OperationDiv; :] | CONCAT_ASSIGN [: (*yynode)->operation = OperationConcat; :] | MOD_ASSIGN [: (*yynode)->operation = OperationMod; :] | AND_ASSIGN [: (*yynode)->operation = OperationAnd; :] | OR_ASSIGN [: (*yynode)->operation = OperationOr; :] | XOR_ASSIGN [: (*yynode)->operation = OperationXor; :] | SL_ASSIGN [: (*yynode)->operation = OperationSl; :] | SR_ASSIGN [: (*yynode)->operation = OperationSr; :] ) assignmentExpressionCheckIfVariable assignmentExpression=assignmentExpression) | 0) -> assignmentExpression [ member variable operation: OperationType; ];; --=& is special: -- $foo =& $var; is allowed but not $foo =& 'static'; -- $foo =& new bar(); is allowed too but deprecated and reports a warning --we set a flag (varExpressionState) with that var_expression accepts only valid parts --this is done in such a strage way because we need the full expression to allow --things like $foo =& $bar || e(); ASSIGN assignmentExpressionCheckIfVariable --as in assignmentExpression (BIT_AND [: if (yytoken == Token_NEW) { reportProblem(Warning, QStringLiteral("=& new foo() is deprecated"), -2); m_state.varExpressionState = OnlyNewObject; } else { m_state.varExpressionState = OnlyVariable; }:] | 0) assignmentExpression=assignmentExpression [: m_state.varExpressionState = Normal; :] -> assignmentExpressionEqual ;; -- check if var_expression was a variable, if not report an error -- varExpressionIsVariable is set in var_expression 0 --to allow cpp-code [: if (!m_state.varExpressionIsVariable) { reportProblem(Error, QStringLiteral("Left side is not a variable")); return false; } :] -> assignmentExpressionCheckIfVariable ;; expression=nullCoalesceExpression ( QUESTION (ifExpression=expr|0) COLON elseExpression=conditionalExpression | 0 ) -> conditionalExpression ;; #expression=booleanOrExpression @ NULL_COALESCE -> nullCoalesceExpression ;; #expression=booleanAndExpression @ BOOLEAN_OR -> booleanOrExpression ;; #expression=bitOrExpression @ BOOLEAN_AND -> booleanAndExpression ;; #expression=bitXorExpression @ BIT_OR -> bitOrExpression ;; #expression=bitAndExpression @ BIT_XOR -> bitXorExpression ;; #expression=equalityExpression @ BIT_AND -> bitAndExpression ;; expression=relationalExpression (#additionalExpression=equalityExpressionRest)* -> equalityExpression ;; - ( IS_EQUAL | IS_NOT_EQUAL | IS_IDENTICAL | IS_NOT_IDENTICAL | SPACESHIP ) + ( IS_EQUAL | IS_NOT_EQUAL | IS_IDENTICAL | IS_NOT_IDENTICAL | SPACESHIP [: (*yynode)->operation = OperationSpaceship; :] ) expression=relationalExpression --> equalityExpressionRest ;; +-> equalityExpressionRest [ + member variable operation: OperationType; +];; expression=shiftExpression ( (#additionalExpression=relationalExpressionRest)+ --instanceof as in java.g (correct??) | INSTANCEOF instanceofType=classNameReference | 0 ) -> relationalExpression ;; ( IS_SMALLER | IS_GREATER | IS_SMALLER_OR_EQUAL | IS_GREATER_OR_EQUAL ) expression=shiftExpression -> relationalExpressionRest ;; expression=additiveExpression (#additionalExpression=shiftExpressionRest)* -> shiftExpression ;; ( SL | SR ) expression=additiveExpression -> shiftExpressionRest ;; expression=multiplicativeExpression (#additionalExpression=additiveExpressionRest)* -> additiveExpression ;; ( PLUS [: (*yynode)->operation = OperationPlus; :] | MINUS [: (*yynode)->operation = OperationMinus; :] | CONCAT [: (*yynode)->operation = OperationConcat; :] ) expression=multiplicativeExpression -> additiveExpressionRest [ member variable operation: OperationType; ];; expression=unaryExpression (#additionalExpression=multiplicativeExpressionRest)* -> multiplicativeExpression ;; ( MUL [: (*yynode)->operation = OperationMul; :] | DIV [: (*yynode)->operation = OperationDiv; :] | EXP [: (*yynode)->operation = OperationExp; :] | MOD [: (*yynode)->operation = OperationMod; :] ) expression=unaryExpression -> multiplicativeExpressionRest [ member variable operation: OperationType; ];; ( MINUS unaryExpression=unaryExpression | PLUS unaryExpression=unaryExpression | BANG unaryExpression=unaryExpression | TILDE unaryExpression=unaryExpression | INT_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastInt; :] | DOUBLE_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastDouble; :] | STRING_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastString; :] | ARRAY_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastArray; :] | OBJECT_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastObject; :] | BOOL_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastBool; :] | UNSET_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastUnset; :] | AT unaryExpression=unaryExpression | LIST LPAREN assignmentList=assignmentList RPAREN ASSIGN unaryExpression=unaryExpression | EXIT (LPAREN (expression=expr | 0) RPAREN | 0) | EVAL LPAREN expression=expr RPAREN | INCLUDE includeExpression=unaryExpression | INCLUDE_ONCE includeExpression=unaryExpression | REQUIRE includeExpression=unaryExpression | REQUIRE_ONCE includeExpression=unaryExpression | unaryExpressionNotPlusminus=unaryExpressionNotPlusminus ) -> unaryExpression [ member variable castType: CastType; ];; (#prefixOperator=postprefixOperator)* varExpression=varExpression (#postfixOperator=postprefixOperator)* -> unaryExpressionNotPlusminus ;; op=INC | op=DEC -> postprefixOperator ;; --first/first conflict - no problem because of ifs ?[: m_state.varExpressionState == OnlyVariable :] 0 [: m_state.varExpressionState = Normal; :] variable=variable | ?[: m_state.varExpressionState == OnlyNewObject :] 0 [: m_state.varExpressionState = Normal; :] newObject=varExpressionNewObject | varExpressionNormal=varExpressionNormal | varExpressionArray=varExpressionArray arrayIndex=arrayIndexSpecifier* -> varExpression ;; (?[: LA(1).kind == Token_LPAREN && LA(2).kind == Token_FUNCTION && LA(3).kind == Token_LPAREN :] iife=iifeSyntax ) | LPAREN try/rollback (newObject=varExpressionNewObject RPAREN (#variableProperties=instantiationAccess*)) catch (expression=expr RPAREN) | BACKTICK encapsList=encapsList BACKTICK --try/rollback resolves conflict scalar vs. staticMember (foo::bar vs. foo::$bar) --varExpressionIsVariable flag is needed for assignmentExpression | try/rollback (variable=variable [: m_state.varExpressionIsVariable = true; :]) catch (scalar=scalar) | ISSET LPAREN (#issetVariable=variable @ COMMA) RPAREN | EMPTY LPAREN emptyVarialbe=variable RPAREN | newObject=varExpressionNewObject | CLONE cloneCar=varExpressionNormal | closure=closure -> varExpressionNormal ;; ARRAY LPAREN (#arrayValues=arrayPairValue -- break because array(1,) is allowed (solves FIRST/FOLLOW conflict) @ (COMMA [: if (yytoken == Token_RPAREN) { break; } :] ) | 0) RPAREN | LBRACKET (#arrayValues=arrayPairValue -- break because [1,] is allowed (solves FIRST/FOLLOW conflict) @ (COMMA [: if (yytoken == Token_RBRACKET) { break; } :] ) | 0) RBRACKET -> varExpressionArray ;; -- http://wiki.php.net/rfc/closures FUNCTION (isRef=BIT_AND|0) LPAREN parameters=parameterList RPAREN ( USE LPAREN lexicalVars=lexicalVarList RPAREN | 0) ( COLON returnType=returnType | 0) LBRACE try/recover(functionBody=innerStatementList) RBRACE -> closure ;; LPAREN try/rollback (closure=closure RPAREN LPAREN parameterList=functionCallParameterList RPAREN) catch (expression=expr RPAREN) -> iifeSyntax ;; (#lexicalVars=lexicalVar @ COMMA) | 0 [: reportProblem(Error, QStringLiteral("Use list of closure must not be empty.")); :] -> lexicalVarList ;; (isRef=BIT_AND | 0) variable=variableIdentifier -> lexicalVar ;; NEW className=classNameReference ctor=ctorArguments -> varExpressionNewObject ;; LPAREN parameterList=functionCallParameterList RPAREN | 0 -> ctorArguments ;; #parameters=functionCallParameterListElement @ COMMA | 0 -> functionCallParameterList ;; (BIT_AND variable=variable) | (isVariadic=ELLIPSIS | 0) expr=expr -> functionCallParameterListElement ;; #element=assignmentListElement @COMMA -> assignmentList ;; variable=variable | LIST LPAREN assignmentList=assignmentList RPAREN | 0 -> assignmentListElement ;; expr=expr (DOUBLE_ARROW (exprValue=expr | BIT_AND varValue=variable) | 0) | BIT_AND variable=variable -> arrayPairValue ;; var=baseVariableWithFunctionCalls (#variableProperties=variableObjectProperty*) -> variable ;; OBJECT_OPERATOR | PAAMAYIM_NEKUDOTAYIM -> objectOperator ;; ( ?[: LA(1).kind == Token_DOLLAR:] LBRACE variable=variable RBRACE | objectProperty=objectProperty ) (isFunctionCall=LPAREN parameterList=functionCallParameterList RPAREN arrayIndex=arrayIndexSpecifier* | 0) -> variableProperty ;; objectOperator variableProperty=variableProperty -> variableObjectProperty ;; OBJECT_OPERATOR variableProperty=variableProperty -> instantiationAccess ;; --Conflict -- foo::$bar[0] (=baseVariable-staticMember) --vs.foo::$bar[0](); (=static function call) try/rollback (functionCall=functionCall arrayIndex=arrayIndexSpecifier*) catch (baseVariable=baseVariable) -> baseVariableWithFunctionCalls ;; LBRACKET (expr=expr | 0) RBRACKET -> arrayIndexSpecifier ;; LBRACKET (expr=expr) RBRACKET -> stringIndexSpecifier ;; stringFunctionNameOrClass=namespacedIdentifier ( LPAREN stringParameterList=functionCallParameterList RPAREN | PAAMAYIM_NEKUDOTAYIM ( stringFunctionName=semiReservedIdentifier LPAREN stringParameterList=functionCallParameterList RPAREN | varFunctionName=variableWithoutObjects LPAREN stringParameterList=functionCallParameterList RPAREN | LBRACE (expr=expr) RBRACE LPAREN stringParameterList=functionCallParameterList RPAREN ) ) | varFunctionName=variableWithoutObjects LPAREN varParameterList=functionCallParameterList RPAREN -> functionCall ;; var=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* | staticMember=staticMember -> baseVariable ;; variable=variableIdentifier | DOLLAR LBRACE expr=expr RBRACE -> compoundVariable ;; ( DOLLAR ( DOLLAR+ | 0 ) ( indirectVariable=variableIdentifier | LBRACE expr=expr RBRACE ) | variable=variableIdentifier ) -> compoundVariableWithSimpleIndirectReference ;; className=namespacedIdentifier PAAMAYIM_NEKUDOTAYIM variable=variableWithoutObjects -> staticMember ;; LBRACE try/recover(statements=innerStatementList) RBRACE | IF LPAREN ifExpr=expr RPAREN ( COLON statements=innerStatementList newElseifList newElseSingle ENDIF semicolonOrCloseTag | ifStatement=statement elseifList=elseifList elseSingle=elseSingle ) | WHILE LPAREN whileExpr=expr RPAREN whileStatement=whileStatement | FOR LPAREN forExpr1=forExpr SEMICOLON forExpr2=forExpr SEMICOLON forExpr3=forExpr RPAREN forStatement=forStatement | SWITCH LPAREN swtichExpr=expr RPAREN switchCaseList=switchCaseList | FOREACH LPAREN ( -- allow $var as &$i and not expr() as &$i try/rollback(foreachVar=variable AS foreachVarAsVar=foreachVariable) catch(foreachExpr=expr AS foreachExprAsVar=variable)) (DOUBLE_ARROW foreachVariable=foreachVariable | 0) RPAREN foreachStatement=foreachStatement | DECLARE LPAREN declareItem=declareItem @ COMMA RPAREN declareStatement | SEMICOLON -- empty statement | TRY LBRACE try/recover(statements=innerStatementList) RBRACE #catches=catchItem* (FINALLY LBRACE finallyBody=innerStatementList RBRACE | 0) | UNSET LPAREN #unsetVariables=variable @ COMMA RPAREN semicolonOrCloseTag -- fix first/follow with goto target | ( ?[: LA(1).kind != Token_STRING || LA(2).kind != Token_COLON :] expr=expr semicolonOrCloseTag ) | DO doStatement=statement WHILE LPAREN whileExpr=expr RPAREN semicolonOrCloseTag | BREAK (breakExpr=expr | 0) semicolonOrCloseTag | CONTINUE (continueExpr=expr | 0) semicolonOrCloseTag | RETURN (returnExpr=expr | 0) semicolonOrCloseTag | GLOBAL #globalVars=globalVar @ COMMA semicolonOrCloseTag | STATIC #staticVars=staticVar @ COMMA semicolonOrCloseTag | ECHO #echoExprs=expr @ COMMA semicolonOrCloseTag | THROW throwExpr=expr semicolonOrCloseTag -- throws error in zend parser, so ignored | USE use_filename semicolonOrCloseTag | CLOSE_TAG | OPEN_TAG | OPEN_TAG_WITH_ECHO expr=expr semicolonOrCloseTag | INLINE_HTML | CONST #consts=constantDeclaration @ COMMA SEMICOLON | USE #useNamespace=useNamespace @ COMMA SEMICOLON | GOTO gotoLabel=STRING SEMICOLON | gotoTarget=STRING COLON -> statement ;; identifier=namespacedIdentifier (AS aliasIdentifier=identifier | 0) -> useNamespace ;; identifier=identifier ASSIGN scalar=expr -> constantDeclaration ;; identifier=semiReservedIdentifier ASSIGN scalar=expr -> classConstantDeclaration ;; SEMICOLON | CLOSE_TAG -> semicolonOrCloseTag ;; LBRACE (SEMICOLON | 0) try/recover(caseList=caseList) RBRACE | COLON (SEMICOLON | 0) caseList=caseList ENDSWITCH semicolonOrCloseTag -> switchCaseList ;; #caseItems=case_item* -> caseList ;; CASE expr=expr (COLON | SEMICOLON) statements=innerStatementList | def=DEFAULT (COLON | SEMICOLON) statements=innerStatementList -> case_item ;; CATCH LPAREN catchClass=namespacedIdentifier var=variableIdentifier RPAREN LBRACE try/recover(statements=innerStatementList) RBRACE -> catchItem ;; statement=statement | COLON statements=innerStatementList ENDDECLARE semicolonOrCloseTag -> declareStatement ;; STRING ASSIGN scalar=staticScalar -> declareItem ;; (BIT_AND | 0) variable=variable -> foreachVariable ;; statement=statement | COLON statements=innerStatementList ENDFOREACH semicolonOrCloseTag -> foreachStatement ;; var=variableIdentifier (ASSIGN value=staticScalar | 0) -> staticVar ;; var=variableIdentifier | DOLLAR (dollarVar=variable | LBRACE expr=expr RBRACE) -> globalVar ;; #exprs=expr @ COMMA | 0 -> forExpr ;; statement=statement | COLON statements=innerStatementList ENDFOR semicolonOrCloseTag -> forStatement ;; statement=statement | COLON statements=innerStatementList ENDWHILE semicolonOrCloseTag -> whileStatement ;; --first/follow conflict; todo check if this is a problem #elseifListItem=elseifListItem* -> elseifList ;; ELSEIF LPAREN expr=expr RPAREN statement=statement -> elseifListItem ;; ELSE statement=statement | 0 -> elseSingle ;; #newElseifListItem=newelseifListItem* -> newElseifList ;; ELSEIF LPAREN expr=expr RPAREN COLON statements=innerStatementList -> newelseifListItem ;; ELSE COLON statements=innerStatementList | 0 -> newElseSingle ;; --TODO --resolve STRING vs. staticMember conflict -- ?[: LA(2).kind != Token_PAAMAYIM_NEKUDOTAYIM :] identifier=namespacedIdentifier | staticIdentifier = STATIC | dynamicClassNameReference=dynamicClassNameReference -> classNameReference ;; baseVariable=baseVariable (OBJECT_OPERATOR objectProperty=objectProperty properties=dynamicClassNameVariableProperties | 0) -> dynamicClassNameReference ;; #properties=dynamicClassNameVariableProperty* -> dynamicClassNameVariableProperties ;; OBJECT_OPERATOR property=objectProperty -> dynamicClassNameVariableProperty ;; objectDimList=objectDimList | variableWithoutObjects=variableWithoutObjects -> objectProperty ;; variableName=variableName #offsetItems=dimListItem* -> objectDimList ;; variable=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* -> variableWithoutObjects ;; arrayIndex=arrayIndexSpecifier | LBRACE expr=expr RBRACE -> dimListItem ;; name=identifier | LBRACE expr=expr RBRACE -> variableName ;; commonScalar=commonScalar | constantOrClassConst=constantOrClassConst | varname=STRING_VARNAME | DOUBLE_QUOTE encapsList=encapsList DOUBLE_QUOTE stringIndex=stringIndexSpecifier* | START_HEREDOC encapsList=encapsList END_HEREDOC -> scalar ;; constant=namespacedIdentifier ( PAAMAYIM_NEKUDOTAYIM classConstant=classConstant | 0 ) -> constantOrClassConst ;; semiReservedIdentifier -> classConstant ;; #encaps=encaps* -> encapsList ;; var=encapsVar | value=ENCAPSED_AND_WHITESPACE -> encaps ;; -- first/first conflict resolved by LA(2) --(expr allows STRING_VARNAME too - but without [expr]) DOLLAR_OPEN_CURLY_BRACES ( ?[: LA(2).kind == Token_LBRACKET:] STRING_VARNAME arrayIndex=arrayIndexSpecifier RBRACE | expr=expr RBRACE ) | variable=variableIdentifier (OBJECT_OPERATOR propertyIdentifier=identifier | LBRACKET offset=encapsVarOffset RBRACKET | 0) | CURLY_OPEN expr=expr RBRACE -> encapsVar ;; STRING | NUM_STRING | variableIdentifier -> encapsVarOffset ;; LNUMBER [: (*yynode)->scalarType = ScalarTypeInt; :] | DNUMBER [: (*yynode)->scalarType = ScalarTypeFloat; :] | string=CONSTANT_ENCAPSED_STRING [: (*yynode)->scalarType = ScalarTypeString; :] stringIndex=stringIndexSpecifier* | LINE [: (*yynode)->scalarType = ScalarTypeInt; :] | DIR [: (*yynode)->scalarType = ScalarTypeString; :] | FILE [: (*yynode)->scalarType = ScalarTypeString; :] | CLASS_C [: (*yynode)->scalarType = ScalarTypeString; :] | TRAIT_C [: (*yynode)->scalarType = ScalarTypeString; :] | METHOD_C [: (*yynode)->scalarType = ScalarTypeString; :] | FUNC_C [: (*yynode)->scalarType = ScalarTypeString; :] | NAMESPACE_C [: (*yynode)->scalarType = ScalarTypeString; :] | START_NOWDOC STRING END_NOWDOC [: (*yynode)->scalarType = ScalarTypeString; :] -> commonScalar [ member variable scalarType: ScalarTypes; ] ;; FUNCTION (BIT_AND | 0) functionName=identifier LPAREN parameters=parameterList RPAREN (COLON returnType=returnType | 0) LBRACE try/recover(functionBody=innerStatementList) RBRACE -> functionDeclarationStatement ;; (#parameters=parameter @ COMMA) | 0 -> parameterList ;; (parameterType=parameterType | 0) (isRef=BIT_AND | 0) (isVariadic=ELLIPSIS | 0) variable=variableIdentifier (ASSIGN defaultValue=expr | 0) -> parameter ;; genericType=namespacedIdentifier | arrayType=ARRAY | callableType=CALLABLE -> genericTypeHint ;; (isNullable=QUESTION | 0) typehint=genericTypeHint -> parameterType ;; (isNullable=QUESTION | 0) ( typehint=genericTypeHint | voidType=VOID ) -> returnType ;; value=commonScalar | constantOrClassConst=constantOrClassConst | PLUS plusValue=staticScalar | MINUS minusValue=staticScalar | array=ARRAY LPAREN (#arrayValues=staticArrayPairValue -- break because array(1,) is allowed @ (COMMA [: if (yytoken == Token_RPAREN) { break; } :] ) | 0) RPAREN | array=LBRACKET (#arrayValues=staticArrayPairValue -- break because [1,] is allowed @ (COMMA [: if (yytoken == Token_RBRACKET) { break; } :] ) | 0) RBRACKET -> staticScalar ;; #val1=staticScalar (DOUBLE_ARROW #val2=staticScalar | 0) -> staticArrayPairValue ;; (isGlobal=BACKSLASH | 0) #namespaceName=identifier+ @ BACKSLASH -> namespacedIdentifier ;; string=STRING -> identifier ;; INCLUDE | INCLUDE_ONCE | EVAL | REQUIRE | REQUIRE_ONCE | LOGICAL_OR | LOGICAL_XOR | LOGICAL_AND | INSTANCEOF | NEW | CLONE | EXIT | IF | ELSEIF | ELSE | ENDIF | ECHO | DO | WHILE | ENDWHILE | FOR | ENDFOR | FOREACH | ENDFOREACH | DECLARE | ENDDECLARE | AS | TRY | CATCH | FINALLY | THROW | USE | INSTEADOF | GLOBAL | VAR | UNSET | ISSET | EMPTY | CONTINUE | GOTO | FUNCTION | CONST | RETURN | PRINT | YIELD | LIST | SWITCH | ENDSWITCH | CASE | DEFAULT | BREAK | ARRAY | CALLABLE | EXTENDS | IMPLEMENTS | NAMESPACE | TRAIT | INTERFACE | CLASS | CLASS_C | TRAIT_C | FUNC_C | METHOD_C | LINE | FILE | DIR | NAMESPACE_C -> reservedNonModifiers ;; reservedNonModifiers | STATIC | ABSTRACT | FINAL | PRIVATE | PROTECTED | PUBLIC -> semiReserved ;; identifier [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] | semiReserved [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] -> semiReservedIdentifier [ member variable string: qint64; ] ;; identifier [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] | reservedNonModifiers [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] -> reservedNonModifierIdentifier [ member variable string: qint64; ] ;; variable=VARIABLE -> variableIdentifier ;; NAMESPACE #namespaceName=identifier* @ BACKSLASH ( -- the semicolon case needs at least one namespace identifier, the {...} case not... SEMICOLON [: if (!(*yynode)->namespaceNameSequence) { reportProblem(Error, QStringLiteral("Missing namespace identifier."), -2); } :] | LBRACE try/recover(body=innerStatementList) RBRACE ) -> namespaceDeclarationStatement ;; INTERFACE interfaceName=identifier (EXTENDS extends=classImplements | 0) LBRACE try/recover(body=classBody) RBRACE -> interfaceDeclarationStatement ;; TRAIT traitName=identifier LBRACE body=classBody RBRACE -> traitDeclarationStatement ;; modifier=optionalClassModifier CLASS className=identifier (EXTENDS extends=classExtends | 0) (IMPLEMENTS implements=classImplements | 0) LBRACE body=classBody RBRACE -> classDeclarationStatement ;; identifier=namespacedIdentifier -> classExtends ;; #implements=namespacedIdentifier @ COMMA -> classImplements ;; -- error recovery, to understand it you probably have to look at the generated code ;-) [: bool reported = false; while ( true ) { :] try/recover(#classStatements=classStatement)* [: if (yytoken != Token_RBRACE && yytoken != Token_EOF && yytoken != Token_CLOSE_TAG) { if (!reported) { reportProblem(Error, QStringLiteral("Unexpected token in class context.")); reported = true; } yylex(); } else { break; } } :] RBRACE [: rewind(tokenStream->index() - 2); :] -> classBody ;; CONST #consts=classConstantDeclaration @ COMMA SEMICOLON | VAR variable=classVariableDeclaration SEMICOLON | modifiers=optionalModifiers ( variable=classVariableDeclaration SEMICOLON | FUNCTION (BIT_AND | 0) methodName=semiReservedIdentifier LPAREN parameters=parameterList RPAREN ( COLON returnType=returnType | 0) methodBody=methodBody ) | USE #traits=namespacedIdentifier @ COMMA (imports=traitAliasDeclaration|SEMICOLON) -> classStatement ;; LBRACE #statements=traitAliasStatement @ (SEMICOLON [: if (yytoken == Token_RBRACE) { break; } :]) RBRACE -> traitAliasDeclaration ;; importIdentifier=traitAliasIdentifier -- first/first conflict resolved by LA(2) -- We can either have a single token (modifier or identifier), or a combination ( AS (?[: LA(2).kind == Token_SEMICOLON :] (modifiers=traitVisibilityModifiers | aliasNonModifierIdentifier=reservedNonModifierIdentifier) | modifiers=traitVisibilityModifiers aliasIdentifier=semiReservedIdentifier ) | INSTEADOF #conflictIdentifier=namespacedIdentifier @ COMMA ) -> traitAliasStatement ;; identifier=namespacedIdentifier PAAMAYIM_NEKUDOTAYIM methodIdentifier=semiReservedIdentifier -> traitAliasIdentifier ;; SEMICOLON -- abstract method | LBRACE try/recover(statements=innerStatementList) RBRACE -> methodBody ;; #vars=classVariable @ COMMA -> classVariableDeclaration ;; variable=variableIdentifier (ASSIGN value=staticScalar | 0) -> classVariable ;; PUBLIC [: (*yynode)->modifiers |= ModifierPublic; :] | PROTECTED [: (*yynode)->modifiers |= ModifierProtected; :] | PRIVATE [: (*yynode)->modifiers |= ModifierPrivate; :] | STATIC [: (*yynode)->modifiers |= ModifierStatic; :] | ABSTRACT [: (*yynode)->modifiers |= ModifierAbstract; :] | FINAL [: (*yynode)->modifiers |= ModifierFinal; :] -> traitVisibilityModifiers[ member variable modifiers: unsigned int; ] ;; ( PUBLIC [: (*yynode)->modifiers |= ModifierPublic; :] | PROTECTED [: (*yynode)->modifiers |= ModifierProtected; :] | PRIVATE [: (*yynode)->modifiers |= ModifierPrivate; :] | STATIC [: (*yynode)->modifiers |= ModifierStatic; :] | ABSTRACT [: (*yynode)->modifiers |= ModifierAbstract; :] | FINAL [: (*yynode)->modifiers |= ModifierFinal; :] | 0 )* -> optionalModifiers[ member variable modifiers: unsigned int; ] ;; ( ABSTRACT [: (*yynode)->modifier = AbstractClass; :] | FINAL [: (*yynode)->modifier = FinalClass; :] | 0 ) -> optionalClassModifier[ member variable modifier: ClassModifier; ] ;; ----------------------------------------------------------------- -- Code segments copied to the implementation (.cpp) file. -- If existent, kdevelop-pg's current syntax requires this block -- to occur at the end of the file. ----------------------------------------------------------------- [: #include #include namespace Php { void Parser::tokenize(const QString& contents, int initialState) { m_contents = contents; Lexer lexer(tokenStream, contents, initialState); int kind = Parser::Token_EOF; int lastDocCommentBegin; int lastDocCommentEnd; do { lastDocCommentBegin = 0; lastDocCommentEnd = 0; kind = lexer.nextTokenKind(); while (kind == Parser::Token_WHITESPACE || kind == Parser::Token_COMMENT || kind == Parser::Token_DOC_COMMENT) { if (kind == Parser::Token_COMMENT || kind == Parser::Token_DOC_COMMENT) { extractTodosFromComment(tokenText(lexer.tokenBegin(), lexer.tokenEnd()), lexer.tokenBegin()); } if (kind == Parser::Token_DOC_COMMENT) { lastDocCommentBegin = lexer.tokenBegin(); lastDocCommentEnd = lexer.tokenEnd(); } kind = lexer.nextTokenKind(); } if ( !kind ) // when the lexer returns 0, the end of file is reached { kind = Parser::Token_EOF; } Parser::Token &t = tokenStream->push(); t.begin = lexer.tokenBegin(); t.end = lexer.tokenEnd(); t.kind = kind; t.docCommentBegin = lastDocCommentBegin; t.docCommentEnd = lastDocCommentEnd; //if ( m_debug ) qDebug() << kind << tokenText(t.begin,t.end) << t.begin << t.end; } while ( kind != Parser::Token_EOF ); yylex(); // produce the look ahead token } void Parser::extractTodosFromComment(const QString& comment, qint64 startPosition) { auto i = m_todoMarkers.globalMatch(comment); while (i.hasNext()) { auto match = i.next(); auto p = reportProblem(Todo, match.captured(1), 0); if (!p) { continue; } qint64 line = 0; qint64 column = 0; tokenStream->locationTable()->positionAt(startPosition, &line, &column); auto location = p->finalLocation(); location.setStart(KTextEditor::Cursor(line, column + match.capturedStart(1))); location.setEnd(KTextEditor::Cursor(line, column + match.capturedEnd(1))); p->setFinalLocation(location); }; } void Parser::setTodoMarkers(const QStringList& markers) { QString pattern = QStringLiteral("^(?:[/\\*\\s]*)(.*(?:"); bool first = true; foreach(const QString& marker, markers) { if (!first) { pattern += '|'; } pattern += QRegularExpression::escape(marker); first = false; } pattern += QStringLiteral(").*?)(?:[/\\*\\s]*)$"); m_todoMarkers.setPatternOptions(QRegularExpression::MultilineOption); m_todoMarkers.setPattern(pattern); } QString Parser::tokenText(qint64 begin, qint64 end) { return m_contents.mid(begin,end-begin+1); } KDevelop::ProblemPointer Parser::reportProblem( Parser::ProblemType type, const QString& message, int offset ) { qint64 sLine; qint64 sCol; qint64 index = tokenStream->index() + offset; if (index >= tokenStream->size()) { return {}; } tokenStream->startPosition(index, &sLine, &sCol); qint64 eLine; qint64 eCol; tokenStream->endPosition(index, &eLine, &eCol); auto p = KDevelop::ProblemPointer(new KDevelop::Problem()); p->setSource(KDevelop::IProblem::Parser); switch ( type ) { case Error: p->setSeverity(KDevelop::IProblem::Error); break; case Warning: p->setSeverity(KDevelop::IProblem::Warning); break; case Info: p->setSeverity(KDevelop::IProblem::Hint); break; case Todo: p->setSeverity(KDevelop::IProblem::Hint); p->setSource(KDevelop::IProblem::ToDo); break; } p->setDescription(message); KTextEditor::Range range(sLine, sCol, eLine, eCol + 1); p->setFinalLocation(KDevelop::DocumentRange(m_currentDocument, range)); m_problems << p; return p; } // custom error recovery void Parser::expectedToken(int /*expected*/, qint64 /*where*/, const QString& name) { reportProblem( Parser::Error, QStringLiteral("Expected token \"%1\"").arg(name)); } void Parser::expectedSymbol(int /*expectedSymbol*/, const QString& name) { qint64 line; qint64 col; qint64 index = tokenStream->index()-1; Token &token = tokenStream->at(index); qCDebug(PARSER) << "token starts at:" << token.begin; qCDebug(PARSER) << "index is:" << index; tokenStream->startPosition(index, &line, &col); QString tokenValue = tokenText(token.begin, token.end); qint64 eLine; qint64 eCol; tokenStream->endPosition(index, &eLine, &eCol); reportProblem( Parser::Error, QStringLiteral("Expected symbol \"%1\" (current token: \"%2\" [%3] at %4:%5 - %6:%7)") .arg(name, token.kind != 0 ? tokenValue : QStringLiteral("EOF")) .arg(token.kind) .arg(line) .arg(col) .arg(eLine) .arg(eCol)); } void Parser::setDebug( bool debug ) { m_debug = debug; } void Parser::setCurrentDocument(KDevelop::IndexedString url) { m_currentDocument = url; } Parser::ParserState *Parser::copyCurrentState() { ParserState *state = new ParserState(); state->varExpressionState = m_state.varExpressionState; state->varExpressionIsVariable = m_state.varExpressionIsVariable; return state; } void Parser::restoreState( Parser::ParserState* state) { m_state.varExpressionState = state->varExpressionState; m_state.varExpressionIsVariable = state->varExpressionIsVariable; } } // end of namespace Php :] -- kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on; mode KDevelop-PG[-Qt] diff --git a/phpparsejob.cpp b/phpparsejob.cpp index c980378..5b39308 100644 --- a/phpparsejob.cpp +++ b/phpparsejob.cpp @@ -1,250 +1,250 @@ /***************************************************************************** * Copyright (c) 2007 Piyush verma * * Copyright (c) 2008 Niko Sams * * Copyright (c) 2010 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *****************************************************************************/ #include "phpparsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include "editorintegrator.h" #include "parsesession.h" #include "phplanguagesupport.h" #include "phpdebugvisitor.h" #include "duchain/builders/declarationbuilder.h" #include "duchain/builders/usebuilder.h" #include "duchain/helper.h" #include "phpducontext.h" #include "phpdebug.h" #include #include #include #include using namespace KDevelop; namespace Php { ParseJob::ParseJob(const IndexedString& url, ILanguageSupport* languageSupport) : KDevelop::ParseJob(url, languageSupport) , m_parentJob(nullptr) { } ParseJob::~ParseJob() { } LanguageSupport* ParseJob::php() const { return dynamic_cast(languageSupport()); } void ParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread * /*thread*/) { if (document() != internalFunctionFile()) { // make sure we loaded the internal file already const auto &phpSupport = languageSupport(); static std::once_flag once; std::call_once(once, [phpSupport] { qCDebug(PHP) << "Initializing internal function file" << internalFunctionFile(); ParseJob internalJob(internalFunctionFile(), phpSupport); internalJob.setMinimumFeatures(TopDUContext::AllDeclarationsAndContexts); internalJob.run({}, nullptr); Q_ASSERT(internalJob.success()); }); } UrlParseLock urlLock(document()); if (!(minimumFeatures() & Resheduled) && !isUpdateRequired(phpLanguageString())) { return; } qCDebug(PHP) << "parsing" << document().str(); KDevelop::ProblemPointer p = readContents(); if (p) { //TODO: associate problem with topducontext return abortJob();; } ParseSession session; //TODO: support different charsets session.setContents(QString::fromUtf8(contents().contents)); session.setCurrentDocument(document()); // 2) parse StartAst* ast = nullptr; bool matched = session.parse(&ast); if (abortRequested() || ICore::self()->shuttingDown()) { return abortJob(); } KDevelop::ReferencedTopDUContext toUpdate; { KDevelop::DUChainReadLocker duchainlock(KDevelop::DUChain::lock()); toUpdate = KDevelop::DUChainUtils::standardContextForUrl(document().toUrl()); } KDevelop::TopDUContext::Features newFeatures = minimumFeatures(); if (toUpdate) newFeatures = (KDevelop::TopDUContext::Features)(newFeatures | toUpdate->features()); //Remove update-flags like 'Recursive' or 'ForceUpdate' newFeatures = static_cast(newFeatures & KDevelop::TopDUContext::AllDeclarationsContextsUsesAndAST); if (matched) { if (abortRequested()) { return abortJob(); } EditorIntegrator editor(&session); QReadLocker parseLock(php()->parseLock()); DeclarationBuilder builder(&editor); KDevelop::ReferencedTopDUContext chain = builder.build(document(), ast, toUpdate); if (abortRequested()) { return abortJob(); } setDuChain(chain); bool hadUnresolvedIdentifiers = builder.hadUnresolvedIdentifiers(); if ( newFeatures & TopDUContext::AllDeclarationsContextsAndUses && document() != internalFunctionFile() ) { UseBuilder useBuilder(&editor); useBuilder.buildUses(ast); if (useBuilder.hadUnresolvedIdentifiers()) hadUnresolvedIdentifiers = true; } if (hadUnresolvedIdentifiers) { - if (!(minimumFeatures() & Resheduled) && KDevelop::ICore::self()->languageController()->backgroundParser()->queuedCount()) { + if (!(minimumFeatures() & Resheduled)) { // Need to create new parse job with lower priority qCDebug(PHP) << "Reschedule file " << document().str() << "for parsing"; KDevelop::TopDUContext::Features feat = static_cast( minimumFeatures() | KDevelop::TopDUContext::VisibleDeclarationsAndContexts | Resheduled ); int priority = qMin(parsePriority()+100, (int)KDevelop::BackgroundParser::WorstPriority); KDevelop::ICore::self()->languageController()->backgroundParser() ->addDocument(document(), feat, priority); } else { // We haven't resolved all identifiers, but by now, we don't expect to qCDebug(PHP) << "Builder found unresolved identifiers when they should have been resolved! (if there was no coding error)"; } } if (abortRequested()) { return abortJob(); } if (abortRequested()) { return abortJob(); } { DUChainWriteLocker lock(DUChain::lock()); foreach(const ProblemPointer &p, session.problems()) { chain->addProblem(p); } chain->setFeatures(newFeatures); ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); file->setModificationRevision(contents().modification); DUChain::self()->updateContextEnvironment( chain->topContext(), file.data() ); } highlightDUChain(); } else { ReferencedTopDUContext top; DUChainWriteLocker lock; { top = DUChain::self()->chainForDocument(document()); } if (top) { ///NOTE: if we clear the imported parent contexts, autocompletion of built-in PHP stuff won't work! //top->clearImportedParentContexts(); top->parsingEnvironmentFile()->clearModificationRevisions(); top->clearProblems(); } else { ParsingEnvironmentFile *file = new ParsingEnvironmentFile(document()); file->setLanguage(phpLanguageString()); top = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file); DUChain::self()->addDocumentChain(top); } foreach(const ProblemPointer &p, session.problems()) { top->addProblem(p); } setDuChain(top); qCDebug(PHP) << "===Failed===" << document().str(); } DUChain::self()->emitUpdateReady(document(), duChain()); } void ParseJob::setParentJob(ParseJob *job) { m_parentJob = job; } bool ParseJob::hasParentDocument(const IndexedString &doc) { if (document() == doc) return true; if (!m_parentJob) return false; if (m_parentJob->document() == doc) return true; return m_parentJob->hasParentDocument(doc); } ProblemPointer ParseJob::createProblem(const QString &description, AstNode* node, EditorIntegrator * editor, IProblem::Source source, IProblem::Severity severity) { ProblemPointer p(new Problem()); p->setSource(source); p->setSeverity(severity); p->setDescription(description); p->setFinalLocation(DocumentRange(document(), editor->findRange(node).castToSimpleRange())); qCDebug(PHP) << p->description(); return p; } } // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on