在文章开始之前,假设运行在debug模式且模板文件都放在bin\debug\templates目录下,并且已经在应用程序启动时设置了默认的模板组loader(详细):
void Application_Start(object sender, EventArgs e) { StringTemplateGroup.RegisterGroupLoader(new LWMEGroupLoader(new string[] { AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.BaseDirectory + "Templates"}, null)); }
1、简介
ST的模板组继承是指继承父模板组的所有成员(实际上是直接调用父模板组的成员,而非复制成员到子模板组),ST模板组只有2种成员:模板、Maps(算数据成员)。
ST的模板组接口仅仅作为一种约束,只提供模板定义,没有任何实现。
ST支持对模板组的单继承及接口的多继承,这一点从StringTemplateGroup的字段定义可以直接看出:
private List<StringTemplateGroupInterface> _interfaces; private StringTemplateGroup _superGroup;
在使用模板组或模板组接口的时候应该注意以下2点:
- 无论是模板组或模板组接口,它的名称应当与文件名一致,主要是因为子模板组会根据定义的继承去自动加载父模板组或模板组接口文件。
- 在整个应用程序中都应该避免出现相同的模板组名称或模板组接口名称,还是因为文件的加载,同时还有缓存的原因(缓存key为它们名称)。
2、模板组继承
它的格式为:
group mygroup : supergroup; ...
与普通模板组文件一样,子模板组文件必须包含至少一个成员。
接下来看例子(文件都存放在bin\debug\templates),basegroup.stg:
group basegroup; dict ::= [ "int" : "0", "long" : "00", "float" : "0.0", "bool" : "false", "first" : "1", default : "null" ] t1() ::= << <dict.int> <dict.float> <dict.none> <dict.("first")> >>child.stg:
group child : basegroup; //必须包含至少一个成员 t2() ::= "<dict.int> in childgroup"
调用代码:
StringTemplateGroup g = StringTemplateGroup.LoadGroup("child"); Console.WriteLine(g.GetInstanceOf("t1").ToString());
输出:0 0.0 null 1
可以看出模板和Maps都继承过来了,同时模板组继承还支持模板覆盖,把child.stg中的t2改成t1,再运行以上代码:
输出:0 in childgroup
覆盖了父模板组的模板之后,若还需要调用父模板组的模板,则需要在模板名称前加"super."(详细可以查看StringTemplateGroup.LookupTemplate(StringTemplate enclosingInstance, string name)):
StringTemplateGroup g = StringTemplateGroup.LoadGroup("child"); Console.WriteLine(g.GetInstanceOf("t1").ToString()); Console.WriteLine(g.GetInstanceOf("super.t1").ToString());
输出:0 in childgroup 0 0.0 null 1
同样的,可以直接在模板组文件里调用父模板的方法:
group child : basegroup; //必须包含至少一个成员 t1() ::= "<dict.int> in childgroup <\n> <super.t1()>"
此外,ST还支持模板文本区域替换(一种在模板内部定义的类似虚方法的定义,区块前加@),它只能定义在模板内部,它有两种方式(无实现与有实现):
//文件basegroup.stg group basegroup; t1() ::= << <@virtualMethod1()> <!这是没有任何实现的模板文本区域!> <@virtualMethod2> 假如此模板文本区域未在子模板组中被覆盖,则会显示这些字 <@end><!这是有实现的模板文本区域!> >> //文件child.stg group child; @t1.virtualMethod1() ::= << 模板文本区域1被子模板组模板覆盖 >> @t2.virtualMethod1() ::= << 模板文本区域2被子模板组模板覆盖 >>
调用t1模板,则会输出:
模板文本区域1被子模板组模板覆盖 模板文本区域2被子模板组模板覆盖
假如注释掉@t2.virtualMethod1(),则输出:
模板文本区域1在子模板组被覆盖 假如此模板文本区域未在子模板组中被覆盖,则会显示这些字
有一点需要注意,在子模板组中定义的模板文本区域不能有自己的参数,但可以调用父模板组模板的参数。
在覆盖了父模板组模板的模板文本区域之后,还需要调用父模板组模板的模板文本区域,则可以在模板文本区域前加@super.:
@t2.virtualMethod1() ::= << 模板文本区域2被子模板组模板覆盖 <@super.virtualMethod2()> >>
前面说到对于模板组继承“实际上是直接调用父模板组的成员,而非复制成员到子模板组”,答案也在LookupTemplate方法中,当调用一个模板的时候,ST会在当前模板组的_templates字典中查找,若找不到且父模板组不为空,再从父模板组的_templates中查找。
ST不支持Maps的覆盖,假如子模板组定义了与父模板组相同名称的Maps,则会提示map重定义,并且只会使用父模板组的Maps。
ST在文件中定义继承时不支持多级继承,主要表现为,定义了多级继承之后不会自动加载父模板组。
3、缓存与自动加载
StringTemplateGroup内部使用静态的字典变量来缓存模板组、模板组接口:
public static IDictionary<string, StringTemplateGroup> _nameToGroupMap; public static IDictionary<string, StringTemplateGroupInterface> _nameToInterfaceMap;
对于模板组、模板组接口都使用它们的名称(非文件名)作为字典的key,所以应该避免使用相同的模板组、模板组接口名称,否则可能会引起怪异的问题。
模板组或模板组接口文件变更的时候,这2个缓存并不会自动刷新它们,同时也没有像模板文件那样提供RefreshInterval属性,不过好在它们是公开的,可以直接调用它们的Clear方法清除缓存。
先看StringTemplateGroup的2个方法的定义:
public virtual void SetSuperGroup(string superGroupName) { StringTemplateGroup superGroup = _nameToGroupMap.get<string, StringTemplateGroup>(superGroupName); if (superGroup != null) { this.SuperGroup = superGroup; } else { superGroup = LoadGroup(superGroupName, this._templateLexerClass, null); if (superGroup != null) { _nameToGroupMap[superGroupName] = superGroup; this.SuperGroup = superGroup; } else if (_groupLoader == null) { this._listener.Error("no group loader registered", null); } } } public virtual void ImplementInterface(string interfaceName) { StringTemplateGroupInterface I = _nameToInterfaceMap.get<string, StringTemplateGroupInterface>(interfaceName); if (I != null) { this.ImplementInterface(I); } else { I = LoadInterface(interfaceName); if (I != null) { _nameToInterfaceMap[interfaceName] = I; this.ImplementInterface(I); } else if (_groupLoader == null) { this._listener.Error("no group loader registered", null); } } }
在某一章中有说到的_groupLoader,现在重新提一下,实际上它最大的用处就是用在以上2个方法中,在使用的时候需要注意的是:初始化loader的时候要传递目录参数,自动加载的时候将从这些目录加载父模板组文件、模板组接口文件,如果这些文件位于多个目录则指定多个目录参数。
当继承定义在文件中时,对于被继承的模板组、模板组接口,Antlr3.ST.Language.GroupParser会自动调用SetSuperGroup、ImplementInterface方法来实现自动加载父模板组、模板组接口;从上面方法可以看出,第二次都会直接从缓存的字典中加载(通过LoadGroup、LoadInterface方法加载的不在此列),所以要特别注意避免整个应用中模板组或模板组接口重名。
4、模板组接口
模板组接口文件默认使用.sti扩展名,它的定义更简单:
interface i1; t1(args); t2(arg1,arg2,arg3); //单行注释 /* 多行注释 */ optional t3(args);//可选的
实现接口的时候要保持签名(模板名称、参数列表和参数名称)一致,另外需要说明的就是optional关键字,通过这个关键字声明的模板,实现此接口的模板组可以不实现此模板:
group child2 implements i1; t1(args) ::= "11" t2(arg1,arg2,arg3) ::= "22" //可以不实现t3(args) ::= "33"
模板组可以同时实现多个接口:
group child2 implements i1,i2,i3;
假如父模板组实现了某个接口的方法,子模板组继承此接口的时候可以不去实现它。
interface i1; optional t2(arg1); t3(arg1); interface i2; t1(arg1, arg2, arg3); group baseg; t3(arg1) ::= "<arg1>" group child2 : baseg implements i1, i2; t1(arg1,arg2,arg3) ::= "<arg1> <arg2> <arg3>"
最后,接口之间没有继承的概念。
5、通过代码实现继承
方法一:
StringTemplateGroup g1 = StringTemplateGroup.LoadGroup("child"); StringTemplateGroup g2 = StringTemplateGroup.LoadGroup("child2", g1); StringTemplateGroup g3 = StringTemplateGroup.LoadGroup("child3", g2); StringTemplateGroup g4 = StringTemplateGroup.LoadGroup("child4", g3);
通过LoadGroup可以实现N级继承。方法二:
StringTemplateGroup g1 = StringTemplateGroup.LoadGroup("child2"); g.SetSuperGroup("child");
继承接口同样有两种方法,方法一:
StringTemplateGroup g = StringTemplateGroup.LoadGroup("Templates/child2"); g.ImplementInterface("i1");
方法二:
StringTemplateGroup g = StringTemplateGroup.LoadGroup("Templates/child2"); g.ImplementInterface(StringTemplateGroup.LoadInterface("i1"));
以上方法不需要在模板组文件里设置继承,但它们将会导致继承体系的不稳定,所以不推荐使用它们。
本文地址:http://www.cnblogs.com/lwme/archive/2010/05/02/1726108.html
参考:http://www.antlr.org/wiki/display/ST/Group+Files#GroupFiles-Supergroupsandinterfaces