Search code examples
typecheckingrascal

Writing Semantic Rules with TypePal


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


Solution

  • 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:

    • uses ref
    • uses 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.