I am using Rascal MPL to design a DSL for data modeling here is a snippet of my grammar specification:
syntax Declaration
= @Foldable entity: "entity" EntityId name "{" Field+ fields “}”
;
syntax Field
= field: Id name ":" Type t Constraints? constraint
| uniReference: Id name "-\>" Type typ
| biReference: Id name "-\>" Type typ "inverse" Id ref "::" Id attr
;
entity Employee {
hireDate : Date
payRate : Currency
boss -> Manager inverse Manager::subordinates
}
entity Manager {
subordinates -> Set<Employee> inverse Employee::boss
}
I have implemented a good deal of the Typechecking with TypePal but here is my problem: How do I enforce the the rule that for attribute boss in Employee entity there must be a corresponding attribute subordinates in the Manager entity and vice versa. Thanks
Interesting question, your problem can be solved rather easily. The secret weapon is useViaType
that is used to check a type that is defined in another type such as, for instance, a structure declaration or as in your case an entity declaration.
The essential collect
rules to achieve this are as follows:
void collect(current: (Declaration) `entity <Id name> { <Field+ fields> }`, Collector c){
c.define("<name>", entityId(), current, defType(entityType("<name>")));
c.enterScope(current);
collect(fields, c);
c.leaveScope(current);
}
void collect(current: (Field) `<Id name> -\> <Type typ> inverse <Id ref> :: <Id attr>`, Collector c){
c.define("<name>", fieldId(), current, defType(typ));
c.use(ref, {entityId()});
c.useViaType(ref, attr, {fieldId()});
collect(typ, ref, attr, c);
}
The first rule creates a separate scope to surround the field declarations in the current entity declaration.
The second rule:
ref
attr
via the type of ref
.For your convenience I have placed the solution to (a slightly simplified version of) your problem as a separate example in the TypePal repository, see https://github.com/usethesource/typepal/tree/master/src/examples/dataModel
Given the (erroneous) input:
entity Employee {
boss -> Manager inverse Manager::subordinates
}
entity Manager {
subordinates -> Set<Employee> inverse Employee::bos
}
Your will now get the following error message:
error("No definition found for field `bos` in type `Employee`",
|project://typepal/src/examples/dataModel/example1.dm|(139,3,<6,51>,<6,54>))
Replace bos
by boss
and the error will disappear.
I hope this will help you to complete your project.
Response to question: an extra check on the types could look like this:
void collect(current: (Field) `<Id name> -\> <Type typ> inverse <Id ref> :: <Id attr>`, Collector c){
c.define("<name>", fieldId(), current, defType(typ));
c.use(ref, {entityId()});
c.useViaType(ref, attr, {fieldId()});
c.require("check inverse", current, [attr],
void(Solver s){
field_type = s.getType(typ);
attr_type = s.getType(attr);
ref_type = s.getType(ref);
if(setType(elm_type) := field_type){
s.requireEqual(elm_type, ref_type, error(attr, "Field type %t does not match reference type %t", typ, ref));
} else {
s.requireEqual(ref_type, field_type, error(attr, "Field type %t should be equal to reference type %t", field_type, ref_type));
}
});
collect(typ, ref, attr, c);
}
You may want to adapt this to your specific needs. I have updated the example in the TypePal repo.