今天早上原本想整理一篇利用 regexp 对象的 to_s 方法实现正则表达式嵌套的文章,但在整理代码的过程中确偶然出现了一个小插曲:使用 puts 语句输出一个正则总会报语法错误。出错的代码如下:
puts / s /xi
这时会报一个语法错误: syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '(' . 这个表达式是完全正确的一个正则表达式,这时你如果这样把 puts 省略的括号添加上就没有问题了,说明表达式就没有问题的。更有意思的在后面,如果我们在代的前面添加两行代码,变成下面这样:
s = 5
xi = 6
puts / s /xi
还是报错,不过错误提示换了:undefined method '/' for nil:NilClass (NoMethodError) 有意思吧。如果一件事情的结果总是摇摆不定,很有可能就是我们没有搞清楚事情背后的真正原因。就这件事情来说,造成这种现象的根本原因应该是最初的那行代码因为省略的括号导致了语法歧义。
在原生支持正则的语言中除号有两个作用:1. 作为除法运算的符号 2. 作为正则表达式的开始。那么对于那行代码的理解也就有了两种可能:1. 作为除号去理解所以那行代码会等价于:
(foo) / (s) / (xi)
foo (/ s /xi)
一开始的时候解释器按正则来理解的,但比较诡异的是他报了正则错误,这有点让人搞不懂,明明已经知道是正则了,不知道正则有 x 模式吗?感觉这似乎是词法程序的一个 bug。第二次解释器可能根据上下文做了判断认为是除法的可能性更大一些,所以按除法去理解的,但因为 puts 返回的结果是 nil 所以报了哪个错误。如果我们把括号补全那么这种歧义也就消失了,就都会按正则来理解。感觉 ruby 的这种处理方式是有一些危险性的。如果在真实的项目中真的出现了这种歧义的代码而 Ruby 不报错而是做猜测,并且恰好猜对了,之后有人添加了两行代码,并且恰好改变了当时的语义环境使解释器做了另一个决策,那就会出问题了。
在 JS 中因为不能省略括号,所以这种歧义代码是不可能出现的,因为 JS 的判断策略是固定的,只要除号的前面是 name 也就是我们定义的变量或对象属性,那就一定是除号否则就是正则。但 Ruby 中因为可以省略括号所以造成了这种二义性代码的存在。 因此建议大家写代码的时候最好给方法调用带上括号,避免这种二义性代码的产生,省略是有代价的。
以上代码的测试环境: win7, ruby 2.0.0p247。自己是业余 ruby 玩家,文中如果错误,欢迎大家指正