blob: 02eca3dd4e17382126b56bff814535c800c7e38f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007 University of Illinois at Urbana-Champaign 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:
* UIUC - Initial API and implementation
*******************************************************************************/
package org.eclipse.photran.internal.core.vpg;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.photran.core.IFortranAST;
import org.eclipse.photran.internal.core.FortranAST;
import org.eclipse.photran.internal.core.SyntaxException;
import org.eclipse.photran.internal.core.analysis.binding.Binder;
import org.eclipse.photran.internal.core.analysis.binding.Definition;
import org.eclipse.photran.internal.core.analysis.binding.Definition.Visibility;
import org.eclipse.photran.internal.core.analysis.binding.ImplicitSpec;
import org.eclipse.photran.internal.core.analysis.binding.ScopingNode;
import org.eclipse.photran.internal.core.lexer.ASTLexerFactory;
import org.eclipse.photran.internal.core.lexer.FixedFormReplacement;
import org.eclipse.photran.internal.core.lexer.IAccumulatingLexer;
import org.eclipse.photran.internal.core.lexer.LexerException;
import org.eclipse.photran.internal.core.lexer.Terminal;
import org.eclipse.photran.internal.core.lexer.Token;
import org.eclipse.photran.internal.core.lexer.preprocessor.fortran_include.IncludeLoaderCallback;
import org.eclipse.photran.internal.core.parser.ASTExecutableProgramNode;
import org.eclipse.photran.internal.core.parser.ASTNodeWithErrorRecoverySymbols;
import org.eclipse.photran.internal.core.sourceform.ISourceForm;
import org.eclipse.photran.internal.core.sourceform.SourceForm;
import org.eclipse.rephraserengine.core.vpg.VPGDependency;
import org.eclipse.rephraserengine.core.vpg.VPGEdge;
/**
* The <code>PhotranVPGBuilder</code> provides methods for manipulating the
* VPG database; these methods are used only when collecting bindings.
*
* @author Jeff Overbey
*/
public class PhotranVPGBuilder extends PhotranVPG
{
protected PhotranVPGBuilder()
{
super();
}
public void markFileAsExportingSubprogram(IFile file, String subprogramName)
{
db.ensure(new VPGDependency<IFortranAST, Token, PhotranTokenRef>(this, "subprogram:" + canonicalizeIdentifier(subprogramName), getFilenameForIFile(file))); //$NON-NLS-1$
}
public void markFileAsImportingSubprogram(IFile file, String subprogramName)
{
db.ensure(new VPGDependency<IFortranAST, Token, PhotranTokenRef>(this, getFilenameForIFile(file), "subprogram:" + canonicalizeIdentifier(subprogramName))); //$NON-NLS-1$
}
public void markFileAsExportingModule(IFile file, String moduleName)
{
db.ensure(new VPGDependency<IFortranAST, Token, PhotranTokenRef>(this, "module:" + canonicalizeIdentifier(moduleName), getFilenameForIFile(file))); //$NON-NLS-1$
}
public void markFileAsImportingModule(IFile file, String moduleName)
{
db.ensure(new VPGDependency<IFortranAST, Token, PhotranTokenRef>(this, getFilenameForIFile(file), "module:" + canonicalizeIdentifier(moduleName))); //$NON-NLS-1$
}
public void markFileAsUsingCommonBlock(IFile file, String commonBlockName)
{
db.ensure(new VPGDependency<IFortranAST, Token, PhotranTokenRef>(this, getFilenameForIFile(file), "common:" + canonicalizeIdentifier(commonBlockName))); //$NON-NLS-1$
}
public void setDefinitionFor(PhotranTokenRef tokenRef, Definition definition)
{
db.setAnnotation(tokenRef, DEFINITION_ANNOTATION_TYPE, definition);
}
public void markDefinitionVisibilityInScope(PhotranTokenRef definitionTokenRef, ScopingNode scope, Visibility visibility)
{
//System.err.println("Marking " + definitionTokenRef.findToken().getText() + " " + visibility + " in " + scope.getRepresentativeToken().findTokenOrReturnNull());
VPGEdge<IFortranAST, Token, PhotranTokenRef> privateEdge = new VPGEdge<IFortranAST, Token, PhotranTokenRef>(this, definitionTokenRef, scope.getRepresentativeToken(), DEFINITION_IS_PRIVATE_IN_SCOPE_EDGE_TYPE);
if (visibility.equals(Visibility.PRIVATE))
db.ensure(privateEdge);
else
db.delete(privateEdge);
}
public void markScope(PhotranTokenRef identifier, ScopingNode scope)
{
db.ensure(new VPGEdge<IFortranAST, Token, PhotranTokenRef>(this, identifier, scope.getRepresentativeToken(), DEFINED_IN_SCOPE_EDGE_TYPE));
}
public void markIllegalShadowing(PhotranTokenRef shadowingIdent, PhotranTokenRef shadowedIdent)
{
db.ensure(new VPGEdge<IFortranAST, Token, PhotranTokenRef>(this, shadowingIdent, shadowedIdent, ILLEGAL_SHADOWING_EDGE_TYPE));
}
public void markBinding(PhotranTokenRef reference, PhotranTokenRef definition)
{
db.ensure(new VPGEdge<IFortranAST, Token, PhotranTokenRef>(this, reference, definition, BINDING_EDGE_TYPE));
}
public void markRenamedBinding(PhotranTokenRef reference, PhotranTokenRef definition)
{
db.ensure(new VPGEdge<IFortranAST, Token, PhotranTokenRef>(this, reference, definition, RENAMED_BINDING_EDGE_TYPE));
}
public void setScopeImplicitSpec(ScopingNode scope, ImplicitSpec implicitSpec)
{
if (implicitSpec != null)
db.setAnnotation(scope.getRepresentativeToken(), SCOPE_IMPLICIT_SPEC_ANNOTATION_TYPE, implicitSpec);
else
db.deleteAnnotation(scope.getRepresentativeToken(), SCOPE_IMPLICIT_SPEC_ANNOTATION_TYPE);
}
public void setDefaultScopeVisibilityToPrivate(ScopingNode scope)
{
db.setAnnotation(scope.getRepresentativeToken(), SCOPE_DEFAULT_VISIBILITY_IS_PRIVATE_ANNOTATION_TYPE, Boolean.TRUE);
}
public void setModuleSymbolTable(Token moduleNameToken, List<Definition> symbolTable)
{
clearModuleSymbolTableEntries(moduleNameToken);
String filename = "module:" + canonicalizeIdentifier(moduleNameToken.getText()); //$NON-NLS-1$
PhotranTokenRef tokenRef = createTokenRef(filename, 0, 0);
db.setAnnotation(tokenRef, MODULE_TOKENREF_ANNOTATION_TYPE, moduleNameToken.getTokenRef());
int entries = 0;
for (Definition def : symbolTable)
{
tokenRef = createTokenRef(filename, entries++, 0);
db.setAnnotation(tokenRef, MODULE_SYMTAB_ENTRY_ANNOTATION_TYPE, def);
}
tokenRef = createTokenRef(filename, 0, 0);
db.setAnnotation(tokenRef, MODULE_SYMTAB_ENTRY_COUNT_ANNOTATION_TYPE, Integer.valueOf(entries));
}
private void clearModuleSymbolTableEntries(Token moduleNameToken)
{
String canonicalizedModuleName = canonicalizeIdentifier(moduleNameToken.getText());
moduleSymTabCache.remove(canonicalizedModuleName);
int entries = countModuleSymbolTableEntries(canonicalizedModuleName);
if (entries > 0)
{
String filename = "module:" + canonicalizedModuleName; //$NON-NLS-1$
for (int i = 0; i < entries; i++)
{
PhotranTokenRef tokenRef = createTokenRef(filename, i, 0);
db.deleteAnnotation(tokenRef, MODULE_SYMTAB_ENTRY_ANNOTATION_TYPE);
}
PhotranTokenRef tokenRef = createTokenRef(filename, 0, 0);
db.setAnnotation(tokenRef, MODULE_SYMTAB_ENTRY_COUNT_ANNOTATION_TYPE, Integer.valueOf(0));
}
}
@Override
public boolean isVirtualFile(String filename)
{
return filename.startsWith("module:") || filename.startsWith("common:") || filename.startsWith("subprogram:"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
@Override public PhotranTokenRef createTokenRef(String filename, int offset, int length)
{
return new PhotranTokenRef(filename, offset, length);
}
@Override
protected void calculateDependencies(final String filename)
{
if (isVirtualFile(filename)) return;
ISourceForm sourceForm = determineSourceForm(filename);
try
{
IAccumulatingLexer lexer = new ASTLexerFactory().createLexer(getIFileForFilename(filename), sourceForm);
long start = System.currentTimeMillis();
calculateDependencies(filename, lexer);
debug(" - Elapsed time in calculateDependencies: " + (System.currentTimeMillis()-start) + " ms", filename); //$NON-NLS-1$ //$NON-NLS-2$
}
catch (Exception e)
{
log.logError(e, new PhotranTokenRef(filename, 0, 0));
}
}
private ISourceForm determineSourceForm(final String filename)
{
IFile file = getIFileForFilename(filename);
return SourceForm.of(file).configuredWith(new IncludeLoaderCallback(file.getProject())
{
@Override
public Reader getIncludedFileAsStream(String fileToInclude) throws FileNotFoundException
{
// When we encounter an INCLUDE directive, set up a file dependency in the VPG
db.ensure(new VPGDependency<IFortranAST, Token, PhotranTokenRef>(PhotranVPGBuilder.this,
filename,
getFilenameForIFile(getIncludedFile(fileToInclude))));
return super.getIncludedFileAsStream(fileToInclude);
}
@Override
public void logError(String message, IFile topLevelFile, int offset)
{
PhotranVPG.getInstance().log.clearEntriesFor(PhotranVPG.getFilenameForIFile(topLevelFile));
PhotranVPG.getInstance().log.logError(message, new PhotranTokenRef(topLevelFile, offset, 0));
}
});
}
private void calculateDependencies(String filename, IAccumulatingLexer lexer)
{
if (!isVirtualFile(filename))
{
db.deleteAllIncomingDependenciesFor(filename);
db.deleteAllOutgoingDependenciesFor(filename);
}
if (lexer == null) return;
try
{
calculateFileDepsFromModuleAndUseStmts(filename, lexer);
}
catch (Exception e)
{
log.logError(e);
}
}
/**
* Calculates what modules are imported or exported by a particular file and marks these dependencies in the VPG.
*
* Since the dependency calculation will be run on every file in the workspace, it is essential that this method
* be fast (while remaining accurate). This implementation uses the lexer to find the token sequences "USE x"
* and "MODULE x", which is equivalent to but significantly faster than parsing the file and traversing the AST
* to find USE and MODULE statements.
*/
private void calculateFileDepsFromModuleAndUseStmts(String filename, IAccumulatingLexer lexer) throws Exception
{
IFile file = getIFileForFilename(filename);
final int WAITING_FOR_TOKEN = 0, LAST_TOKEN_WAS_USE = 1, LAST_TOKEN_WAS_MODULE = 2;
int state = WAITING_FOR_TOKEN;
for (Token tok = lexer.yylex(); tok != null && tok.getTerminal() != Terminal.END_OF_INPUT; tok = lexer.yylex())
{
if (state == WAITING_FOR_TOKEN && tok.getTerminal() == Terminal.T_USE)
{
state = LAST_TOKEN_WAS_USE;
}
else if (state == WAITING_FOR_TOKEN && tok.getTerminal() == Terminal.T_MODULE)
{
state = LAST_TOKEN_WAS_MODULE;
}
else if (state == LAST_TOKEN_WAS_USE)
{
if (tok.getTerminal() == Terminal.T_IDENT)
markFileAsImportingModule(file, tok.getText());
state = WAITING_FOR_TOKEN;
}
else if (state == LAST_TOKEN_WAS_MODULE)
{
if (tok.getTerminal() == Terminal.T_IDENT)
markFileAsExportingModule(file, tok.getText());
state = WAITING_FOR_TOKEN;
}
}
}
@Override
protected void doCommitChangeFromAST(String filename)
{
if (isVirtualFile(filename)) return;
IFortranAST ast = acquireTransientAST(filename);
if (ast == null)
throw new IllegalArgumentException(filename + " returned null AST"); //$NON-NLS-1$
try
{
final String charset = Charset.defaultCharset().name(); // Platform's default charset
ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
ast.getRoot().printOn(new PrintStream(out, false, charset), null);
ast = parse(filename, new InputStreamReader(new ByteArrayInputStream(out.toByteArray()), charset));
computeEdgesAndAnnotations(filename, ast);
}
catch (UnsupportedEncodingException e)
{
throw new Error(e);
}
}
@Override public String getSourceCodeFromAST(IFortranAST ast)
{
return ast.getRoot().toString();
}
@Override
protected IFortranAST parse(final String filename)
{
return parse(filename, null);
}
private IFortranAST parse(String filename, Reader stream)
{
if (filename == null || isVirtualFile(filename)) return null;
IFile file = getIFileForFilename(filename); if (file == null) return null;
try
{
ISourceForm sourceForm = determineSourceForm(filename);
try
{
if (stream == null) stream = new BufferedReader(new InputStreamReader(file.getContents(true), file.getCharset()));
IAccumulatingLexer lexer = new ASTLexerFactory().createLexer(stream, file, filename, sourceForm);
long start = System.currentTimeMillis();
ASTExecutableProgramNode ast = parser.parse(lexer);
checkForErrors(ast, filename);
debug(" - Elapsed time in Parser#parse: " + (System.currentTimeMillis()-start) + " ms", filename); //$NON-NLS-1$ //$NON-NLS-2$
return new FortranAST(file, ast, lexer.getTokenList());
}
catch (SyntaxException e)
{
if (e.getFile() != null && e.getFile().getIFile() != null)
filename = PhotranVPG.getFilenameForIFile(e.getFile().getIFile());
log.clearEntriesFor(PhotranVPG.getFilenameForIFile(e.getFile().getIFile()));
log.logError(
Messages.bind(
Messages.PhotranVPGBuilder_ErrorParsingFileMessage,
filename,
e.getMessage()),
new PhotranTokenRef(filename, e.getTokenOffset(), e.getTokenLength()));
// else
// logError(file, Messages.bind(Messages.PhotranVPGBuilder_ErrorParsingFile, filename), e);
return null;
}
catch (LexerException e)
{
if (e.getFile() != null && e.getFile().getIFile() != null)
filename = PhotranVPG.getFilenameForIFile(e.getFile().getIFile());
log.clearEntriesFor(filename);
log.logError(
Messages.bind(
Messages.PhotranVPGBuilder_ErrorParsingFileMessage,
filename,
e.getMessage()),
new PhotranTokenRef(filename, e.getTokenOffset(), e.getTokenLength()));
// }
// else
// logError(file, Messages.bind(Messages.PhotranVPGBuilder_ErrorParsingFile, filename), e);
return null;
}
// catch (CoreException e)
// {
// IFile errorFile = getFileFromStatus(e.getStatus());
// if (errorFile != null)
// log.logError("Error parsing " + filename + ": " + e.getMessage(),
// new PhotranTokenRef(errorFile, 0, 0));
// else
// logError(file, "Error parsing " + filename, e);
// return null;
// }
catch (Throwable e)
{
logError(file, Messages.bind(Messages.PhotranVPGBuilder_ErrorParsingFile, filename), e);
return null;
}
}
finally
{
try
{
if (stream != null) stream.close();
}
catch (Throwable x)
{
// Ignore
}
}
}
private void checkForErrors(ASTExecutableProgramNode ast, String filename)
{
ASTNodeWithErrorRecoverySymbols firstError = findFirstErrorIn(ast);
if (firstError != null)
{
PhotranTokenRef errorTokenRef = getErrorTokenRef(filename, firstError.getErrorToken());
log.clearEntriesFor(filename);
log.logError(
Messages.bind(
Messages.PhotranVPGBuilder_FileContainsSyntaxErrors,
filename),
errorTokenRef);
}
}
private PhotranTokenRef getErrorTokenRef(String filename, Token errorToken)
{
if (isPreprocessed(errorToken))
return null;
else
// errorToken will have its filename set to null, so we must construct a TokenRef manually
return new PhotranTokenRef(filename,
errorToken.getStreamOffset(),
errorToken.getLength());
}
private boolean isPreprocessed(Token token)
{
return token.getPreprocessorDirective() != null
&& !(token.getPreprocessorDirective() instanceof FixedFormReplacement);
}
private void logError(IFile file, String message, Throwable e)
{
StringBuilder sb = new StringBuilder();
sb.append(message);
sb.append(": "); //$NON-NLS-1$
sb.append(e.getMessage());
sb.append('\n');
sb.append(e.getClass().getName());
sb.append(":\n"); //$NON-NLS-1$
ByteArrayOutputStream bs = new ByteArrayOutputStream();
e.printStackTrace(new PrintStream(bs));
sb.append(bs);
if (file != null)
log.logError(sb.toString(), new PhotranTokenRef(file, 0, 0));
else
log.logError(sb.toString());
}
@Override
protected void populateVPG(String filename, IFortranAST ast)
{
if (!isVirtualFile(filename))
{
db.deleteAllIncomingDependenciesFor(filename);
db.deleteAllOutgoingDependenciesFor(filename);
}
if (ast == null || isEmpty(ast.getRoot())) return;
long start = System.currentTimeMillis();
Binder.bind(ast, getIFileForFilename(filename));
debug(" - Elapsed time in Binder#bind: " + (System.currentTimeMillis()-start) + " ms", filename); //$NON-NLS-1$ //$NON-NLS-2$
}
public static boolean isEmpty(ASTExecutableProgramNode ast)
{
return ast.findFirstToken() == null;
}
}