Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,16 @@ public String generate() {
// show selected target classes
selectedClasses.stream()
.sorted(Comparator.comparing(EClassifier::getName))
.forEach(this::targetClazz);
// class might have already been generated as super class of another class

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, actually not, because that part is not implemented yet :p

.forEach(this::targetClazzIfNew);
} else {
// show all target classes
targetMetaModel.pack().getEClassifiers().stream()
.sorted(Comparator.comparing(EClassifier::getName))
.forEach(classifier -> {
if (classifier instanceof EClass clazz) {
targetClazz(clazz);
// class might have already been generated as super class of another class
targetClazzIfNew(clazz);
} else if (classifier instanceof EEnum eEnum) {
// enum might have already been generated from a class attribute
enumerationIfNew(eEnum);
Expand Down Expand Up @@ -195,6 +197,12 @@ private void packages() {
out.pack("\"Target: %s\" as %s".formatted(targetName, targetName), Empty);
}

private void targetClazzIfNew(EClass target) {
if (!seenClazzes.contains(target)) {
targetClazz(target);
}
}

private void targetClazz(EClass target) {
clazz(target);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class AQRBuilder {
private final ViewTypeDefinition viewTypeDefinition;
private final ExpressionHelper expressionHelper;

private final Queue<Pair<AQRTargetClass, @Nullable Body>> populationQueue = new ArrayDeque<>(); // target class + query it originated from (or null if implicit)
private final Queue<Pair<AQRTargetClass, @Nullable Query>> populationQueue = new ArrayDeque<>(); // target class + query it originated from (or null if implicit)

private final Set<EDataType> encounteredDataTypes = new HashSet<>();

Expand All @@ -90,10 +90,21 @@ public AQR build() {
// create empty (= without features) target classes for all queries
AstUtils.getAllQueries(viewTypeDefinition).forEach(this::createTargetClass);

// add super classes to target classes
for (var entry : populationQueue) {
addSuperClassesToTargetClass(entry.left(), entry.right());
}

// populate target classes
while (!populationQueue.isEmpty()) {
var entry = populationQueue.poll();
//noinspection DataFlowIssue - false positive
populateTargetClass(entry.left(), entry.right());
populateTargetClass(entry.left(), entry.right() == null ? null : entry.right().getBody());
}

// identify and verify inheritance
for (var targetClass : targetClasses) {
applyInheritance(targetClass);
}

var root = createRootIfNeededAndInit();
Expand Down Expand Up @@ -128,7 +139,7 @@ private AQRTargetClass getTargetForQuery(Query query) {
}

private AQRTargetClass createTargetClass(String name, @Nullable AQRSource source, @Nullable Query query) {
var target = new AQRTargetClass(name, source, new ArrayList<>());
var target = new AQRTargetClass(name, source, new ArrayList<>(), new ArrayList<>());

targetClasses.add(target);

Expand All @@ -143,7 +154,7 @@ private AQRTargetClass createTargetClass(String name, @Nullable AQRSource source
});
}

populationQueue.add(new Pair<>(target, query != null ? query.getBody() : null));
populationQueue.add(new Pair<>(target, query));
return target;
}

Expand Down Expand Up @@ -191,6 +202,23 @@ private AQRTargetClass getOrCreateTargetClass(EClass source) {
return targets.iterator().next();
}

private void addSuperClassesToTargetClass(AQRTargetClass targetClazz, @Nullable Query query) {
if (query != null) {
switch (query) {
case MainQuery mainQuery -> {
for (String superClassName : mainQuery.getSuperClasses()) {
var superClassCandidates = targetClasses.stream().filter(targetClass -> targetClass.name().equals(superClassName)).toList();
Comment thread
larsk21 marked this conversation as resolved.
Outdated
invariant(!superClassCandidates.isEmpty(), "Classes can only extend target classes");
invariant(superClassCandidates.size() == 1, "Class names must be unique");

targetClazz.superClasses().add(superClassCandidates.getFirst());
}
}
default -> {}
}
}
}

/**
* Populates the target class with features specified in the given body or copies all features if no body was given.
*
Expand Down Expand Up @@ -287,6 +315,7 @@ private static String getFeatureName(Feature feature, AQRFeature.Kind kind) {
case AQRFeature.Kind.Copy copy -> copy.source().getName();
case AQRFeature.Kind.Calculate ignored ->
invariantFailed("Calculated feature must have a name: " + feature.getExpression());
case AQRFeature.Kind.Overwrite overwriding -> overwriding.overwritten().name();
Comment thread
larsk21 marked this conversation as resolved.
Outdated
default -> fail();
};
}
Expand Down Expand Up @@ -382,14 +411,57 @@ private TypeInfo inferType(XExpression expression) {
}
}

private void applyInheritance(AQRTargetClass targetClass) {
var superClassFeatures = targetClass.allSuperClasses().stream().flatMap(superClass -> superClass.features().stream()).toList();

// update overwriting features
targetClass.features().replaceAll(feature -> {
var overwrittenFeatures = superClassFeatures.stream().filter(superClassFeature -> superClassFeature.name().equals(feature.name())).toList();
if (overwrittenFeatures.isEmpty()) {
return feature;
}

invariant(overwrittenFeatures.size() == 1);
var overwrittenFeature = overwrittenFeatures.getFirst();

invariant(feature.options().equals(overwrittenFeature.options()), "Overwriting features may not change modifiers");

switch (feature) {
case AQRFeature.Attribute attribute -> {
invariant(overwrittenFeature instanceof AQRFeature.Attribute, "Attribute must overwrite attribute");
var overwrittenAttribute = (AQRFeature.Attribute)overwrittenFeature;
invariant(overwrittenAttribute.type().equals(attribute.type()), "Type of overwriting feature must be equal to type of overwritten feature");

return attribute.setFeatureKind(new AQRFeature.Kind.Overwrite(overwrittenFeature, feature.kind().expression()));
}
case AQRFeature.Reference reference -> {
invariant(overwrittenFeature instanceof AQRFeature.Reference, "Reference must overwrite reference");
var overwrittenReference = (AQRFeature.Reference)overwrittenFeature;
invariant(overwrittenReference.type().equals(reference.type()), "Type of overwriting feature must be equal to type of overwritten feature");

return reference.setFeatureKind(new AQRFeature.Kind.Overwrite(overwrittenFeature, feature.kind().expression()));
}
}
});

// check if all inherited features are overwritten
var missingFeatures = superClassFeatures.stream().filter(superClassFeature -> !targetClass.features().stream().filter(feature ->
feature.name().equals(superClassFeature.name()) &&
(feature.kind() instanceof AQRFeature.Kind.Overwrite)
).findAny().isPresent()).toList();
if (!missingFeatures.isEmpty()) {
invariant(!missingFeatures.isEmpty(), "Sub classes must overwrite all inherited features");
}
}

private AQRTargetClass createRootIfNeededAndInit() {
AQRTargetClass root;

var rootQuery = findRootQuery();
if (rootQuery != null) {
root = getTargetForQuery(rootQuery);
} else {
root = new AQRTargetClass(Constants.DefaultRootClassName, null, new ArrayList<>());
root = new AQRTargetClass(Constants.DefaultRootClassName, null, new ArrayList<>(), new ArrayList<>());
targetClasses.add(root);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ protected EClass createClass(AQRTargetClass targetClass) {

private void populateClass(AQRTargetClass targetClass) {
var target = Objects.requireNonNull(trace.aqrToTarget().get(targetClass));
var features = targetClass.features().stream().map(this::createFeature).toList();

var superTypes = targetClass.superClasses().stream().map(superClass -> trace.aqrToTarget().get(superClass)).toList();
target.getESuperTypes().addAll(superTypes);

var features = targetClass.features().stream().filter(feature -> !(feature.kind() instanceof AQRFeature.Kind.Overwrite)).map(this::createFeature).toList();
target.getEStructuralFeatures().addAll(features);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,12 @@ public IScope getScope(EObject context, EReference reference) {
if (left != null && right != null) {
return createJoinConditionFieldsScope(left, right);
}
} else if (reference == AstPackage.Literals.MAIN_QUERY__SUPER_CLASSES) {
createSuperClassScope(AstUtils.getViewType(context));
} else if (reference == AstPackage.Literals.FEATURE__TYPE) {
return createFeatureTypeScope(AstUtils.getViewType(context));
} else if (reference == AstPackage.Literals.ABSTRACT_FEATURE__TYPE) {
return createFeatureTypeScope(AstUtils.getViewType(context));
} else {
return super.getScope(context, reference);
}
Expand Down Expand Up @@ -137,6 +141,14 @@ private IScope createJoinConditionFieldsScope(EClass left, EClass right) {
return new SimpleScope(IScope.NULLSCOPE, candidates);
}

private IScope createSuperClassScope(ViewTypeDefinition viewType) {
var queryCandidates = AstUtils.getAllQueries(viewType)
.map(query -> EObjectDescription.create(AstUtils.getTargetName(query, expressionHelper), query))
.toList();

return new SimpleScope(queryCandidates);
}

/**
* Scope for available types when specifying an explicit type for a feature.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void checkQueryWithoutSource(MainQuery mainQuery) {
if (mainQuery.getName() == null) {
error("Query without source must have a target name", mainQuery, AstPackage.Literals.QUERY__NAME);
}
if (mainQuery.getBody() == null) {
if (mainQuery.getBody() == null && mainQuery.getAbstractBody() == null) {
error("Query without source must have a body", mainQuery, AstPackage.Literals.QUERY__BODY);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ Import:

MainQuery:
source=Source?
'create' root?='root'? name=ID?
body=Body?;
(
'create' root?='root'? name=ID?
('extends' superClasses+=QualifiedName (',' superClasses+=QualifiedName)*)?
Comment thread
larsk21 marked this conversation as resolved.
Outdated
body=Body?
)|(
'abstract' name=ID?
('extends' superClasses+=QualifiedName (',' superClasses+=QualifiedName)*)?
abstractBody=AbstractBody
);

Source:
'from' from=From
Expand All @@ -45,6 +52,9 @@ JoinExpressionCondition:
Body:
{Body} '{' features+=Feature* '}'; // {Body} ensures that the object is created to differentiate an empty body {} from a missing body

AbstractBody:
{AbstractBody} '{' abstractFeatures+=AbstractFeature* '}';

Feature:
(
name=ID
Expand All @@ -55,6 +65,11 @@ Feature:
expression=XOrExpression
subQuery=SubQuery?;

AbstractFeature:
name=ID
':' type=[ecore::EObject|QualifiedName] // type refers either to an EDataType or a Query
(hasModifiers?='[' (modifiers+=Modifier (',' modifiers+=Modifier)*)? ']')?; // allow empty modifier list in grammar and forbid it using a validator for improved error messages
Comment thread
iTob191 marked this conversation as resolved.

enum FeatureOp:
COPY='=' | CALCULATE=':=';

Expand Down
15 changes: 15 additions & 0 deletions lang/model/src/main/java/tools/vitruv/neojoin/aqr/AQRFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ record Implicit(EStructuralFeature source) implements Copy {}
*/
record Generate() implements Kind {}

/**
* The feature overwrites a feature in a super classs.
*
* @param overwritten overwritten feature in a super class
* @param expression expression calculating the value of the feature (not inherited)
*/
record Overwrite(AQRFeature overwritten, XExpression expression) implements Kind {}
Comment thread
larsk21 marked this conversation as resolved.
Outdated
}

/**
Expand Down Expand Up @@ -151,6 +158,10 @@ public String toString() {
return "Attribute[name='%s', type=%s, kind=%s, options=%s]".formatted(name, type.getName(), kind, options);
}

Attribute setFeatureKind(Kind kind) {
Comment thread
larsk21 marked this conversation as resolved.
Outdated
return new Attribute(name, type, kind, options);
}

}

/**
Expand All @@ -173,6 +184,10 @@ public String toString() {
return "Reference[name='%s', type=%s, kind=%s, options=%s]".formatted(name, type.name(), kind, options);
}

Reference setFeatureKind(Kind kind) {
return new Reference(name, type, kind, options);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import org.jspecify.annotations.Nullable;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

/**
* Class within the target model.
Expand All @@ -20,6 +24,7 @@ public final class AQRTargetClass {

private final String name;
private final @Nullable AQRSource source;
private final List<AQRTargetClass> superClasses;
private final List<AQRFeature> features;

/**
Expand All @@ -30,10 +35,12 @@ public final class AQRTargetClass {
public AQRTargetClass(
String name,
@Nullable AQRSource source,
List<AQRTargetClass> superClasses,
List<AQRFeature> features
) {
this.name = name;
this.source = source;
this.superClasses = superClasses;
this.features = features;
}

Expand All @@ -45,15 +52,37 @@ public String name() {
return source;
}

public List<AQRTargetClass> superClasses() {
return superClasses;
}

public List<AQRTargetClass> allSuperClasses() {
// BFS
Queue<AQRTargetClass> queue = new LinkedList<>(superClasses());
Set<AQRTargetClass> allSuperClasses = new HashSet<>();

while (!queue.isEmpty()) {
var superClass = queue.poll();
allSuperClasses.add(superClass);

var newSuperClasses = superClass.superClasses();
newSuperClasses.removeAll(allSuperClasses);
queue.addAll(newSuperClasses);
}

return allSuperClasses.stream().toList();
}

public List<AQRFeature> features() {
return features;
}

@Override
public String toString() {
return "TargetClass[name=%s, source=%s, features=%s]".formatted(
return "TargetClass[name=%s, source=%s, superClasses=%s, features=%s]".formatted(
name,
source,
superClasses,
features
);
}
Expand Down
2 changes: 1 addition & 1 deletion vscode-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
{
"language": "neojoin",
"scopeName": "source.neojoin",
"path": "./src/language/neojoin.tmGrammar.json"
"path": "./src/language/neojoin.tmLanguage.json"
}
],
"commands": [
Expand Down
Loading
Loading