英文:
Writing Semantic Rules with TypePal
问题
我正在使用Rascal MPL设计数据建模的DSL,以下是我的语法规范的一部分:
语法 声明
= @可折叠 实体: "entity" 实体标识符 名称 "{" 字段+ 字段 "}"
;
语法 字段
= 字段: 标识符 名称 ":" 类型 类型约束? 约束
| 单向引用: 标识符 名称 "->" 类型 类型标识符
| 双向引用: 标识符 名称 "->" 类型 类型标识符 "inverse" 标识符 引用 "::" 标识符 属性
;
实体 雇员 {
雇佣日期 : 日期
工资率 : 货币
上司 -> 经理 逆经理::下属
}
实体 经理 {
下属 -> 集合<雇员> 逆雇员::上司
}
我已经使用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
答案1
得分: 1
有趣的问题,你的问题可以相对容易地解决。秘密武器是 useViaType
,它用于检查另一种类型中定义的类型,比如结构声明或者在你的情况下,实体声明。
实现这一点的基本 collect
规则如下:
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);
}
第一个规则创建一个单独的作用域,用于包围当前实体声明中的字段声明。
第二个规则:
- 使用了
ref
- 通过
ref
的类型使用了attr
为了方便起见,我已经将解决方案(稍微简化了一些)放在了 TypePal 存储库中的一个单独示例中,参见 https://github.com/usethesource/typepal/tree/master/src/examples/dataModel
给定(错误的)输入:
entity Employee {
boss -> Manager inverse Manager::subordinates
}
entity Manager {
subordinates -> Set<Employee> inverse Employee::bos
}
现在你将收到以下错误消息:
error("No definition found for field `bos` in type `Employee`",
|project://typepal/src/examples/dataModel/example1.dm|(139,3,<6,51>,<6,54>))
将 bos
替换为 boss
,错误将消失。
希望这能帮助你完成你的项目。
对问题的回应:关于类型的额外检查可能如下所示:
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);
}
你可能需要根据你的具体需求进行调整。我已经在 TypePal 存储库中更新了示例。
英文:
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 ofref
.
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论