每周源代码59—开放源宝藏:Irony.NET语言应用包
[原文发表地址]The Weekly Source Code 59 - An Open Source Treasure: Irony .NET Language Implementation Kit
[原文发表时间]2011-10-14 08:41
要锐化武器,不断更新自己的软件开发技术,最好的方法就是阅读代码。当然,你得不断练习写代码,但是千万不要忽略了别人的成果。要创建一个“数据文本框”应用通常都会有超过15种的方法可以实现,有时候看看创建商业软件的最新方法是很有趣的,而有时候看看像分析程序,语法分析器和抽象句法树之类的经典应用也不失为一种消遣。如果你没有去学校参加过编译课程,那么你至少得了解软件工程中是有这部分内容的,可以通过很多途径来应用,这是非常重要的。
发现一些我原本不知道的开放源项目是很让人高兴的。这里就有一个项目,是我在帮客户搜索一些内容时偶然发现的,“Irony”.NET语言应用包。CodePlex网站上这样写:
Irony是应用在.NET平台上的开发工具包。和现有的大部分yacc/lex风格方案不同,Irony不通过特定meta语言写成的语法规范生成的代码来部署任何扫描仪或者解析器。在Irony中,目标语言语法使用操作器超载表达语法构造,从而在C#中直接编码。Irony的扫描仪和解析器模型使用编译的语法,如同C#类别掌控解析进程一样。参见表达语法样本,包含C#类别内定义的语法样本,在实际解析进程中应用。
Irony囊括了所需的“C#简化语法,设计方案,SQL,GwBasic,JSON等”。你可能熟悉很多种支持语法生成的解析器,比如,ANTLR就被称为LL(*) 语法生成器,而Irony则是一个LALR(向前看,从左到右)语法生成器。
以下是一个很基础的获取播客数据库显示的SQL陈述:
SELECT ID, Title FROM Shows WHERE ID = 1
以下是在irony语法资源管理器中显示的Irony解析树
在我的经验中,创建解析器时,大家会使用像BNF(Backus-Naur Form)表达语法上构建的GOLD Meta语言这样的DSL(特定域语言)。这些特定域语言不断优化来准确表达语言是如何建造,如何被解析的。要创造语言,你必须先学一种语言。
还记得先前有关Irony的介绍吗?我再重复一下:
和现有的大部分yacc/lex风格方案不同,Irony不通过特定meta语言写成的语法规范生成的代码来部署任何扫描仪或者解析器。在Irony中,目标语言语法使用操作器超载表达语法构造,从而在C#中直接编码。
Irony的Roman在这里做的就是把C#语言构建当做DSL来用。这是一个非常流畅的解析器。他使用C#类别和函数来表达语言语法。如果你对创建DSL有浓厚兴趣,那么这个想法趣味十足,而且功能强大,不过要是你对学习像GOLD这样的解析器没什么兴趣,那就不过是个乐子而已了。
他有一个叫做Grammar的丰富基础类别,你可以从中获取数据,比如:
1: [Language("SQL", "89", "SQL 89 grammar")]
2: public class SqlGrammar : Grammar {
3: public SqlGrammar() : base(false) { //SQL is case insensitive
4: ...
除了这样的语法语言(我已将之简化)来表达SQL选择陈述:
1:
2: !
3: =============================================================================
4: !
5: Select Statement
6: !
7: =============================================================================
8: <Select
9: Stm> ::= SELECT <Columns> <Into Clause> <From Clause>
10: <Where Clause> <Group Clause> <Having Clause> <Order
11: Clause>
12: <Columns> ::= <Restriction> '*'
13: |
14: <Restriction> <Column List>
15: ...snip for
16: clarity...
17: <Restriction> ::= ALL
18: |
19: DISTINCT
20: |
21: <Aggregate> ::= Count '(' '*' ')'
22: | Count '('
23: <Expression> ')'
24: | Avg '(' <Expression> ')'
25: | Min '('
26: <Expression> ')'
27: | Max '(' <Expression> ')'
28: | StDev '('
29: <Expression> ')'
30: | StDevP '(' <Expression> ')'
31: | Sum '('
32: <Expression> ')'
33: | Var '(' <Expression> ')'
34: | VarP '('
35: <Expression> ')'
36: <Into Clause> ::= INTO Id
37: |
38: <From
39: Clause> ::= FROM <Id List> <Join Chain>
40: <Join Chain> ::=
41: <Join> <Join Chain>
42: |
43: ...snip for clarity...
你会看到这样的内容,同样,我将其简化了,这样就不会出现一大堆代码列表,比一整篇博客还长了。
1: //Select stmt
2: selectStmt.Rule = SELECT + selRestrOpt + selList + intoClauseOpt + fromClauseOpt + whereClauseOpt +
3: groupClauseOpt + havingClauseOpt + orderClauseOpt;
4: selRestrOpt.Rule = Empty | "ALL" | "DISTINCT";
5: selList.Rule = columnItemList | "*";
6: columnItemList.Rule = MakePlusRule(columnItemList, comma, columnItem);
7: columnItem.Rule = columnSource + aliasOpt;
8: aliasOpt.Rule = Empty | asOpt + Id;
9: asOpt.Rule = Empty | AS;
10: columnSource.Rule = aggregate | Id;
11: aggregate.Rule = aggregateName + "(" + aggregateArg + ")";
12: aggregateArg.Rule = expression | "*";
13: aggregateName.Rule = COUNT | "Avg" | "Min" | "Max" | "StDev" | "StDevP" | "Sum" | "Var" | "VarP";
14: intoClauseOpt.Rule = Empty | INTO + Id;
15: fromClauseOpt.Rule = Empty | FROM + idlist + joinChainOpt;
16: joinChainOpt.Rule = Empty | joinKindOpt + JOIN + idlist + ON + Id + "=" + Id;
17: joinKindOpt.Rule = Empty | "INNER" | "LEFT" | "RIGHT";
18: whereClauseOpt.Rule = Empty | "WHERE" + expression;
19: groupClauseOpt.Rule = Empty | "GROUP" + BY + idlist;
20: havingClauseOpt.Rule = Empty | "HAVING" + expression;
21: orderClauseOpt.Rule = Empty | "ORDER" + BY + orderList;
以下是在构建语法时使用的变量和名词,在之前就做了如下的定义:
1: var SELECT = ToTerm("SELECT");
2: var FROM = ToTerm("FROM");
3: var AS = ToTerm("AS");
亲爱的读者们,你们可能会立马提出质疑,这简直就是亵渎!C#怎么能和像BNF这样的特定DSL相提并论呢?这是C#型限定的循环中解决内容。当然也许是这样,不过有趣的是SQL GOLD语法需要259行,而同样的内容C#版本则要247行。我说这个行数的意思就是,这个方法更好,是有效的一对一比较。不过,C#类别更贴切。你们大概以为这个可能会大很多。我觉得它比较贴切因为Roman作为Irony的开发者,有一个全面具化的库类,是获取类的很好的依靠。他所有的样本语法都很紧凑。比如:
· "Mini" Python - ~140行
· Java - ~130行
· Scheme - ~200 行
· JSON - 39 行
总结一下,以下是JSON语法生成器。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using Irony.Parsing;
6:
7: namespace Irony.Samples.Json {
8: [Language("JSON", "1.0", "JSON data format")]
9: public class JsonGrammar : Grammar {
10: public JsonGrammar() {
11: //Terminals
12: var jstring = new StringLiteral("string", "\"");
13: var jnumber = new NumberLiteral("number");
14: var comma = ToTerm(",");
15:
16: //Nonterminals
17: var jobject = new NonTerminal("Object");
18: var jobjectBr = new NonTerminal("ObjectBr");
19: var jarray = new NonTerminal("Array");
20: var jarrayBr = new NonTerminal("ArrayBr");
21: var jvalue = new NonTerminal("Value");
22: var jprop = new NonTerminal("Property");
23:
24: //Rules
25: jvalue.Rule = jstring | jnumber | jobjectBr | jarrayBr | "true" | "false" | "null";
26: jobjectBr.Rule = "{" + jobject + "}";
27: jobject.Rule = MakeStarRule(jobject, comma, jprop);
28: jprop.Rule = jstring + ":" + jvalue;
29: jarrayBr.Rule = "[" + jarray + "]";
30: jarray.Rule = MakeStarRule(jarray, comma, jvalue);
31:
32: //Set grammar root
33: this.Root = jvalue;
34: MarkPunctuation("{", "}", "[", "]", ":", ",");
35: this.MarkTransient(jvalue, jarrayBr, jobjectBr);
36:
37: }//constructor
38: }//class
39: }//namespace
很聪明的想法,是一个很好的组合项目方案,构建很合理。我自己也会在C#或者编译课上用这个来教授一些这样的理念。
这还是你创建自己语言的小工具。也许你有公司特定的维基方言,想摆脱那些人工的混乱的解析?或者在你们的应用中有陈旧的自定义工作流引擎或者自定义表达系统,他们不会改进你的解析,用合适的语法?那么现在你可以把你朝思暮想思索已久的语言想法付诸行动了!
亲爱的读者,我鼓励你们支持这样的开放源项目。快去你喜欢的开放源项目网站上留下你的感想吧,让他们知道你很欣赏他们正在努力的工作。
相关链接
· Irony博客Roman (更新不多)
· CodePlex的Irony – 10月刚出现Alpha发布!