From 29e1eb56808e6644d77b3ae80d15e39e4cd874e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20L=C3=BCpges?= Date: Fri, 13 Oct 2023 09:20:22 +0200 Subject: [PATCH] DSL-specific tagging --- .gitlab-ci.yml | 8 +- gradle.properties | 1 + monticore-generator/build.gradle | 3 + .../src/main/java/de/monticore/MCTask.java | 44 ++- .../de/monticore/MontiCoreConfiguration.java | 18 ++ .../java/de/monticore/MontiCoreScript.java | 65 ++++- .../java/de/monticore/cli/MontiCoreTool.java | 7 + .../cd2java/_tagging/CDTaggingDecorator.java | 40 +++ .../_tagging/MC2CDTaggingTranslation.java | 173 ++++++++++++ .../TagConformsToSchemaCoCoDecorator.java | 142 ++++++++++ .../cd2java/_tagging/TaggerDecorator.java | 265 ++++++++++++++++++ .../cd2java/_tagging/TaggingConstants.java | 10 + .../de/monticore/dstlgen/DSTLGenScript.java | 3 +- .../tagging/TagDefinitionDerivVisitor.java | 124 ++++++++ .../de/monticore/tagging/TagGenerator.java | 123 ++++++++ .../tagging/TagLanguageOverrideVisitor.java | 107 +++++++ .../tagging/TagSchemaDerivVisitor.java | 64 +++++ .../de/monticore/monticore_emf.groovy | 9 + .../de/monticore/monticore_noreports.groovy | 9 + .../de/monticore/monticore_standard.groovy | 9 + .../src/main/resources/tagging/coco/Check.ftl | 18 ++ .../resources/tagging/coco/Constructor.ftl | 3 + .../resources/tagging/coco/GetTraverser.ftl | 6 + .../tagging/coco/IdentifierTraverser.ftl | 15 + .../tagging/coco/VisitorConstructor.ftl | 9 + .../main/resources/tagging/itagger/AddTag.ftl | 3 + .../resources/tagging/itagger/GetTags.ftl | 3 + .../resources/tagging/itagger/RemoveTag.ftl | 3 + .../main/resources/tagging/tagger/AddTag.ftl | 17 ++ .../tagging/tagger/FindTargetsBy.ftl | 5 + .../tagging/tagger/GetArtifactScope.ftl | 8 + .../resources/tagging/tagger/GetInstance.ftl | 6 + .../main/resources/tagging/tagger/GetTags.ftl | 48 ++++ .../resources/tagging/tagger/IsIdentified.ftl | 6 + .../resources/tagging/tagger/RemoveTag.ftl | 58 ++++ .../travchecker/TravCheckerConstructor.ftl | 12 + monticore-grammar/build.gradle | 78 +++++- .../de/monticore/tagging/TagSchema.mc4 | 72 +++++ .../grammars/de/monticore/tagging/Tags.mc4 | 34 +++ .../de/monticore/tagging/AbstractTagger.java | 53 ++++ .../conforms/TagConformanceChecker.java | 186 ++++++++++++ .../conforms/TagConformsToSchemaCoCo.java | 100 +++++++ .../monticore/tagging/conforms/TagData.java | 26 ++ .../tagging/conforms/TagDataVisitor.java | 91 ++++++ .../tagging/conforms/TagSchemaData.java | 81 ++++++ .../tags/_ast/ASTModelElementIdentifier.java | 10 + .../tagschema/TagSchemaAfterParseTrafo.java | 116 ++++++++ .../_symboltable/TagSchemaScopesGenitor.java | 73 +++++ .../01.experiments/tagging/build.gradle | 80 ++++++ .../01.experiments/tagging/gradle.properties | 4 + .../tagging/src/main/grammars/Automata.mc4 | 42 +++ .../grammars/de/monticore/fqn/FQNAutomata.mc4 | 45 +++ .../de/monticore/fqn/FQNEnhancedAutomata.mc4 | 27 ++ .../FQNEnhancedAutomataTagDefinitionHC.mc4 | 7 + .../src/test/java/AutomataSchemaTest.java | 214 ++++++++++++++ .../java/FQNEnhancedAutomataSchemaTest.java | 116 ++++++++ .../src/test/java/FQNInheritedTagTest.java | 217 ++++++++++++++ .../tagging/src/test/java/FQNTagTest.java | 180 ++++++++++++ .../src/test/java/InvalidTagSchemaTest.java | 38 +++ .../test/java/TagSchemaSerializationTest.java | 80 ++++++ .../tagging/src/test/java/TagTest.java | 179 ++++++++++++ .../tagging/src/test/java/util/TestUtil.java | 25 ++ .../src/test/resources/models/Enhanced.aut | 35 +++ .../src/test/resources/models/Enhanced.tags | 37 +++ .../src/test/resources/models/Invalid.tags | 11 + .../resources/models/InvalidComplexTags1.tags | 12 + .../resources/models/InvalidComplexTags2.tags | 13 + .../resources/models/InvalidComplexTags3.tags | 12 + .../resources/models/InvalidComplexTags4.tags | 12 + .../resources/models/InvalidComplexTags5.tags | 12 + .../resources/models/InvalidComplexTags6.tags | 12 + .../resources/models/InvalidComplexTags7.tags | 12 + .../models/InvalidEnhancedTags1.tags | 7 + .../resources/models/InvalidPatternTags1.tags | 7 + .../resources/models/InvalidPatternTags2.tags | 9 + .../test/resources/models/InvalidTags1.tags | 8 + .../test/resources/models/InvalidTags2.tags | 8 + .../test/resources/models/InvalidTags3.tags | 8 + .../test/resources/models/InvalidTags4.tags | 8 + .../test/resources/models/InvalidTags5.tags | 8 + .../test/resources/models/InvalidTags6.tags | 10 + .../test/resources/models/InvalidTags7.tags | 7 + .../test/resources/models/InvalidTags8.tags | 7 + .../resources/models/InvalidTagsPrivate.tags | 8 + .../resources/models/NonConformingTags.tags | 16 ++ .../src/test/resources/models/Simple.aut | 26 ++ .../src/test/resources/models/Simple.tags | 34 +++ .../resources/schema/AutomataSchema.tagschema | 26 ++ .../schema/EnhancedAutomataSchema.tagschema | 18 ++ .../schema/invalid/InvalidTagSchema.tagschema | 6 + settings.gradle | 3 + 91 files changed, 3998 insertions(+), 12 deletions(-) create mode 100644 monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/CDTaggingDecorator.java create mode 100644 monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/MC2CDTaggingTranslation.java create mode 100644 monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TagConformsToSchemaCoCoDecorator.java create mode 100644 monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TaggerDecorator.java create mode 100644 monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TaggingConstants.java create mode 100644 monticore-generator/src/main/java/de/monticore/tagging/TagDefinitionDerivVisitor.java create mode 100644 monticore-generator/src/main/java/de/monticore/tagging/TagGenerator.java create mode 100644 monticore-generator/src/main/java/de/monticore/tagging/TagLanguageOverrideVisitor.java create mode 100644 monticore-generator/src/main/java/de/monticore/tagging/TagSchemaDerivVisitor.java create mode 100644 monticore-generator/src/main/resources/tagging/coco/Check.ftl create mode 100644 monticore-generator/src/main/resources/tagging/coco/Constructor.ftl create mode 100644 monticore-generator/src/main/resources/tagging/coco/GetTraverser.ftl create mode 100644 monticore-generator/src/main/resources/tagging/coco/IdentifierTraverser.ftl create mode 100644 monticore-generator/src/main/resources/tagging/coco/VisitorConstructor.ftl create mode 100644 monticore-generator/src/main/resources/tagging/itagger/AddTag.ftl create mode 100644 monticore-generator/src/main/resources/tagging/itagger/GetTags.ftl create mode 100644 monticore-generator/src/main/resources/tagging/itagger/RemoveTag.ftl create mode 100644 monticore-generator/src/main/resources/tagging/tagger/AddTag.ftl create mode 100644 monticore-generator/src/main/resources/tagging/tagger/FindTargetsBy.ftl create mode 100644 monticore-generator/src/main/resources/tagging/tagger/GetArtifactScope.ftl create mode 100644 monticore-generator/src/main/resources/tagging/tagger/GetInstance.ftl create mode 100644 monticore-generator/src/main/resources/tagging/tagger/GetTags.ftl create mode 100644 monticore-generator/src/main/resources/tagging/tagger/IsIdentified.ftl create mode 100644 monticore-generator/src/main/resources/tagging/tagger/RemoveTag.ftl create mode 100644 monticore-generator/src/main/resources/tagging/travchecker/TravCheckerConstructor.ftl create mode 100644 monticore-grammar/src/main/grammars/de/monticore/tagging/TagSchema.mc4 create mode 100644 monticore-grammar/src/main/grammars/de/monticore/tagging/Tags.mc4 create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/AbstractTagger.java create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagConformanceChecker.java create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagConformsToSchemaCoCo.java create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagData.java create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagDataVisitor.java create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagSchemaData.java create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/tags/_ast/ASTModelElementIdentifier.java create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/tagschema/TagSchemaAfterParseTrafo.java create mode 100644 monticore-grammar/src/main/java/de/monticore/tagging/tagschema/_symboltable/TagSchemaScopesGenitor.java create mode 100644 monticore-test/01.experiments/tagging/build.gradle create mode 100644 monticore-test/01.experiments/tagging/gradle.properties create mode 100644 monticore-test/01.experiments/tagging/src/main/grammars/Automata.mc4 create mode 100644 monticore-test/01.experiments/tagging/src/main/grammars/de/monticore/fqn/FQNAutomata.mc4 create mode 100644 monticore-test/01.experiments/tagging/src/main/grammars/de/monticore/fqn/FQNEnhancedAutomata.mc4 create mode 100644 monticore-test/01.experiments/tagging/src/main/grammarsHC/de/monticore/fqn/FQNEnhancedAutomataTagDefinitionHC.mc4 create mode 100644 monticore-test/01.experiments/tagging/src/test/java/AutomataSchemaTest.java create mode 100644 monticore-test/01.experiments/tagging/src/test/java/FQNEnhancedAutomataSchemaTest.java create mode 100644 monticore-test/01.experiments/tagging/src/test/java/FQNInheritedTagTest.java create mode 100644 monticore-test/01.experiments/tagging/src/test/java/FQNTagTest.java create mode 100644 monticore-test/01.experiments/tagging/src/test/java/InvalidTagSchemaTest.java create mode 100644 monticore-test/01.experiments/tagging/src/test/java/TagSchemaSerializationTest.java create mode 100644 monticore-test/01.experiments/tagging/src/test/java/TagTest.java create mode 100644 monticore-test/01.experiments/tagging/src/test/java/util/TestUtil.java create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/Enhanced.aut create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/Enhanced.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/Invalid.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags1.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags2.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags3.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags4.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags5.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags6.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags7.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidEnhancedTags1.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidPatternTags1.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidPatternTags2.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags1.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags2.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags3.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags4.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags5.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags6.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags7.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags8.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTagsPrivate.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/NonConformingTags.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/Simple.aut create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/models/Simple.tags create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/schema/AutomataSchema.tagschema create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/schema/EnhancedAutomataSchema.tagschema create mode 100644 monticore-test/01.experiments/tagging/src/test/resources/schema/invalid/InvalidTagSchema.tagschema diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 35af322434..fb59997993 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,7 +34,7 @@ build: stage: build script: - "cd monticore-generator && gradle build $GRADLE_OPTS -Pci -i" - - "cd .. && gradle buildMC $GRADLE_OPTS -Pci -PgenTR=true -PgenEMF=true -i" + - "cd .. && gradle buildMC $GRADLE_OPTS -Pci -PgenTR=true -PgenEMF=true -PgenTagging=true -i" artifacts: paths: - ".gradle" @@ -121,7 +121,7 @@ test_grammar_it: dependencies: - build script: - - "gradle -p monticore-test/monticore-grammar-it build -PgenEMF=true $GRADLE_OPTS -Pci" + - "gradle -p monticore-test/monticore-grammar-it build $GRADLE_OPTS -PgenEMF=true -Pci" only: - merge_requests - branches @@ -133,7 +133,7 @@ test_montitrans: dependencies: - build script: - - "gradle -p monticore-test/montitrans build $GRADLE_OPTS -PgenTR=true -PgenEMF=true -Pci" + - "gradle -p monticore-test/montitrans build $GRADLE_OPTS -PgenTR=true -PgenEMF=true -PgenTagging=true -Pci" only: - merge_requests - branches @@ -145,7 +145,7 @@ deploy: dependencies: - build script: - - "gradle deployMC -PmavenPassword=$password -PmavenUser=$username $GRADLE_OPTS -PgenTR=true -PgenEMF=true -Pci" + - "gradle deployMC -PmavenPassword=$password -PmavenUser=$username $GRADLE_OPTS -PgenTR=true -PgenEMF=true -PgenTagging=true -Pci" only: - dev except: diff --git a/gradle.properties b/gradle.properties index fb6659f75c..16bff8a4a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,7 @@ useLocalRepo=false showTestOutput=false genTR=false genEMF=false +genTagging=false org.gradle.jvmargs=-Xms3072m -Dfile.encoding=UTF-8 diff --git a/monticore-generator/build.gradle b/monticore-generator/build.gradle index 02af8594a2..f0e771934f 100644 --- a/monticore-generator/build.gradle +++ b/monticore-generator/build.gradle @@ -64,6 +64,9 @@ dependencies { testImplementation 'com.github.javaparser:javaparser-symbol-solver-core:3.24.2' testImplementation "junit:junit:$junit_version" testImplementation 'org.mockito:mockito-core:4.5.1' + + implementation "de.monticore:tagging:7.6.0-SNAPSHOT" // TODO: Remove both with next release, once the tagging grammars have been added to monticore-grammar + grammar "de.monticore:tagging:7.6.0-SNAPSHOT:grammars" } sourceSets { diff --git a/monticore-generator/src/main/java/de/monticore/MCTask.java b/monticore-generator/src/main/java/de/monticore/MCTask.java index 526a1157b4..fa2e205cf2 100644 --- a/monticore-generator/src/main/java/de/monticore/MCTask.java +++ b/monticore-generator/src/main/java/de/monticore/MCTask.java @@ -4,8 +4,11 @@ import com.google.common.collect.Iterables; import de.monticore.cli.MontiCoreTool; +import de.monticore.codegen.cd2java._tagging.TaggingConstants; import de.monticore.mcbasics.MCBasicsMill; import de.monticore.dstlgen.util.DSTLPathUtil; +import de.monticore.symboltable.serialization.json.*; +import de.monticore.tagging.TagGenerator; import de.se_rwth.commons.logging.Finding; import de.se_rwth.commons.logging.Log; import org.apache.commons.io.FileUtils; @@ -119,7 +122,10 @@ public MCTask() { // Whether the grammar is a TR grammar and transformation related artifacts should be generated public boolean isDSTL = false; - + + // Whether tagging related artifacts should be generated (the grammar must be a tagdef or tagschema grammar) + public boolean genTag = false; + @OutputDirectory public DirectoryProperty getOutputDir() { return outputDir; @@ -243,6 +249,11 @@ public boolean isDSTL() { return isDSTL; } + @Input + public boolean getGenTag() { + return genTag; + } + public void handcodedPath(String... paths) { getHandcodedPath().addAll(List.of(paths)); } @@ -380,6 +391,8 @@ protected String[] getParameters(Function printPath){ } params.add("-genDST"); params.add(Boolean.toString(isDSTL)); + params.add("-genTag"); + params.add(Boolean.toString(genTag)); if (configTemplate != null) { String cfgTemplateStr = configTemplate.toString(); params.add("-ct"); @@ -465,7 +478,7 @@ public boolean incCheck(String grammar) throws IOException { String base = this.getProject().getProjectDir().toPath().toAbsolutePath().toString(); return IncChecker.incCheck(inout, grammar, getLogger(), "mc4", base); } -/** + /** * Returns the path to the TR grammar. * Please ensure that the outputDir is previously set * @param originalGrammar the original grammar file @@ -479,6 +492,33 @@ public File getTRFile(File originalGrammar, @Nullable File outputDir) { modelPath.isEmpty() ? List.of(getProject().getLayout().getProjectDirectory().file("src/main/grammars").toString()) : modelPath, originalGrammar).toString()); } + /** + * Returns the path to the Tag Def grammar. + * Please ensure that the outputDir is previously set + * @param originalGrammar the original grammar file + * @return the tag def grammar + */ + public File getTagDefinitionFile(File originalGrammar, @Nullable File outputDir) { + return new File((outputDir == null ? this.outputDir.get().getAsFile() : outputDir.toString()) + + "/" + + TagGenerator.getTagGrammar( + modelPath.isEmpty() ? List.of(getProject().getLayout().getProjectDirectory().file("src/main/grammars").toString()) : modelPath, + originalGrammar, TaggingConstants.TAGDEFINITION_SUFFIX).toString()); + } + /** + * Returns the path to the Tag Def grammar. + * Please ensure that the outputDir is previously set + * @param originalGrammar the original grammar file + * @return the tag def grammar + */ + public File getTagSchemaFile(File originalGrammar, @Nullable File outputDir) { + return new File((outputDir == null ? this.outputDir.get().getAsFile() : outputDir.toString()) + + "/" + + TagGenerator.getTagGrammar( + modelPath.isEmpty() ? List.of(getProject().getLayout().getProjectDirectory().file("src/main/grammars").toString()) : modelPath, + originalGrammar, TaggingConstants.TAGSCHEMA_SUFFIX).toString()); + } + protected File fromBasePath(String filePath) { File file = new File(filePath); return !file.isAbsolute() diff --git a/monticore-generator/src/main/java/de/monticore/MontiCoreConfiguration.java b/monticore-generator/src/main/java/de/monticore/MontiCoreConfiguration.java index 2fac1497a8..66cd70c40c 100644 --- a/monticore-generator/src/main/java/de/monticore/MontiCoreConfiguration.java +++ b/monticore-generator/src/main/java/de/monticore/MontiCoreConfiguration.java @@ -96,6 +96,7 @@ public final class MontiCoreConfiguration implements Configuration { public static final String REPORT_LONG = "report"; public static final String REPORT_BASE_LONG = "report_base"; public static final String GENDST_LONG = "genDST"; + public static final String GENTAG_LONG = "genTag"; public static final String HELP_LONG = "help"; protected final CommandLine cmdConfig; @@ -389,6 +390,15 @@ public Optional getGenDST() { return getAsBoolean(GENDST_LONG); } + /** + * Getter for the optional tagging generation. + * + * @return Optional boolean for the tagging generation + */ + public Optional getGenTag() { + return getAsBoolean(GENTAG_LONG); + } + /** * @param files as String names to convert * @return list of files by creating file objects from the Strings @@ -432,6 +442,14 @@ public JsonElement getStatJson() { result.putMember(GENDST_LONG, dstlGen); } + // GenTag + { + JsonElement tagGen = this.getGenTag() + .map(p-> (JsonElement) new JsonBoolean(p)) + .orElse(new JsonNull()); + result.putMember(GENTAG_LONG, tagGen); + } + // Custom Script set? result.putMember(SCRIPT_LONG, new JsonBoolean(cmdConfig.hasOption(SCRIPT))); diff --git a/monticore-generator/src/main/java/de/monticore/MontiCoreScript.java b/monticore-generator/src/main/java/de/monticore/MontiCoreScript.java index f51e2d88bd..538d2c724c 100644 --- a/monticore-generator/src/main/java/de/monticore/MontiCoreScript.java +++ b/monticore-generator/src/main/java/de/monticore/MontiCoreScript.java @@ -60,6 +60,7 @@ import de.monticore.codegen.cd2java._symboltable.serialization.Symbols2JsonDecorator; import de.monticore.codegen.cd2java._symboltable.symbol.*; import de.monticore.codegen.cd2java._symboltable.symbol.symbolsurrogatemutator.MandatoryMutatorSymbolSurrogateDecorator; +import de.monticore.codegen.cd2java._tagging.*; import de.monticore.codegen.cd2java._visitor.*; import de.monticore.codegen.cd2java.cli.CDCLIDecorator; import de.monticore.codegen.cd2java.cli.CLIDecorator; @@ -109,6 +110,7 @@ import de.monticore.io.paths.MCPath; import de.monticore.symbols.basicsymbols.BasicSymbolsMill; import de.monticore.symbols.basicsymbols._symboltable.DiagramSymbol; +import de.monticore.tagging.TagGenerator; import de.se_rwth.commons.Joiners; import de.se_rwth.commons.Names; import de.se_rwth.commons.configuration.Configuration; @@ -694,7 +696,14 @@ public ASTCDCompilationUnit decorateCD(GlobalExtensionManagement glex, ICD4Analy return decoratedCD; } - public void decorateForSymbolTablePackage(GlobalExtensionManagement glex, ICD4AnalysisScope cdScope, + public ASTCDCompilationUnit decorateTagCD(GlobalExtensionManagement glex, ICD4AnalysisScope cdScope, + List cds, MCPath handCodedPath, ASTCDCompilationUnit decoratedCD, ASTMCGrammar astGrammar) { + decorateTagging(glex, cdScope, cds.get(0), decoratedCD, handCodedPath, astGrammar); + return decoratedCD; + } + + + public void decorateForSymbolTablePackage(GlobalExtensionManagement glex, ICD4AnalysisScope cdScope, ASTCDCompilationUnit astClassDiagram, ASTCDCompilationUnit symbolClassDiagramm, ASTCDCompilationUnit scopeClassDiagramm, ASTCDCompilationUnit decoratedCD, MCPath handCodedPath) { @@ -895,6 +904,52 @@ protected void generateAuxiliary(ASTCDCompilationUnit cd, ASTCDCompilationUnit d cdAuxiliaryDecorator.decorate(cd, decoratedCD); } + public void decorateTagging(GlobalExtensionManagement glex, ICD4AnalysisScope cdScope, + ASTCDCompilationUnit cd, ASTCDCompilationUnit decoratedCD, + MCPath handCodedPath, ASTMCGrammar astTagGrammar){ + generateTagging(cd, decoratedCD, glex, handCodedPath, astTagGrammar); + } + + protected void generateTagging(ASTCDCompilationUnit cd, ASTCDCompilationUnit decoratedCD, + GlobalExtensionManagement glex, MCPath handCodedPath, ASTMCGrammar astTagGrammar) { + ASTMCGrammar originalGrammar = getOriginalGrammarFromTagGrammar(astTagGrammar).get(); + + SymbolTableService symbolTableService = new SymbolTableService(cd); + VisitorService visitorService = new VisitorService(cd); + ParserService parserService = new ParserService(cd); + AbstractService abstractService = new AbstractService<>(cd); + + TaggerDecorator taggerDecorator = new TaggerDecorator(glex, abstractService, visitorService, originalGrammar); + TagConformsToSchemaCoCoDecorator tagCoCoDecorator = new TagConformsToSchemaCoCoDecorator(glex, abstractService, visitorService, originalGrammar); + CDTaggingDecorator taggingDecorator = new CDTaggingDecorator(glex, taggerDecorator, tagCoCoDecorator); + + taggingDecorator.decorate(cd, decoratedCD); + } + + protected ASTCDCompilationUnit deriveTaggingCD(ASTMCGrammar astGrammar, ICD4AnalysisGlobalScope cdScope ) { + return new MC2CDTaggingTranslation(cdScope).apply(getOriginalGrammarFromTagGrammar(astGrammar).get()); + } + + // Find & load the grammar A for given ATagDefinition grammar + protected Optional getOriginalGrammarFromTagGrammar(ASTMCGrammar astTagGrammar) { + String originalGrammarName; + if (astTagGrammar.getSymbol().getName().endsWith(TaggingConstants.TAGDEFINITION_SUFFIX)) { + originalGrammarName = astTagGrammar.getSymbol().getFullName().substring(0, astTagGrammar.getSymbol().getFullName().length() - TaggingConstants.TAGDEFINITION_SUFFIX.length()); + }else if (astTagGrammar.getSymbol().getName().endsWith(TaggingConstants.TAGSCHEMA_SUFFIX)) { + originalGrammarName = astTagGrammar.getSymbol().getFullName().substring(0, astTagGrammar.getSymbol().getFullName().length() - TaggingConstants.TAGSCHEMA_SUFFIX.length()); + }else{ + Log.error("0xA1018 Unable to generate Tagging infrastructure on non TagSchema/TagDef Grammar:" + astTagGrammar.getSymbol().getFullName()); + return Optional.empty(); + } + Optional originalGrammarOpt = GrammarFamilyMill.globalScope().resolveMCGrammar(originalGrammarName); + if (originalGrammarOpt.isEmpty()){ + Log.error("0xA1026 Failed to resolve original grammar " + originalGrammarName + " of tag grammar " + astTagGrammar.getSymbol().getFullName()); + return Optional.empty(); + } + return Optional.of(originalGrammarOpt.get().getAstNode()); + } + + public void decoratePrettyPrinter(GlobalExtensionManagement glex, ASTCDCompilationUnit input, ICD4AnalysisScope cdScope, ASTCDCompilationUnit prettyPrintCD, ASTCDCompilationUnit decoratedCD, MCPath handCodedPath) { @@ -1306,6 +1361,13 @@ public void generateDSTInfrastructure(ASTMCGrammar astTRGrammar, File out, MCPat dstlgenUtil.generateTFGenToolClass(astGrammar, dstlGenerator, modelPathHC); } + /** + * Generate the TagSchema and TagDefinition grammars and output them as mc4 files + */ + public void generateTaggingLanguages(ASTMCGrammar astGrammar, File outputDirectory, MCPath modelPathHC) throws IOException { + TagGenerator.generateTaggingLanguages(astGrammar, outputDirectory, modelPathHC); + } + /** * Instantiates the glex and initializes it with all available default * options based on the current configuration. @@ -1408,6 +1470,7 @@ protected void doRun(String script, Configuration configuration) { builder.addVariable(MODELPATH_LONG, modelPath); builder.addVariable(HANDCODEDMODELPATH_LONG, handcodedModelPath); builder.addVariable(GENDST_LONG, mcConfig.getGenDST().orElse(false)); // no transformation infrastructure generation by default + builder.addVariable(GENTAG_LONG, mcConfig.getGenTag().orElse(false)); // no tagging generation by default builder.addVariable(OUT_LONG, mcConfig.getOut()); builder.addVariable(TOOL_JAR_NAME_LONG, mcConfig.getToolName()); builder.addVariable(REPORT_LONG, mcConfig.getReport()); diff --git a/monticore-generator/src/main/java/de/monticore/cli/MontiCoreTool.java b/monticore-generator/src/main/java/de/monticore/cli/MontiCoreTool.java index d60c17e0fa..dff59b0cdd 100644 --- a/monticore-generator/src/main/java/de/monticore/cli/MontiCoreTool.java +++ b/monticore-generator/src/main/java/de/monticore/cli/MontiCoreTool.java @@ -404,6 +404,13 @@ protected Options initOptions() { .desc("Specifies if transformation infrastructure should be generated for the given TR grammar.") .build()); + // toggle tagging generation + options.addOption(Option.builder(GENTAG_LONG) + .argName("boolean") + .hasArg(true) + .desc("Specifies if tagging infrastructure should be generated for the given tagging grammar.") + .build()); + // help dialog options.addOption(Option.builder(HELP) .longOpt(HELP_LONG) diff --git a/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/CDTaggingDecorator.java b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/CDTaggingDecorator.java new file mode 100644 index 0000000000..016dcd5e4f --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/CDTaggingDecorator.java @@ -0,0 +1,40 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.codegen.cd2java._tagging; + +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; +import de.monticore.cdbasis._ast.ASTCDPackage; +import de.monticore.codegen.cd2java.AbstractDecorator; +import de.monticore.generating.templateengine.GlobalExtensionManagement; +import de.se_rwth.commons.Joiners; +import de.se_rwth.commons.Names; + +public class CDTaggingDecorator extends AbstractDecorator { + protected final TaggerDecorator taggerDecorator; + protected final TagConformsToSchemaCoCoDecorator coCoDecorator; + + public CDTaggingDecorator(GlobalExtensionManagement glex, TaggerDecorator taggerDecorator, TagConformsToSchemaCoCoDecorator coCoDecorator) { + super(glex); + this.taggerDecorator = taggerDecorator; + this.coCoDecorator = coCoDecorator; + } + + public void decorate(ASTCDCompilationUnit input, ASTCDCompilationUnit decoratedCD) { + ASTCDPackage taggingPackage = getPackage(input, decoratedCD, TaggingConstants.TAGGING_PACKAGE); + + taggingPackage.addAllCDElements(taggerDecorator.decorate()); + taggingPackage.addAllCDElements(coCoDecorator.decorate()); + } + + @Override + protected String getPackageName(ASTCDCompilationUnit origCD, String subPackage) { + String cdDefName = origCD.getCDDefinition().getName(); + // Note: We change from the TagDef/TagSchema package into the original grammars package + if (cdDefName.toLowerCase().endsWith(TaggingConstants.TAGDEFINITION_SUFFIX.toLowerCase())) + cdDefName = cdDefName.substring(0, cdDefName.length() - TaggingConstants.TAGDEFINITION_SUFFIX.length()); + if (cdDefName.toLowerCase().endsWith(TaggingConstants.TAGSCHEMA_SUFFIX.toLowerCase())) + cdDefName = cdDefName.substring(0, cdDefName.length() - TaggingConstants.TAGSCHEMA_SUFFIX.length()); + // end changes + String origPackage = Names.constructQualifiedName(origCD.getCDPackageList(), cdDefName); + return (subPackage.isEmpty() ? origPackage : Joiners.DOT.join(origPackage, subPackage)).toLowerCase(); + } +} diff --git a/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/MC2CDTaggingTranslation.java b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/MC2CDTaggingTranslation.java new file mode 100644 index 0000000000..51dd6daa38 --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/MC2CDTaggingTranslation.java @@ -0,0 +1,173 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.codegen.cd2java._tagging; + +import de.monticore.cd.facade.CDMethodFacade; +import de.monticore.cd.facade.CDParameterFacade; +import de.monticore.cd4analysis.CD4AnalysisMill; +import de.monticore.cd4analysis._symboltable.ICD4AnalysisGlobalScope; +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.cd4codebasis._ast.ASTCDMethod; +import de.monticore.cdbasis._ast.ASTCDClass; +import de.monticore.cdbasis._ast.ASTCDCompilationUnit; +import de.monticore.cdinterfaceandenum._ast.ASTCDInterface; +import de.monticore.codegen.cd2java._ast.ast_class.ASTConstants; +import de.monticore.codegen.prettyprint.PrettyPrinterConstants; +import de.monticore.grammar.grammar._ast.ASTMCGrammar; +import de.monticore.grammar.grammar._symboltable.ProdSymbol; +import de.monticore.grammar.grammar._symboltable.RuleComponentSymbol; +import de.monticore.tagging.tags._ast.ASTTag; +import de.monticore.tagging.tags._ast.ASTTagUnit; +import de.monticore.types.MCTypeFacade; +import de.monticore.types.mcbasictypes._ast.ASTMCPackageDeclaration; +import de.se_rwth.commons.Joiners; +import de.se_rwth.commons.Splitters; +import de.se_rwth.commons.StringTransformations; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import static de.monticore.cd.facade.CDModifier.PACKAGE_PRIVATE_ABSTRACT; +import static de.monticore.cd.facade.CDModifier.PUBLIC; + +public class MC2CDTaggingTranslation implements Function { + + protected final ICD4AnalysisGlobalScope cdScope; + + protected final MCTypeFacade mcTypeFacade = MCTypeFacade.getInstance(); + + protected final CDMethodFacade cdMethodFacade = CDMethodFacade.getInstance(); + protected final CDParameterFacade cdParameterFacade = CDParameterFacade.getInstance(); + + public MC2CDTaggingTranslation(ICD4AnalysisGlobalScope cdScope) { + this.cdScope = cdScope; + } + + @Override + public ASTCDCompilationUnit apply(ASTMCGrammar originalGrammar) { + List packageName = new ArrayList<>(originalGrammar.getPackageList()); + packageName.add(PrettyPrinterConstants.PRETTYPRINT_PACKAGE); + + ASTMCPackageDeclaration packageDecl = CD4CodeMill.mCPackageDeclarationBuilder().setMCQualifiedName(CD4CodeMill.mCQualifiedNameBuilder().setPartsList(packageName).build()).build(); + + ASTCDCompilationUnit compilationUnit = CD4CodeMill.cDCompilationUnitBuilder() + .setMCPackageDeclaration(packageDecl) + .setCDDefinition(CD4CodeMill.cDDefinitionBuilder() + .setModifier(CD4AnalysisMill.modifierBuilder().build()) + .setName(originalGrammar.getName()) + .uncheckedBuild()) + .uncheckedBuild(); + + compilationUnit.getCDDefinition().setDefaultPackageName(Joiners.DOT.join(packageName)); + + // specific Interface with add/get/remove Tag methods + + String taggerName = StringTransformations.capitalize(originalGrammar.getName()) + "Tagger"; + + ASTCDInterface taggerInterface = CD4CodeMill.cDInterfaceBuilder() + .setModifier(PUBLIC.build()) + .setName("I" + taggerName) + .build(); + + ASTCDClass taggerClass = CD4CodeMill.cDClassBuilder() + .setModifier(PUBLIC.build()) + .setName(taggerName) + .setCDInterfaceUsage(CD4CodeMill.cDInterfaceUsageBuilder().addInterface(mcTypeFacade.createQualifiedType("I" + taggerName)).build()) + .build(); + + for (ProdSymbol prodSymbol : originalGrammar.getSymbol().getProds()) { + // only add tagging methods for concrete productions + if (prodSymbol.isIsInterface() || prodSymbol.isIsExternal() || prodSymbol.isIsLexerProd()) continue; + // that are not left recursive + // DISCUSS: left recursive tagging? + if (prodSymbol.isIsDirectLeftRecursive() || prodSymbol.isIsIndirectLeftRecursive()) continue; + // which are either a symbol or have a single name:Name + if (!prodSymbol.isIsSymbolDefinition() && !hasName(prodSymbol)) continue; + + taggerInterface.addAllCDMembers(createITaggerMethods(prodSymbol)); + taggerClass.addAllCDMembers(createTaggerMethods(prodSymbol)); + } + + compilationUnit.getCDDefinition().addCDElement(taggerInterface); + compilationUnit.getCDDefinition().addCDElement(taggerClass); + + + return compilationUnit; + } + + protected List createITaggerMethods(ProdSymbol prodSymbol) { + final String symbolASTFQN = getASTPackageName(prodSymbol) + ".AST" + StringTransformations.capitalize(prodSymbol.getName()); + + List methods = new ArrayList<>(); + + methods.add(cdMethodFacade.createMethod(PACKAGE_PRIVATE_ABSTRACT.build(), mcTypeFacade.createListTypeOf(ASTTag.class.getName()), + "getTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"))); + + methods.add(cdMethodFacade.createMethod(PACKAGE_PRIVATE_ABSTRACT.build(), mcTypeFacade.createBooleanType(), + "removeTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"), + cdParameterFacade.createParameter(symbolASTFQN, "astTag") + )); + + methods.add(cdMethodFacade.createMethod(PACKAGE_PRIVATE_ABSTRACT.build(), + "addTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"), + cdParameterFacade.createParameter(symbolASTFQN, "astTag") + )); + + return methods; + } + + protected List createTaggerMethods(ProdSymbol prodSymbol) { + final String symbolASTFQN = getASTPackageName(prodSymbol) + ".AST" + StringTransformations.capitalize(prodSymbol.getName()); + + List methods = new ArrayList<>(); + + methods.add(cdMethodFacade.createMethod(PUBLIC.build(), mcTypeFacade.createListTypeOf(ASTTag.class.getName()), + "getTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"))); + + methods.add(cdMethodFacade.createMethod(PUBLIC.build(), mcTypeFacade.createBooleanType(), + "removeTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"), + cdParameterFacade.createParameter(symbolASTFQN, "astTag") + )); + + methods.add(cdMethodFacade.createMethod(PUBLIC.build(), + "addTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"), + cdParameterFacade.createParameter(symbolASTFQN, "astTag") + )); + + return methods; + } + + protected String getASTPackageName(ProdSymbol symbol) { + List pck = new ArrayList<>(Splitters.DOT.splitToList(symbol.getPackageName())); + pck.add(symbol.getEnclosingScope().getName().toLowerCase()); + pck.add(ASTConstants.AST_PACKAGE); + return Joiners.DOT.join(pck); + } + + // If the production symbol has exactly one name production + public static boolean hasName(ProdSymbol prodSymbol) { + int nNames = 0; + for (RuleComponentSymbol comp : prodSymbol.getProdComponents()) { + if (comp.isIsNonterminal() && comp.isPresentReferencedType()) { + if (comp.getName().equals("name") && comp.getReferencedType().equals("Name")) { + if (comp.isIsList() || comp.isIsOptional() || nNames > 1) return false; + nNames++; + } + } + } + return nNames == 1; + } + +} diff --git a/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TagConformsToSchemaCoCoDecorator.java b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TagConformsToSchemaCoCoDecorator.java new file mode 100644 index 0000000000..7ea9a7d300 --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TagConformsToSchemaCoCoDecorator.java @@ -0,0 +1,142 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.codegen.cd2java._tagging; + +import de.monticore.cd.methodtemplates.CD4C; +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.cd4codebasis._ast.ASTCDConstructor; +import de.monticore.cd4codebasis._ast.ASTCDMethod; +import de.monticore.cdbasis._ast.ASTCDClass; +import de.monticore.cdbasis._ast.ASTCDElement; +import de.monticore.codegen.cd2java.AbstractDecorator; +import de.monticore.codegen.cd2java.AbstractService; +import de.monticore.codegen.cd2java._visitor.VisitorService; +import de.monticore.generating.templateengine.GlobalExtensionManagement; +import de.monticore.generating.templateengine.TemplateHookPoint; +import de.monticore.grammar.grammar._ast.ASTMCGrammar; +import de.monticore.grammar.grammar._symboltable.MCGrammarSymbol; +import de.monticore.grammar.grammar._symboltable.ProdSymbol; +import de.monticore.tagging.tags._ast.*; + +import de.monticore.tagging.tagschema._symboltable.TagSchemaSymbol; + +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedType; +import de.se_rwth.commons.Joiners; +import de.se_rwth.commons.Splitters; +import de.se_rwth.commons.StringTransformations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static de.monticore.cd.codegen.CD2JavaTemplates.EMPTY_BODY; +import static de.monticore.cd.facade.CDModifier.*; + +/** + * Decorate the TagConformsToSchemaCoCo, etc. + */ +public class TagConformsToSchemaCoCoDecorator extends AbstractDecorator { + protected final AbstractService service; + protected final VisitorService visitorService; + protected final ASTMCGrammar originalGrammar; + + public TagConformsToSchemaCoCoDecorator(GlobalExtensionManagement glex, AbstractService service, VisitorService visitorService, ASTMCGrammar originalGrammar) { + super(glex); + this.service = service; + this.visitorService = visitorService; + this.originalGrammar = originalGrammar; + } + + public List decorate() { + if (originalGrammar.getSymbol().getStartProd().isEmpty()) { + return new ArrayList<>(); // Abort for grammars without a start node + } + List elements = new ArrayList<>(); + String cocoName = StringTransformations.capitalize(originalGrammar.getName()) + "TagConformsToSchemaCoCo"; + + // Retrieve the FQN of the start production (may be a prod of another grammar) + List pck = new ArrayList<>(); + if (!originalGrammar.getSymbol().getStartProd().get().getPackageName().isEmpty()) + pck.add(originalGrammar.getSymbol().getStartProd().get().getPackageName()); + pck.add(originalGrammar.getSymbol().getStartProd().get().getEnclosingScope().getName().toLowerCase()); + pck.add("_ast"); + pck.add("AST" + StringTransformations.capitalize(originalGrammar.getSymbol().getStartProd().get().getName())); + String startProd = Joiners.DOT.join(pck); + + ASTCDClass cocoClass = CD4CodeMill.cDClassBuilder() + .setModifier(PUBLIC.build()) + .setName(cocoName) + .setCDExtendUsage(CD4CodeMill.cDExtendUsageBuilder().addSuperclass(mcTypeFacade.createBasicGenericTypeOf("de.monticore.tagging.conforms.TagConformsToSchemaCoCo", startProd)).build()) + .build(); + CD4C.getInstance().addImport(cocoClass, "de.monticore.tagging.conforms.TagConformanceChecker"); + CD4C.getInstance().addImport(cocoClass, "de.monticore.tagging.conforms.TagDataVisitor"); + CD4C.getInstance().addImport(cocoClass, "de.monticore.tagging.tags._visitor.TagsTraverser"); + CD4C.getInstance().addImport(cocoClass, "de.monticore.tagging.tags.TagsMill"); + CD4C.getInstance().addImport(cocoClass, getNames(originalGrammar.getSymbol(), "_ast", "*")); + CD4C.getInstance().addImport(cocoClass, getNames(originalGrammar.getSymbol(), "_visitor", originalGrammar.getName() + "Traverser")); + CD4C.getInstance().addImport(cocoClass, getNames(originalGrammar.getSymbol(), originalGrammar.getName() + "Mill")); + + elements.add(cocoClass); + + ASTCDConstructor constructor = cdConstructorFacade.createConstructor(PUBLIC.build(), cocoClass.getName(), cdParameterFacade.createParameter(startProd, "model")); + cocoClass.addCDMember(constructor); + this.replaceTemplate(EMPTY_BODY, constructor, new TemplateHookPoint("tagging.coco.Constructor")); + + ASTCDMethod method; + cocoClass.addCDMember((method = cdMethodFacade.createMethod(PUBLIC.build(), "check", + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "tagUnit"), + cdParameterFacade.createParameter(TagSchemaSymbol.class.getName(), "tagSchemaSymbol") + ))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.coco.Check", originalGrammar.getName())); + + ASTCDClass tagDataVisitorClass = CD4CodeMill.cDClassBuilder() + .setName(originalGrammar.getName() + "TagDataVisitor") + .setModifier(PUBLIC.build()) + .setCDExtendUsage(CD4CodeMill.cDExtendUsageBuilder().addSuperclass(mcTypeFacade.createQualifiedType("de.monticore.tagging.conforms.TagDataVisitor")).build()) + .build(); + elements.add(tagDataVisitorClass); + ASTMCQualifiedType specificTagDefinitionTraverserType = mcTypeFacade.createQualifiedType(originalGrammar.getName() + "TagDefinitionTraverser"); + + // Traverser working on de.monticore.tagging.tags._ast.ASTModelElementIdentifier for tag-TargetElements + tagDataVisitorClass.addCDMember(cdAttributeFacade.createAttribute(PROTECTED.build(), specificTagDefinitionTraverserType, "identifierTravers")); + // Traverser working on de.monticore.tagging.tags._ast.ASTModelElementIdentifier for within-contexts + tagDataVisitorClass.addCDMember(cdAttributeFacade.createAttribute(PROTECTED.build(), specificTagDefinitionTraverserType, "contextWithinTravers")); + + tagDataVisitorClass.addCDMember((method = cdMethodFacade.createMethod(PROTECTED.build(), specificTagDefinitionTraverserType, "getIdentifierTraverser"))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.coco.GetTraverser", originalGrammar.getName(), "identifierTravers")); + + tagDataVisitorClass.addCDMember((method = cdMethodFacade.createMethod(PROTECTED.build(), specificTagDefinitionTraverserType, "getContextWithinTraverser"))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.coco.GetTraverser", originalGrammar.getName(), "contextWithinTravers")); + + tagDataVisitorClass.addCDMember((constructor = cdConstructorFacade.createConstructor(PUBLIC.build(), tagDataVisitorClass.getName(), + cdParameterFacade.createParameter(TagSchemaSymbol.class.getName(), "tagSchemaSymbol"), + cdParameterFacade.createParameter("de.monticore.tagging.conforms.TagConformanceChecker", "tagConformanceChecker")))); + this.replaceTemplate(EMPTY_BODY, constructor, new TemplateHookPoint("tagging.coco.VisitorConstructor", + originalGrammar.getName(), + originalGrammar.getSymbol().getProds().stream() + .filter(p -> !p.isIsInterface() && !p.isIsExternal() && !p.isIsLexerProd()) + .filter(p -> !p.isIsIndirectLeftRecursive() && !p.isIsDirectLeftRecursive()) // skip left-rec (as not Identifiers are present in the TagSchemas) + .map(ProdSymbol::getName) + .collect(Collectors.toList()))); + + CD4C.getInstance().addImport(tagDataVisitorClass, getTagDefNames(originalGrammar.getSymbol(), "_visitor", "*")); + CD4C.getInstance().addImport(tagDataVisitorClass, getTagDefNames(originalGrammar.getSymbol(), "_ast", "*")); + CD4C.getInstance().addImport(tagDataVisitorClass, getTagDefNames(originalGrammar.getSymbol(), originalGrammar.getName() + TaggingConstants.TAGDEFINITION_SUFFIX + "Mill")); + CD4C.getInstance().addImport(tagDataVisitorClass, ASTTag.class.getName()); + return elements; + } + + protected String getNames(MCGrammarSymbol symbol, String... parts) { + List pck = symbol.getPackageName().isEmpty() ? new ArrayList<>() : new ArrayList<>(Splitters.DOT.splitToList(symbol.getPackageName())); + pck.add(symbol.getName().toLowerCase()); + pck.addAll(Arrays.asList(parts)); + return Joiners.DOT.join(pck); + } + + protected String getTagDefNames(MCGrammarSymbol symbol, String... parts) { + List pck = symbol.getPackageName().isEmpty() ? new ArrayList<>() : new ArrayList<>(Splitters.DOT.splitToList(symbol.getPackageName())); + pck.add(symbol.getName().toLowerCase() + TaggingConstants.TAGDEFINITION_SUFFIX.toLowerCase()); + pck.addAll(Arrays.asList(parts)); + return Joiners.DOT.join(pck); + } +} diff --git a/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TaggerDecorator.java b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TaggerDecorator.java new file mode 100644 index 0000000000..f91b462baf --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TaggerDecorator.java @@ -0,0 +1,265 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.codegen.cd2java._tagging; + +import de.monticore.cd.methodtemplates.CD4C; +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.cd4codebasis._ast.ASTCDConstructor; +import de.monticore.cd4codebasis._ast.ASTCDMethod; +import de.monticore.cdbasis._ast.ASTCDClass; +import de.monticore.cdbasis._ast.ASTCDElement; +import de.monticore.cdinterfaceandenum._ast.ASTCDInterface; +import de.monticore.codegen.cd2java.AbstractDecorator; +import de.monticore.codegen.cd2java.AbstractService; +import de.monticore.codegen.cd2java._ast.ast_class.ASTConstants; +import de.monticore.codegen.cd2java._visitor.VisitorService; +import de.monticore.generating.templateengine.GlobalExtensionManagement; +import de.monticore.generating.templateengine.TemplateHookPoint; +import de.monticore.grammar.grammar._ast.ASTMCGrammar; +import de.monticore.grammar.grammar._symboltable.MCGrammarSymbol; +import de.monticore.grammar.grammar._symboltable.MCGrammarSymbolSurrogate; +import de.monticore.grammar.grammar._symboltable.ProdSymbol; +import de.monticore.symboltable.IScope; +import de.monticore.tagging.tags._ast.*; +import de.monticore.types.mcbasictypes._ast.ASTMCObjectType; +import de.se_rwth.commons.Joiners; +import de.se_rwth.commons.Names; +import de.se_rwth.commons.StringTransformations; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static de.monticore.cd.codegen.CD2JavaTemplates.EMPTY_BODY; +import static de.monticore.cd.facade.CDModifier.*; + +/** + * Decorate the ITagger interface and Tagger class (along with used visitor classes) + */ +public class TaggerDecorator extends AbstractDecorator { + protected final AbstractService service; + protected final VisitorService visitorService; + protected final ASTMCGrammar originalGrammar; + + public TaggerDecorator(GlobalExtensionManagement glex, AbstractService service, VisitorService visitorService, ASTMCGrammar originalGrammar) { + super(glex); + this.service = service; + this.visitorService = visitorService; + this.originalGrammar = originalGrammar; + } + + public List decorate() { + List elements = new ArrayList<>(); + String taggerName = StringTransformations.capitalize(originalGrammar.getName()) + "Tagger"; + + + ASTCDInterface taggerInterface = CD4CodeMill.cDInterfaceBuilder() + .setModifier(PUBLIC.build()) + .setName("I" + taggerName) + .build(); + elements.add(taggerInterface); + // Add I${super}Tagger interfaces + List superInterfaces = new ArrayList<>(); + for (MCGrammarSymbolSurrogate superGrammar : originalGrammar.getSymbol().getSuperGrammars()) { + String packageName = superGrammar.lazyLoadDelegate().getPackageName(); + List pck = new ArrayList<>(); + if (!packageName.isEmpty()) + pck.add(packageName); + pck.add(superGrammar.lazyLoadDelegate().getName().toLowerCase()); + pck.add(TaggingConstants.TAGGING_PACKAGE); + pck.add("I" + superGrammar.lazyLoadDelegate().getName() + "Tagger"); + superInterfaces.add(mcTypeFacade.createQualifiedType(Joiners.DOT.join(pck))); + } + if (!superInterfaces.isEmpty()) { + taggerInterface.setCDExtendUsage(CD4CodeMill.cDExtendUsageBuilder().addAllSuperclass(superInterfaces).build()); + } + + + ASTCDClass taggerClass = CD4CodeMill.cDClassBuilder() + .setModifier(PUBLIC.build()) + .setName(taggerName) + .setCDInterfaceUsage(CD4CodeMill.cDInterfaceUsageBuilder().addInterface(mcTypeFacade.createQualifiedType("I" + taggerName)).build()) + .setCDExtendUsage(CD4CodeMill.cDExtendUsageBuilder().addSuperclass(mcTypeFacade.createQualifiedType("de.monticore.tagging.AbstractTagger")).build()) + .build(); + CD4C.getInstance().addImport(taggerClass, "de.monticore.tagging.tags.TagsMill"); + CD4C.getInstance().addImport(taggerClass, "de.monticore.tagging.tags._ast.ASTContext"); + CD4C.getInstance().addImport(taggerClass, "de.monticore.tagging.tags._ast.ASTTag"); + CD4C.getInstance().addImport(taggerClass, "java.util.stream.Collectors"); + CD4C.getInstance().addImport(taggerClass, "de.se_rwth.commons.Joiners"); + elements.add(taggerClass); + + for (ProdSymbol prodSymbol : originalGrammar.getSymbol().getProds()) { + if (prodSymbol.isIsInterface() || prodSymbol.isIsExternal() || prodSymbol.isIsLexerProd()) continue; + + // Skip left recursive productions + // DISCUSS: Support for left-recursive + if (prodSymbol.isIsDirectLeftRecursive() || prodSymbol.isIsIndirectLeftRecursive()) continue; + boolean isSymbolLike = prodSymbol.isIsSymbolDefinition() || MC2CDTaggingTranslation.hasName(prodSymbol); + + taggerInterface.addAllCDMembers(createITaggerMethods(prodSymbol, taggerClass.getName())); + taggerClass.addAllCDMembers(createTaggerMethods(prodSymbol, isSymbolLike)); + + elements.add(createTravCheckerClass(prodSymbol)); + } + + ASTCDMethod method = cdMethodFacade.createMethod(PROTECTED.build(), mcTypeFacade.createQualifiedType(IScope.class.getName()), + "getArtifactScope", + cdParameterFacade.createParameter(IScope.class.getName(), "s")); + taggerClass.addCDMember(method); + + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.tagger.GetArtifactScope", getMillName(originalGrammar.getSymbol()))); + + // Singleton + taggerClass.addCDMember(cdAttributeFacade.createAttribute(PROTECTED_STATIC.build(), mcTypeFacade.createQualifiedType(taggerInterface.getName()), "INSTANCE")); + + method = cdMethodFacade.createMethod(PUBLIC_STATIC.build(), mcTypeFacade.createQualifiedType(taggerInterface.getName()), "getInstance"); + taggerClass.addCDMember(method); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.tagger.GetInstance", taggerClass.getName())); + + return elements; + } + + + protected List createITaggerMethods(ProdSymbol prodSymbol, String clazzname) { + final String symbolASTFQN = getASTPackageName(prodSymbol) + ".AST" + StringTransformations.capitalize(prodSymbol.getName()); + + List methods = new ArrayList<>(); + + ASTCDMethod m; + methods.add((m = cdMethodFacade.createMethod(PACKAGE_PRIVATE.build(), mcTypeFacade.createListTypeOf(ASTTag.class.getName()), + "getTags", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit")))); + this.replaceTemplate(EMPTY_BODY, m, new TemplateHookPoint("tagging.itagger.GetTags", clazzname)); + + methods.add((m = cdMethodFacade.createMethod(PACKAGE_PRIVATE.build(), mcTypeFacade.createBooleanType(), + "removeTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"), + cdParameterFacade.createParameter(ASTTag.class.getName(), "astTag") + ))); + this.replaceTemplate(EMPTY_BODY, m, new TemplateHookPoint("tagging.itagger.RemoveTag", clazzname)); + + + methods.add((m = cdMethodFacade.createMethod(PACKAGE_PRIVATE.build(), + "addTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"), + cdParameterFacade.createParameter(ASTTag.class.getName(), "astTag") + ))); + this.replaceTemplate(EMPTY_BODY, m, new TemplateHookPoint("tagging.itagger.AddTag", clazzname)); + + return methods; + } + + protected List createTaggerMethods(ProdSymbol prodSymbol, boolean isSymbolLike) { + final String symbolASTFQN = getASTPackageName(prodSymbol) + ".AST" + StringTransformations.capitalize(prodSymbol.getName()); + + List methods = new ArrayList<>(); + ASTCDMethod method; + + methods.add((method = cdMethodFacade.createMethod(PUBLIC.build(), + mcTypeFacade.createListTypeOf(ASTTag.class.getName()), + "getTags", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit")))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.tagger.GetTags", prodSymbol.getName(), isSymbolLike)); + + methods.add((method = cdMethodFacade.createMethod(PUBLIC.build(), + mcTypeFacade.createBooleanType(), + "removeTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"), + cdParameterFacade.createParameter(ASTTag.class.getName(), "astTag") + ))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.tagger.RemoveTag", prodSymbol.getName(), isSymbolLike)); + + methods.add((method = cdMethodFacade.createMethod(PUBLIC.build(), + "addTag", + cdParameterFacade.createParameter(symbolASTFQN, "model"), + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "astTagUnit"), + cdParameterFacade.createParameter(ASTTag.class.getName(), "astTag") + ))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.tagger.AddTag", + prodSymbol.getName(), originalGrammar.getName(), getPackageName(originalGrammar.getSymbol()), prodSymbol.isIsSymbolDefinition())); + + methods.add((method = cdMethodFacade.createMethod(PROTECTED.build(), + mcTypeFacade.createBasicGenericTypeOf(Stream.class.getName(), ASTTargetElement.class.getName()), + "findTargetsBy", + cdParameterFacade.createParameter(ASTTagUnit.class.getName(), "ast"), + cdParameterFacade.createParameter(symbolASTFQN, "element"), + cdParameterFacade.createParameter("TravChecker" + prodSymbol.getName(), "travChecker") + ))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.tagger.FindTargetsBy")); + + if (!isSymbolLike) { + methods.add((method = cdMethodFacade.createMethod(PROTECTED.build(), + mcTypeFacade.createBasicGenericTypeOf(Stream.class.getName(), ASTTargetElement.class.getName()), + "findTargetsBy", + cdParameterFacade.createParameter(ASTContext.class.getName(), "ast"), + cdParameterFacade.createParameter(symbolASTFQN, "element"), + cdParameterFacade.createParameter("TravChecker" + prodSymbol.getName(), "travChecker") + ))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.tagger.FindTargetsBy")); + } + + methods.add((method = cdMethodFacade.createMethod(PROTECTED.build(), + mcTypeFacade.createBooleanType(), + "isIdentified", + cdParameterFacade.createParameter(ASTModelElementIdentifier.class.getName(), "elementIdentifier"), + cdParameterFacade.createParameter(symbolASTFQN, "element"), + cdParameterFacade.createParameter("TravChecker" + prodSymbol.getName(), "travChecker") + ))); + this.replaceTemplate(EMPTY_BODY, method, new TemplateHookPoint("tagging.tagger.IsIdentified")); + + return methods; + } + + // The TravChecker, a class used to check if an AST element is present + protected ASTCDClass createTravCheckerClass(ProdSymbol prodSymbol) { + final String symbolASTFQN = getASTPackageName(prodSymbol) + ".AST" + StringTransformations.capitalize(prodSymbol.getName()); + String traverserPkg = getPackageName(originalGrammar.getSymbol()); + traverserPkg += "tagdefinition._visitor." + originalGrammar.getName() + "TagDefinitionTraverser"; + + ASTCDClass clazz = CD4CodeMill.cDClassBuilder().setName("TravChecker" + StringTransformations.capitalize(prodSymbol.getName())) + .setModifier(PACKAGE_PRIVATE.build()) + .build(); + clazz.addCDMember(cdAttributeFacade.createAttribute(PROTECTED_FINAL.build(), mcTypeFacade.createArrayType(mcTypeFacade.createBooleanType(), 1), "ret")); + clazz.addCDMember(cdAttributeFacade.createAttribute(PROTECTED_FINAL.build(), mcTypeFacade.createQualifiedType(traverserPkg), "traverser")); + clazz.addCDMember(cdAttributeFacade.createAttribute(PROTECTED.build(), mcTypeFacade.createQualifiedType(symbolASTFQN), "element")); + + ASTCDConstructor constructor = cdConstructorFacade.createDefaultConstructor(PUBLIC.build(), clazz); + clazz.addCDMember(constructor); + this.replaceTemplate(EMPTY_BODY, constructor, new TemplateHookPoint("tagging.travchecker.TravCheckerConstructor", prodSymbol.getName(), originalGrammar.getName(), getPackageName(originalGrammar.getSymbol()))); + + return clazz; + } + + /** + * Retrieve the AST package name of the original grammar of a production + * @param symbol the production's symbol + * @return the _ast package name + */ + protected String getASTPackageName(ProdSymbol symbol) { + return Names.getQualifiedName(symbol.getPackageName(), symbol.getEnclosingScope().getName().toLowerCase() + "." + ASTConstants.AST_PACKAGE); + } + + /** + * Retrieve the FQN of a grammar's mill + * @param symbol the grammar's symbol + * @return the FQN mill classname + */ + protected String getMillName(MCGrammarSymbol symbol) { + return Names.getQualifiedName(symbol.getPackageName(), + symbol.getName().toLowerCase() + + "." + StringTransformations.capitalize(symbol.getName()) + "Mill"); + } + + /** + * Retrieve the AST package name of a grammar + * @param symbol the symbol of the grammar + * @return the _ast package name + */ + protected String getPackageName(MCGrammarSymbol symbol) { + return Names.getQualifiedName(symbol.getPackageName(), symbol.getName().toLowerCase()); + } +} diff --git a/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TaggingConstants.java b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TaggingConstants.java new file mode 100644 index 0000000000..0724403dd6 --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/codegen/cd2java/_tagging/TaggingConstants.java @@ -0,0 +1,10 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.codegen.cd2java._tagging; + +public class TaggingConstants { + public static final String TAGGING_PACKAGE = "_tagging"; + + public final static String TAGDEFINITION_SUFFIX = "TagDefinition"; + + public final static String TAGSCHEMA_SUFFIX = "TagSchema"; +} diff --git a/monticore-generator/src/main/java/de/monticore/dstlgen/DSTLGenScript.java b/monticore-generator/src/main/java/de/monticore/dstlgen/DSTLGenScript.java index b18097e8d9..ca9916e9c0 100644 --- a/monticore-generator/src/main/java/de/monticore/dstlgen/DSTLGenScript.java +++ b/monticore-generator/src/main/java/de/monticore/dstlgen/DSTLGenScript.java @@ -56,7 +56,6 @@ */ public class DSTLGenScript { - protected final String HC_SUFFIX = "HC"; protected final String LOG_ID = "DSTLGenScript"; protected final String modelFileExtension = "mtr"; @@ -104,7 +103,7 @@ public IGrammarFamilyGlobalScope createMCGlobalScope(MCPath modelPath, String fi public Optional parseGrammarHC(ASTMCGrammar grammar, MCPath paths) { List trGrammarNames = new ArrayList<>(grammar.getPackageList()); trGrammarNames.add("tr"); - trGrammarNames.add(grammar.getName() + "TR" + HC_SUFFIX + ".mc4"); + trGrammarNames.add(grammar.getName() + "TRHC.mc4"); Path trGrammarPath = Paths.get(trGrammarNames.stream().collect(Collectors.joining(File.separator))); Optional hwGrammar = paths.find(trGrammarPath.toString()); diff --git a/monticore-generator/src/main/java/de/monticore/tagging/TagDefinitionDerivVisitor.java b/monticore-generator/src/main/java/de/monticore/tagging/TagDefinitionDerivVisitor.java new file mode 100644 index 0000000000..ce7d1481e5 --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/tagging/TagDefinitionDerivVisitor.java @@ -0,0 +1,124 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging; + +import de.monticore.ast.Comment; +import de.monticore.cd4code.CD4CodeMill; +import de.monticore.codegen.cd2java._tagging.TaggingConstants; +import de.monticore.grammar.grammar.GrammarMill; +import de.monticore.grammar.grammar._ast.ASTClassProd; +import de.monticore.grammar.grammar._ast.ASTMCGrammar; +import de.monticore.grammar.grammar._ast.ASTMCGrammarBuilder; +import de.monticore.grammar.grammar._ast.ASTTerminal; +import de.monticore.grammar.grammar._symboltable.MCGrammarSymbolSurrogate; +import de.monticore.grammar.grammar._symboltable.RuleComponentSymbol; +import de.monticore.grammar.grammar._visitor.GrammarVisitor2; +import de.monticore.grammar.grammar_withconcepts.Grammar_WithConceptsMill; +import de.monticore.grammar.grammar_withconcepts._ast.ASTAction; +import de.monticore.statements.mcreturnstatements.MCReturnStatementsMill; +import de.monticore.types.MCTypeFacade; +import de.se_rwth.commons.Splitters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The following visitor derives a language specific TagDefinition grammar. + * It mainly produces the ModelElementIdentifier productions + */ +public class TagDefinitionDerivVisitor implements GrammarVisitor2 { + + protected ASTMCGrammar grammar; + protected final MCTypeFacade mcTypeFacade = MCTypeFacade.getInstance(); + + @Override + public void visit(ASTMCGrammar node) { + ASTMCGrammarBuilder builder = GrammarMill.mCGrammarBuilder().setName(node.getName() + TaggingConstants.TAGDEFINITION_SUFFIX); + if (!node.getPackageList().isEmpty()) { + builder.setPackageList(new ArrayList<>(node.getPackageList())); + } + + // extends node + builder.addSupergrammar(GrammarMill.grammarReferenceBuilder().setNamesList(Splitters.DOT.splitToList(node.getSymbol().getFullName())).build()); + // extends Tags + builder.addSupergrammar(GrammarMill.grammarReferenceBuilder().setNamesList(Arrays.asList("de", "monticore", "tagging", "Tags")).build()); + + builder.setComponent(node.isComponent()); + + for (MCGrammarSymbolSurrogate superSurrogate : node.getSymbol().getSuperGrammars()) { + List namesList = new ArrayList<>(Splitters.DOT.splitToList(superSurrogate.lazyLoadDelegate().getFullName())); + namesList.set(namesList.size() - 1, namesList.get(namesList.size() - 1) + TaggingConstants.TAGDEFINITION_SUFFIX); + + builder.addSupergrammar(GrammarMill.grammarReferenceBuilder().setNamesList(namesList).build()); + } + this.grammar = builder.build(); + this.grammar.setStartRule(GrammarMill.startRuleBuilder().setName("TagUnit").build()); + } + + @Override + public void visit(ASTClassProd node) { + if (node.getSymbol().isIsDirectLeftRecursive() || node.getSymbol().isIsIndirectLeftRecursive()) { + grammar.add_PostComment(new Comment("/* Unable to create a TagDef Identifier for left-recursive production " + node.getName() + " */")); + return; + } + boolean ignore = node.getSymbol().isIsSymbolDefinition() || hasName(node); + ASTClassProd classProd = GrammarMill.classProdBuilder().setName(node.getName() + "Identifier") + .addSuperInterfaceRule(GrammarMill.ruleReferenceBuilder().setName("ModelElementIdentifier").build()) + .addAlt(GrammarMill.altBuilder().addComponent(terminal("[")).addComponent(GrammarMill.nonTerminalBuilder().setName(node.getName()).build()).addComponent(terminal(("]"))).build()) + .build(); + + if (ignore) { + // Add a comment to the ${prod}DeclarationIdentifier to annotate, that this production can also be referenced by its (symbol) name + classProd.add_PreComment(new Comment("/* DefaultIdent (using the Name) works too */")); + } + + grammar.addClassProd(classProd); + // Add the getIdentifies method to the Identifier + grammar.addASTRule(GrammarMill.aSTRuleBuilder() + .setType(node.getName() + "Identifier") + .addGrammarMethod(GrammarMill.grammarMethodBuilder() + .setPublic(true) + .setMCReturnType(GrammarMill.mCReturnTypeBuilder().setMCType(mcTypeFacade.createStringType()).build()) + .setBody(returnStringLiteral(node.getName())) + .setName("getIdentifies") + .build()) + .build()); + } + + // Create a return "source"; method body + protected ASTAction returnStringLiteral(String source) { + return Grammar_WithConceptsMill.actionBuilder() + .addMCBlockStatement( + MCReturnStatementsMill.returnStatementBuilder() + .setExpression( + CD4CodeMill.literalExpressionBuilder() + .setLiteral(CD4CodeMill.stringLiteralBuilder().setSource(source).build()) + .build() + ) + .build() + ) + .build(); + } + + protected boolean hasName(ASTClassProd node) { + // If the production symbol has a single name production + boolean isFirstName = true; + for (RuleComponentSymbol comp : node.getSymbol().getProdComponents()) { + if (comp.isIsNonterminal() && comp.isPresentReferencedType()) { + if (comp.getName().equals("name") && comp.getReferencedType().equals("Name")) { + if (comp.isIsList() || comp.isIsOptional() || !isFirstName) return false; + isFirstName = false; + } + } + } + return true; + } + + protected ASTTerminal terminal(String term) { + return GrammarMill.terminalBuilder().setName(term).build(); + } + + public ASTMCGrammar getGrammar() { + return grammar; + } +} diff --git a/monticore-generator/src/main/java/de/monticore/tagging/TagGenerator.java b/monticore-generator/src/main/java/de/monticore/tagging/TagGenerator.java new file mode 100644 index 0000000000..961d8ba1d7 --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/tagging/TagGenerator.java @@ -0,0 +1,123 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging; + +import com.google.common.base.Joiner; +import de.monticore.codegen.cd2java._tagging.TaggingConstants; +import de.monticore.generating.templateengine.reporting.Reporting; +import de.monticore.grammar.grammar.GrammarMill; +import de.monticore.grammar.grammar._ast.ASTMCGrammar; +import de.monticore.grammar.grammar._visitor.GrammarTraverser; +import de.monticore.grammar.grammarfamily.GrammarFamilyMill; +import de.monticore.grammar.prettyprint.Grammar_WithConceptsFullPrettyPrinter; +import de.monticore.io.paths.MCPath; +import de.monticore.prettyprint.IndentPrinter; +import de.se_rwth.commons.logging.Log; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class TagGenerator { + + + /** + * Generate the TagSchema and TagDefinition grammars and output them as mc4 files + */ + public static void generateTaggingLanguages(ASTMCGrammar grammar, File outDirectory, MCPath modelPathHC) throws IOException { + GrammarTraverser traverser = GrammarMill.traverser(); + TagDefinitionDerivVisitor definitionDerivVisitor = new TagDefinitionDerivVisitor(); + traverser.add4Grammar(definitionDerivVisitor); + grammar.accept(traverser); + ASTMCGrammar defDerivGrammar = definitionDerivVisitor.getGrammar(); + + traverser = GrammarMill.traverser(); + TagSchemaDerivVisitor tagSchemaDerivVisitor = new TagSchemaDerivVisitor(); + traverser.add4Grammar(tagSchemaDerivVisitor); + grammar.accept(traverser); + ASTMCGrammar schemaDerivGrammar = tagSchemaDerivVisitor.getGrammar(); + + + Grammar_WithConceptsFullPrettyPrinter fpp = new Grammar_WithConceptsFullPrettyPrinter(new IndentPrinter()); + + // TOP-ish / LanguageOverrideVisitor + Optional tagDefHC = parseGrammarHC(grammar, modelPathHC, TaggingConstants.TAGDEFINITION_SUFFIX); + if (tagDefHC.isPresent()) { + traverser = GrammarMill.traverser(); + TagLanguageOverrideVisitor overrideVisitor = new TagLanguageOverrideVisitor(defDerivGrammar); + traverser.add4Grammar(overrideVisitor); + tagDefHC.get().accept(traverser); + } + + // TOP-ish / LanguageOverrideVisitor + Optional tagSchemaHC = parseGrammarHC(grammar, modelPathHC, TaggingConstants.TAGSCHEMA_SUFFIX); + if (tagSchemaHC.isPresent()) { + traverser = GrammarMill.traverser(); + TagLanguageOverrideVisitor overrideVisitor = new TagLanguageOverrideVisitor(schemaDerivGrammar); + traverser.add4Grammar(overrideVisitor); + tagSchemaHC.get().accept(traverser); + } + + saveGrammar(defDerivGrammar, fpp, outDirectory); + saveGrammar(schemaDerivGrammar, fpp, outDirectory); + } + + static Path saveGrammar(ASTMCGrammar defDerivGrammar, Grammar_WithConceptsFullPrettyPrinter fpp, File outDirectory) throws IOException { + // compute name of output file + String directories = outDirectory.getPath().replace('\\', '/') + "/" + Joiner.on("/").join(defDerivGrammar.getPackageList()); + if (!Paths.get(directories).toFile().exists()) { + Files.createDirectories(Paths.get(directories)); + } + Path path = Paths.get(directories + "/" + defDerivGrammar.getName() + ".mc4"); + Reporting.reportFileCreation(path.toString()); + return Files.writeString(path, fpp.prettyprint(defDerivGrammar)); + } + + static Optional parseGrammarHC(ASTMCGrammar grammar, MCPath paths, String suffix) { + List hcGrammarNames = new ArrayList<>(grammar.getPackageList()); + hcGrammarNames.add(grammar.getName() + suffix + "HC.mc4"); + + Path hcGrammarPath = Paths.get(hcGrammarNames.stream().collect(Collectors.joining(File.separator))); + Optional hwGrammar = paths.find(hcGrammarPath.toString()); + if (hwGrammar.isPresent()) { + try{ + return GrammarFamilyMill.parser().parse(MCPath.toPath(hwGrammar.get()).get().toString()); + }catch (IOException ignored){ + + } + } + return Optional.empty(); + } + + /** + * Get the relative path of a Tag grammar from the model path and the original grammar location + * @param modelPath the model path + * @param grammar the original grammar file + * @return a relative File (from the output dir) + */ + public static File getTagGrammar(List modelPath, File grammar, String tagtype) { + Path grammarP = grammar.toPath(); + for (String mF : modelPath) { + Path mP = new File(mF).toPath(); + if (!mP.getFileSystem().equals(grammarP.getFileSystem())) + continue; + if (!Objects.equals(mP.getRoot(), grammarP.getRoot())) + continue; + Path relP = mP.relativize(grammarP); + if (relP.startsWith("../")) + continue; // the grammar is not a file within the mP path + File relFile = relP.toFile(); + return new File(relFile.getParent(), relFile.getName().replace(".mc4", tagtype +".mc4")); + } + Log.error("0xA5C01 Could not determine Tagging path for grammar " + grammar); + return null; + } +} \ No newline at end of file diff --git a/monticore-generator/src/main/java/de/monticore/tagging/TagLanguageOverrideVisitor.java b/monticore-generator/src/main/java/de/monticore/tagging/TagLanguageOverrideVisitor.java new file mode 100644 index 0000000000..00bf7b079c --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/tagging/TagLanguageOverrideVisitor.java @@ -0,0 +1,107 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging; + +import de.monticore.grammar.grammar._ast.*; +import de.monticore.grammar.grammar._visitor.GrammarVisitor2; +import de.se_rwth.commons.Joiners; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enable the overriding of generated grammars using hand-coded grammars + * Traverse/visit the hand-coded grammar to apply it to a provided grammar + */ +public class TagLanguageOverrideVisitor implements GrammarVisitor2 { + protected final ASTMCGrammar grammar; + + protected final Map classProdMap = new HashMap<>(); + protected final Map enumProdMap = new HashMap<>(); + protected final Map interfaceProdMap = new HashMap<>(); + protected final Map abstractProdMap = new HashMap<>(); + protected final Map externalProdMap = new HashMap<>(); + protected final Map lexProdMap = new HashMap<>(); + protected final Map astRuleMap = new HashMap<>(); + + /** + * + * @param grammar the grammar to be enriched/modified - will be updated + */ + public TagLanguageOverrideVisitor(ASTMCGrammar grammar) { + this.grammar = grammar; + + this.grammar.getClassProdList().forEach(e -> classProdMap.put(e.getName(), e)); + this.grammar.getEnumProdList().forEach(e -> enumProdMap.put(e.getName(), e)); + this.grammar.getInterfaceProdList().forEach(e -> interfaceProdMap.put(e.getName(), e)); + this.grammar.getAbstractProdList().forEach(e -> abstractProdMap.put(e.getName(), e)); + this.grammar.getExternalProdList().forEach(e -> externalProdMap.put(e.getName(), e)); + this.grammar.getLexProdList().forEach(e -> lexProdMap.put(e.getName(), e)); + this.grammar.getASTRuleList().forEach(e -> astRuleMap.put(e.getType(), e)); + } + + @Override + public void visit(ASTMCGrammar srcNode) { + this.grammar.setComponent(srcNode.isComponent()); + for (ASTGrammarReference overrideSuperGrammar : srcNode.getSupergrammarList()) { + boolean isNewSuper = true; + for (ASTGrammarReference existingSuperGrammar : this.grammar.getSupergrammarList()) { + String overrideSuperGrammarName = Joiners.DOT.join(overrideSuperGrammar.getNameList()); + String existingSuperGrammarName = Joiners.DOT.join(existingSuperGrammar.getNameList()); + if (overrideSuperGrammarName.equals(existingSuperGrammarName)) { + isNewSuper = false; + } + } + if (isNewSuper) { + this.grammar.addSupergrammar(overrideSuperGrammar); + } + } + } + + @Override + public void visit(ASTClassProd node) { + this.grammar.removeClassProd(classProdMap.get(node.getName())); + this.grammar.addClassProd(node); + } + + @Override + public void visit(ASTEnumProd node) { + this.grammar.removeEnumProd(enumProdMap.get(node.getName())); + this.grammar.addEnumProd(node); + } + + @Override + public void visit(ASTInterfaceProd node) { + this.grammar.removeInterfaceProd(interfaceProdMap.get(node.getName())); + this.grammar.addInterfaceProd(node); + } + + @Override + public void visit(ASTAbstractProd node) { + this.grammar.removeAbstractProd(abstractProdMap.get(node.getName())); + this.grammar.addAbstractProd(node); + } + + @Override + public void visit(ASTExternalProd node) { + this.grammar.removeExternalProd(externalProdMap.get(node.getName())); + this.grammar.addExternalProd(node); + } + + @Override + public void visit(ASTLexProd node) { + this.grammar.removeLexProd(lexProdMap.get(node.getName())); + this.grammar.addLexProd(node); + } + + @Override + public void visit(ASTASTRule node) { + this.grammar.removeASTRule(astRuleMap.get(node.getType())); + this.grammar.addASTRule(node); + } + + @Override + public void visit(ASTGrammarOption srcNode) { + this.grammar.setGrammarOption(srcNode); + } + +} diff --git a/monticore-generator/src/main/java/de/monticore/tagging/TagSchemaDerivVisitor.java b/monticore-generator/src/main/java/de/monticore/tagging/TagSchemaDerivVisitor.java new file mode 100644 index 0000000000..04f1f41412 --- /dev/null +++ b/monticore-generator/src/main/java/de/monticore/tagging/TagSchemaDerivVisitor.java @@ -0,0 +1,64 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging; + +import de.monticore.codegen.cd2java._tagging.TaggingConstants; +import de.monticore.grammar.grammar.GrammarMill; +import de.monticore.grammar.grammar._ast.ASTClassProd; +import de.monticore.grammar.grammar._ast.ASTMCGrammar; +import de.monticore.grammar.grammar._ast.ASTMCGrammarBuilder; +import de.monticore.grammar.grammar._ast.ASTTerminal; +import de.monticore.grammar.grammar._symboltable.MCGrammarSymbolSurrogate; +import de.monticore.grammar.grammar._visitor.GrammarVisitor2; +import de.se_rwth.commons.Splitters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TagSchemaDerivVisitor implements GrammarVisitor2 { + + protected ASTMCGrammar grammar; + + @Override + public void visit(ASTMCGrammar node) { + ASTMCGrammarBuilder builder = GrammarMill.mCGrammarBuilder().setName(node.getName() + TaggingConstants.TAGSCHEMA_SUFFIX); + if (!node.getPackageList().isEmpty()) { + builder.setPackageList(new ArrayList<>(node.getPackageList())); + } + + // extends TagSchema + builder.getSupergrammarList().add(GrammarMill.grammarReferenceBuilder().setNamesList(Arrays.asList("de", "monticore", "tagging", TaggingConstants.TAGSCHEMA_SUFFIX)).build()); + + // extend the TagSchemas of all super languages + for (MCGrammarSymbolSurrogate superSurrogate : node.getSymbol().getSuperGrammars()) { + // We currently have to load the delegates, as the package name of a delegate might not be correct + List namesList = new ArrayList<>(Splitters.DOT.splitToList(superSurrogate.lazyLoadDelegate().getFullName())); + namesList.set(namesList.size() - 1, namesList.get(namesList.size() - 1) + TaggingConstants.TAGSCHEMA_SUFFIX); + + builder.addSupergrammar(GrammarMill.grammarReferenceBuilder().setNamesList(namesList).build()); + } + + builder.setComponent(node.isComponent()); + + this.grammar = builder.build(); + + this.grammar.setStartRule(GrammarMill.startRuleBuilder().setName(TaggingConstants.TAGSCHEMA_SUFFIX).build()); + } + + @Override + public void visit(ASTClassProd node) { + ASTClassProd classProd = GrammarMill.classProdBuilder().setName(node.getName() + "SchemaIdentifier").addSuperInterfaceRule(GrammarMill.ruleReferenceBuilder().setName("ScopeIdentifier").build()).addAlt(GrammarMill.altBuilder().addComponent(terminal(node.getName())).build()).build(); + + grammar.addClassProd(classProd); + } + + // DISCUSS: IV-D.2. via referenced symbols? or all Names? + + protected ASTTerminal terminal(String term) { + return GrammarMill.terminalBuilder().setName(term).build(); + } + + public ASTMCGrammar getGrammar() { + return grammar; + } +} diff --git a/monticore-generator/src/main/resources/de/monticore/monticore_emf.groovy b/monticore-generator/src/main/resources/de/monticore/monticore_emf.groovy index f7898b6bcc..a8a82277de 100644 --- a/monticore-generator/src/main/resources/de/monticore/monticore_emf.groovy +++ b/monticore-generator/src/main/resources/de/monticore/monticore_emf.groovy @@ -79,6 +79,10 @@ while (grammarIterator.hasNext()) { // M7: Decorate class diagrams decoratedCD = decorateEmfCD(glex, mcScope, cd, handcodedPath) + if (genTag) { + // Also decorate infrastructure for domain-specific tagging IFF this task is run on a tagging grammar + decoratedCD = decorateTagCD(glex, mcScope, cd, handcodedPath, decoratedCD, astGrammar) + } // groovy script hook point hook(gh2, glex, astGrammar, decoratedCD, cd) @@ -97,6 +101,11 @@ while (grammarIterator.hasNext()) { generateDSTLanguage(astGrammar, out, modelPathHC) } + if (!genTag) { + // Generate the tagging grammars (ending in TagSchema.mc4 and TagDefinition.mc4) + generateTaggingLanguages(astGrammar, out, modelPathHC) + } + // M9: Write reports to files // M9.1: Inform about successful completion for grammar Log.info("Grammar " + astGrammar.getName() + " processed successfully!", LOG_ID) diff --git a/monticore-generator/src/main/resources/de/monticore/monticore_noreports.groovy b/monticore-generator/src/main/resources/de/monticore/monticore_noreports.groovy index 9327a817af..b7c1213cb4 100644 --- a/monticore-generator/src/main/resources/de/monticore/monticore_noreports.groovy +++ b/monticore-generator/src/main/resources/de/monticore/monticore_noreports.groovy @@ -82,6 +82,10 @@ while (grammarIterator.hasNext()) { // M7: Decorate class diagrams decoratedCD = decorateCD(glex, mcScope, cd, handcodedPath) + if (genTag) { + // Also decorate infrastructure for domain-specific tagging IFF this task is run on a tagging grammar + decoratedCD = decorateTagCD(glex, mcScope, cd, handcodedPath, decoratedCD, astGrammar) + } // groovy script hook point hook(gh2, glex, astGrammar, decoratedCD, cd) @@ -100,6 +104,11 @@ while (grammarIterator.hasNext()) { generateDSTLanguage(astGrammar, out, modelPathHC) } + if (!genTag) { + // Generate the tagging grammars (ending in TagSchema.mc4 and TagDefinition.mc4) + generateTaggingLanguages(astGrammar, out, modelPathHC) + } + // M9: Write reports to files // M9.1: Inform about successful completion for grammar Log.info("Grammar " + astGrammar.getName() + " processed successfully!", LOG_ID) diff --git a/monticore-generator/src/main/resources/de/monticore/monticore_standard.groovy b/monticore-generator/src/main/resources/de/monticore/monticore_standard.groovy index 26ca05bfdd..4b02f6dfc7 100644 --- a/monticore-generator/src/main/resources/de/monticore/monticore_standard.groovy +++ b/monticore-generator/src/main/resources/de/monticore/monticore_standard.groovy @@ -84,6 +84,10 @@ while (grammarIterator.hasNext()) { // M7: Decorate class diagrams and report it decoratedCD = decorateCD(glex, mcScope, cd, handcodedPath) + if (genTag) { + // Also decorate infrastructure for domain-specific tagging IFF this task is run on a tagging grammar + decoratedCD = decorateTagCD(glex, mcScope, cd, handcodedPath, decoratedCD, astGrammar) + } reportDecoratedCD(decoratedCD, report) // groovy script hook point @@ -104,6 +108,11 @@ while (grammarIterator.hasNext()) { generateDSTLanguage(astGrammar, out, modelPathHC) } + if (!genTag) { + // Generate the tagging grammars (ending in TagSchema.mc4 and TagDefinition.mc4) for non-tag grammars + generateTaggingLanguages(astGrammar, out, modelPathHC) + } + // M9: Write reports to files // M9.1: Inform about successful completion for grammar Log.info("Grammar " + astGrammar.getName() + diff --git a/monticore-generator/src/main/resources/tagging/coco/Check.ftl b/monticore-generator/src/main/resources/tagging/coco/Check.ftl new file mode 100644 index 0000000000..027b87e63f --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/coco/Check.ftl @@ -0,0 +1,18 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("lang")} +TagConformanceChecker tagConformanceChecker = new TagConformanceChecker(tagSchemaSymbol); + +TagDataVisitor dataVisitor = new ${lang}TagDataVisitor(tagSchemaSymbol, tagConformanceChecker); +TagsTraverser dataVisitorTraverser = TagsMill.traverser(); +dataVisitorTraverser.add4Tags(dataVisitor); + +// Build the TagData object +tagUnit.accept(dataVisitorTraverser); + +// Check Default Identified + +TagElementVisitor tagElementVisitor = new TagElementVisitor(dataVisitor.getTagDataStack().peek(), tagConformanceChecker); +${lang}Traverser modelTraverser = ${lang}Mill.inheritanceTraverser(); +modelTraverser.add4IVisitor(tagElementVisitor); + +model.getEnclosingScope().accept(modelTraverser); \ No newline at end of file diff --git a/monticore-generator/src/main/resources/tagging/coco/Constructor.ftl b/monticore-generator/src/main/resources/tagging/coco/Constructor.ftl new file mode 100644 index 0000000000..82a9161488 --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/coco/Constructor.ftl @@ -0,0 +1,3 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature()} +super(model); diff --git a/monticore-generator/src/main/resources/tagging/coco/GetTraverser.ftl b/monticore-generator/src/main/resources/tagging/coco/GetTraverser.ftl new file mode 100644 index 0000000000..5ec795a3f6 --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/coco/GetTraverser.ftl @@ -0,0 +1,6 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("lang", "attribute")} +if (${attribute} == null) { + ${attribute} = ${lang}TagDefinitionMill.traverser(); +} +return ${attribute}; \ No newline at end of file diff --git a/monticore-generator/src/main/resources/tagging/coco/IdentifierTraverser.ftl b/monticore-generator/src/main/resources/tagging/coco/IdentifierTraverser.ftl new file mode 100644 index 0000000000..40f02a5c14 --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/coco/IdentifierTraverser.ftl @@ -0,0 +1,15 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("prodname")} +<#--Generate the visit method for Tag-Identifiers--> +<#assign service = glex.getGlobalVar("service")> + +@Override +public void visit(AST${prodname}Identifier node) { + String type = node.getIdentifies(); + + for (ASTTag tag : tagsToAdd) { + if (!tagConformanceChecker.verifyFor(tag, type)) { + Log.error("0xA5141${service.getGeneratedErrorCode(prodname)}: Concrete-Syntax tag " + tag + " could not be found: " + tag.get_SourcePositionStart()); + } + } +} diff --git a/monticore-generator/src/main/resources/tagging/coco/VisitorConstructor.ftl b/monticore-generator/src/main/resources/tagging/coco/VisitorConstructor.ftl new file mode 100644 index 0000000000..d918565649 --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/coco/VisitorConstructor.ftl @@ -0,0 +1,9 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("lang", "productions")} +super(tagSchemaSymbol, tagConformanceChecker); + +getIdentifierTraverser().add4${lang}TagDefinition(new ${lang}TagDefinitionVisitor2() { +<#list productions as p> + ${tc.includeArgs("IdentifierTraverser", p)} + +}); diff --git a/monticore-generator/src/main/resources/tagging/itagger/AddTag.ftl b/monticore-generator/src/main/resources/tagging/itagger/AddTag.ftl new file mode 100644 index 0000000000..f513ccb04c --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/itagger/AddTag.ftl @@ -0,0 +1,3 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("clazzname")} +${clazzname}.getInstance().addTag(model, astTagUnit, astTag); diff --git a/monticore-generator/src/main/resources/tagging/itagger/GetTags.ftl b/monticore-generator/src/main/resources/tagging/itagger/GetTags.ftl new file mode 100644 index 0000000000..bb6cb0c0eb --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/itagger/GetTags.ftl @@ -0,0 +1,3 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("clazzname")} +return ${clazzname}.getInstance().getTags(model, astTagUnit); diff --git a/monticore-generator/src/main/resources/tagging/itagger/RemoveTag.ftl b/monticore-generator/src/main/resources/tagging/itagger/RemoveTag.ftl new file mode 100644 index 0000000000..ff80162bee --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/itagger/RemoveTag.ftl @@ -0,0 +1,3 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("clazzname")} +return ${clazzname}.getInstance().removeTag(model, astTagUnit, astTag); diff --git a/monticore-generator/src/main/resources/tagging/tagger/AddTag.ftl b/monticore-generator/src/main/resources/tagging/tagger/AddTag.ftl new file mode 100644 index 0000000000..dfff582000 --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/tagger/AddTag.ftl @@ -0,0 +1,17 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("prodname", "grammarname", "package", "isSymbol")} +<#-- isSymbol: true for symbols --> +astTagUnit.addTags( + TagsMill.targetElementBuilder() + .addModelElementIdentifier( + <#if isSymbol> + TagsMill.defaultIdentBuilder().setMCQualifiedName( + TagsMill.mCQualifiedNameBuilder().setPartsList(de.se_rwth.commons.Splitters.QUALIFIED_NAME_DELIMITERS.splitToList(model.getSymbol().getFullName())).build() + ).build() + <#else> + ${package}tagdefinition.${grammarname}TagDefinitionMill.${prodname?uncap_first}IdentifierBuilder().set${prodname?cap_first}(model).build() + + ) + .addTag(astTag) + .build() +); \ No newline at end of file diff --git a/monticore-generator/src/main/resources/tagging/tagger/FindTargetsBy.ftl b/monticore-generator/src/main/resources/tagging/tagger/FindTargetsBy.ftl new file mode 100644 index 0000000000..212370279f --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/tagger/FindTargetsBy.ftl @@ -0,0 +1,5 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature()} +return ast.getTagsList().stream() +.filter(t -> t.getModelElementIdentifierList().stream() +.anyMatch(i -> isIdentified(i, element, travChecker))); diff --git a/monticore-generator/src/main/resources/tagging/tagger/GetArtifactScope.ftl b/monticore-generator/src/main/resources/tagging/tagger/GetArtifactScope.ftl new file mode 100644 index 0000000000..febb951aba --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/tagger/GetArtifactScope.ftl @@ -0,0 +1,8 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("millname")} +// While we could use instanceof, we instead use the semantic knowledge, that artifact scopes are subscopes of a global scope +List globalChildren = ${millname}.globalScope().getSubScopes(); +while (!(globalChildren.contains(s))) { + s = s.getEnclosingScope(); +} +return s; diff --git a/monticore-generator/src/main/resources/tagging/tagger/GetInstance.ftl b/monticore-generator/src/main/resources/tagging/tagger/GetInstance.ftl new file mode 100644 index 0000000000..fb4b1d12ca --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/tagger/GetInstance.ftl @@ -0,0 +1,6 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("clazz")} +if (INSTANCE == null) { + INSTANCE = new ${clazz}(); +} +return INSTANCE; diff --git a/monticore-generator/src/main/resources/tagging/tagger/GetTags.ftl b/monticore-generator/src/main/resources/tagging/tagger/GetTags.ftl new file mode 100644 index 0000000000..7bbc386bd7 --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/tagger/GetTags.ftl @@ -0,0 +1,48 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("prodname", "hasName")} +<#-- hasName: true for symbols or symbol-like productions --> +<#-- isSymbol: true for symbols --> +List< ASTTag> tags = new ArrayList<>(); + +List< String> scopeStack = getScopeDifferences(model.getEnclosingScope(), getArtifactScope(model.getEnclosingScope())); + +TravChecker${prodname} travChecker = new TravChecker${prodname}(); + +List< ASTContext> contexts; +if (scopeStack.isEmpty()) { + <#if hasName>findTargetsBy(astTagUnit, model.getName()).forEach(t -> tags.addAll(t.getTagList())); + findTargetsBy(astTagUnit, model, travChecker).forEach(t -> tags.addAll(t.getTagList())); +} else { + // within/context must always be on scopes, so we can use name matching instead of pattern matching + <#if hasName>scopeStack.add(model.getName()); + + contexts = findContextBy(astTagUnit, scopeStack.get(0)).collect(Collectors.toList()); + + <#if hasName> + String joinedNames = Joiners.DOT.join(scopeStack); + findTargetsBy(astTagUnit, joinedNames).forEach(t -> tags.addAll(t.getTagList())); + + + scopeStack.remove(0); + + while (scopeStack.size() > 1) { + List< ASTContext> tempContexts = contexts; + contexts = new ArrayList<>(); + <#if hasName>joinedNames = Joiners.DOT.join(scopeStack); + String name = scopeStack.remove(0); + + for (ASTContext context : tempContexts) { + findContextBy(context, name).forEach(contexts::add); + <#if hasName>findTargetsBy(context, joinedNames).forEach(t -> tags.addAll(t.getTagList())); + } + } + for (ASTContext context : contexts) { + <#if hasName> + findTargetsBy(context, scopeStack.get(0)).forEach(t -> tags.addAll(t.getTagList())); + <#else> + findTargetsBy(context, model, travChecker).forEach(t -> tags.addAll(t.getTagList())); + + } + +} +return tags; diff --git a/monticore-generator/src/main/resources/tagging/tagger/IsIdentified.ftl b/monticore-generator/src/main/resources/tagging/tagger/IsIdentified.ftl new file mode 100644 index 0000000000..61bca8d58c --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/tagger/IsIdentified.ftl @@ -0,0 +1,6 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature()} +travChecker.ret[0] = false; +travChecker.element = element; +elementIdentifier.accept(travChecker.traverser); +return travChecker.ret[0]; diff --git a/monticore-generator/src/main/resources/tagging/tagger/RemoveTag.ftl b/monticore-generator/src/main/resources/tagging/tagger/RemoveTag.ftl new file mode 100644 index 0000000000..539d4a470f --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/tagger/RemoveTag.ftl @@ -0,0 +1,58 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("prodname", "hasName")} +<#-- hasName: true for symbols or symbol-like productions --> +<#-- isSymbol: true for symbols --> + +List< String> scopeStack = getScopeDifferences(model.getEnclosingScope(), getArtifactScope(model.getEnclosingScope())); + +<#if !hasName> +TravChecker${prodname} travChecker = new TravChecker${prodname}(); + + +List< ASTContext> contexts; +if (scopeStack.isEmpty()) { + if( findTargetsBy(astTagUnit, model<#if hasName>.getName()<#else>, travChecker).map(x -> x.getTagList().remove(astTag)).filter(x -> x).findFirst() + .orElse(false)) { + return true; + } +} else { + // within/context must always be on scopes, so we can use name matching instead of pattern matching + <#if hasName>scopeStack.add(model.getName()); + + contexts = findContextBy(astTagUnit, scopeStack.get(0)).collect(Collectors.toList()); + + <#if hasName> + String joinedNames = Joiners.DOT.join(scopeStack); + if(findTargetsBy(astTagUnit, joinedNames).map(x -> x.getTagList().remove(astTag)).filter(x -> x).findFirst() + .orElse(false)) { + return true; + } + + + scopeStack.remove(0); + + while (scopeStack.size() > 1) { + List< ASTContext> tempContexts = contexts; + contexts = new ArrayList<>(); + <#if hasName>joinedNames = Joiners.DOT.join(scopeStack); + String name = scopeStack.remove(0); + + for (ASTContext context : tempContexts) { + findContextBy(context, name).forEach(contexts::add); + <#if hasName> + if(findTargetsBy(context, joinedNames).map(x -> x.getTagList().remove(astTag)).filter(x -> x).findFirst() + .orElse(false)) { + return true; + } + + } + } + for (ASTContext context : contexts) { + if (findTargetsBy(context, <#if hasName>scopeStack.get(0)<#else>model, travChecker).map(x -> x.getTagList().remove(astTag)).filter(x -> x).findFirst() + .orElse(false)) { + return true; + } + } + +} +return false; diff --git a/monticore-generator/src/main/resources/tagging/travchecker/TravCheckerConstructor.ftl b/monticore-generator/src/main/resources/tagging/travchecker/TravCheckerConstructor.ftl new file mode 100644 index 0000000000..cd0bf4094f --- /dev/null +++ b/monticore-generator/src/main/resources/tagging/travchecker/TravCheckerConstructor.ftl @@ -0,0 +1,12 @@ +<#-- (c) https://github.com/MontiCore/monticore --> +${tc.signature("prodname", "grammarname", "package")} +this.ret = new boolean[]{false}; +this.traverser = ${package}tagdefinition.${grammarname}TagDefinitionMill.traverser(); +this.traverser.add4${grammarname}TagDefinition(new ${package}tagdefinition._visitor.${grammarname}TagDefinitionVisitor2() { + @Override + public void visit(${package}tagdefinition._ast.AST${prodname}Identifier node) { + if (node.get${prodname}().deepEquals(element)) { + ret[0] = true; + } + } +}); diff --git a/monticore-grammar/build.gradle b/monticore-grammar/build.gradle index 991baab4a9..0879a3a810 100644 --- a/monticore-grammar/build.gradle +++ b/monticore-grammar/build.gradle @@ -24,6 +24,7 @@ if (!hasProperty('bootstrap')) { def grammarOutDir = "$buildDir/generated-sources/monticore/sourcecode" def testGrammarOutDir = "$buildDir/generated-test-sources/monticore/sourcecode" def trafoOutDir = "$buildDir/generated-sources/monticore/trafo" +def taggingOutDir = "$buildDir/generated-sources/monticore/tagging" sourceSets { @@ -41,6 +42,9 @@ sourceSets { trafo { java.srcDirs = [trafoOutDir] } + tagging { + java.srcDirs = [taggingOutDir] + } } dependencies { @@ -49,12 +53,15 @@ dependencies { implementation "org.apache.commons:commons-lang3:$commons_lang3_version" testImplementation "junit:junit:$junit_version" trafoCompileOnly project(path) - // use compileOnly as the trafo-artifact is only an extension to the default artifact - // ensure the trafoCompileOnly dependency line is below the trafo sourceset definition + taggingCompileOnly project(path) + // use compileOnly as the tagging- and trafo-artifact are only an extension to the default artifact } task generate {} task generateTR {} +task generateTagDef {} +task generateTagSchema {} + // one task per file fileTree("src/main/grammars").matching { include '**/*.mc4' }.each { @@ -62,6 +69,8 @@ fileTree("src/main/grammars").matching { include '**/*.mc4' }.each { def taskname = "generateGrammar${it.getName().substring(0, it.getName().lastIndexOf('.'))}" def withDSTLGen = ("true").equals(getProperty('genTR')) && !["ODRuleGeneration", "ODRules", "TFBasisExts", "TFCommons", // Do not generate TR grammars for TR-component grammars "Grammar", "Grammar_WithConcepts"].contains(it.getName().substring(0, it.getName().lastIndexOf('.'))) + def withTagGen = ("true").equals(getProperty('genTagging')) && !["TagSchema", "Tags", "Grammar", "Grammar_WithConcepts", "ODRuleGeneration", "ODRules", + "TFBasisExts", "TFCommons"].contains(it.getName().substring(0, it.getName().lastIndexOf('.'))) task "$taskname"(type: MCTask) { grammar = file g outputDir = file grammarOutDir @@ -91,6 +100,39 @@ fileTree("src/main/grammars").matching { include '**/*.mc4' }.each { } generateTR.dependsOn("${taskname}TR") } + if (withTagGen) { // run MC on the tag grammars + task "${taskname}TagDef"(type: MCTask) { + outputDir = file taggingOutDir + grammar = getTagDefinitionFile(g, file(grammarOutDir)) + modelPath(grammarOutDir) + modelPath(file("src/main/grammars").toString()) + if (findProperty("ci") != null) { + script = "de/monticore/monticore_noreports.groovy" + } + def grammarIncludingPackage = file("src/main/grammars").toURI().relativize(g.toURI()).toString() + def tagGrammarIncludingPackage = file(grammarOutDir).toURI().relativize(file(grammar).toURI()).toString() + outputs.upToDateWhen { incCheck(grammarIncludingPackage) } + outputs.upToDateWhen { incCheck(tagGrammarIncludingPackage) } + genTag = true // Generate Tagging-Infrastructure + dependsOn(generate) + } + generateTagDef.dependsOn("${taskname}TagDef") + task "${taskname}TagSchema"(type: MCTask) { + outputDir = file taggingOutDir + grammar = getTagSchemaFile(g, file(grammarOutDir)) + modelPath(grammarOutDir) + modelPath(file("src/main/grammars").toString()) + if (findProperty("ci") != null) { + script = "de/monticore/monticore_noreports.groovy" + } + def grammarIncludingPackage = file("src/main/grammars").toURI().relativize(g.toURI()).toString() + def tagGrammarIncludingPackage = file(grammarOutDir).toURI().relativize(file(grammar).toURI()).toString() + outputs.upToDateWhen { incCheck(grammarIncludingPackage) } + outputs.upToDateWhen { incCheck(tagGrammarIncludingPackage) } + dependsOn(generate) + } + generateTagSchema.dependsOn("${taskname}TagSchema") + } } // grammar dependencies without generated TR grammars @@ -139,6 +181,8 @@ def grammarDependencies = ext { // three dependencies LambdaExpressions = ["BasicSymbols", "MCBasicTypes", "ExpressionsBasis"] TFBasisExts = ["JavaLight", "MCSimpleGenericTypes", "MCCommonLiterals"] + Tags = ["MCBasics", "BasicSymbols", "MCCommonLiterals"] + TagSchema = ["MCBasics", "MCBasicTypes", "MCCommonLiterals"] // four dependencies MCCommon = ["Cardinality", "Completeness", "UMLModifier", "UMLStereotype"] @@ -156,7 +200,7 @@ def grammarDependencies = ext { } compileJava { - tasks.findAll { task -> task.name.startsWith("generateGrammar") && !task.name.contains("TR") }.each { + tasks.findAll { task -> task.name.startsWith("generateGrammar") && !task.name.contains("TR") && !task.name.contains("TagDef") && !task.name.contains("TagSchema") }.each { def grammarName = it.getName().substring('generateGrammar'.length()) def dependsOnGrammars = grammarDependencies[grammarName].collect { name -> tasks["generateGrammar${name}"] } it.dependsOn dependsOnGrammars @@ -171,6 +215,8 @@ compileTestJava { compileJava.dependsOn generate compileTrafoJava.dependsOn generateTR +compileTaggingJava.dependsOn generateTagDef +compileTaggingJava.dependsOn generateTagSchema sourcesJar.dependsOn project.collect { it.tasks.withType(MCTask) } jar.dependsOn(sourcesJar) @@ -468,3 +514,29 @@ if (("true").equals(getProperty('genTR'))) { trafo(trafoJar) } } +if (("true").equals(getProperty('genTagging'))) { + // Bundle and publish the generated tagging artifacts separately as monticore-grammar-tagging + tasks.register('taggingJar', Jar) { + from sourceSets.tagging.output + group 'build' + } + tasks.register('taggingSourcesJar', Jar) { + from sourceSets.tagging.java + group 'build' + } + publishing { + publications { + "tagging"(MavenPublication) { + artifactId = project.name + "-tagging" + artifact tasks.taggingJar { appendix 'tagging' } + artifact tasks.taggingSourcesJar { appendix 'tagging'; classifier 'sources' } + } + } + } + configurations { + tagging + } + artifacts { + tagging(taggingJar) + } +} diff --git a/monticore-grammar/src/main/grammars/de/monticore/tagging/TagSchema.mc4 b/monticore-grammar/src/main/grammars/de/monticore/tagging/TagSchema.mc4 new file mode 100644 index 0000000000..5146006405 --- /dev/null +++ b/monticore-grammar/src/main/grammars/de/monticore/tagging/TagSchema.mc4 @@ -0,0 +1,72 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging; + + +component grammar TagSchema + extends de.monticore.MCBasics, de.monticore.literals.MCCommonLiterals, de.monticore.types.MCBasicTypes { + symbol scope TagSchema = + ("package" package:MCQualifiedName ";")? + "tagschema" Name "{" TagType* "}" ; + + /** + * The various tag-types which can be used + * @attribute private this type can only be used within other tag types and not as a first level type + */ + interface symbol TagType; + symbolrule TagType = + r__private:boolean + scopeWildcard:boolean + scopes: List; + + /** + * Ways to identify a scope in a tagschema (mainly the name of the nonterminal) + */ + interface ScopeIdentifier; + + /** scope for a tagschema + * @attribute scopeIdentifier the productions targeted by this tagtype (mainly the name of the production) + * @attribute wildcard if all productions may be targeted by this tagtype + */ + TagScope = "for" ( (ScopeIdentifier || ",")+ | wildcard:["*"]) ; + + + /** A simple tag type, known by a pre-determined name + * @attribute name the name of the simple tag type + */ + symbol SimpleTagType implements TagType = + ["private"]? "tagtype" Name TagScope? ";" ; + + /** A tag type with a pre-determined name and arbitrary value + * @attribute name the name of the valued tag type + * @attribute type the type (Integer, Boolean, or String) for the value + */ + symbol ValuedTagType implements TagType = + ["private"]? "tagtype" Name ":" type:["int"|"String"|"Boolean"] TagScope? ";" ; + symbolrule ValuedTagType = type:int; + + /** A tag type with a pre-determined name and set of pre-determined values + * @attribute name the name of the enumerated tag type + * @attribute strings the list of allowed values + */ + symbol EnumeratedTagType implements TagType = + ["private"]? "tagtype" Name ":" "[" (String || "|")+ "]" TagScope? ";" ; + symbolrule EnumeratedTagType = values:List; + + /** A complex tag type, known by a pre-determined name and a number of inner variables + * @attribute name the name of the complex tag type + */ + symbol scope ComplexTagType implements TagType = + ["private"]? "tagtype" Name TagScope? + "{" (Reference || ",")+ ";" "}" ; + + symbol Reference = Name ":" ReferenceTyp iteration:["?"|"+"|"*"]? ; + symbolrule Reference = + referenceType: int + referencedTag: Optional; + + /** + * @attribute type if the type is a primitive-ish type, such as with valued tag types + * @attribute name if the reference is to another complex tag type (@TagType) + */ + ReferenceTyp = type:["int"|"String"|"Boolean"] | Name@TagType ; +} diff --git a/monticore-grammar/src/main/grammars/de/monticore/tagging/Tags.mc4 b/monticore-grammar/src/main/grammars/de/monticore/tagging/Tags.mc4 new file mode 100644 index 0000000000..c9760f9d9a --- /dev/null +++ b/monticore-grammar/src/main/grammars/de/monticore/tagging/Tags.mc4 @@ -0,0 +1,34 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging; + +/** + * Grammar for tagging of non-functional properties + */ +grammar Tags extends de.monticore.MCBasics, de.monticore.literals.MCCommonLiterals, de.monticore.types.MCBasicTypes { + scope TagUnit = + ("package" package:MCQualifiedName ";")? + MCImportStatement* + ("conforms" "to" conformsTo:(MCQualifiedName || ",")+ ";")? + + "tags" Name ("for" targetModel:ModelElementIdentifier)? + "{" (contexts:Context | tags:TargetElement)* "}" + ; + + scope Context = "within" ModelElementIdentifier "{" + (contexts:Context | tags:TargetElement)* + "}"; + + interface ModelElementIdentifier; + + // to identify elements by a FQN (elements with a name and/or symbol) + DefaultIdent implements ModelElementIdentifier = MCQualifiedName; + astrule DefaultIdent = method public String getIdentifies() { throw new IllegalStateException("DefaultIdent#getIdentifies() called"); }; + + interface Tag; + + TargetElement = "tag" (ModelElementIdentifier || ",")+ "with" (Tag || ",")+ ";"; + + SimpleTag implements Tag = Name; + ValuedTag implements Tag = Name "=" value:String; + ComplexTag implements Tag = Name "{" (Tag ("," Tag)* ";")? "}"; +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/AbstractTagger.java b/monticore-grammar/src/main/java/de/monticore/tagging/AbstractTagger.java new file mode 100644 index 0000000000..5a3ee02d66 --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/AbstractTagger.java @@ -0,0 +1,53 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging; + +import de.monticore.symboltable.IScope; +import de.monticore.tagging.tags._ast.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +public class AbstractTagger { + + /** + * Construct the difference of two scopes in regards to the named scopes + */ + protected List getScopeDifferences(IScope scope, IScope target) { + List scopeStack = new ArrayList<>(); + while (scope != target) { + if (!scope.isPresentName()) break; + scopeStack.add(0, scope.getName()); + scope = scope.getEnclosingScope(); + } + return scopeStack; + } + + protected static Stream findTargetsBy(ASTTagUnit astTag, String symbolName) { + return astTag.getTagsList().stream() + .filter(t -> t.getModelElementIdentifierList().stream().anyMatch(i -> isIdentified(i, symbolName))); + } + + protected static Stream findTargetsBy(ASTContext astContext, String symbolName) { + return astContext.getTagsList().stream() + .filter(t -> t.getModelElementIdentifierList().stream().anyMatch(i -> isIdentified(i, symbolName))); + } + + public static boolean isIdentified(ASTModelElementIdentifier elementIdentifier, String string) { + if (elementIdentifier instanceof ASTDefaultIdent) { + return ((ASTDefaultIdent) elementIdentifier).getMCQualifiedName().toString().equals(string); + } + return false; + } + + protected static Stream findContextBy(ASTTagUnit astTag, String symbolName) { + return astTag.getContextsList().stream() + .filter(c -> isIdentified(c.getModelElementIdentifier(), symbolName)); + } + + protected static Stream findContextBy(ASTContext astTag, String symbolName) { + return astTag.getContextsList().stream() + .filter(c -> isIdentified(c.getModelElementIdentifier(), symbolName)); + } + +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagConformanceChecker.java b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagConformanceChecker.java new file mode 100644 index 0000000000..8a781d634c --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagConformanceChecker.java @@ -0,0 +1,186 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging.conforms; + +import de.monticore.tagging.tags.TagsMill; +import de.monticore.tagging.tags._ast.*; +import de.monticore.tagging.tags._visitor.TagsHandler; +import de.monticore.tagging.tags._visitor.TagsTraverser; +import de.monticore.tagging.tags._visitor.TagsVisitor2; +import de.monticore.tagging.tagschema._ast.*; +import de.monticore.tagging.tagschema._symboltable.*; +import de.se_rwth.commons.logging.Log; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Visitor that checks if a tag conforms to the schema + */ +public class TagConformanceChecker implements TagsVisitor2 { + protected TagsTraverser traverser = TagsMill.traverser(); + protected TagSchemaData tagSchemaData; + + protected Optional scopeIdentifier = Optional.empty(); + protected boolean found = false; + + public TagConformanceChecker(TagSchemaSymbol tagSchemaSymbol) { + traverser.add4Tags(this); + + this.tagSchemaData = new TagSchemaData(tagSchemaSymbol); + } + + public boolean verifyFor(ASTTag node, String scopeIdentifier) { + this.scopeIdentifier = Optional.of(scopeIdentifier); + this.found = false; + node.accept(this.traverser); + this.traverser.clearTraversedElements(); + this.scopeIdentifier = Optional.empty(); + return this.found; + } + + @Override + public void visit(ASTSimpleTag node) { + if (tagSchemaData.getSimpleTagTypes().containsKey(scopeIdentifier.get()) && tagSchemaData.getSimpleTagTypes().get(scopeIdentifier.get()).stream().filter(x -> !x.isPrivate()).anyMatch(x -> x.getName().equals(node.getName()))) { + this.found = true; + } + if (tagSchemaData.getSimpleTagTypes().containsKey(TagSchemaData.WILDCARD) && tagSchemaData.getSimpleTagTypes().get(TagSchemaData.WILDCARD).stream().filter(x -> !x.isPrivate()).anyMatch(x -> x.getName().equals(node.getName()))) { + this.found = true; + } + } + + protected boolean checkValuedTag(ASTValuedTag node, ValuedTagTypeSymbol type) { + if (type.isPrivate()) return false; // Private may only be referenced by complex tags + if (!node.getName().equals(type.getName())) return false; + if (type.getType() == ASTConstantsTagSchema.STRING) + return true; + else if (type.getType() == ASTConstantsTagSchema.BOOLEAN) { + // By definition of {@link Boolean#parseBoolean(String)} anything except the string "true" is false + return true; + } else if (type.getType() == ASTConstantsTagSchema.INT) { + try { + Integer.parseInt(node.getValue()); + return true; + } catch (NumberFormatException expected) { + return false; + } + } + return false; + } + + @Override + public void visit(ASTValuedTag node) { + if (tagSchemaData.getValuedTagTypes().containsKey(scopeIdentifier.get()) && tagSchemaData.getValuedTagTypes().get(scopeIdentifier.get()) + .stream().anyMatch(type -> checkValuedTag(node, type))) { + this.found = true; + } else if (tagSchemaData.getValuedTagTypes().containsKey(TagSchemaData.WILDCARD) && tagSchemaData.getValuedTagTypes().get(TagSchemaData.WILDCARD) + .stream().anyMatch(type -> checkValuedTag(node, type))) { + this.found = true; + } + if (tagSchemaData.getEnumeratedTagTypes().containsKey(scopeIdentifier.get())) { + List enumeratedTagTypes = tagSchemaData.getEnumeratedTagTypes().get(scopeIdentifier.get()); + checkEnumeratedTagType(node, enumeratedTagTypes); + } + if (tagSchemaData.getEnumeratedTagTypes().containsKey(TagSchemaData.WILDCARD)) { + List enumeratedTagTypes = tagSchemaData.getEnumeratedTagTypes().get(TagSchemaData.WILDCARD); + checkEnumeratedTagType(node, enumeratedTagTypes); + } + } + + protected void checkEnumeratedTagType(ASTValuedTag node, List enumeratedTagTypes) { + if (enumeratedTagTypes.stream().filter(t -> !t.isPrivate()).filter(t -> t.getName().equals(node.getName())).anyMatch(tt -> tt.getValuesList().contains(node.getValue()))) { + this.found = true; + } else { + List allowedValues = enumeratedTagTypes.stream().filter(t -> t.getName().equals(node.getName())).map( + EnumeratedTagTypeSymbol::getValuesList).flatMap(Collection::stream).collect(Collectors.toList()); + Log.error("0x74683: Valued tag " + node.getName() + " is not in the set of allowed values: " + allowedValues + ", but " + node.getValue() + " present"); + } + } + + @Override + public void visit(ASTComplexTag node) { + if (tagSchemaData.getComplexTagTypes().containsKey(scopeIdentifier.get())) { + List tagTypes = tagSchemaData.getComplexTagTypes().get(scopeIdentifier.get()); + checkComplexTagType(node, tagTypes); + } + if (tagSchemaData.getComplexTagTypes().containsKey(TagSchemaData.WILDCARD)) { + List tagTypes = tagSchemaData.getComplexTagTypes().get(TagSchemaData.WILDCARD); + checkComplexTagType(node, tagTypes); + } + } + + protected void checkComplexTagType(ASTComplexTag node, List complexTagTypes) { + Optional complexTagTypesCandidate = complexTagTypes.stream().filter(t -> !t.isPrivate()).filter(t -> t.getName().equals(node.getName())).findFirst(); + if (complexTagTypesCandidate.isPresent()) { + checkComplexTag(node, complexTagTypesCandidate.get()); + this.found = true; + } else { + Log.error("0x74684: Complex tag " + node.getName() + " is not in the set of available complex tags"); + } + } + + protected void checkComplexTag(ASTComplexTag node, ComplexTagTypeSymbol schemaType) { + List referenceSymbols = schemaType.getSpannedScope().getLocalReferenceSymbols(); + if (node.getTagList().size() != referenceSymbols.size()) { + Log.error("0x74685: Complex tag " + node.getName() + " does not have the correct inner tag count"); + return; + } + + // use the visitor-pattern to check the inner elements + TagsTraverser tagsTraverser = TagsMill.traverser(); + tagsTraverser.setTagsHandler(new TagsHandler() { + TagsTraverser traverser; + + @Override + public TagsTraverser getTraverser() { + return traverser; + } + + @Override + public void setTraverser(TagsTraverser traverser) { + this.traverser = traverser; + } + + @Override + public void handle(ASTSimpleTag node) { + Log.error("0x74686: Simple tag used within complex tag"); + } + + @Override + public void handle(ASTValuedTag node) { + Optional reference = referenceSymbols.stream().filter(x -> x.getName().equals(node.getName())).findFirst(); + if (reference.isEmpty()) { + Log.error("0x74687: Complex tag " + schemaType.getName() + " does not allow inner tag of type " + node.getName()); + return; + } + if (reference.get().isPresentReferencedTag()) { + Log.error("0x74688: Complex tag " + schemaType.getName() + " requires a complex tag instead of simple tag " + node.getName()); + return; + } + checkSimpleRefType(reference.get(), node.getValue()); + } + + @Override + public void handle(ASTComplexTag node) { + // Check complex tags using the other traverser + } + }); + tagsTraverser.traverse(node); // traverse only inner elements + + found = true; + } + + protected void checkSimpleRefType(ReferenceSymbol referenceSymbol, String value) { + if (referenceSymbol.getReferenceType() == ASTConstantsTagSchema.STRING) { + // allow any value + } else if (referenceSymbol.getReferenceType() == ASTConstantsTagSchema.INT) { + try { + Integer.parseInt(value); + } catch (NumberFormatException ex) { + Log.error("0x74689: Complex tag requires integer reference"); + } + } else if (referenceSymbol.getReferenceType() == ASTConstantsTagSchema.BOOLEAN) { + if (!value.equals("true") && !value.equals("false")) + Log.error("0x74690: Complex tag requires boolean reference"); + } + } +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagConformsToSchemaCoCo.java b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagConformsToSchemaCoCo.java new file mode 100644 index 0000000000..7d9bd6e2dc --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagConformsToSchemaCoCo.java @@ -0,0 +1,100 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging.conforms; + +import de.monticore.ast.ASTNode; +import de.monticore.symboltable.IScope; +import de.monticore.symboltable.ISymbol; +import de.monticore.tagging.tags.TagsMill; +import de.monticore.tagging.tags._ast.ASTTag; +import de.monticore.tagging.tags._ast.ASTTagUnit; +import de.monticore.tagging.tags._cocos.TagsASTTagUnitCoCo; +import de.monticore.tagging.tagschema.TagSchemaMill; +import de.monticore.tagging.tagschema._symboltable.TagSchemaSymbol; +import de.monticore.types.mcbasictypes._ast.ASTMCQualifiedName; +import de.monticore.visitor.IVisitor; +import de.se_rwth.commons.logging.Log; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Stack; + +/** + * CoCo for checking if a TagDefinition conforms to a TagSchema + * + * @param the StartRule + */ +public abstract class TagConformsToSchemaCoCo implements TagsASTTagUnitCoCo { + + protected T model; + + public TagConformsToSchemaCoCo(T model) { + this.model = model; + } + + @Override + public void check(ASTTagUnit node) { + if (node.isEmptyConformsTo()) return; + + // Check for Schema from the "conforms to FQN;" + for (ASTMCQualifiedName schemaQN : node.getConformsToList()) { + Optional tagSchemaOpt = TagSchemaMill.globalScope().resolveTagSchema(schemaQN.toString()); + if (tagSchemaOpt.isPresent()) { + check(node, tagSchemaOpt.get()); + } else { + Log.error("0x74680: Unable to resolve for TagSchema " + schemaQN); + } + } + } + + protected abstract void check(ASTTagUnit tagUnit, TagSchemaSymbol tagSchema); + + public static class TagElementVisitor implements IVisitor { + protected final Stack qualifiedNameStack = new Stack<>(); + protected final TagData rootTagData; + protected final TagConformanceChecker tagConformanceChecker; + + public TagElementVisitor(TagData rootTagData, TagConformanceChecker tagConformanceChecker) { + this.rootTagData = rootTagData; + this.tagConformanceChecker = tagConformanceChecker; + } + + @Override + public void visit(ISymbol symbol) { + String identifierName = symbol.getAstNode().getClass().getSimpleName().substring("AST".length()); + + List ll = new ArrayList<>(qualifiedNameStack); + ll.add(symbol.getName()); + + List tags = rootTagData.getTagsQualified(ll); + + for (ASTTag tag : tags) { + if (!tagConformanceChecker.verifyFor(tag, identifierName)) { + // Most likely no tagtype was found for this tag + Log.error("0x74681: Tag " + getPrintableName(tag) + " (of " + identifierName + ") violates Schema at " + tag.get_SourcePositionStart()); + } + } + + } + + protected String getPrintableName(ASTTag tag) { + return StringUtils.abbreviate(StringUtils.normalizeSpace(TagsMill.prettyPrint(tag, false)), 40); + } + + @Override + public void visit(IScope scope) { + if (scope.isPresentName()) { + qualifiedNameStack.add(scope.getName()); + } + } + + @Override + public void endVisit(IScope scope) { + if (scope.isPresentName()) { + qualifiedNameStack.pop(); + } + } + } + +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagData.java b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagData.java new file mode 100644 index 0000000000..48fb020a7f --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagData.java @@ -0,0 +1,26 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging.conforms; + +import de.monticore.tagging.tags._ast.ASTTag; + +import java.util.*; + +/** + * Recursive data structure for hierarchical tag definition + */ +public class TagData { + protected final Map inner = new HashMap<>(); + + protected final List tags = new ArrayList<>(); + + public List getTagsQualified(Iterable parts) { + return getFromIterator(parts.iterator()).tags; + } + + public TagData getFromIterator(Iterator parts) { + if (parts.hasNext()) { + return inner.computeIfAbsent(parts.next(), s -> new TagData()).getFromIterator(parts); + } + return this; + } +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagDataVisitor.java b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagDataVisitor.java new file mode 100644 index 0000000000..d7be7a9e23 --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagDataVisitor.java @@ -0,0 +1,91 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging.conforms; + +import de.monticore.tagging.tags._ast.*; +import de.monticore.tagging.tags._visitor.TagsTraverser; +import de.monticore.tagging.tags._visitor.TagsVisitor2; +import de.monticore.tagging.tagschema._symboltable.TagSchemaSymbol; +import de.se_rwth.commons.logging.Log; + +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +/** + * Orchestration of visitors that build a {@link TagData} data-structure + * Implement me with the concrete traverser and concrete pattern matching visitors + */ +public abstract class TagDataVisitor implements TagsVisitor2 { + protected Stack tagDataStack; + + // the given tags for when traversing the element identifier + protected List tagsToAdd; + + protected final TagSchemaSymbol tagSchemaSymbol; + protected final TagConformanceChecker tagConformanceChecker; + + // Traverser working on de.monticore.tagging.tags._ast.ASTModelElementIdentifier for tag-TargetElements + protected abstract TagsTraverser getIdentifierTraverser(); + + // Traverser working on de.monticore.tagging.tags._ast.ASTModelElementIdentifier for within-contexts + protected abstract TagsTraverser getContextWithinTraverser(); + + protected TagDataVisitor(TagSchemaSymbol tagSchemaSymbol, TagConformanceChecker tagConformanceChecker) { + this.tagSchemaSymbol = tagSchemaSymbol; + this.tagConformanceChecker = tagConformanceChecker; + getIdentifierTraverser().add4Tags(new TagsVisitor2() { + @Override + public void visit(ASTDefaultIdent astDefaultIdent) { + TagData scope = tagDataStack.peek(); + List tags = scope.getTagsQualified(astDefaultIdent.getMCQualifiedName().getPartsList()); + tags.addAll(tagsToAdd); + } + }); + + + getContextWithinTraverser().add4Tags(new TagsVisitor2() { + @Override + public void visit(ASTDefaultIdent astDefaultIdent) { + TagData scope = tagDataStack.peek(); + TagData newScope = scope.getFromIterator(astDefaultIdent.getMCQualifiedName().getPartsList().iterator()); + tagDataStack.push(newScope); + } + // Discuss for next iteration: Are Pattern Matching withins desired? + }); + } + + public Stack getTagDataStack() { + return tagDataStack; + } + + + @Override + public void visit(ASTTagUnit node) { + this.tagDataStack = new Stack<>(); + TagData rootElem = new TagData(); + this.tagDataStack.push(rootElem); + } + + @Override + public void visit(final ASTTargetElement node) { + // "tag" (ModelElementIdentifier || ",")+ "with" (Tag || ",")+ ";"; + tagsToAdd = node.getTagList(); + node.getModelElementIdentifierList().forEach(id -> id.accept(getIdentifierTraverser())); + tagsToAdd = Collections.emptyList(); // invalidate tagsToAdd by means of an empty list + } + + @Override + public void visit(ASTContext node) { + int prevSize = this.tagDataStack.size(); + node.getModelElementIdentifier().accept(getContextWithinTraverser()); + if (prevSize + 1 != this.tagDataStack.size()) { + Log.error("0x74682: Invalid grow of ASTContext node to " + tagDataStack.size() + " from " + prevSize); + throw new IllegalStateException("0x74682: Invalid grow of ASTContext node to " + tagDataStack.size() + " from " + prevSize); + } + } + + @Override + public void endVisit(ASTContext node) { + this.tagDataStack.pop(); + } +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagSchemaData.java b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagSchemaData.java new file mode 100644 index 0000000000..c0fea39af3 --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/conforms/TagSchemaData.java @@ -0,0 +1,81 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging.conforms; + +import de.monticore.tagging.tagschema.TagSchemaMill; +import de.monticore.tagging.tagschema._symboltable.*; +import de.monticore.tagging.tagschema._visitor.TagSchemaTraverser; +import de.monticore.tagging.tagschema._visitor.TagSchemaVisitor2; + +import java.util.*; + +/** + * Stores data of TagTypeSymbols in a [NonTerminal => [TagTypeSymbols]] form + */ +public class TagSchemaData { + protected Map> simpleTagTypes = new HashMap<>(); + protected Map> valuedTagTypes = new HashMap<>(); + protected Map> enumeratedTagTypes = new HashMap<>(); + protected Map> complexTagTypes = new HashMap<>(); + + public final static String WILDCARD = "___WILDCARD_TAG_TYPE_%"; + + public TagSchemaData(TagSchemaSymbol symbol) { + TagSchemaTraverser tagSchemaTraverser = TagSchemaMill.traverser(); + tagSchemaTraverser.add4TagSchema(new CollectorVisitor()); + symbol.getSpannedScope().accept(tagSchemaTraverser); + } + + public Map> getSimpleTagTypes() { + return simpleTagTypes; + } + + public Map> getValuedTagTypes() { + return valuedTagTypes; + } + + public Map> getEnumeratedTagTypes() { + return enumeratedTagTypes; + } + + public Map> getComplexTagTypes() { + return complexTagTypes; + } + + public class CollectorVisitor implements TagSchemaVisitor2 { + @Override + public void visit(SimpleTagTypeSymbol node) { + for (String nonTermName : getFors(node)) { + getSimpleTagTypes().computeIfAbsent(nonTermName, (s) -> new ArrayList<>()).add(node); + } + } + + @Override + public void visit(ValuedTagTypeSymbol node) { + for (String nonTermName : getFors(node)) { + getValuedTagTypes().computeIfAbsent(nonTermName, (s) -> new ArrayList<>()).add(node); + } + } + + @Override + public void visit(EnumeratedTagTypeSymbol node) { + for (String nonTermName : getFors(node)) { + getEnumeratedTagTypes().computeIfAbsent(nonTermName, (s) -> new ArrayList<>()).add(node); + } + } + + @Override + public void visit(ComplexTagTypeSymbol node) { + for (String nonTermName : getFors(node)) { + getComplexTagTypes().computeIfAbsent(nonTermName, (s) -> new ArrayList<>()).add(node); + } + } + + protected Iterable getFors(TagTypeSymbol symbol) { + if (symbol.isScopeWildcard()) { + return Collections.singleton(WILDCARD); + } + return symbol.getScopesList(); + } + } + +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/tags/_ast/ASTModelElementIdentifier.java b/monticore-grammar/src/main/java/de/monticore/tagging/tags/_ast/ASTModelElementIdentifier.java new file mode 100644 index 0000000000..a318e14788 --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/tags/_ast/ASTModelElementIdentifier.java @@ -0,0 +1,10 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging.tags._ast; + +public interface ASTModelElementIdentifier extends ASTModelElementIdentifierTOP { + /** + * @return which production is identified and thus may be tagged by a tag-type + */ + public String getIdentifies(); + +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/tagschema/TagSchemaAfterParseTrafo.java b/monticore-grammar/src/main/java/de/monticore/tagging/tagschema/TagSchemaAfterParseTrafo.java new file mode 100644 index 0000000000..8463b67296 --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/tagschema/TagSchemaAfterParseTrafo.java @@ -0,0 +1,116 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging.tagschema; + +import de.monticore.tagging.tagschema._ast.*; +import de.monticore.tagging.tagschema._visitor.TagSchemaTraverser; +import de.monticore.tagging.tagschema._visitor.TagSchemaVisitor2; +import de.se_rwth.commons.logging.Log; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +// Convert +// tagtype A for P1; tagtype A for P2; +// to a single +// tagtype A for P1, P2; +public class TagSchemaAfterParseTrafo { + protected TagSchemaTraverser traverser; + + // TagType => Scope + protected Map tagScopeMap = new HashMap<>(); + // Ensure all tagtypes of the same name are of the same kind (simple, valued, enumerated, ...) + protected Map> tagKindMap = new HashMap<>(); + + protected Set markedForDeletion = new HashSet<>(); + + public TagSchemaAfterParseTrafo() { + this.traverser = TagSchemaMill.traverser(); + + this.traverser.add4TagSchema(new TagSchemaVisitor2() { + + @Override + public void visit(ASTTagSchema node) { + markedForDeletion.clear(); + } + + void ensureUniqueKind(ASTTagType node){ + if (tagKindMap.computeIfAbsent(node.getName(), s -> node.getClass()) != node.getClass()) { + Log.error("0x74691: Tagtype " + node.getName() + " is re-defined with a different kind at " + node.get_SourcePositionStart()); + } + } + + @Override + public void visit(ASTSimpleTagType node) { + ensureUniqueKind(node); + if (tagScopeMap.containsKey(node.getName())) { + addToTagScope(tagScopeMap.get(node.getName()), node.getTagScope()); + markedForDeletion.add(node); + } else { + tagScopeMap.put(node.getName(), node.getTagScope()); + } + } + + @Override + public void visit(ASTValuedTagType node) { + ensureUniqueKind(node); + if (tagScopeMap.containsKey(node.getName())) { + addToTagScope(tagScopeMap.get(node.getName()), node.getTagScope()); + markedForDeletion.add(node); + } else { + tagScopeMap.put(node.getName(), node.getTagScope()); + } + } + + @Override + public void visit(ASTEnumeratedTagType node) { + ensureUniqueKind(node); + if (tagScopeMap.containsKey(node.getName())) { + addToTagScope(tagScopeMap.get(node.getName()), node.getTagScope()); + markedForDeletion.add(node); + } else { + tagScopeMap.put(node.getName(), node.getTagScope()); + } + } + + @Override + public void visit(ASTComplexTagType node) { + ensureUniqueKind(node); + if (tagScopeMap.containsKey(node.getName())) { + addToTagScope(tagScopeMap.get(node.getName()), node.getTagScope()); + markedForDeletion.add(node); + } else { + tagScopeMap.put(node.getName(), node.getTagScope()); + } + } + + @Override + public void endVisit(ASTTagSchema node) { + node.removeAllTagTypes(markedForDeletion); + } + }); + } + + protected void addToTagScope(ASTTagScope target, ASTTagScope source) { + if (target.isWildcard()) { + return; + } + if (source.isWildcard()) { + target.setWildcard(true); + target.clearScopeIdentifiers(); + return; + } + target.addAllScopeIdentifiers(source.getScopeIdentifierList()); + } + + protected TagSchemaTraverser getTraverser() { + return this.traverser; + } + + public void transform(ASTTagSchema tagSchema) { + this.getTraverser().clearTraversedElements(); + tagSchema.accept(this.getTraverser()); + } + +} diff --git a/monticore-grammar/src/main/java/de/monticore/tagging/tagschema/_symboltable/TagSchemaScopesGenitor.java b/monticore-grammar/src/main/java/de/monticore/tagging/tagschema/_symboltable/TagSchemaScopesGenitor.java new file mode 100644 index 0000000000..c6f1cb6b99 --- /dev/null +++ b/monticore-grammar/src/main/java/de/monticore/tagging/tagschema/_symboltable/TagSchemaScopesGenitor.java @@ -0,0 +1,73 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.tagging.tagschema._symboltable; + +import de.monticore.tagging.tagschema._ast.*; + + +public class TagSchemaScopesGenitor extends TagSchemaScopesGenitorTOP { + + @Override + public void visit(ASTTagSchema node) { + super.visit(node); + } + + @Override + public void endVisit(ASTTagSchema node) { + super.endVisit(node); + } + + + @Override + public void endVisit(ASTSimpleTagType node) { + super.endVisit(node); + node.getSymbol().setPrivate(node.isPrivate()); + node.getSymbol().setScopeWildcard(node.getTagScope().isWildcard()); + node.getTagScope().getScopeIdentifierList().forEach(si -> node.getSymbol().addScopes(getScopeIdentifier(si))); + } + + @Override + public void endVisit(ASTValuedTagType node) { + super.endVisit(node); + node.getSymbol().setPrivate(node.isPrivate()); + node.getSymbol().setScopeWildcard(node.getTagScope().isWildcard()); + node.getTagScope().getScopeIdentifierList().forEach(si -> node.getSymbol().addScopes(getScopeIdentifier(si))); + node.getSymbol().setType(node.getType()); + } + + @Override + public void endVisit(ASTEnumeratedTagType node) { + super.endVisit(node); + node.getSymbol().setPrivate(node.isPrivate()); + node.getSymbol().setScopeWildcard(node.getTagScope().isWildcard()); + node.getTagScope().getScopeIdentifierList().forEach(si -> node.getSymbol().addScopes(getScopeIdentifier(si))); + node.getSymbol().setValuesList(node.getStringList()); + } + + @Override + public void endVisit(ASTComplexTagType node) { + super.endVisit(node); + node.getSymbol().setPrivate(node.isPrivate()); + node.getSymbol().setScopeWildcard(node.getTagScope().isWildcard()); + node.getTagScope().getScopeIdentifierList().forEach(si -> node.getSymbol().addScopes(getScopeIdentifier(si))); + } + + @Override + public void endVisit(ASTReference node){ + super.endVisit(node); + node.getSymbol().setReferenceType(node.getReferenceTyp().getType()); + if (node.getReferenceTyp().isPresentName()) + node.getSymbol().setReferencedTag(node.getReferenceTyp().getName()); + else + node.getSymbol().setReferencedTagAbsent(); + } + + + /** + * Returns the concrete syntax represented by a SchemaIdentifier + */ + protected String getScopeIdentifier(ASTScopeIdentifier scopeIdentifier) { + String n = scopeIdentifier.getClass().getSimpleName().substring("AST".length()); + + return n.substring(0, n.length() - "SchemaIdentifier".length()); + } +} diff --git a/monticore-test/01.experiments/tagging/build.gradle b/monticore-test/01.experiments/tagging/build.gradle new file mode 100644 index 0000000000..1bf6b7b209 --- /dev/null +++ b/monticore-test/01.experiments/tagging/build.gradle @@ -0,0 +1,80 @@ +/* (c) https://github.com/MontiCore/monticore */ +description = 'Experiments: tagging' + +def grammarOutDir = "$buildDir/generated-sources/monticore/sourcecode" + + +dependencies { + implementation project(path: ':monticore-grammar', configuration: 'tagging') +} + +task generate (type: MCTask) { + grammar = file "$projectDir/$grammarDir/Automata.mc4" + outputDir = file outDir + def uptoDate = incCheck("Automata.mc4") + outputs.upToDateWhen { uptoDate } +} + +task generateTagDefinition (type: MCTask, dependsOn: generate) { + grammar = file "$grammarOutDir/AutomataTagDefinition.mc4" + outputDir = file outDir + def uptoDate = incCheck("AutomataTagDefinition.mc4") + outputs.upToDateWhen { uptoDate } + genTag = true // generate tagging related infrastructure, such as the Taggers +} +task generateTagSchema (type: MCTask, dependsOn: generate) { + grammar = file "$grammarOutDir/AutomataTagSchema.mc4" + outputDir = file outDir + def uptoDate = incCheck("AutomataTagSchema.mc4") + outputs.upToDateWhen { uptoDate } +} + +// ############ + +task generateFQN (type: MCTask) { + grammar = file "$projectDir/$grammarDir/de/monticore/fqn/FQNAutomata.mc4" + outputDir = file outDir + def uptoDate = incCheck("de/monticore/fqn/FQNAutomata.mc4") + outputs.upToDateWhen { uptoDate } +} +task generateFQNTagDefinition (type: MCTask, dependsOn: generateFQN) { + grammar = file "$grammarOutDir/de/monticore/fqn/FQNAutomataTagDefinition.mc4" + outputDir = file outDir + def uptoDate = incCheck("de/monticore/fqn/FQNAutomataTagDefinition.mc4") + outputs.upToDateWhen { uptoDate } + genTag = true +} +task generateFQNTagSchema (type: MCTask, dependsOn: generateFQN) { + grammar = file "$grammarOutDir/de/monticore/fqn/FQNAutomataTagSchema.mc4" + outputDir = file outDir + def uptoDate = incCheck("de/monticore/fqn/FQNAutomataTagSchema.mc4") + outputs.upToDateWhen { uptoDate } +} + +// Note: This revision of tagging does not come with automatic support for tagging of super-grammars +// Here, we manually add this support using the FQNEnhancedAutomataHC mc4 grammar +task generateEnhancedFQN (type: MCTask, dependsOn: generateFQN) { + grammar = file "$projectDir/$grammarDir/de/monticore/fqn/FQNEnhancedAutomata.mc4" + modelPath("$projectDir/$grammarDir") + modelPath("$projectDir/target/generated-sources/monticore/sourcecode") // provide the model path for Automata-Tag-Grammars + outputDir = file outDir + def uptoDate = incCheck("de/monticore/fqn/FQNEnhancedAutomata.mc4") + outputs.upToDateWhen { uptoDate } +} +task generateEnhancedFQNTagDefinition (type: MCTask, dependsOn: generateEnhancedFQN) { + grammar = file "$grammarOutDir/de/monticore/fqn/FQNEnhancedAutomataTagDefinition.mc4" + modelPath("$projectDir/$grammarDir") + modelPath("$grammarOutDir") // provide the model path for Automata-Tag-Grammars + outputDir = file outDir + def uptoDate = incCheck("de/monticore/fqn/FQNEnhancedAutomataTagDefinition.mc4") + outputs.upToDateWhen { uptoDate } + genTag = true +} +task generateEnhancedFQNTagSchema (type: MCTask, dependsOn: generateEnhancedFQN) { + grammar = file "$grammarOutDir/de/monticore/fqn/FQNEnhancedAutomataTagSchema.mc4" + modelPath("$projectDir/$grammarDir") + modelPath("$grammarOutDir") // provide the model path for Automata-Tag-Grammars + outputDir = file outDir + def uptoDate = incCheck("de/monticore/fqn/FQNEnhancedAutomataTagSchema.mc4") + outputs.upToDateWhen { uptoDate } +} diff --git a/monticore-test/01.experiments/tagging/gradle.properties b/monticore-test/01.experiments/tagging/gradle.properties new file mode 100644 index 0000000000..2af9575b46 --- /dev/null +++ b/monticore-test/01.experiments/tagging/gradle.properties @@ -0,0 +1,4 @@ +# This is used for testing the --version command, the versions given here are meaningless! + +version=1.2.3 +mc_version=7.3.0 diff --git a/monticore-test/01.experiments/tagging/src/main/grammars/Automata.mc4 b/monticore-test/01.experiments/tagging/src/main/grammars/Automata.mc4 new file mode 100644 index 0000000000..31ded1563e --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/main/grammars/Automata.mc4 @@ -0,0 +1,42 @@ +/* (c) https://github.com/MontiCore/monticore */ + +grammar Automata extends de.monticore.MCBasics { + +/** A ASTAutomaton represents a finite automaton + @attribute name Name of the automaton + @attribute states List of states + @attribute transitions List of transitions +*/ +symbol Automaton = + "automaton" Name "{" (State | Transition | ScopedState)* "}" ; + +/** A ASTState represents a state of a finite automaton + @attribute name Name of state + @attribute initial True if state is initial state + @attribute final True if state is a final state + @attribute states List of sub states + @attribute transitions List of transitions +*/ +symbol State implements ScopedStateElement = + "state" Name + + (("<<" ["initial"] ">>" ) | ("<<" ["final"] ">>" ))* + + ( ("{" (State | Transition)* "}") | ";") ; + + +/** A ASTTransition represents a transition + @attribute from Name of the state from which the transitions starts + @attribute input Activation signal for this transition + @attribute to Name of the state to which the transitions goes +*/ +Transition implements ScopedStateElement = + from:Name "-" input:Name ">" to:Name ";" ; + + +symbol scope ScopedState implements ScopedStateElement = + "scopedstate" Name "{" ScopedStateElement* "}" ; + +interface ScopedStateElement; + +} diff --git a/monticore-test/01.experiments/tagging/src/main/grammars/de/monticore/fqn/FQNAutomata.mc4 b/monticore-test/01.experiments/tagging/src/main/grammars/de/monticore/fqn/FQNAutomata.mc4 new file mode 100644 index 0000000000..4f9a2727fd --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/main/grammars/de/monticore/fqn/FQNAutomata.mc4 @@ -0,0 +1,45 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.fqn; + +grammar FQNAutomata extends de.monticore.MCBasics { + +/** A ASTAutomaton represents a finite automaton + @attribute name Name of the automaton + @attribute states List of states + @attribute transitions List of transitions +*/ +symbol Automaton = + "automaton" Name "{" (AutomatonElement)* "}" ; + +interface AutomatonElement; + +/** A ASTState represents a state of a finite automaton + @attribute name Name of state + @attribute initial True if state is initial state + @attribute final True if state is a final state + @attribute states List of sub states + @attribute transitions List of transitions +*/ +symbol State implements ScopedStateElement = + "state" Name + + (("<<" ["initial"] ">>" ) | ("<<" ["final"] ">>" ))* + + ( ("{" (State | Transition)* "}") | ";") ; + + +/** A ASTTransition represents a transition + @attribute from Name of the state from which the transitions starts + @attribute input Activation signal for this transition + @attribute to Name of the state to which the transitions goes +*/ +Transition implements ScopedStateElement = + from:Name "-" input:Name ">" to:Name ";" ; + + +symbol scope ScopedState implements ScopedStateElement = + "scopedstate" Name "{" ScopedStateElement* "}" ; + +interface ScopedStateElement extends AutomatonElement; + +} diff --git a/monticore-test/01.experiments/tagging/src/main/grammars/de/monticore/fqn/FQNEnhancedAutomata.mc4 b/monticore-test/01.experiments/tagging/src/main/grammars/de/monticore/fqn/FQNEnhancedAutomata.mc4 new file mode 100644 index 0000000000..5f3a55b507 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/main/grammars/de/monticore/fqn/FQNEnhancedAutomata.mc4 @@ -0,0 +1,27 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.fqn; + +grammar FQNEnhancedAutomata extends de.monticore.fqn.FQNAutomata { + start Automaton; + symbol RedState implements ScopedStateElement = + "red" "state" Name + + (("<<" ["initial"] ">>" ) | ("<<" ["final"] ">>" ))* + + ( ("{" (State | Transition)* "}") | ";") ; + + + /** A ASTTransition represents a transitio0n + @attribute from Name of the state from which the transitions starts + @attribute input Activation signal for this transition + @attribute to Name of the state to which the transitions goes + */ + RedTransition implements ScopedStateElement = + "red" from:Name "-" input:Name ">" to:Name ";" ; + + + symbol scope RedScopedState implements ScopedStateElement = + "red" "scopedstate" Name "{" ScopedStateElement* "}" ; + + +} diff --git a/monticore-test/01.experiments/tagging/src/main/grammarsHC/de/monticore/fqn/FQNEnhancedAutomataTagDefinitionHC.mc4 b/monticore-test/01.experiments/tagging/src/main/grammarsHC/de/monticore/fqn/FQNEnhancedAutomataTagDefinitionHC.mc4 new file mode 100644 index 0000000000..39de92ac8e --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/main/grammarsHC/de/monticore/fqn/FQNEnhancedAutomataTagDefinitionHC.mc4 @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.fqn; + +// Include FQNAutomata-TagDefinition +grammar FQNEnhancedAutomataTagDefinitionHC extends de.monticore.fqn.FQNAutomataTagDefinition { + +} diff --git a/monticore-test/01.experiments/tagging/src/test/java/AutomataSchemaTest.java b/monticore-test/01.experiments/tagging/src/test/java/AutomataSchemaTest.java new file mode 100644 index 0000000000..c6571c4ec4 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/java/AutomataSchemaTest.java @@ -0,0 +1,214 @@ +/* (c) https://github.com/MontiCore/monticore */ + +import automata.AutomataMill; +import automata._ast.ASTAutomaton; +import automata._tagging.AutomataTagConformsToSchemaCoCo; +import automatatagdefinition.AutomataTagDefinitionMill; +import automatatagdefinition._cocos.AutomataTagDefinitionCoCoChecker; +import automatatagschema.AutomataTagSchemaMill; +import automatatagschema._symboltable.AutomataTagSchemaSymbols2Json; +import automatatagschema._symboltable.IAutomataTagSchemaArtifactScope; +import com.google.common.io.Files; +import de.monticore.tagging.tags._ast.ASTTagUnit; +import de.monticore.tagging.tagschema._ast.ASTTagSchema; +import de.se_rwth.commons.logging.Log; +import de.se_rwth.commons.logging.LogStub; +import org.apache.commons.io.FileUtils; +import org.junit.*; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +public class AutomataSchemaTest { + protected static ASTAutomaton model; + + static File tempDir; + + @BeforeClass + public static void prepare() throws Exception { + tempDir = java.nio.file.Files.createTempDirectory("AutomataSchemaTest").toFile(); + if (!tempDir.exists()) + tempDir.mkdirs(); + LogStub.init(); + Log.enableFailQuick(false); + + AutomataMill.init(); + model = AutomataMill.parser() + .parse("src/test/resources/models/Simple.aut").orElseThrow(); + AutomataMill.scopesGenitorDelegator().createFromAST(model); + + AutomataTagSchemaMill.init(); + + AutomataTagSchemaSymbols2Json symbols2Json = new AutomataTagSchemaSymbols2Json(); + + // Serialize all schemas and store the .sym files (so they can be loaded later on) + // Note, that the resolving using packages is not supported for tagschemas + for (File f : Objects.requireNonNull( + new File("src/test/resources/schema/").listFiles(x -> x.getName().endsWith(".tagschema")))) { + Optional schemaOpt = AutomataTagSchemaMill.parser().parse(f.getAbsolutePath()); + if (schemaOpt.isPresent()) { + new de.monticore.tagging.tagschema.TagSchemaAfterParseTrafo().transform(schemaOpt.get()); + IAutomataTagSchemaArtifactScope artifactScope = AutomataTagSchemaMill.scopesGenitorDelegator().createFromAST(schemaOpt.get()); + String symFile = new File(new File(tempDir, "schema"), Files.getNameWithoutExtension(f.getName()) + ".sym").toString(); + symbols2Json.store(artifactScope, symFile); + Assert.assertEquals("TagSchema " + f + " logged errors",0, Log.getErrorCount()); + } else { + Log.clearFindings(); + Log.warn("Failed to load TagSchema " + f); + } + } + // Clear the scope again + AutomataTagSchemaMill.globalScope().clear(); + // And add the serialized schema-symbols files to the symbol path + AutomataTagSchemaMill.globalScope().getSymbolPath().addEntry(new File(tempDir, "schema").toPath()); + + // Finally, switch to the TagDef language + AutomataTagDefinitionMill.init(); + Assert.assertEquals(0, Log.getErrorCount()); + } + + @Before + public void beforeEach() { + Log.clearFindings(); + } + + @Test + public void testResolve() throws IOException { + Assert.assertTrue(AutomataTagSchemaMill.globalScope().resolveTagSchema("AutomataSchema").isPresent()); + Assert.assertEquals(0, Log.getErrorCount()); + } + + @Test + public void testValidTags1() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/Simple.tags"); + Assert.assertEquals(0, Log.getErrorCount()); + } + + @Test + public void testSpotRootWithSimpleInsteadOfValued() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidTags1.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNWithInvalidSimpleTag() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidTags2.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNHWithInvalidSimpleTag() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidTags3.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNHWithInvalidValuedTag() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidTags4.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNWithinWithInvalidSimpleTag() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidTags5.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testComplexMissing() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidTags7.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testComplexTooMany() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidTags8.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + + @Test + public void testFQNWithinWithInvalidPrivateTag() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidTagsPrivate.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNComplexMissingInner() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidComplexTags1.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNComplexTooManyInner() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidComplexTags2.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNComplexTypoInner() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidComplexTags3.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNComplexNotANumber() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidComplexTags4.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNComplexNotABoolean() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidComplexTags5.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNComplexUnknownInnerComplex() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidComplexTags6.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNComplexInvalidInnerComplex() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidComplexTags7.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNPattern() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidPatternTags1.tags"); + Assert.assertEquals(1, Log.getErrorCount()); + } + + @Test + public void testFQNPatternWithin() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/InvalidPatternTags2.tags"); + Assert.assertEquals(2, Log.getErrorCount()); + } + + @Test + public void testFQNNonConformingTags() throws IOException { + testTagDefCoCoChecker("src/test/resources/models/NonConformingTags.tags"); + Assert.assertEquals(0, Log.getErrorCount()); + } + + protected void testTagDefCoCoChecker(String file) throws IOException { + AutomataTagDefinitionCoCoChecker coCoChecker = new AutomataTagDefinitionCoCoChecker(); + coCoChecker.addCoCo(new AutomataTagConformsToSchemaCoCo(model)); + + Optional n = AutomataTagDefinitionMill.parser().parse(file); + AutomataTagDefinitionMill.scopesGenitorDelegator().createFromAST(n.get()); + + coCoChecker.checkAll(n.get()); + } + + @AfterClass + public static void cleanUp() throws IOException { + if (tempDir.exists()) { + FileUtils.deleteDirectory(tempDir); + } + } +} diff --git a/monticore-test/01.experiments/tagging/src/test/java/FQNEnhancedAutomataSchemaTest.java b/monticore-test/01.experiments/tagging/src/test/java/FQNEnhancedAutomataSchemaTest.java new file mode 100644 index 0000000000..9f63b26446 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/java/FQNEnhancedAutomataSchemaTest.java @@ -0,0 +1,116 @@ +/* (c) https://github.com/MontiCore/monticore */ + +import de.monticore.fqn.fqnautomata._ast.ASTAutomaton; +import de.monticore.fqn.fqnautomata._tagging.FQNAutomataTagConformsToSchemaCoCo; +import de.monticore.fqn.fqnenhancedautomata.FQNEnhancedAutomataMill; +import de.monticore.fqn.fqnenhancedautomata._tagging.FQNEnhancedAutomataTagConformsToSchemaCoCo; +import de.monticore.fqn.fqnenhancedautomatatagdefinition.FQNEnhancedAutomataTagDefinitionMill; +import de.monticore.fqn.fqnenhancedautomatatagdefinition._cocos.FQNEnhancedAutomataTagDefinitionCoCoChecker; +import de.monticore.fqn.fqnenhancedautomatatagschema.FQNEnhancedAutomataTagSchemaMill; +import de.monticore.tagging.tags._ast.ASTTagUnit; +import de.monticore.tagging.tagschema._ast.ASTTagSchema; +import de.se_rwth.commons.logging.Log; +import de.se_rwth.commons.logging.LogStub; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +public class FQNEnhancedAutomataSchemaTest { + protected static ASTAutomaton model; + + @BeforeClass + public static void prepare() throws Exception { + LogStub.init(); + Log.enableFailQuick(false); + + FQNEnhancedAutomataMill.init(); + model = FQNEnhancedAutomataMill.parser() + .parse("src/test/resources/models/Simple.aut").orElseThrow(); + FQNEnhancedAutomataMill.scopesGenitorDelegator().createFromAST(model); + + FQNEnhancedAutomataTagSchemaMill.init(); + FQNEnhancedAutomataTagSchemaMill.globalScope().getSymbolPath().addEntry(new File("src/test/resources/").toPath()); + + for (File f : Objects.requireNonNull( + new File("src/test/resources/schema/").listFiles(x -> x.getName().endsWith(".tagschema")))) { + Optional schemaOpt = FQNEnhancedAutomataTagSchemaMill.parser().parse(f.getAbsolutePath()); + if (schemaOpt.isPresent()) { + new de.monticore.tagging.tagschema.TagSchemaAfterParseTrafo().transform(schemaOpt.get()); + FQNEnhancedAutomataTagSchemaMill.scopesGenitorDelegator().createFromAST(schemaOpt.get()); + } else + Log.warn("Failed to load TagSchema " + f); + } + + FQNEnhancedAutomataTagDefinitionMill.init(); + } + + @Before + public void beforeEach() { + Log.clearFindings(); + } + + @Test + public void testValidTags1() throws IOException { + testCoCo("src/test/resources/models/Simple.tags"); + Assert.assertEquals(0, Log.getErrorCount()); + } + + @Test + public void testSpotRootWithSimpleInsteadOfValued() throws IOException { + testCoCo("src/test/resources/models/InvalidTags1.tags"); + Assert.assertEquals(2, Log.getErrorCount()); + } + + @Test + public void testFQNWithInvalidSimpleTag() throws IOException { + testCoCo("src/test/resources/models/InvalidTags2.tags"); + Assert.assertEquals(2, Log.getErrorCount()); + } + + @Test + public void testFQNHWithInvalidSimpleTag() throws IOException { + testCoCo("src/test/resources/models/InvalidTags3.tags"); + Assert.assertEquals(2, Log.getErrorCount()); + } + + @Test + public void testFQNHWithInvalidValuedTag() throws IOException { + testCoCo("src/test/resources/models/InvalidTags4.tags"); + Assert.assertEquals(2, Log.getErrorCount()); + } + + @Test + public void testFQNWithinWithInvalidSimpleTag() throws IOException { + testCoCo("src/test/resources/models/InvalidTags5.tags"); + Assert.assertEquals(2, Log.getErrorCount()); + } + + @Test + public void testFQNWithinWithInvalidPrivateTag() throws IOException { + testCoCo("src/test/resources/models/InvalidTagsPrivate.tags"); + Assert.assertEquals(2, Log.getErrorCount()); + } + + @Test + public void testEnhancedFQNNonExtendedTag() throws IOException { + testCoCo("src/test/resources/models/InvalidEnhancedTags1.tags"); + Assert.assertEquals(2, Log.getErrorCount()); + } + + protected void testCoCo(String file) throws IOException { + FQNEnhancedAutomataTagDefinitionCoCoChecker coCoChecker = new FQNEnhancedAutomataTagDefinitionCoCoChecker(); + coCoChecker.addCoCo(new FQNEnhancedAutomataTagConformsToSchemaCoCo(model)); + coCoChecker.addCoCo(new FQNAutomataTagConformsToSchemaCoCo(model)); // TODO: Move this into one CoCo? + + Optional n = FQNEnhancedAutomataTagDefinitionMill.parser().parse(file); + FQNEnhancedAutomataTagDefinitionMill.scopesGenitorDelegator().createFromAST(n.get()); + + coCoChecker.checkAll(n.get()); + } +} diff --git a/monticore-test/01.experiments/tagging/src/test/java/FQNInheritedTagTest.java b/monticore-test/01.experiments/tagging/src/test/java/FQNInheritedTagTest.java new file mode 100644 index 0000000000..3a13f00b65 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/java/FQNInheritedTagTest.java @@ -0,0 +1,217 @@ +/* (c) https://github.com/MontiCore/monticore */ + +import de.monticore.fqn.fqnautomata._ast.ASTAutomaton; +import de.monticore.fqn.fqnautomata._ast.ASTState; +import de.monticore.fqn.fqnautomata._ast.ASTTransition; +import de.monticore.fqn.fqnautomata._tagging.FQNAutomataTagger; +import de.monticore.fqn.fqnautomata._tagging.IFQNAutomataTagger; +import de.monticore.fqn.fqnautomata._visitor.FQNAutomataVisitor2; +import de.monticore.fqn.fqnenhancedautomata.FQNEnhancedAutomataMill; +import de.monticore.fqn.fqnenhancedautomata._ast.ASTRedState; +import de.monticore.fqn.fqnenhancedautomata._ast.ASTRedTransition; +import de.monticore.fqn.fqnenhancedautomata._symboltable.IFQNEnhancedAutomataScope; +import de.monticore.fqn.fqnenhancedautomata._tagging.FQNEnhancedAutomataTagger; +import de.monticore.fqn.fqnenhancedautomata._tagging.IFQNEnhancedAutomataTagger; +import de.monticore.fqn.fqnenhancedautomata._visitor.FQNEnhancedAutomataTraverser; +import de.monticore.fqn.fqnenhancedautomata._visitor.FQNEnhancedAutomataVisitor2; +import de.monticore.fqn.fqnenhancedautomatatagdefinition.FQNEnhancedAutomataTagDefinitionMill; +import de.monticore.tagging.tags.TagsMill; +import de.monticore.tagging.tags._ast.ASTSimpleTag; +import de.monticore.tagging.tags._ast.ASTTag; +import de.monticore.tagging.tags._ast.ASTTagUnit; +import de.monticore.tagging.tags._ast.ASTValuedTag; +import de.se_rwth.commons.logging.Log; +import de.se_rwth.commons.logging.LogStub; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import util.TestUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// Copy of TagTest, just with a grammar in a package +// Also tests inheritance +public class FQNInheritedTagTest { + + static ASTAutomaton model; + + static ASTTagUnit tagDefinition; + static Map states = new HashMap<>(); + static Map red_states = new HashMap<>(); + + protected IFQNEnhancedAutomataTagger fqnAutomataTagger = FQNEnhancedAutomataTagger.getInstance(); + + @BeforeClass + public static void init() throws Exception { + LogStub.init(); + Log.enableFailQuick(false); + + // Load all relevant models + FQNEnhancedAutomataTagDefinitionMill.init(); + tagDefinition = FQNEnhancedAutomataTagDefinitionMill.parser().parse("src/test/resources/models/Enhanced.tags").get(); + FQNEnhancedAutomataTagDefinitionMill.scopesGenitorDelegator().createFromAST(tagDefinition); + + FQNEnhancedAutomataMill.init(); + model = FQNEnhancedAutomataMill.parser().parse("src/test/resources/models/Enhanced.aut").get(); + FQNEnhancedAutomataMill.scopesGenitorDelegator().createFromAST(model); + + FQNEnhancedAutomataTraverser traverser = FQNEnhancedAutomataMill.traverser(); + traverser.add4FQNAutomata(new FQNAutomataVisitor2() { + @Override + public void visit(ASTState node) { + states.put(node.getSymbol().getFullName(), node); + } + }); + traverser.add4FQNEnhancedAutomata(new FQNEnhancedAutomataVisitor2() { + @Override + public void visit(ASTRedState node) { + red_states.put(node.getSymbol().getFullName(), node); + } + }); + + model.accept(traverser); + } + + @Test + public void testAutomaton() { + List tags = fqnAutomataTagger.getTags(model, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "Method", "App.call()"); + } + + @Test + public void testStateA() { + List tags = fqnAutomataTagger.getTags(states.get("A"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "Monitored"); + } + + @Test + public void testStateB() { + List tags = fqnAutomataTagger.getTags(states.get("B"), tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + @Test + public void testStateBA() { + List tags = fqnAutomataTagger.getTags(states.get("BA"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag1"); + } + + @Test + public void testStateBB() { + List tags = fqnAutomataTagger.getTags(states.get("BB"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag2"); + } + + @Test + public void testSomeScopeC() { + List tags = fqnAutomataTagger.getTags(model.getEnclosingScope().resolveScopedState("C").get().getAstNode(), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "Log", "doLogC"); + } + + @Test + public void testStateC_CA() { + List tags = fqnAutomataTagger.getTags(states.get("C.CA"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag1"); + } + + @Test + public void testStateC_CB() { + List tags = fqnAutomataTagger.getTags(states.get("C.CB"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag2"); + } + + @Test + public void testSomeScopeC_Transition() { + List tags = fqnAutomataTagger.getTags((ASTTransition) model.getEnclosingScope().resolveScopedState("C").get().getAstNode() + .getScopedStateElement(2), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "Log", "timestamp"); + } + + @Test + public void testStateD() { + List tags = fqnAutomataTagger.getTags(states.get("D"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "WildcardedTag"); + } + + @Test + public void testAddStateE() { + ASTState stateE = states.get("E"); + List tags = fqnAutomataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(0, tags.size()); + // Add new Tag + ASTTag tag = TagsMill.simpleTagBuilder().setName("TestTag").build(); + fqnAutomataTagger.addTag(stateE, tagDefinition, tag); + tags = fqnAutomataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "TestTag"); + // Remove tag again + fqnAutomataTagger.removeTag(stateE, tagDefinition, tag); + tags = fqnAutomataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + @Test + public void testAddTransition() { + ASTTransition transition = TestUtil.getTransition(model).stream().filter(e->e.getFrom().equals("E") && e.getTo().equals("E")).findAny().get(); + List tags = fqnAutomataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(0, tags.size()); + // Add new Tag + ASTTag tag = TagsMill.simpleTagBuilder().setName("TestTag").build(); + fqnAutomataTagger.addTag(transition, tagDefinition, tag); + tags = fqnAutomataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "TestTag"); + // Remove tag again + fqnAutomataTagger.removeTag(transition, tagDefinition, tag); + tags = fqnAutomataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + @Test + public void testStateRC_CA() { + List tags = fqnAutomataTagger.getTags(states.get("RC.CA"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag1"); + } + + @Test + public void testStateRC_RCB() { + List tags = fqnAutomataTagger.getTags(red_states.get("RC.RCB"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag2"); + } + + @Test + public void testSomeScopeRC_Transition() { + List tags = fqnAutomataTagger.getTags((ASTTransition) ((IFQNEnhancedAutomataScope) model.getEnclosingScope()) + .resolveRedScopedState("RC").get().getAstNode() + .getScopedStateElement(2), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "Log", "timestamp"); + } + + + protected void assertValuedTag(ASTTag tag, String name, String value) { + Assert.assertTrue(tag instanceof ASTValuedTag); + ASTValuedTag valuedTag = (ASTValuedTag) tag; + Assert.assertEquals(name, valuedTag.getName()); + Assert.assertEquals(value, valuedTag.getValue()); + } + + protected void assertSimpleTag(ASTTag tag, String name) { + Assert.assertTrue(tag instanceof ASTSimpleTag); + ASTSimpleTag simpleTag = (ASTSimpleTag) tag; + Assert.assertEquals(name, simpleTag.getName()); + } +} diff --git a/monticore-test/01.experiments/tagging/src/test/java/FQNTagTest.java b/monticore-test/01.experiments/tagging/src/test/java/FQNTagTest.java new file mode 100644 index 0000000000..7d820f181e --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/java/FQNTagTest.java @@ -0,0 +1,180 @@ +/* (c) https://github.com/MontiCore/monticore */ + +import de.monticore.fqn.fqnautomata.FQNAutomataMill; +import de.monticore.fqn.fqnautomata._ast.ASTAutomaton; +import de.monticore.fqn.fqnautomata._ast.ASTState; +import de.monticore.fqn.fqnautomata._ast.ASTTransition; +import de.monticore.fqn.fqnautomata._tagging.FQNAutomataTagger; +import de.monticore.fqn.fqnautomata._tagging.IFQNAutomataTagger; +import de.monticore.fqn.fqnautomata._visitor.FQNAutomataTraverser; +import de.monticore.fqn.fqnautomata._visitor.FQNAutomataVisitor2; +import de.monticore.fqn.fqnautomatatagdefinition.FQNAutomataTagDefinitionMill; +import de.monticore.tagging.tags.TagsMill; +import de.monticore.tagging.tags._ast.ASTSimpleTag; +import de.monticore.tagging.tags._ast.ASTTag; +import de.monticore.tagging.tags._ast.ASTTagUnit; +import de.monticore.tagging.tags._ast.ASTValuedTag; +import de.se_rwth.commons.logging.Log; +import de.se_rwth.commons.logging.LogStub; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import util.TestUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// Copy of TagTest, just with a grammar in a package +public class FQNTagTest { + + static ASTAutomaton model; + + static ASTTagUnit tagDefinition; + static Map states = new HashMap<>(); + + protected IFQNAutomataTagger fqnAutomataTagger = FQNAutomataTagger.getInstance(); + + @BeforeClass + public static void init() throws Exception { + LogStub.init(); + Log.enableFailQuick(false); + + // Load all relevant models + FQNAutomataTagDefinitionMill.init(); + tagDefinition = FQNAutomataTagDefinitionMill.parser().parse("src/test/resources/models/Simple.tags").get(); + FQNAutomataTagDefinitionMill.scopesGenitorDelegator().createFromAST(tagDefinition); + + //no FQNAutomataMill.init() required + model = FQNAutomataMill.parser().parse("src/test/resources/models/Simple.aut").get(); + FQNAutomataMill.scopesGenitorDelegator().createFromAST(model); + + FQNAutomataTraverser traverser = FQNAutomataMill.traverser(); + traverser.add4FQNAutomata(new FQNAutomataVisitor2() { + @Override + public void visit(ASTState node) { + states.put(node.getSymbol().getFullName(), node); + } + }); + + model.accept(traverser); + } + + @Test + public void testAutomaton() { + List tags = fqnAutomataTagger.getTags(model, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "Method", "App.call()"); + } + + @Test + public void testStateA() { + List tags = fqnAutomataTagger.getTags(states.get("A"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "Monitored"); + } + + @Test + public void testStateB() { + List tags = fqnAutomataTagger.getTags(states.get("B"), tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + @Test + public void testStateBA() { + List tags = fqnAutomataTagger.getTags(states.get("BA"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag1"); + } + + @Test + public void testStateBB() { + List tags = fqnAutomataTagger.getTags(states.get("BB"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag2"); + } + + @Test + public void testSomeScopeC() { + List tags = fqnAutomataTagger.getTags(model.getEnclosingScope().resolveScopedState("C").get().getAstNode(), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "VerboseLog", "doLogC"); + } + + @Test + public void testStateC_CA() { + List tags = fqnAutomataTagger.getTags(states.get("C.CA"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag1"); + } + + @Test + public void testStateC_CB() { + List tags = fqnAutomataTagger.getTags(states.get("C.CB"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag2"); + } + + @Test + public void testSomeScopeC_Transition() { + List tags = fqnAutomataTagger.getTags((ASTTransition) model.getEnclosingScope().resolveScopedState("C").get().getAstNode() + .getScopedStateElement(2), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "Log", "timestamp"); + } + + @Test + public void testStateD() { + List tags = fqnAutomataTagger.getTags(states.get("D"), tagDefinition); + Assert.assertEquals(2, tags.size()); + assertSimpleTag(tags.get(0), "WildcardedTag"); + } + + @Test + public void testAddStateE() { + ASTState stateE = states.get("E"); + List tags = fqnAutomataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(0, tags.size()); + // Add new Tag + ASTTag tag = TagsMill.simpleTagBuilder().setName("TestTag").build(); + fqnAutomataTagger.addTag(stateE, tagDefinition, tag); + tags = fqnAutomataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "TestTag"); + // Remove tag again + fqnAutomataTagger.removeTag(stateE, tagDefinition, tag); + tags = fqnAutomataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + @Test + public void testAddTransition() { + ASTTransition transition = TestUtil.getTransition(model).stream().filter(e->e.getFrom().equals("E") && e.getTo().equals("E")).findAny().get(); + List tags = fqnAutomataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(0, tags.size()); + // Add new Tag + ASTTag tag = TagsMill.simpleTagBuilder().setName("TestTag").build(); + fqnAutomataTagger.addTag(transition, tagDefinition, tag); + tags = fqnAutomataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "TestTag"); + // Remove tag again + fqnAutomataTagger.removeTag(transition, tagDefinition, tag); + tags = fqnAutomataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + + protected void assertValuedTag(ASTTag tag, String name, String value) { + Assert.assertTrue(tag instanceof ASTValuedTag); + ASTValuedTag valuedTag = (ASTValuedTag) tag; + Assert.assertEquals(name, valuedTag.getName()); + Assert.assertEquals(value, valuedTag.getValue()); + } + + protected void assertSimpleTag(ASTTag tag, String name) { + Assert.assertTrue(tag instanceof ASTSimpleTag); + ASTSimpleTag simpleTag = (ASTSimpleTag) tag; + Assert.assertEquals(name, simpleTag.getName()); + } +} diff --git a/monticore-test/01.experiments/tagging/src/test/java/InvalidTagSchemaTest.java b/monticore-test/01.experiments/tagging/src/test/java/InvalidTagSchemaTest.java new file mode 100644 index 0000000000..87fad7094b --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/java/InvalidTagSchemaTest.java @@ -0,0 +1,38 @@ +/* (c) https://github.com/MontiCore/monticore */ + +import automatatagschema.AutomataTagSchemaMill; +import de.monticore.tagging.tagschema._ast.ASTTagSchema; +import de.se_rwth.commons.logging.Log; +import de.se_rwth.commons.logging.LogStub; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.util.Optional; + +public class InvalidTagSchemaTest { + + @BeforeClass + public static void prepare() { + LogStub.init(); + Log.enableFailQuick(false); + + AutomataTagSchemaMill.init(); + } + + @Before + public void beforeEach() { + Log.clearFindings(); + } + + @Test + public void test() throws IOException { + Optional astOpt = AutomataTagSchemaMill.parser().parse("src/test/resources/schema/invalid/InvalidTagSchema.tagschema"); + Assert.assertTrue(astOpt.isPresent()); + new de.monticore.tagging.tagschema.TagSchemaAfterParseTrafo().transform(astOpt.get()); + Assert.assertEquals(1, Log.getErrorCount()); + } + +} diff --git a/monticore-test/01.experiments/tagging/src/test/java/TagSchemaSerializationTest.java b/monticore-test/01.experiments/tagging/src/test/java/TagSchemaSerializationTest.java new file mode 100644 index 0000000000..52eaae3c93 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/java/TagSchemaSerializationTest.java @@ -0,0 +1,80 @@ +/* (c) https://github.com/MontiCore/monticore */ + +import automata._ast.ASTAutomaton; +import automata._tagging.AutomataTagConformsToSchemaCoCo; +import automatatagdefinition.AutomataTagDefinitionMill; +import automatatagdefinition._cocos.AutomataTagDefinitionCoCoChecker; +import automatatagschema._symboltable.AutomataTagSchemaSymbols2Json; +import automatatagschema._symboltable.IAutomataTagSchemaArtifactScope; +import de.monticore.fqn.fqnenhancedautomata._symboltable.IFQNEnhancedAutomataArtifactScope; +import de.monticore.fqn.fqnenhancedautomatatagschema.FQNEnhancedAutomataTagSchemaMill; +import de.monticore.fqn.fqnenhancedautomatatagschema._symboltable.FQNEnhancedAutomataTagSchemaSymbols2Json; +import de.monticore.fqn.fqnenhancedautomatatagschema._symboltable.IFQNEnhancedAutomataTagSchemaArtifactScope; +import de.monticore.symboltable.IArtifactScope; +import de.monticore.tagging.tags._ast.ASTTagUnit; +import de.monticore.tagging.tagschema._ast.ASTTagSchema; +import de.se_rwth.commons.logging.Log; +import de.se_rwth.commons.logging.LogStub; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +public class TagSchemaSerializationTest { + protected static ASTAutomaton model; + + @BeforeClass + public static void prepare() throws Exception { + LogStub.init(); + Log.enableFailQuick(false); + + FQNEnhancedAutomataTagSchemaMill.init(); + + // Load all schemas into the global scope + for (File f : Objects.requireNonNull( + new File("src/test/resources/schema/").listFiles(x -> x.getName().endsWith(".tagschema")))) { + Optional schemaOpt = FQNEnhancedAutomataTagSchemaMill.parser().parse(f.getAbsolutePath()); + if (schemaOpt.isPresent()) + FQNEnhancedAutomataTagSchemaMill.scopesGenitorDelegator().createFromAST(schemaOpt.get()); + else + Log.warn("Failed to load TagSchema " + f); + } + + } + + @Before + public void beforeEach() { + Log.clearFindings(); + } + + @Test + public void test() throws IOException { + for (File f : Objects.requireNonNull( + new File("src/test/resources/schema/").listFiles(x -> x.getName().endsWith(".tagschema")))) { + Optional schemaOpt = FQNEnhancedAutomataTagSchemaMill.parser().parse(f.getAbsolutePath()); + if (schemaOpt.isEmpty()) { + Log.warn("Failed to load TagSchema " + f); + return; + } + Log.warn(f.getName()); + IFQNEnhancedAutomataTagSchemaArtifactScope s = FQNEnhancedAutomataTagSchemaMill.scopesGenitorDelegator().createFromAST(schemaOpt.get()); + + FQNEnhancedAutomataTagSchemaSymbols2Json schemaSymbols2Json = new FQNEnhancedAutomataTagSchemaSymbols2Json(); + String serialized = schemaSymbols2Json.serialize(s); + + IFQNEnhancedAutomataTagSchemaArtifactScope copy = schemaSymbols2Json.deserialize(serialized); + + System.err.println(copy); + String serializedCopy = schemaSymbols2Json.serialize(copy); + + Assert.assertEquals(serialized, serializedCopy); + + } + } + +} diff --git a/monticore-test/01.experiments/tagging/src/test/java/TagTest.java b/monticore-test/01.experiments/tagging/src/test/java/TagTest.java new file mode 100644 index 0000000000..16745df510 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/java/TagTest.java @@ -0,0 +1,179 @@ +/* (c) https://github.com/MontiCore/monticore */ + +import automata.AutomataMill; +import automata._ast.ASTAutomaton; +import automata._ast.ASTState; +import automata._ast.ASTTransition; +import automata._tagging.AutomataTagger; +import automata._tagging.IAutomataTagger; +import automata._visitor.AutomataTraverser; +import automata._visitor.AutomataVisitor2; +import automatatagdefinition.AutomataTagDefinitionMill; +import de.monticore.tagging.tags.TagsMill; +import de.monticore.tagging.tags._ast.ASTSimpleTag; +import de.monticore.tagging.tags._ast.ASTTag; +import de.monticore.tagging.tags._ast.ASTTagUnit; +import de.monticore.tagging.tags._ast.ASTValuedTag; +import de.se_rwth.commons.logging.Log; +import de.se_rwth.commons.logging.LogStub; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TagTest { + + static ASTAutomaton model; + + static ASTTagUnit tagDefinition; + + static Map states = new HashMap<>(); + + protected IAutomataTagger automataTagger = AutomataTagger.getInstance(); + + @BeforeClass + public static void init() throws Exception { + LogStub.init(); + Log.enableFailQuick(false); + + // Load all relevant models + AutomataTagDefinitionMill.init(); + tagDefinition = AutomataTagDefinitionMill.parser().parse("src/test/resources/models/Simple.tags").get(); + AutomataTagDefinitionMill.scopesGenitorDelegator().createFromAST(tagDefinition); + + // No AutomataMill.init() required + model = AutomataMill.parser().parse("src/test/resources/models/Simple.aut").get(); + AutomataMill.scopesGenitorDelegator().createFromAST(model); + + AutomataTraverser traverser = AutomataMill.traverser(); + traverser.add4Automata(new AutomataVisitor2() { + @Override + public void visit(ASTState node) { + states.put(node.getSymbol().getFullName(), node); + } + }); + + model.accept(traverser); + } + + @Test + public void testAutomaton() { + List tags = automataTagger.getTags(model, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "Method", "App.call()"); + } + + @Test + public void testStateA() { + List tags = automataTagger.getTags(states.get("A"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "Monitored"); + } + + @Test + public void testStateB() { + List tags = automataTagger.getTags(states.get("B"), tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + @Test + public void testStateBA() { + List tags = automataTagger.getTags(states.get("BA"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag1"); + } + + @Test + public void testStateBB() { + List tags = automataTagger.getTags(states.get("BB"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag2"); + } + + @Test + public void testSomeScopeC() { + List tags = automataTagger.getTags(model.getEnclosingScope().resolveScopedState("C").get().getAstNode(), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "VerboseLog", "doLogC"); + } + + @Test + public void testStateC_CA() { + List tags = automataTagger.getTags(states.get("C.CA"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag1"); + } + + @Test + public void testStateC_CB() { + List tags = automataTagger.getTags(states.get("C.CB"), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "StateTag2"); + } + + @Test + public void testSomeScopeC_Transition() { + List tags = automataTagger.getTags((ASTTransition) model.getEnclosingScope().resolveScopedState("C").get().getAstNode() + .getScopedStateElement(2), tagDefinition); + Assert.assertEquals(1, tags.size()); + assertValuedTag(tags.get(0), "Log", "timestamp"); + } + + @Test + public void testStateD() { + List tags = automataTagger.getTags(states.get("D"), tagDefinition); + Assert.assertEquals(2, tags.size()); + assertSimpleTag(tags.get(0), "WildcardedTag"); + } + + @Test + public void testAddStateE() { + ASTState stateE = states.get("E"); + List tags = automataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(0, tags.size()); + // Add new Tag + ASTTag tag = TagsMill.simpleTagBuilder().setName("TestTag").build(); + automataTagger.addTag(stateE, tagDefinition, tag); + tags = automataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "TestTag"); + // Remove tag again + automataTagger.removeTag(stateE, tagDefinition, tag); + tags = automataTagger.getTags(stateE, tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + @Test + public void testAddTransition() { + ASTTransition transition = model.getTransitionList().stream().filter(e->e.getFrom().equals("E") && e.getTo().equals("E")).findAny().get(); + List tags = automataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(0, tags.size()); + // Add new Tag + ASTTag tag = TagsMill.simpleTagBuilder().setName("TestTag").build(); + automataTagger.addTag(transition, tagDefinition, tag); + tags = automataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(1, tags.size()); + assertSimpleTag(tags.get(0), "TestTag"); + // Remove tag again + automataTagger.removeTag(transition, tagDefinition, tag); + tags = automataTagger.getTags(transition, tagDefinition); + Assert.assertEquals(0, tags.size()); + } + + + protected void assertValuedTag(ASTTag tag, String name, String value) { + Assert.assertTrue(tag instanceof ASTValuedTag); + ASTValuedTag valuedTag = (ASTValuedTag) tag; + Assert.assertEquals(name, valuedTag.getName()); + Assert.assertEquals(value, valuedTag.getValue()); + } + + protected void assertSimpleTag(ASTTag tag, String name) { + Assert.assertTrue(tag instanceof ASTSimpleTag); + ASTSimpleTag simpleTag = (ASTSimpleTag) tag; + Assert.assertEquals(name, simpleTag.getName()); + } +} diff --git a/monticore-test/01.experiments/tagging/src/test/java/util/TestUtil.java b/monticore-test/01.experiments/tagging/src/test/java/util/TestUtil.java new file mode 100644 index 0000000000..47ecc6b667 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/java/util/TestUtil.java @@ -0,0 +1,25 @@ +package util; + +import de.monticore.fqn.fqnautomata.FQNAutomataMill; +import de.monticore.fqn.fqnautomata._ast.ASTAutomaton; +import de.monticore.fqn.fqnautomata._ast.ASTTransition; +import de.monticore.fqn.fqnautomata._visitor.FQNAutomataTraverser; +import de.monticore.fqn.fqnautomata._visitor.FQNAutomataVisitor2; + +import java.util.ArrayList; +import java.util.List; + +public class TestUtil { + public static List getTransition(ASTAutomaton astAutomaton){ + List ret = new ArrayList<>(); + FQNAutomataTraverser traverser = FQNAutomataMill.traverser(); + traverser.add4FQNAutomata(new FQNAutomataVisitor2() { + @Override + public void visit(ASTTransition node) { + ret.add(node); + } + }); + astAutomaton.accept(traverser); + return ret; + } +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/Enhanced.aut b/monticore-test/01.experiments/tagging/src/test/resources/models/Enhanced.aut new file mode 100644 index 0000000000..32fc37e304 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/Enhanced.aut @@ -0,0 +1,35 @@ +/* (c) https://github.com/MontiCore/monticore */ +automaton Enhanced { + + state A <>; + A - x > B; + A - y > A; + + state B <> { + state BA; + state BB; + } + B - x > A; + B - y > A; + + scopedstate C { + state CA <>; + state CB; + CA - x > CB; + } + + state D; + + state E; + E - a > E; + + red state RE; + red E - a > RE; + + red scopedstate RC { + state CA <>; + red state RCB; + CA - x > RCB; + } + +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/Enhanced.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/Enhanced.tags new file mode 100644 index 0000000000..f38af533bf --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/Enhanced.tags @@ -0,0 +1,37 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to EnhancedAutomataSchema; + +tags Enhanced for Enhanced { + + tag Enhanced with Method = "App.call()"; + + tag A with Monitored; + + tag B.BA with Invalid; // Does not work, as B is not a scope + within B { + tag BB with Invalid; // Does not work, as B is not a scope + } + tag BA with StateTag1; + tag BB with StateTag2; + + + tag C with Log = "doLogC"; + tag C.CA with StateTag1; + within C { + tag CB with StateTag2; + tag [CA - x > CB;] with Log="timestamp"; + } + + tag D with WildcardedTag; + + // Inherited + tag RE with StateTag1; + tag RC.CA with StateTag1; + within RC { + tag RCB with StateTag2; + tag [CA - x > RCB;] with Log="timestamp"; + } + + tag RE with RedStateTag; +} \ No newline at end of file diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/Invalid.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/Invalid.tags new file mode 100644 index 0000000000..185bdcffb3 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/Invalid.tags @@ -0,0 +1,11 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags Simple for Simple { + + tag A with Method = "App.call()"; + + tag Simple with Monitored; + +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags1.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags1.tags new file mode 100644 index 0000000000..093c447811 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags1.tags @@ -0,0 +1,12 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidComplexTags1 for Simple { + tag D with Complex { + ofInt="12", + // ofString="foo", // - missing inner tag + ofBoolean="true", + Exception{type="t", msg="m";}; + }; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags2.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags2.tags new file mode 100644 index 0000000000..048ada26d9 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags2.tags @@ -0,0 +1,13 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidComplexTags2 for Simple { + tag D with Complex { + ofInt="12", + ofString="foo", + ofBoolean="true", + Exception{type="t", msg="m";}, + yetAnotherState="12"; + }; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags3.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags3.tags new file mode 100644 index 0000000000..33687336e9 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags3.tags @@ -0,0 +1,12 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidComplexTags3 for Simple { + tag D with Complex { + ofInt="12", + ofTypo="foo", // Typo + ofBoolean="true", + Exception{type="t", msg="m";}; + }; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags4.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags4.tags new file mode 100644 index 0000000000..eb7c1ebb05 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags4.tags @@ -0,0 +1,12 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidComplexTags4 for Simple { + tag D with Complex { + ofInt="notANumber", // not a number + ofString="foo", + ofBoolean="true", + Exception{type="t", msg="m";}; + }; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags5.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags5.tags new file mode 100644 index 0000000000..3bcacf9cf1 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags5.tags @@ -0,0 +1,12 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidComplexTags5 for Simple { + tag D with Complex { + ofInt="12", + ofString="foo", + ofBoolean="NotABoolean", // Not a boolean + Exception{type="t", msg="m";}; + }; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags6.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags6.tags new file mode 100644 index 0000000000..201db86dc3 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags6.tags @@ -0,0 +1,12 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidComplexTags6 for Simple { + tag D with Complex { + ofInt="12", + ofString="foo", + ofBoolean="false", + UnknownComplex{type="t", msg="m";}; // non defined complex type + }; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags7.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags7.tags new file mode 100644 index 0000000000..16481a1164 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidComplexTags7.tags @@ -0,0 +1,12 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidComplexTags7 for Simple { + tag D with Complex { + ofInt="12", + ofString="foo", + ofBoolean="false", + Exception{notTheType="t", msg="m";}; // invalid inner complex + }; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidEnhancedTags1.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidEnhancedTags1.tags new file mode 100644 index 0000000000..858cb1200c --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidEnhancedTags1.tags @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to EnhancedAutomataSchema; + +tags InvalidEnhancedTags1 for Enhanced { + tag Simple with RedStateTag; // Only extended/red states are allowed here +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidPatternTags1.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidPatternTags1.tags new file mode 100644 index 0000000000..473485ffec --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidPatternTags1.tags @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidPatternTags1 for Simple { + tag [A - x > B;] with InvalidTag; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidPatternTags2.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidPatternTags2.tags new file mode 100644 index 0000000000..0cea1f22fe --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidPatternTags2.tags @@ -0,0 +1,9 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidPatternTags2 for Simple { + within C { + tag [CA - x > CB;] with InvalidValueTag="timestamp"; + } +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags1.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags1.tags new file mode 100644 index 0000000000..2a5232a71a --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags1.tags @@ -0,0 +1,8 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTags1 for Simple { + tag Simple with Method; // Value missing +} + diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags2.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags2.tags new file mode 100644 index 0000000000..687e816841 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags2.tags @@ -0,0 +1,8 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTags2 for Simple { + tag A with NotAStateTag; // A state ("A") may not be tagged by the simple NotAStateTag +} + diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags3.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags3.tags new file mode 100644 index 0000000000..6ec46b8ba2 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags3.tags @@ -0,0 +1,8 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTags3 for Simple { + tag BA with NotAStateTag; // A state ("BA") may not be tagged by the simple NotAStateTag +} + diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags4.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags4.tags new file mode 100644 index 0000000000..8b2bfb10cc --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags4.tags @@ -0,0 +1,8 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTags4 for Simple { + tag C.CA with NotAStateTag; // A state ("C.CA") may not be tagged by the simple NotAStateTag +} + diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags5.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags5.tags new file mode 100644 index 0000000000..feba754ceb --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags5.tags @@ -0,0 +1,8 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTags5 for Simple { + tag C.CA with Method = "doStuff()"; // A state ("C.CA") may not be tagged by the valued Method +} + diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags6.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags6.tags new file mode 100644 index 0000000000..6a30b10204 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags6.tags @@ -0,0 +1,10 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTags6 for Simple { + within C { + tag CA with NotAStateTag; // A state ("C.CA") may not be tagged by the simple NotAStateTag + } +} + diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags7.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags7.tags new file mode 100644 index 0000000000..267fffa16d --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags7.tags @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTags7 for Simple { + tag A with Exception {type="Type";}; // Missing complex msg +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags8.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags8.tags new file mode 100644 index 0000000000..6cfae7d058 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTags8.tags @@ -0,0 +1,7 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTags8 for Simple { + tag A with Exception {type="Type", msg="Message", third="TooMuch";}; // Additional complex-element third +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTagsPrivate.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTagsPrivate.tags new file mode 100644 index 0000000000..93ebe3438d --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/InvalidTagsPrivate.tags @@ -0,0 +1,8 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags InvalidTagsPrivate for Simple { + tag A with PrivateTag; // the PrivateTag may only be used within complex tags +} + diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/NonConformingTags.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/NonConformingTags.tags new file mode 100644 index 0000000000..3ca9a6d337 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/NonConformingTags.tags @@ -0,0 +1,16 @@ +/* (c) https://github.com/MontiCore/monticore */ + + + +tags NonConformingTags for Simple { + tag A with Exception {type="Type";}; // Missing complex msg + tag Simple with Method; // Value missing + tag BA with NotAStateTag; // A state ("BA") may not be tagged by the simple NotAStateTag + tag D with Complex { + ofInt="12", + // ofString="foo", // - missing inner tag + ofBoolean="true", + Exception{type="t", msg="m";}; + }; + tag [A - x > B;] with InvalidTag; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/Simple.aut b/monticore-test/01.experiments/tagging/src/test/resources/models/Simple.aut new file mode 100644 index 0000000000..9498f74c95 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/Simple.aut @@ -0,0 +1,26 @@ +/* (c) https://github.com/MontiCore/monticore */ +automaton Simple { + + state A <>; + A - x > B; + A - y > A; + + state B <> { + state BA; + state BB; + } + B - x > A; + B - y > A; + + scopedstate C { + state CA <>; + state CB; + CA - x > CB; + } + + state D; + + state E; + E - a > E; + +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/models/Simple.tags b/monticore-test/01.experiments/tagging/src/test/resources/models/Simple.tags new file mode 100644 index 0000000000..bbd1c4f12c --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/models/Simple.tags @@ -0,0 +1,34 @@ +/* (c) https://github.com/MontiCore/monticore */ + +conforms to AutomataSchema; + +tags Simple for Simple { + + tag Simple with Method = "App.call()"; + + tag A with Monitored; + + tag B.BA with Invalid; // Does not work, as B is not a scope + within B { + tag BB with Invalid; // Does not work, as B is not a scope + } + tag BA with StateTag1; + tag BB with StateTag2; + + + tag C with VerboseLog = "doLogC"; + tag C.CA with StateTag1; + within C { + tag CB with StateTag2; + tag [CA - x > CB;] with Log="timestamp"; + } + + tag D with WildcardedTag; + + tag D with Complex { + ofInt="12", + ofString="foo", + ofBoolean="true", + Exception{type="t", msg="m";}; + }; +} \ No newline at end of file diff --git a/monticore-test/01.experiments/tagging/src/test/resources/schema/AutomataSchema.tagschema b/monticore-test/01.experiments/tagging/src/test/resources/schema/AutomataSchema.tagschema new file mode 100644 index 0000000000..a2254f3fdc --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/schema/AutomataSchema.tagschema @@ -0,0 +1,26 @@ +/* (c) https://github.com/MontiCore/monticore */ + +tagschema AutomataSchema { + tagtype Monitored for State; + tagtype StateTag1 for State; + tagtype StateTag2 for State; + tagtype Invalid for State; + tagtype Log:["timestamp" | "callerID"] for Transition; + tagtype VerboseLog:String for ScopedState; + tagtype Method:String for Automaton; + tagtype Exception for State { + type:String, + msg:String; + } + tagtype Complex for State { + ofInt:int, + ofString:String, + ofBoolean:Boolean, + ofException:Exception; + } + private tagtype PrivateTag for State; + tagtype WildcardedTag for *; + + tagtype AAAAA for State; + tagtype AAAAA for Automaton; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/schema/EnhancedAutomataSchema.tagschema b/monticore-test/01.experiments/tagging/src/test/resources/schema/EnhancedAutomataSchema.tagschema new file mode 100644 index 0000000000..d4aea11132 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/schema/EnhancedAutomataSchema.tagschema @@ -0,0 +1,18 @@ +/* (c) https://github.com/MontiCore/monticore */ + +tagschema EnhancedAutomataSchema { + tagtype Monitored for State; + tagtype StateTag1 for State; + tagtype StateTag2 for State; + tagtype Invalid for State; + tagtype Log:["timestamp" | "callerID"] for Transition; + tagtype VerboseLog:String for ScopedState; + tagtype Method:String for Automaton; + tagtype Exception for State { + type:String, + msg:String; + } + private tagtype PrivateTag for State; + tagtype WildcardedTag for *; + tagtype RedStateTag for RedState; +} diff --git a/monticore-test/01.experiments/tagging/src/test/resources/schema/invalid/InvalidTagSchema.tagschema b/monticore-test/01.experiments/tagging/src/test/resources/schema/invalid/InvalidTagSchema.tagschema new file mode 100644 index 0000000000..2d76bff721 --- /dev/null +++ b/monticore-test/01.experiments/tagging/src/test/resources/schema/invalid/InvalidTagSchema.tagschema @@ -0,0 +1,6 @@ +/* (c) https://github.com/MontiCore/monticore */ + +tagschema AutomataSchema { + tagtype Log:["timestamp" | "callerID"] for Transition; + tagtype Log:String for ScopedState; +} diff --git a/settings.gradle b/settings.gradle index 505dd961c8..befdd621ff 100644 --- a/settings.gradle +++ b/settings.gradle @@ -90,6 +90,9 @@ if(!hasProperty('bootstrap')) { include(':monticore-test:01experiments:stcomposition02') include(':monticore-test:01experiments:stcomposition03') include(':monticore-test:01experiments:strules') + if (("true").equals(getProperty('genTagging'))) { + include(':monticore-test:01experiments:tagging') + } include(':monticore-test:01experiments:visit_L_inherit') project(':monticore-test:01experiments:S01_intro').projectDir = file('monticore-test/01.experiments/S01.intro') project(':monticore-test:01experiments:visit_L_inherit').projectDir = file('monticore-test/01.experiments/visit.L.inherit')