上个月说到了自定义语言,不过在确定自己的语法后,遇到的第一个难题便是如何解析自己定义的语言,至于如何执行,是这之后的事情。
找一个语法解析器
软件中有一点很重要,不要重复造轮子,等等,这话听起来有点怪,自定义语言不也是重复造轮子么?
好吧,我承认我在重复造轮子,不过我的主要目的不是让自己的语言怎么怎么样,而是去更深入的理解那些编程语言,要是运气好,自己找的轮子能够流行起来,那自然更好了。
因此,我要造的是语言,而不是语法解析器,所以不要重复造语法解析器,没有那个时间和精力去消耗在这上面。
所以,随便找了个语法解析器:Grammatica
那么为什么用这个而不用其它的?好吧,没有理由,仅仅是因为随便找了一下。哦,对了,还因为它支持我需要的LL(*)的语法结构。
使用之前
在使用这个开源工具的.net版本之前,先要帮它修个小bug,当然这个小bug不是必须要修,只不过我的运用场景导致了了必须要修复。
要修复什么问题哪?
自定义异常
建议大家都去读一下msdn上面的建议
尤其是这一句:
Do make exceptions serializable. An exception must be serializable to work correctly across application domain and remoting boundaries.
碰巧我的运行环境是在一个独立的AppDomain中,因此,脚本Parse所发生的异常会穿透域,而Grammatica的所有自定义异常都没有实现序列化,直接导致了序列化异常时出错,然后么,就杯具了,谁知道什么错了。
因此,第一件事情,便是修复所有的自定义异常,正确实现序列化(不要以为标记了[Serializable]就已经实现了哦,异常的序列化可没这么简单,想进一步了解的话,可以看msdn,或者google,或者等我那天想到了写一下吧)。
设计语法
一个好的语法绝对是一门语言成败的关键,不过,我目前还没掌握如何定义出一门好的语法。这里,先用一个滥语法来做个演示。
首先,定义语言的一些Token,例如:
LAMBDA = "->"
LEFT_PAREN = "("
RIGHT_PAREN = ")"
LEFT_CURLY_BRACKET = "{"
RIGHT_CURLY_BRACKET = "}"
COLON = ":"
DOT = "."
ARGUMENT_SEPARATOR = ","
和一些基本的字面量和保留字:
NUMBER = <<-?\d+(\.\d+)?>>
STRING_LITERAL = <<'([^'\r\n\\]|\\u[0-9a-f]{4}|\\[\\"'trn])*'>>
TRUE = "true"
FALSE = "false"
NULL_LITERAL = "null"
FUNC = "func"
NEW = "new"
和剩下的标识符:
IDENTIFIER = <<[a-zA-Z]\w*>>
这些是基础,来看看如何把这些Token组和起来(productions):
Expression = BasicExpression {MemberAccessExpression};
表达式就是一个基础表达式,加上0-N个成员访问表达式
MemberAccessExpression = "." MemberFunctionExpression;
成员访问表达式就是”.”加上一个成员或方法表达式
BasicExpression = LiteralExpression | VariableExpression | ExpressionGroup | NEWExpression;
基础表达式就是字面量表达式、变量表达式、表达式组、对象创建表达式中的任意一个
MemberFunctionExpression = FieldPropertyExpression | FunctionCallExpression;
成员或方法表达式就是字段/属性表达式或方法调用表达式中的一个
FieldPropertyExpression = IDENTIFIER;
字段/属性表达式就是标识符
FunctionCallExpression = IDENTIFIER "(" ArgumentList? ")";
方法调用表达式就是标识符+“(”+可能有参数列表+“)”
ArgumentList = Expression {"," Expression};
参数列表就是一个表达式,后面跟0-N个表达式
FUNCExpression = FUNC "(" ParameterList? ")" "->" Expression;
函数表达式就是“func”+”(”+可能有参数列表+“)”+”->”+表达式
ParameterList = IDENTIFIER {"," IDENTIFIER};
参数列表就是一个标识符,后面跟0-N个标识符
NEWExpression = NEW "{" PropertyValueExpression { "," PropertyValueExpression } "}";
对象创建表达式就是”new”+”{”+1-N个属性值表达式+”}”
PropertyValueExpression = IDENTIFIER ":" Expression;
属性值表达式就是一个标识符+”:”+表达式
ExpressionGroup = "(" Expression ")";
表达式组就是”(”+表达式+”)”
VariableExpression = IDENTIFIER;
变量表达式就是标识符
LiteralExpression = NUMBER | STRING_LITERAL | TRUE | FALSE | NULL_LITERAL;
字面量表达式就是数字、字符串、true、false、null中的任意一个
看到这里,如果没有看晕,那么恭喜你,太有天赋了,为了理解这些,我足足花了一个星期的时间。
看看能解析什么
为了让大家知道上面写了这么大一串,究竟发生了什么作用,我们可以从简到繁的写几个例子:
解析:a.b.c
结果:变量a.字段b.字段c
解析:foo.bar(x,y)
结果:变量foo.方法bar(参数列表:变量x,变量y)
解析:foo.bar().foobar
结果:变量foo.方法bar().字段foobar
解析:new { firstName: ‘Zhenway’, lastName: ‘Yan’ }
结果:创建对象,字段firstName的值为“Zhenway”,字段lastName的值为“Yan”
解析:list.select(func(item)->item.abc)
结果:变量list.方法select(参数列表:lambda:形参列表:item -> 变量item.字段abc)
再来个复杂点的
解析:new { firstName: ‘Zhenway’, lastName: ‘Yan’, toString: func() –> ‘Zhenway Yan’ }
结果:创建对象,字段firstName的值为“Zhenway”,字段lastName的值为“Yan”,字段toString是个lambda:形参列表:无 -> 字符串字面量“Zhenway Yan”
解析:x.f(x.g(x.h(1)))
结果:变量x.方法f(参数列表:变量x.方法g(参数列表:变量x.方法h()))
有没有发现了什么?还没?再来一个:
new { name: new { firstName: ‘Zhenway’, lastName: ‘Yan’, toString: func() –> ‘Zhenway Yan’ }, address: new { country:’China’ province: 'Shanghai', toString: func() –> 'Shanghai China’ } }
现在发现什么了?
没错这个结构可以一直嵌套下去,这就是表达式带来的无穷的想象力。
还缺什么
有没有发现缺了什么?现在的定义是在太少了,很多东西无法表达,例如name的toString其实永远是firstName+lastName,看缺了什么,让我们无法这样定义:
- 最明显的是“+”,+、-、*、/、%这类表达式尚未定义(当然你要想的全的话,还有and、or、xor等等)
- 其次是没有this,无法表达当前对象(这里,我的面向对象的惯性思维又冒出来了…)
如果,添加一个Token:THIS=”this”,并且加一个表达式:
THISExpression = THIS;
this表达式就是”this”
再修改一下BasicExpression,添加上一个或THISExpression,那么我们就可以在完全兼容原有语法的基础上,添加了对this的支持,例如:
new { item: ‘abc’, getItemLength: func() -> this.item.length }
是不是很Cool,想象一下,当你有个新Idea,并且实现时,你的所有表达式都可能从中获益,而且对老的表达式又完全兼容。
下集预告
这么Cool的功能如何实现哪?Oh, no!说了半天都是理论啊,具体如何实现,请听下回分解。