Groovy如何区分除法和字符串?
我不完全确定Groovy是如何做到的,但我会描述一下我会如何处理,并且如果Groovy没有类似的工作方式,我会非常惊讶。
我听说过的大多数解析算法(例如Shunting-yard、Pratt等)都识别两种不同的标记:
- 那些期望在表达式之前出现的标记(中缀运算符、后缀运算符、闭合括号等)。如果其中一个标记没有在表达式之前出现,则会出现语法错误。
- 那些不期望在表达式之前出现的标记(前缀运算符、开放括号、标识符、文字等)。如果其中一个标记在表达式之前出现,则会出现语法错误。
为了使事情变得更容易,从这个点开始,我将称前一种标记为运算符,后一种标记为非运算符。
现在,有趣的是,这种区别不是基于标记本身,而是基于立即上下文,特别是前面的标记。因此,相同的标记可以根据其在代码中的位置以及解析器将其分类为运算符或非运算符而被解释得非常不同。例如,-
标记如果在运算符位置,则表示减法,但是如果在非运算符位置,则表示否定。决定-
是否是减法运算符没有问题,因为您可以根据其上下文来判断。
在Groovy中,通常也是如此。一般情况下,/
字符的含义取决于它前面的标记:
- 如果
/
跟随标识符、文字、后缀运算符、闭合括号或其他表示表达式结束的标记,则被解释为除法。
- 如果
/
跟随前缀运算符、中缀运算符、开放括号或其他这样的标记,或者如果它开始一行,则开始一个字符串。
当然,在实践中并不完全如此。Groovy旨在面对各种风格和用途的灵活性,因此分号或括号之类的东西通常是可选的。这可能会使解析有些模糊。例如,假设我们的解析器遇到以下行:
println / foo
这很可能是打印多行字符串的尝试:
foo
是传递给
println
的字符串的开头,可选的参数列表括号被省略了。当然,对于简单的解析器来说,它看起来像一个除法。我预计 Groovy 解析器可以通过读取以下几行来确定哪种解释不会出错,但对于像
groovysh
这样的东西来说,这是不可能的(因为作为 repl,它还没有访问更多行),所以它只能猜测。
为什么我不能在 groovysh 中评估斜杠字符串字面量?
与之前一样,我不知道确切的原因,但我知道因为 groovysh
是一个 repl,它在处理更模糊的规则时会遇到更多麻烦。即便如此,一个简单的单行斜杠字符串也是相当明确的,所以我相信这里可能发生了其他事情。下面是我在 groovysh
中尝试各种形式的结果:
> /foo - unexpected char: '/' @ line 2, column 1.
> /foo/ - awaits further input
> /foo/bar - unexpected char: '/' @ line 2, column 1.
> /foo/bar/ - awaits further input
> /foo/ + 'bar' - unexpected char: '/' @ line 2, column 1.
> 'foo' + /bar/ - evaluates to 'foobar'
> /foo/ - evaluates to 'foo'
> /foo - awaits further input
> /foo/bar - Unknown property: bar
似乎当“/”字符是一行的第一个字符时,会发生一些奇怪的事情。根据我所知,它遵循以下模式:
- 以斜杠作为行首字符开始一种奇怪的解析模式。
- 在此模式下,每行以斜杠结尾且后面只有空格的行会导致repl等待更多行。
- 在第一行以斜杠(或斜杠后面跟着空格)以外的其他字符结束时,将打印错误消息“unexpected char: '/' @ line 2, column 1.”。
我还注意到了以下几点:
- 此特殊模式中正斜杠(/)和反斜杠(\)都似乎是可计算的,并且可以完全互换。
- 这在groovyConsole或实际的Groovy文件中似乎根本不会发生。
- 在开头斜杠之前放置任何空格会使groovysh正确解释它,但仅适用于正斜杠而非反斜杠。
因此,我个人认为这只是groovysh的一个怪癖,可能是一个错误或一些未记录的功能,我还没有听说过。
f
除以 2。没有结束的/
,即使有也会产生歧义,Groovy 也不允许使用f / 2 /
这种形式。不确定为什么/foo/
在 Groovy shell 中不起作用...但在 Groovy Console 中可以。 - tim_yates