Ruby 省略括号导致的语法歧义

__main__ · 2013年09月17日 · 最后由 lolychee 回复于 2013年09月17日 · 3052 次阅读

今天早上原本想整理一篇利用 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)

  1. 作为正则表达式去理解,所以代码等价于 foo (/ s /xi) 一开始的时候解释器按正则来理解的,但比较诡异的是他报了正则错误,这有点让人搞不懂,明明已经知道是正则了,不知道正则有 x 模式吗?感觉这似乎是词法程序的一个 bug。第二次解释器可能根据上下文做了判断认为是除法的可能性更大一些,所以按除法去理解的,但因为 puts 返回的结果是 nil 所以报了哪个错误。如果我们把括号补全那么这种歧义也就消失了,就都会按正则来理解。

感觉 ruby 的这种处理方式是有一些危险性的。如果在真实的项目中真的出现了这种歧义的代码而 Ruby 不报错而是做猜测,并且恰好猜对了,之后有人添加了两行代码,并且恰好改变了当时的语义环境使解释器做了另一个决策,那就会出问题了。

在 JS 中因为不能省略括号,所以这种歧义代码是不可能出现的,因为 JS 的判断策略是固定的,只要除号的前面是 name 也就是我们定义的变量或对象属性,那就一定是除号否则就是正则。但 Ruby 中因为可以省略括号所以造成了这种二义性代码的存在。 因此建议大家写代码的时候最好给方法调用带上括号,避免这种二义性代码的产生,省略是有代价的。

以上代码的测试环境: win7, ruby 2.0.0p247。自己是业余 ruby 玩家,文中如果错误,欢迎大家指正

能省就省

为什么我在 irb 里面试没有这个错误呢?? 输出如下:

2.0.0-p0 :001 > puts / s /xi
2.0.0-p0 :002/> 

#2 楼 @AlphaLiu 是没有出错,但是你看解释器认为你的代码还没有结束

#3 楼 @kikyous 我知道了,要写到文件去运行才会报错

很多时候不能省略,比如

 a.gsub(/xxx/,'aaa') .rot13

 a.gsub /xxx/,'aaa'  .rot13

语法检查有必要,图中是 vim + syntastic

#6 楼 @Rei 高端,大气,上档次了。。

我觉得因为一次错误就全部加上括号那就因咽废食了,不过要给自己项目定语法风格也没问题。

楼主忘了还有这样定义正则的写法:

puts %r/ s /xi

这样修改之后 你的例子是完全没问题的

需要 登录 后方可回复, 如果你还没有账号请 注册新账号