diff --git a/cdlang/build.gradle b/cdlang/build.gradle index 6606747ca..9a7444807 100644 --- a/cdlang/build.gradle +++ b/cdlang/build.gradle @@ -108,9 +108,10 @@ tasks.register('prepareEmptyTemplatesDir') { tasks.withType(MCGenTask).configureEach { // Use an empty templates directory (as we do not override any) - tmplDir = emptyTemplatesDir - dependsOn tasks.named('prepareEmptyTemplatesDir') -} + //tmplDir = emptyTemplatesDir + //dependsOn tasks.named('prepareEmptyTemplatesDir') + groovyHook2 = file "$projectDir/src/main/groovy/gh2sysml.groovy" + } tasks.register('generateTestTrafos') {} if (withDSTLGen) { diff --git a/cdlang/src/main/configTemplates/cdlang/ResolveMany4IScope.ftl b/cdlang/src/main/configTemplates/cdlang/ResolveMany4IScope.ftl new file mode 100644 index 000000000..f6a9fe63e --- /dev/null +++ b/cdlang/src/main/configTemplates/cdlang/ResolveMany4IScope.ftl @@ -0,0 +1,24 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature( "simpleName", "symbolFullName")} + // skip resolution of the symbol, if the symbol has already been resolved in this scope instance + // during the current execution of the resolution algorithm + if (is${simpleName}SymbolsAlreadyResolved()) { + return new de.monticore.symboltable.SetAsListAdapter<>(); + } + + // (1) resolve down per scope first for intra-scope resolution + final List<${symbolFullName}> resolvedSymbols = this.resolve${simpleName}DownMany(foundSymbols, name, modifier, predicate); + foundSymbols = foundSymbols | resolvedSymbols.size() > 0; + + final String resolveCall = "resolveMany(\"" + name + "\", \"" + "${simpleName}Symbol" + + "\") in scope \"" + (isPresentName() ? getName() : "") + "\""; + Log.trace("START " + resolveCall + ". Found #" + resolvedSymbols.size() + " (intra-model down)", "Resolving"); + + // (2) continue with enclosingScope, if either no symbol has been found yet or this scope is non-shadowing + final List<${symbolFullName}> resolvedFromEnclosing = continue${simpleName}WithEnclosingScope(foundSymbols, name, modifier, predicate); + + // (3) unify results + resolvedSymbols.addAll(resolvedFromEnclosing); + Log.trace("END " + resolveCall + ". Found #" + resolvedSymbols.size(), "Resolving"); + + return resolvedSymbols; diff --git a/cdlang/src/main/groovy/gh2sysml.groovy b/cdlang/src/main/groovy/gh2sysml.groovy new file mode 100644 index 000000000..878d7f00e --- /dev/null +++ b/cdlang/src/main/groovy/gh2sysml.groovy @@ -0,0 +1,49 @@ +import de.monticore.cd4codebasis._ast.ASTCDMethod +import de.monticore.cd4codebasis._ast.ASTCDMethodBuilder +import de.monticore.cd4codebasis._ast.ASTCDParameter +import de.monticore.cdbasis._ast.ASTCDClass +import de.monticore.cdbasis._ast.ASTCDCompilationUnit +import de.monticore.cdinterfaceandenum._ast.ASTCDInterface +import de.monticore.generating.templateengine.GlobalExtensionManagement +import de.monticore.generating.templateengine.StringHookPoint +import de.monticore.generating.templateengine.TemplateHookPoint +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedNameBuilder +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedTypeBuilder +import de.monticore.types.mccollectiontypes._ast.ASTMCBasicTypeArgument +import de.monticore.types.mccollectiontypes._ast.ASTMCBasicTypeArgumentBuilder +import de.monticore.types.mccollectiontypes._ast.ASTMCGenericType +import de.monticore.types.mccollectiontypes._ast.ASTMCListTypeBuilder +import de.monticore.types.mccollectiontypes._ast.ASTMCTypeArgument + +/** + * This script replaces generated resolveMany methods to also look in related scopes + */ + +// glex, astGrammar, decoratedCD +GlobalExtensionManagement glex = args[0] as GlobalExtensionManagement +ASTCDCompilationUnit decoratedCD = args[2] as ASTCDCompilationUnit + +// TODO: Replace template is for some reason not transitive +// glex.replaceTemplate("_symboltable.iscope.Filter", new StringHookPoint(...)) +// is never invoked, b/c TC#getTemplateForwardings only ever looks for one template +for (ASTCDInterface cl : decoratedCD.getCDDefinition().getCDInterfacesList()) { + if (cl.getName().endsWith("Scope") && !cl.getName().endsWith("ArtifactScope")) { + for (ASTCDMethod m : cl.getCDMethodList()) { + if (m.getMCReturnType().isPresentMCType() && m.getMCReturnType().getMCType() instanceof ASTMCGenericType) { + ASTMCTypeArgument symbolType = ((ASTMCGenericType) m.getMCReturnType().getMCType()).getMCTypeArgumentList().get(0); + List nameList = ((ASTMCBasicTypeArgument) symbolType).getMCQualifiedType().getNameList(); + String last = nameList.get(nameList.size() - 1); + String actualName = last.substring(0, last.length() - 6); + + String potResolveDownMethodName = "resolve" + actualName + "Many" + + if (m.getName().equals(potResolveDownMethodName) && m.getCDParameterList().size() == 4 && !cl.getName().endsWith("GlobalScope")) { + //System.out.println("replacing " + potResolveDownMethodName) + + glex.replaceTemplate("cd2java.EmptyBody", m, new TemplateHookPoint("cdlang.ResolveMany4IScope", + actualName, symbolType.printType())) + } + } + } + } +} diff --git a/cdlang/src/main/java/de/monticore/cd4analysis/_symboltable/ICD4AnalysisScope.java b/cdlang/src/main/java/de/monticore/cd4analysis/_symboltable/ICD4AnalysisScope.java new file mode 100644 index 000000000..0afc38f08 --- /dev/null +++ b/cdlang/src/main/java/de/monticore/cd4analysis/_symboltable/ICD4AnalysisScope.java @@ -0,0 +1,47 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cd4analysis._symboltable; + +import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol; +import de.se_rwth.commons.logging.Log; + +import java.util.List; + +public interface ICD4AnalysisScope extends ICD4AnalysisScopeTOP { + + @Override + default public List resolveTypeMany(boolean foundSymbols, String name, + de.monticore.symboltable.modifiers.AccessModifier modifier, + java.util.function.Predicate predicate) { + /* generated by template _symboltable.iscope.ResolveMany4IScope*/ + + // skip resolution of the symbol, if the symbol has already been resolved in this scope instance + // during the current execution of the resolution algorithm + if (isTypeSymbolsAlreadyResolved()) { + return new de.monticore.symboltable.SetAsListAdapter<>(); + } + + // (1) resolve symbol locally. During this, the 'already resolved' flag is set to true, + // to prevent resolving cycles caused by cyclic symbol adapters + //setTypeSymbolsAlreadyResolved(true); + final List resolvedSymbols = this + .resolveTypeDownMany(foundSymbols, name, modifier, predicate); + foundSymbols = foundSymbols | resolvedSymbols.size() > 0; + //setTypeSymbolsAlreadyResolved(false); + + final String resolveCall = "resolveMany(\"" + name + "\", \"" + "TypeSymbol" + "\") in scope \"" + + (isPresentName() ? getName() : "") + "\""; + Log.trace("START " + resolveCall + ". Found #" + resolvedSymbols.size() + " (local)", + "Resolving"); + + // (2) continue with enclosingScope, if either no symbol has been found yet or this scope is non-shadowing + final List resolvedFromEnclosing = + continueTypeWithEnclosingScope(foundSymbols, name, modifier, predicate); + + // (3) unify results + resolvedSymbols.addAll(resolvedFromEnclosing); + Log.trace("END " + resolveCall + ". Found #" + resolvedSymbols.size(), "Resolving"); + + return resolvedSymbols; + } + +} diff --git a/cdlang/src/main/java/de/monticore/cd4code/_symboltable/ICD4CodeScope.java b/cdlang/src/main/java/de/monticore/cd4code/_symboltable/ICD4CodeScope.java new file mode 100644 index 000000000..dac49d4cd --- /dev/null +++ b/cdlang/src/main/java/de/monticore/cd4code/_symboltable/ICD4CodeScope.java @@ -0,0 +1,47 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cd4code._symboltable; + +import de.monticore.symbols.basicsymbols._symboltable.TypeSymbol; +import de.se_rwth.commons.logging.Log; + +import java.util.List; + +public interface ICD4CodeScope extends ICD4CodeScopeTOP { + + @Override + default public List resolveTypeMany(boolean foundSymbols, String name, + de.monticore.symboltable.modifiers.AccessModifier modifier, + java.util.function.Predicate predicate) { + /* generated by template _symboltable.iscope.ResolveMany4IScope*/ + + // skip resolution of the symbol, if the symbol has already been resolved in this scope instance + // during the current execution of the resolution algorithm + if (isTypeSymbolsAlreadyResolved()) { + return new de.monticore.symboltable.SetAsListAdapter<>(); + } + + // (1) resolve symbol locally. During this, the 'already resolved' flag is set to true, + // to prevent resolving cycles caused by cyclic symbol adapters + //setTypeSymbolsAlreadyResolved(true); + final List resolvedSymbols = this + .resolveTypeDownMany(foundSymbols, name, modifier, predicate); + foundSymbols = foundSymbols | resolvedSymbols.size() > 0; + //setTypeSymbolsAlreadyResolved(false); + + final String resolveCall = "resolveMany(\"" + name + "\", \"" + "TypeSymbol" + "\") in scope \"" + + (isPresentName() ? getName() : "") + "\""; + Log.trace("START " + resolveCall + ". Found #" + resolvedSymbols.size() + " (local)", + "Resolving"); + + // (2) continue with enclosingScope, if either no symbol has been found yet or this scope is non-shadowing + final List resolvedFromEnclosing = + continueTypeWithEnclosingScope(foundSymbols, name, modifier, predicate); + + // (3) unify results + resolvedSymbols.addAll(resolvedFromEnclosing); + Log.trace("END " + resolveCall + ". Found #" + resolvedSymbols.size(), "Resolving"); + + return resolvedSymbols; + } + +} diff --git a/cdlang/src/main/java/de/monticore/cdbasis/_symboltable/CDBasisSymbolTableCompleter.java b/cdlang/src/main/java/de/monticore/cdbasis/_symboltable/CDBasisSymbolTableCompleter.java index dbd93dd14..7f580ee20 100644 --- a/cdlang/src/main/java/de/monticore/cdbasis/_symboltable/CDBasisSymbolTableCompleter.java +++ b/cdlang/src/main/java/de/monticore/cdbasis/_symboltable/CDBasisSymbolTableCompleter.java @@ -92,6 +92,15 @@ protected void initialize_CDClass(ASTCDClass ast) { @Override public void visit(ASTCDAttribute node) { + // ======= merge test ======= + if (node.getName().equals("worker")) { + System.out.println(" CDBasisCompleter visits 'worker'"); + var res = TypeCheck3.symTypeFromAST(node.getMCType()); + System.out.println("calculated type: " + (res == null ? "null" : res.print()) + + " (is obscure? " + (res != null && res.isObscureType()) + ")"); + } + // ===================================== + final FieldSymbol symbol = node.getSymbol(); // Compute the !final! SymTypeExpression for the type of the field diff --git a/cdlang/src/test/java/de/monticore/cd4code/resolver/CDNoImportResolverTest.java b/cdlang/src/test/java/de/monticore/cd4code/resolver/CDNoImportResolverTest.java new file mode 100644 index 000000000..c759d3c64 --- /dev/null +++ b/cdlang/src/test/java/de/monticore/cd4code/resolver/CDNoImportResolverTest.java @@ -0,0 +1,119 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cd4code.resolver; + +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.cd4code.CD4CodeTestBasis; +import de.monticore.cd4code._symboltable.CD4CodeSymbolTableCompleter; +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; + +import java.io.IOException; +import java.util.Optional; + +import static org.junit.Assert.assertTrue; + +public class CDNoImportResolverTest extends CD4CodeTestBasis { + + String ModelA = """ + classdiagram CD2 { + package p2 { + package p3 { + class ClassA { + public int value; + } + } + class ClassC { + public p2.p3.ClassA value; + } + } + } + """; + + String ModelB = """ + classdiagram CD1 { + package p1 { + class ClassB { + private p2.p3.ClassA myAttribute; + } + } + } + """; + //With Import + String ModelBImport = """ + package p1; + import p2.p3.ClassA; + + classdiagram CD1 { + class ClassB { + private ClassA myAttribute; + } + } + """; + + @BeforeAll + public static void init() throws Exception { + CD4CodeMill.reset(); + CD4CodeMill.init(); + + } + + @Test + public void resolvingWithoutImport() throws IOException { + //parse Models + final Optional optA = CD4CodeMill.parser().parse_String(ModelA); + assertTrue(optA.isPresent()); + ASTCDCompilationUnit astA = optA.get(); + + Optional optB = CD4CodeMill.parser().parse_String(ModelB); + assertTrue(optB.isPresent()); + ASTCDCompilationUnit astB = optB.get(); + + //initalize the Scopes + CD4CodeMill.scopesGenitorDelegator().createFromAST(astA); + CD4CodeMill.scopesGenitorDelegator().createFromAST(astB); + + CD4CodeSymbolTableCompleter completerB = new CD4CodeSymbolTableCompleter(astA); + astA.accept(completerB.getTraverser()); + + CD4CodeSymbolTableCompleter completerA = new CD4CodeSymbolTableCompleter(astB); + astB.accept(completerA.getTraverser()); + + var resolved = astB.getEnclosingScope().resolveCDType("p2.p3.ClassA"); + + assertTrue("ClassA could not be found!", resolved.isPresent()); + + var siblingSymbol = astA.getEnclosingScope().resolveCDType("p2.ClassC"); + assertTrue(siblingSymbol.isPresent()); + + var res2 = siblingSymbol.get().getSpannedScope().resolveCDType("p3.ClassA"); + assertTrue(res2.isPresent()); + } + + @Test + public void resolvingWithImport() throws IOException { + //parse Models + final Optional optA = CD4CodeMill.parser().parse_String(ModelA); + assertTrue(optA.isPresent()); + ASTCDCompilationUnit astA = optA.get(); + + Optional optB = CD4CodeMill.parser().parse_String(ModelBImport); + assertTrue(optB.isPresent()); + ASTCDCompilationUnit astB = optB.get(); + + //initalize the Scopes + CD4CodeMill.scopesGenitorDelegator().createFromAST(astA); + CD4CodeMill.scopesGenitorDelegator().createFromAST(astB); + + CD4CodeSymbolTableCompleter completerB = new CD4CodeSymbolTableCompleter(astA); + astA.accept(completerB.getTraverser()); + + CD4CodeSymbolTableCompleter completerA = new CD4CodeSymbolTableCompleter(astB); + astB.accept(completerA.getTraverser()); + + var resolved = astB.getEnclosingScope().resolveCDType("p2.p3.ClassA"); + + assertTrue("ClassA could not be found!", resolved.isPresent()); + } + +} diff --git a/cdlang/src/test/java/de/monticore/cd4code/resolver/CDSupertypeResolvingTest.java b/cdlang/src/test/java/de/monticore/cd4code/resolver/CDSupertypeResolvingTest.java new file mode 100644 index 000000000..c188bc763 --- /dev/null +++ b/cdlang/src/test/java/de/monticore/cd4code/resolver/CDSupertypeResolvingTest.java @@ -0,0 +1,225 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.cd4code.resolver; + +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.cd4code.CD4CodeTestBasis; +import de.monticore.cd4codebasis._ast.ASTCDClass; +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; + +import java.io.IOException; +import java.util.Optional; + +import static org.junit.Assert.*; + +public class CDSupertypeResolvingTest extends CD4CodeTestBasis { + + String SimpleClass = """ + classdiagram CD1{ + class SimpleClassA extends SimpleClassB { + public int valueA; + public int[] fieldA; + } + class SimpleClassB { + public int valueB; + public int[] fieldB; + } + } + """; + + String SimpleInterfaceClass = """ + classdiagram CD2{ + class InterfaceClassA implements I{ + } + interface I{ + } + } + """; + + String SimpleCascadingClass = """ + classdiagram CD3{ + class SimpleClassA extends SimpleClassB { + public int valueA; + public int[] fieldA; + } + class SimpleClassB extends SimpleClassC{ + public int valueB; + public int[] fieldB; + } + class SimpleClassC + { + public int valueC; + } + } + """; + + String PackageClass = """ + classdiagram CDPackage{ + class ClassA extends p2.ClassB { + public int valueA; + } + package p1 { + class ClassB { + public int valueB; + } + } + } + """; + + String PackageClass2 = """ + classdiagram CDPackage2{ + package p1 { + class ClassA extends ClassB { + public int valueA; + } + } + class ClassB { + public int valueB; + } + } + """; + + String PackageClass3 = """ + classdiagram CDPackage3{ + package p1 { + class ClassA extends ClassB { + public int valueA; + } + class ClassB { + public int valueB; + } + } + } + """; + + @BeforeAll + public static void init() throws Exception { + CD4CodeMill.reset(); + CD4CodeMill.init(); + } + + @Test + //can SimpleClassB be resolved without using a symbol table completer? + public void SimpleClassTest() throws IOException { + + final Optional optSimple = CD4CodeMill.parser().parse_String(SimpleClass); + assertTrue(optSimple.isPresent()); + ASTCDCompilationUnit astSimpleClass = optSimple.get(); + + // Build the symbol table using only the basic scopeGenitor (deliberately omitting the completer) + CD4CodeMill.scopesGenitorDelegator().createFromAST(astSimpleClass); + ASTCDClass simpleClassA = (ASTCDClass) astSimpleClass.getCDDefinition().getCDClassesList() + .getFirst(); + + // can the class be resolved without using the symboltable completer + var resolved = simpleClassA.getEnclosingScope().resolveCDType("SimpleClassB"); + assertTrue("SuperType B can be resolved without the symbol table completer.", resolved + .isPresent()); + + // can an attribute be resolved without using the symboltable completer? it should not work not but who knows + var resolvedVar = simpleClassA.getEnclosingScope().resolveVariable("valueB"); + assertFalse( + "Attribute valueB from SuperClassB was unexpectedly resolved without the symbol table completer", + resolvedVar.isPresent()); + } + + @Test + public void SimpleInterfaceTest() throws IOException { + final Optional optSimple = CD4CodeMill.parser().parse_String( + SimpleInterfaceClass); + assertTrue(optSimple.isPresent()); + ASTCDCompilationUnit astSimpleClass = optSimple.get(); + + CD4CodeMill.scopesGenitorDelegator().createFromAST(astSimpleClass); + ASTCDClass interfaceClassA = (ASTCDClass) astSimpleClass.getCDDefinition().getCDClassesList() + .stream().filter(c -> c.getName().equals("InterfaceClassA")).findFirst().get(); + + var resolvedInterface = interfaceClassA.getEnclosingScope().resolveCDType("I"); + assertTrue("The implemented interface I should be resolvable even without the completer.", + resolvedInterface.isPresent()); + } + + @Test + public void SimpleCascadingTest() throws IOException { + final Optional optSimple = CD4CodeMill.parser().parse_String( + SimpleCascadingClass); + assertTrue(optSimple.isPresent()); + ASTCDCompilationUnit astCascadingClass = optSimple.get(); + + CD4CodeMill.scopesGenitorDelegator().createFromAST(astCascadingClass); + ASTCDClass cascadingClass = (ASTCDClass) astCascadingClass.getCDDefinition().getCDClassesList() + .getFirst(); + + var resolvedClassB = cascadingClass.getEnclosingScope().resolveCDType("SimpleClassB"); + assertTrue("The SuperClass ClassB should be resolvable even without the completer.", + resolvedClassB.isPresent()); + + var resolvedClassC = cascadingClass.getEnclosingScope().resolveCDType("SimpleClassC"); + assertTrue("The SuperClass ClassC should be resolvable even without the completer.", + resolvedClassC.isPresent()); + } + + // test with packages + @Test + public void packageClassTest() throws IOException { + final Optional opt = CD4CodeMill.parser().parse_String(PackageClass); + assertTrue(opt.isPresent()); + ASTCDCompilationUnit ast = opt.get(); + + CD4CodeMill.scopesGenitorDelegator().createFromAST(ast); + ASTCDClass packageClassA = (ASTCDClass) ast.getCDDefinition().getCDClassesList().stream() + .filter(c -> c.getName().equals("ClassA")).findFirst().get(); + + var resolved = packageClassA.getEnclosingScope().resolveCDType("p1.ClassB"); + assertTrue("ClassA should be able to resolve ClassB from package p1.", resolved.isPresent()); + assertEquals(resolved.get().getFullName(), "CDPackage.p1.ClassB"); + } + + @Test + public void packageTest2() throws IOException { + final Optional opt = CD4CodeMill.parser().parse_String(PackageClass2); + assertTrue(opt.isPresent()); + ASTCDCompilationUnit ast = opt.get(); + + CD4CodeMill.scopesGenitorDelegator().createFromAST(ast); + ASTCDClass classA = (ASTCDClass) ast.getCDDefinition().getCDClassesList().stream().filter(c -> c + .getName().equals("ClassA")).findFirst().get(); + + var resolved = classA.getEnclosingScope().resolveCDType("ClassB"); + + assertTrue("ClassA inside package p1 should be able to resolve ClassB outside the package.", + resolved.isPresent()); + assertEquals("CDPackage2.ClassB", resolved.get().getFullName()); + } + + @Test + public void packageTest3() throws IOException { + final Optional opt = CD4CodeMill.parser().parse_String(PackageClass3); + assertTrue(opt.isPresent()); + ASTCDCompilationUnit ast = opt.get(); + + CD4CodeMill.scopesGenitorDelegator().createFromAST(ast); + ASTCDClass classA = (ASTCDClass) ast.getCDDefinition().getCDClassesList().stream().filter(c -> c + .getName().equals("ClassA")).findFirst().get(); + + var resolved = classA.getEnclosingScope().resolveCDType("p1.ClassB"); + + assertTrue("ClassA inside package p1 should be able to resolve ClassB inside package p1.", + resolved.isPresent()); + assertEquals("CDPackage3.p1.ClassB", resolved.get().getFullName()); + } + + //--------------Tests with packages and symboltable completer----------------------------------- + + @Test + public void packageSTCTest() throws IOException { + final Optional opt = CD4CodeMill.parser().parse_String(PackageClass3); + assertTrue(opt.isPresent()); + ASTCDCompilationUnit ast = opt.get(); + + CD4CodeMill.scopesGenitorDelegator().createFromAST(ast); + //TODO + } + +}