/******************************************************************************* * Copyright (c) 2011 Sierra Wireless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sierra Wireless - initial API and implementation *******************************************************************************/ package org.eclipse.koneki.ldt.core.internal.ast.models; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.dltk.ast.ASTNode; import org.eclipse.dltk.ast.ASTVisitor; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.koneki.ldt.core.LuaUtils; import org.eclipse.koneki.ldt.core.internal.Activator; import org.eclipse.koneki.ldt.core.internal.ast.models.api.ExprTypeRef; import org.eclipse.koneki.ldt.core.internal.ast.models.api.ExternalTypeRef; import org.eclipse.koneki.ldt.core.internal.ast.models.api.FunctionTypeDef; import org.eclipse.koneki.ldt.core.internal.ast.models.api.InternalTypeRef; import org.eclipse.koneki.ldt.core.internal.ast.models.api.Item; import org.eclipse.koneki.ldt.core.internal.ast.models.api.LuaFileAPI; import org.eclipse.koneki.ldt.core.internal.ast.models.api.ModuleTypeRef; import org.eclipse.koneki.ldt.core.internal.ast.models.api.PrimitiveTypeRef; import org.eclipse.koneki.ldt.core.internal.ast.models.api.RecordTypeDef; import org.eclipse.koneki.ldt.core.internal.ast.models.api.Return; import org.eclipse.koneki.ldt.core.internal.ast.models.api.TypeDef; import org.eclipse.koneki.ldt.core.internal.ast.models.api.TypeRef; import org.eclipse.koneki.ldt.core.internal.ast.models.common.LuaASTNode; import org.eclipse.koneki.ldt.core.internal.ast.models.common.LuaSourceRoot; import org.eclipse.koneki.ldt.core.internal.ast.models.file.Block; import org.eclipse.koneki.ldt.core.internal.ast.models.file.Call; import org.eclipse.koneki.ldt.core.internal.ast.models.file.Identifier; import org.eclipse.koneki.ldt.core.internal.ast.models.file.Index; import org.eclipse.koneki.ldt.core.internal.ast.models.file.Invoke; import org.eclipse.koneki.ldt.core.internal.ast.models.file.LocalVar; import org.eclipse.koneki.ldt.core.internal.ast.models.file.LuaExpression; import org.eclipse.koneki.ldt.core.internal.ast.models.file.LuaInternalContent; public final class LuaASTUtils { private LuaASTUtils() { } private static class ClosestItemVisitor extends ASTVisitor { private Item result = null; private int position; private String identifierName; public ClosestItemVisitor(int position, String identifierName) { super(); this.position = position; this.identifierName = identifierName; } public Item getResult() { return result; } @Override public boolean visit(ASTNode node) throws Exception { if (node instanceof LocalVar) return false; // we go down util we found the closer block. if (node instanceof Block) { if (node.sourceStart() <= position && position <= node.sourceEnd()) { return true; } return false; } return false; } @Override public boolean endvisit(ASTNode node) throws Exception { if (result == null && node instanceof Block) { // we go up only on the parent block List<LocalVar> localVars = ((Block) node).getLocalVars(); for (LocalVar localVar : localVars) { Item item = localVar.getVar(); if (item.getName().equals(identifierName)) { result = item; } } return true; } return false; } }; public static Item getClosestLocalVar(final LuaSourceRoot luaSourceRoot, final String identifierName, final int position) { // traverse the root block on the file with this visitor try { ClosestItemVisitor closestItemVisitor = new ClosestItemVisitor(position, identifierName); luaSourceRoot.getInternalContent().getContent().traverse(closestItemVisitor); return closestItemVisitor.getResult(); // CHECKSTYLE:OFF } catch (Exception e) { // CHECKSTYLE:ON Activator.logError("unable to collect local var", e); //$NON-NLS-1$ } return null; } public static TypeResolution resolveType(ISourceModule sourceModule, TypeRef typeRef) { if (typeRef instanceof PrimitiveTypeRef) return null; if (typeRef instanceof InternalTypeRef) { return resolveType(sourceModule, (InternalTypeRef) typeRef); } if (typeRef instanceof ExternalTypeRef) { return resolveType(sourceModule, (ExternalTypeRef) typeRef); } if (typeRef instanceof ModuleTypeRef) { return resolveType(sourceModule, (ModuleTypeRef) typeRef); } if (typeRef instanceof ExprTypeRef) { return resolveType(sourceModule, (ExprTypeRef) typeRef); } return null; } public static TypeResolution resolveType(ISourceModule sourceModule, InternalTypeRef internalTypeRef) { LuaSourceRoot luaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(sourceModule); TypeDef typeDef = luaSourceRoot.getFileapi().getTypes().get(internalTypeRef.getTypeName()); return new TypeResolution(sourceModule, typeDef); } public static TypeResolution resolveType(ISourceModule sourceModule, ExternalTypeRef externalTypeRef) { ISourceModule externalSourceModule = LuaUtils.getSourceModule(externalTypeRef.getModuleName(), sourceModule.getScriptProject()); if (externalSourceModule == null) return null; LuaSourceRoot luaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(externalSourceModule); TypeDef typeDef = luaSourceRoot.getFileapi().getTypes().get(externalTypeRef.getTypeName()); return new TypeResolution(externalSourceModule, typeDef); } public static TypeResolution resolveType(ISourceModule sourceModule, ModuleTypeRef moduleTypeRef) { ISourceModule referencedSourceModule = LuaUtils.getSourceModule(moduleTypeRef.getModuleName(), sourceModule.getScriptProject()); if (referencedSourceModule == null) return null; LuaSourceRoot luaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(referencedSourceModule); LuaFileAPI fileapi = luaSourceRoot.getFileapi(); if (fileapi != null) { ArrayList<Return> returns = fileapi.getReturns(); if (returns.size() > 0) { Return returnValues = returns.get(0); if (returnValues.getTypes().size() > moduleTypeRef.getReturnPosition() - 1) { TypeRef typeRef = returnValues.getTypes().get(moduleTypeRef.getReturnPosition() - 1); return resolveType(referencedSourceModule, typeRef); } } } return null; } public static TypeResolution resolveType(ISourceModule sourceModule, ExprTypeRef exprTypeRef) { LuaExpression expression = exprTypeRef.getExpression(); if (expression == null) return null; return resolveType(sourceModule, expression, exprTypeRef.getReturnPosition()); } public static TypeResolution resolveType(ISourceModule sourceModule, LuaExpression expr) { return resolveType(sourceModule, expr, 1); } public static TypeResolution resolveType(ISourceModule sourceModule, LuaExpression expr, int returnposition) { if (expr instanceof Identifier) { Definition definition = getDefinition(sourceModule, expr); // resolve the type of the definition if (definition == null || definition.getItem() == null || definition.getItem().getType() == null) return null; return resolveType(definition.getModule(), definition.getItem().getType()); } else if (expr instanceof Index) { Index index = ((Index) expr); // resolve left part of the index LuaExpression left = index.getLeft(); TypeResolution resolvedLeftType = resolveType(sourceModule, left); if (resolvedLeftType != null && resolvedLeftType.getTypeDef() instanceof RecordTypeDef) { RecordTypeDef recordtype = (RecordTypeDef) resolvedLeftType.getTypeDef(); Item item = recordtype.getFields().get(index.getRight()); if (item != null && item.getType() != null) { return resolveType(resolvedLeftType.getModule(), item.getType()); } } return null; } else if (expr instanceof Call) { Call call = ((Call) expr); // resolve the function which is called TypeResolution resolvedFunctionType = resolveType(sourceModule, call.getFunction()); if (resolvedFunctionType != null && resolvedFunctionType.getTypeDef() instanceof FunctionTypeDef) { FunctionTypeDef functiontype = (FunctionTypeDef) resolvedFunctionType.getTypeDef(); if (functiontype.getReturns().size() > 0) { List<TypeRef> types = functiontype.getReturns().get(0).getTypes(); if (types.size() >= returnposition) { return resolveType(resolvedFunctionType.getModule(), types.get(returnposition - 1)); } } } return null; } else if (expr instanceof Invoke) { Invoke invoke = ((Invoke) expr); // resolve the function which is called TypeResolution resolvedRecordType = resolveType(sourceModule, invoke.getRecord()); if (resolvedRecordType != null && resolvedRecordType.getTypeDef() instanceof RecordTypeDef) { RecordTypeDef recordtype = (RecordTypeDef) resolvedRecordType.getTypeDef(); Item item = recordtype.getFields().get(invoke.getFunctionName()); if (item != null && item.getType() != null) { TypeResolution resolvedFunctionType = resolveType(resolvedRecordType.getModule(), item.getType()); if (resolvedFunctionType != null && resolvedFunctionType.getTypeDef() instanceof FunctionTypeDef) { FunctionTypeDef functiontype = (FunctionTypeDef) resolvedFunctionType.getTypeDef(); if (functiontype.getReturns().size() > 0) { List<TypeRef> types = functiontype.getReturns().get(0).getTypes(); if (types.size() >= returnposition) { return resolveType(resolvedFunctionType.getModule(), types.get(returnposition - 1)); } } } } } } return null; } public static class TypeResolution { private ISourceModule module; private TypeDef typeDef; public TypeResolution(ISourceModule module, TypeDef typeDef) { super(); this.module = module; this.typeDef = typeDef; } public ISourceModule getModule() { return module; } public TypeDef getTypeDef() { return typeDef; } } public static Collection<Item> getLocalVars(LuaSourceRoot luaSourceRoot, final int offset, final String start) { // the localVars collected, indexed by var name; final Map<String, Item> collectedLocalVars = new HashMap<String, Item>(); // the visitor which will collect local vars and store it in the map. ASTVisitor localvarCollector = new ASTVisitor() { @Override public boolean visit(ASTNode node) throws Exception { // we go down util we found the closer block. if (node instanceof Block) { if (node.sourceStart() <= offset && offset <= node.sourceEnd()) { return true; } return false; } return false; } @Override public boolean endvisit(ASTNode node) throws Exception { if (node instanceof Block) { // we go up only on all the parent block which List<LocalVar> localVars = ((Block) node).getLocalVars(); for (LocalVar localVar : localVars) { Item item = localVar.getVar(); if (!collectedLocalVars.containsKey(item.getName()) && (start == null || item.getName().toLowerCase().startsWith(start.toLowerCase()))) { collectedLocalVars.put(item.getName(), item); } } return true; } return false; } }; // traverse the root block on the file with this visitor try { luaSourceRoot.getInternalContent().getContent().traverse(localvarCollector); // CHECKSTYLE:OFF } catch (Exception e) { // CHECKSTYLE:ON Activator.logError("unable to collect local var", e); //$NON-NLS-1$ } return collectedLocalVars.values(); } public static LuaExpression getLuaExpressionAt(LuaSourceRoot luaSourceRoot, final int startOffset, final int endOffset) { // traverse the root block on the file with this visitor try { MatchNodeVisitor matchNodeVisitor = new MatchNodeVisitor(startOffset, endOffset, LuaExpression.class); luaSourceRoot.getInternalContent().getContent().traverse(matchNodeVisitor); return (LuaExpression) matchNodeVisitor.getNode(); // CHECKSTYLE:OFF } catch (Exception e) { // CHECKSTYLE:ON Activator.logError("unable to get expression at", e); //$NON-NLS-1$ } return null; } public static class Definition { private ISourceModule module; private Item item; public Definition(ISourceModule module, Item item) { super(); this.module = module; this.item = item; } public ISourceModule getModule() { return module; } public Item getItem() { return item; } } public static Definition getDefinition(ISourceModule sourceModule, LuaExpression luaExpression) { if (luaExpression instanceof Identifier) { Identifier identifier = (Identifier) luaExpression; if (identifier.getDefinition() != null) { Item definition = identifier.getDefinition(); if (LuaASTUtils.isUnresolvedGlobal(definition)) { // in this case we have a unknown global var definition. // we will try to resolved it Definition globalVarDefinition = getGlobalVarDefinition(sourceModule, definition.getName()); if (globalVarDefinition != null) return globalVarDefinition; } return new Definition(sourceModule, definition); } } else if (luaExpression instanceof Index) { Index index = (Index) luaExpression; TypeResolution resolveType = LuaASTUtils.resolveType(sourceModule, index.getLeft()); if (resolveType != null && resolveType.getTypeDef() instanceof RecordTypeDef) { RecordTypeDef typeDef = (RecordTypeDef) resolveType.getTypeDef(); Item definition = typeDef.getFields().get(index.getRight()); return new Definition(resolveType.getModule(), definition); } } else if (luaExpression instanceof Invoke) { Invoke invoke = (Invoke) luaExpression; TypeResolution resolveType = LuaASTUtils.resolveType(sourceModule, invoke.getRecord()); if (resolveType != null && resolveType.getTypeDef() instanceof RecordTypeDef) { RecordTypeDef typeDef = (RecordTypeDef) resolveType.getTypeDef(); Item definition = typeDef.getFields().get(invoke.getFunctionName()); return new Definition(resolveType.getModule(), definition); } } else if (luaExpression instanceof Call) { Call call = (Call) luaExpression; Definition definition = getDefinition(sourceModule, call.getFunction()); return definition; } return null; } public static List<Definition> getAllGlobalVarsDefinition(ISourceModule sourceModule, String start) { final List<Definition> definitions = new ArrayList<Definition>(); // global vars defined preloaded module. // ---------------------------------------- // get preloaded module ISourceModule preloadedSourceModule = getPreloadSourceModule(sourceModule); if (preloadedSourceModule != null) { // get lua source root LuaSourceRoot preloadedLuaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(preloadedSourceModule); if (preloadedLuaSourceRoot != null) { // global var which start with 'start' for (Item globalvar : preloadedLuaSourceRoot.getFileapi().getGlobalvars().values()) { if (start == null || start.isEmpty() || globalvar.getName().toLowerCase().startsWith(start.toLowerCase())) definitions.add(new Definition(preloadedSourceModule, globalvar)); } } } // global vars defined in current module. // ---------------------------------------- LuaSourceRoot currentluaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(sourceModule); if (currentluaSourceRoot != null) { // global var which start with 'start' for (Item globalvar : currentluaSourceRoot.getFileapi().getGlobalvars().values()) { if (start == null || start.isEmpty() || globalvar.getName().toLowerCase().startsWith(start.toLowerCase())) definitions.add(new Definition(sourceModule, globalvar)); } } return definitions; } public static Definition getGlobalVarDefinition(ISourceModule sourceModule, String varname) { // SEARCH IN PRELOADED SOURCE MODULE Definition definition = getGlobalVarDefinitionInPreloadedSourceModule(sourceModule, varname); if (definition != null) return definition; // SEARCH IN CURRENT SOURCE MODULE definition = getInternalGlobalVarDefinition(sourceModule, varname); if (definition != null) return definition; return null; } public static Definition getGlobalVarDefinitionInPreloadedSourceModule(ISourceModule sourceModule, String varname) { // get preloaded module ISourceModule preloadedSourceModule = getPreloadSourceModule(sourceModule); if (preloadedSourceModule == null) return null; // get luasourceroot LuaSourceRoot preloadedLuaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(preloadedSourceModule); if (preloadedLuaSourceRoot == null) return null; // get a global var with this name Item item = preloadedLuaSourceRoot.getFileapi().getGlobalvars().get(varname); if (item == null) return null; return new Definition(preloadedSourceModule, item); } public static Definition getInternalGlobalVarDefinition(ISourceModule sourceModule, String varname) { // get luasourceroot LuaSourceRoot luaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(sourceModule); if (luaSourceRoot == null) return null; // get a global var with this name Item item = luaSourceRoot.getFileapi().getGlobalvars().get(varname); if (item == null) return null; return new Definition(sourceModule, item); } public static ISourceModule getPreloadSourceModule(ISourceModule sourceModule) { if (sourceModule != null && sourceModule.getScriptProject() != null) { return LuaUtils.getSourceModule("global", sourceModule.getScriptProject()); //$NON-NLS-1$ } return null; } public static boolean isLocal(Item item) { return item.getParent() instanceof Block; } public static boolean isGlobal(Item item) { return item.getParent() instanceof LuaFileAPI; } public static boolean isTypeField(Item item) { return item.getParent() instanceof RecordTypeDef; } public static boolean isModuleTypeField(LuaFileAPI luaFileAPI, Item item) { LuaASTNode parent = item.getParent(); if (parent instanceof RecordTypeDef) { return isModule(luaFileAPI, (RecordTypeDef) parent); } return false; } public static boolean isUnresolvedGlobal(Item item) { return item.getParent() instanceof LuaInternalContent; } public static TypeDef resolveTypeLocaly(ISourceModule sourceModuile, Item item) { LuaSourceRoot luaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(sourceModuile); if (luaSourceRoot != null) return resolveTypeLocaly(luaSourceRoot.getFileapi(), item); return null; } public static TypeDef resolveTypeLocaly(LuaFileAPI luaFileAPI, Item item) { if (luaFileAPI == null) return null; TypeRef typeref = item.getType(); if (!(typeref instanceof InternalTypeRef)) return null; InternalTypeRef internaltyperef = (InternalTypeRef) typeref; TypeDef typeDef = luaFileAPI.getTypes().get(internaltyperef.getTypeName()); return typeDef; } public static boolean isModule(LuaFileAPI luaFileAPI, RecordTypeDef recordTypeDef) { TypeRef moduleReturnTypeRef = getModuleReturnType(luaFileAPI); if (!(moduleReturnTypeRef instanceof InternalTypeRef)) return false; String typename = ((InternalTypeRef) moduleReturnTypeRef).getTypeName(); return luaFileAPI.getTypes().get(typename) == recordTypeDef; } public static TypeRef getModuleReturnType(LuaFileAPI luaFileAPI) { ArrayList<Return> returns = luaFileAPI.getReturns(); if (returns.isEmpty()) return null; Return returnValues = returns.get(0); if (returnValues.getTypes().isEmpty()) return null; return returnValues.getTypes().get(0); } }