blob: d74d9dd6bde7b92bc4ee29abe0bccb96c5a7fefe [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 The University of York.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Dimitrios Kolovos - initial API and implementation
******************************************************************************/
package org.eclipse.epsilon.eol;
import java.util.*;
import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.Lexer;
import org.antlr.runtime.TokenStream;
import org.eclipse.epsilon.common.module.IModule;
import org.eclipse.epsilon.common.module.ModuleElement;
import org.eclipse.epsilon.common.module.ModuleMarker;
import org.eclipse.epsilon.common.parse.AST;
import org.eclipse.epsilon.common.parse.EpsilonParser;
import org.eclipse.epsilon.common.parse.problem.ParseProblem;
import org.eclipse.epsilon.common.util.AstUtil;
import org.eclipse.epsilon.common.util.ListSet;
import org.eclipse.epsilon.eol.compile.context.EolCompilationContext;
import org.eclipse.epsilon.eol.compile.context.IEolCompilationContext;
import org.eclipse.epsilon.eol.dom.*;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.execute.Return;
import org.eclipse.epsilon.eol.execute.context.EolContext;
import org.eclipse.epsilon.eol.execute.context.IEolContext;
import org.eclipse.epsilon.eol.execute.context.Variable;
import org.eclipse.epsilon.eol.parse.EolLexer;
import org.eclipse.epsilon.eol.parse.EolParser;
import org.eclipse.epsilon.eol.tools.EolSystem;
public class EolModule extends AbstractModule implements IEolModule {
protected StatementBlock main;
protected IEolContext context;
protected List<Statement> postOperationStatements = new ArrayList<>();
protected OperationList declaredOperations = new OperationList();
protected List<Import> imports = new ArrayList<>();
protected OperationList operations = new OperationList();
protected List<ModelDeclaration> declaredModelDeclarations;
protected Set<ModelDeclaration> modelDeclarations;
protected IEolCompilationContext compilationContext;
private IEolModule parent;
/**
* The type of {@link #context} when using {@link #getContext()} and {@link #setContext(IEolContext)}.
* @since 1.6
*/
Class<? extends IEolContext> expectedContextType = IEolContext.class;
public EolModule() {
this(null);
}
/**
* Instantiates the module with the specified execution context.
*
* @param context The execution context
* @since 1.6
*/
@SuppressWarnings("unchecked")
public EolModule(IEolContext context) {
// Ensure that setContext is consistent with getContext
try {
expectedContextType = (Class<? extends IEolContext>) getClass().getMethod("getContext").getReturnType();
}
catch (NoSuchMethodException | SecurityException | ClassCastException ex) {
// Use the default - no need to do anything here.
}
setContext(context != null ? context : new EolContext());
}
@Override
public void build(AST cst, IModule module) {
super.build(cst, module);
checkImports(cst);
for (Map.Entry<String, Class<?>> entry : getImportConfiguration().entrySet()) {
imports.addAll(getImportsByExtension(cst, entry.getKey(), entry.getValue()));
}
for (AST operationAst : AstUtil.getChildren(cst, EolParser.HELPERMETHOD)) {
declaredOperations.add((Operation) createAst(operationAst, this));
}
List<AST> modelDeclarationAsts = AstUtil.getChildren(cst, EolParser.MODELDECLARATION);
declaredModelDeclarations = new ArrayList<>(modelDeclarationAsts.size());
for (AST modelDeclarationAst : modelDeclarationAsts) {
declaredModelDeclarations.add((ModelDeclaration) createAst(modelDeclarationAst, this));
}
if (AstUtil.getChild(cst, EolParser.BLOCK) != null) {
main = (StatementBlock) createAst(AstUtil.getChild(cst, EolParser.BLOCK), this);
for (AST child : cst.getChildren()) {
int type = child.getType();
if (type == EolParser.BLOCK ||
type == EolParser.ANNOTATIONBLOCK ||
type == EolParser.HELPERMETHOD ||
type == EolParser.MODELDECLARATION ||
type == EolParser.IMPORT) {
continue;
}
ModuleElement exprAst = module.createAst(child, this);
final Expression expression;
if (exprAst instanceof ReturnStatement) {
expression = ((ReturnStatement) exprAst).getReturnedExpression();
}
else expression = (Expression) exprAst;
ExpressionStatement expressionStatement = new ExpressionStatement(expression);
expressionStatement.setParent(this);
postOperationStatements.add(expressionStatement);
}
}
operations.addAll(this.getDeclaredOperations());
for (Import import_ : imports) {
import_.setContext(context);
if (import_.isLoaded() && import_.getModule() instanceof IEolModule) {
operations.addAll(((IEolModule)import_.getModule()).getOperations());
}
}
}
@Override
public ModuleElement adapt(AST cst, ModuleElement parentAst) {
if (cst == null) return null;
AST cstParent = cst.getParent();
if (cstParent != null && cstParent.getType() == EolParser.EOLMODULE && cst.getType() == EolParser.BLOCK) {
return new StatementBlock();
}
OperatorExpressionFactory operatorExpressionFactory = new OperatorExpressionFactory();
if (parentAst == null) return this;
switch (cst.getType()) {
case EolParser.FOR: return new ForStatement();
case EolParser.WHILE: return new WhileStatement();
case EolParser.DEFAULT:
case EolParser.CASE: return new Case();
case EolParser.SWITCH: return new SwitchStatement();
case EolParser.IF: return new IfStatement();
case EolParser.ITEMSELECTOR: return new ItemSelectorExpression();
case EolParser.ARROW: case EolParser.NAVIGATION: case EolParser.POINT: {
AST secondChild = cst.getSecondChild();
if (!secondChild.hasChildren()) {
return new PropertyCallExpression();
}
else if (secondChild.getExtraTokens().size() >= 2) {
if (secondChild.getChildren().stream().anyMatch(ast -> ast.getType() == EolParser.LAMBDAEXPR)) {
return new ComplexOperationCallExpression();
}
else {
return new FirstOrderOperationCallExpression();
}
}
else {
return new OperationCallExpression();
}
}
case EolParser.NAME: {
if (cst.hasChildren()) {
AST firstChild = cst.getFirstChild();
if (firstChild.getType() == EolParser.PARAMLIST) {
return new FirstOrderOperationCallExpression();
}
else if (firstChild.getExtraTokens().size() >= 1) {
if (firstChild.getChildren().stream().anyMatch(ast -> ast.getType() == EolParser.LAMBDAEXPR)) {
return new ComplexOperationCallExpression();
}
else return new FirstOrderOperationCallExpression();
}
}
return new NameExpression();
}
case EolParser.FORMAL: return new Parameter();
case EolParser.BLOCK: return new StatementBlock();
case EolParser.FEATURECALL: {
int parentType = cstParent.getType();
if (cst.hasChildren() && cst.getFirstChild().getType() == EolParser.PARAMETERS &&
(
(parentType != EolParser.ARROW && parentType != EolParser.POINT && parentType != EolParser.NAVIGATION) ||
(parentType == EolParser.ARROW || parentType == EolParser.POINT || parentType == EolParser.NAVIGATION) &&
cstParent.getFirstChild() == cst)
) {
return new OperationCallExpression(true);
}
else {
return new NameExpression();
}
}
case EolParser.STRING: return new StringLiteral();
case EolParser.INT: return new IntegerLiteral();
case EolParser.BOOLEAN: return new BooleanLiteral();
case EolParser.FLOAT: return new RealLiteral();
case EolParser.ASSIGNMENT: return new AssignmentStatement();
case EolParser.SPECIAL_ASSIGNMENT: return new SpecialAssignmentStatement();
case EolParser.EXPRESSIONINBRACKETS: return new ExpressionInBrackets();
case EolParser.VAR: return new VariableDeclaration();
case EolParser.NEW: {
if (cst.getFirstChild().getType() == EolParser.TYPE) {
return new NewInstanceExpression();
}
else {
return new VariableDeclaration();
}
}
case EolParser.IMPORT: return new Import();
case EolParser.OPERATOR: {
if (cst.getText().equals("=") && ((
(parentAst instanceof IfStatement || parentAst instanceof ForStatement || parentAst instanceof WhileStatement)
&& (cstParent.getFirstChild() != cst)) ||
parentAst instanceof StatementBlock /*||
"BLOCK".equals(parentAst.getText())*/)) {
return new AssignmentStatement();
}
else {
return operatorExpressionFactory.createOperatorExpression(cst);
}
}
case EolParser.TERNARY: return new TernaryExpression();
case EolParser.CONTINUE: return new ContinueStatement();
case EolParser.DELETE: return new DeleteStatement();
case EolParser.HELPERMETHOD: return new Operation();
case EolParser.RETURN: return new ReturnStatement();
case EolParser.ENUMERATION_VALUE: return new EnumerationLiteralExpression();
case EolParser.Annotation: return new SimpleAnnotation();
case EolParser.EXECUTABLEANNOTATION: return new ExecutableAnnotation();
case EolParser.ANNOTATIONBLOCK: return new AnnotationBlock();
case EolParser.COLLECTION: case EolParser.MAP: {
boolean isMap = cst.getType() == EolParser.MAP;
String typeName = cst.getText();
if (isMap && MapLiteralExpression.createMap(typeName) != null) {
return new MapLiteralExpression<>();
}
else if (CollectionLiteralExpression.createCollection(typeName) != null) {
return new CollectionLiteralExpression<>();
}
else {
getParseProblems().add(new ParseProblem("Unknown "+(isMap ? "collection" : "map")+" type: "+typeName, this));
}
}
case EolParser.TYPE: return new TypeExpression();
case EolParser.BREAK: return new BreakStatement(false);
case EolParser.BREAKALL: return new BreakStatement(true);
case EolParser.THROW: return new ThrowStatement();
case EolParser.ABORT: return new AbortStatement();
case EolParser.TRANSACTION: return new TransactionStatement();
case EolParser.MODELDECLARATION: return new ModelDeclaration();
case EolParser.MODELDECLARATIONPARAMETER: return new ModelDeclarationParameter();
default: return null;
}
}
@Override
protected Lexer createLexer(ANTLRInputStream inputStream) {
return new EolLexer(inputStream);
}
@Override
public EpsilonParser createParser(TokenStream tokenStream) {
return new EolParser(tokenStream);
}
@Override
public String getMainRule() {
return "eolModule";
}
@Override
public OperationList getDeclaredOperations() {
return declaredOperations;
}
protected HashMap<String, Class<?>> getImportConfiguration() {
HashMap<String, Class<?>> importConfiguration = new HashMap<>(4);
importConfiguration.put("eol", EolModule.class);
return importConfiguration;
}
@Override
public IEolCompilationContext getCompilationContext() {
if (compilationContext == null) {
compilationContext = new EolCompilationContext();
compilationContext.setModelDeclarations(getDeclaredModelDeclarations());
}
compilationContext.setRuntimeContext(getContext());
return compilationContext;
}
protected void prepareContext() throws EolRuntimeException {
IEolContext context = getContext();
EolSystem system = new EolSystem();
system.setContext(context);
context.setModule(this);
context.getFrameStack().putGlobal(
Variable.createReadOnlyVariable("null", null),
Variable.createReadOnlyVariable("System", system)
);
}
@Override
public List<Import> getImports() {
return imports;
}
@Override
public String toString() {
if (this.sourceFile != null) {
return this.sourceFile.getAbsolutePath();
}
else return super.toString();
}
@Override
public Set<ModelDeclaration> getModelDelcarations() {
if (modelDeclarations == null) {
modelDeclarations = new ListSet<>();
for (Import import_ : imports) {
if (import_.isLoaded() && import_.getModule() instanceof IEolModule){
modelDeclarations.addAll(((IEolModule)import_.getModule()).getModelDelcarations());
}
}
modelDeclarations.addAll(this.getDeclaredModelDeclarations());
}
return modelDeclarations;
}
@Override
public OperationList getOperations() {
return operations;
}
protected Collection<Import> getImportsByExtension(AST cst, String extension, Class<?> moduleImplClass) {
List<AST> importAsts = AstUtil.getChildren(cst, EolParser.IMPORT);
List<Import> imports = new ArrayList<>(importAsts.size());
for (AST importAst : importAsts) {
IModule module = null;
try {
module = (IModule) moduleImplClass.getDeclaredConstructor().newInstance();
if (module instanceof IEolModule) {
((IEolModule)module).setParentModule(this);
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
Import import_ = (Import) createAst(importAst, this);
if (!import_.getPath().endsWith("." + extension)) continue;
import_.setParentModule(this);
import_.setImportedModule(module);
if (sourceUri == null && sourceFile == null) {
import_.load(null);
} else if (sourceUri != null) {
import_.load(sourceUri);
} else {
import_.load(sourceFile.toURI());
}
if (!import_.isLoaded()) {
ParseProblem problem = new ParseProblem();
problem.setLine(import_.getRegion().getStart().getLine());
String reason;
if (!import_.isFound()) {
reason = "File " + import_.getPath() + " not found";
}
else {
reason = "File " + import_.getPath() + " contains errors: " + import_.getModule().getParseProblems();
}
problem.setReason(reason);
getParseProblems().add(problem);
}
imports.add(import_);
}
return imports;
}
protected void checkImports(AST cst) {
for (AST importAst : AstUtil.getChildren(cst, EolParser.IMPORT)) {
String importedFile = importAst.getFirstChild().getText();
boolean validExtension = false;
for (String extension : getImportConfiguration().keySet()) {
if (importedFile.endsWith("." + extension)) {
validExtension = true;
}
}
if (!validExtension) {
ParseProblem problem = new ParseProblem();
problem.setLine(importAst.getLine());
problem.setReason("Importing " + importAst.getFirstChild().getText() + " is not supported in this language");
problem.setSeverity(ParseProblem.WARNING);
getParseProblems().add(problem);
}
}
}
@Override
public List<ModelDeclaration> getDeclaredModelDeclarations() {
return declaredModelDeclarations;
}
@Override
public IEolModule getParentModule() {
return parent;
}
@Override
public void setParentModule(IEolModule parent) {
this.parent = parent;
}
@Override
public Object execute() throws EolRuntimeException {
IEolContext context = getContext();
prepareContext();
return context.getExecutorFactory().execute(this, context);
}
public Object executeImpl() throws EolRuntimeException {
IEolContext context = getContext();
return Return.getValue(context.getExecutorFactory().execute(main, context));
}
@Override
public List<ModuleMarker> compile() {
IEolCompilationContext context = getCompilationContext();
for (ModelDeclaration modelDeclaration : getDeclaredModelDeclarations()) {
modelDeclaration.compile(context);
}
for (Operation operation : getDeclaredOperations()) {
operation.compile(context);
}
if (main != null) {
main.compile(context);
}
return context.getMarkers();
}
@Override
public List<Statement> getPostOperationStatements() {
return postOperationStatements;
}
@Override
public StatementBlock getMain() {
return main;
}
public void setMain(StatementBlock main) {
this.main = main;
}
@Override
public IEolContext getContext() {
return context;
}
@Override
public void setContext(IEolContext context) {
if (context != null && !expectedContextType.isInstance(context)) {
throw new IllegalArgumentException(
"Invalid context type: expected "+expectedContextType.getName()
+ " but got "+context.getClass().getName()
);
}
if (this.context != context) {
this.context = context;
for (Import import_ : getImports()) {
import_.setContext(context);
}
}
}
/**
* Clear all cached results and type information, and all extended
* properties. Useful for rerunning the same EolModule with different sets
* of models, without having to parse it again.
*/
public void clearCache() {
for (Operation op : getOperations()) {
op.clearCache();
}
getContext().getExtendedProperties().clear();
}
}