为什么语句像1+;在Ruby中允许n*=3?
许多Ruby文档中的优先级表都列出了二进制算术运算,它们的优先级高于相应的复合赋值运算符。这让我相信像这样的代码不应该是有效的Ruby代码,但事实确实如此为什么语句像1+;在Ruby中允许n*=3?,ruby,Ruby,许多Ruby文档中的优先级表都列出了二进制算术运算,它们的优先级高于相应的复合赋值运算符。这让我相信像这样的代码不应该是有效的Ruby代码,但事实确实如此 1 + age *= 2 如果优先规则是正确的,我希望上面的代码会像下面这样插入括号: ((1 + age) *= 2) #ERROR: Doesn't compile 但事实并非如此 那么给出了什么呢?NB这个答案不应该被标记为解决了问题。有关正确的解释,请参见@Amadan的答案。 我不确定你提到的“许多Ruby文档”是什么,这里是最
1 + age *= 2
如果优先规则是正确的,我希望上面的代码会像下面这样插入括号:
((1 + age) *= 2) #ERROR: Doesn't compile
但事实并非如此
那么给出了什么呢?NB这个答案不应该被标记为解决了问题。有关正确的解释,请参见@Amadan的答案。
我不确定你提到的“许多Ruby文档”是什么,这里是最新的 Ruby解析器尽力理解并成功解析输入;当它看到语法错误时,它会尝试另一种方法。也就是说,语法错误比所有运算符优先级规则具有更高的优先级 因为LHO必须是可变的,所以它从赋值开始。在这种情况下,可以使用默认的优先顺序完成解析,并且在
*=
之前完成+
:
age = 2
age *= age + 1
#⇒ 6
Ruby在代码实际执行之前有3个阶段 标记化->解析->编译 让我们看看Ruby生成的AST(抽象语法树),这是解析阶段
# @ NODE_SCOPE (line: 1, location: (1,0)-(1,12))
# | # new scope
# | # format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body
# +- nd_tbl (local table): :age
# +- nd_args (arguments):
# | (null node)
# +- nd_body (body):
# @ NODE_OPCALL (line: 1, location: (1,0)-(1,12))*
# | # method invocation
# | # format: [nd_recv] [nd_mid] [nd_args]
# | # example: foo + bar
# +- nd_mid (method id): :+
# +- nd_recv (receiver):
# | @ NODE_LIT (line: 1, location: (1,0)-(1,1))
# | | # literal
# | | # format: [nd_lit]
# | | # example: 1, /foo/
# | +- nd_lit (literal): 1
# +- nd_args (arguments):
# @ NODE_ARRAY (line: 1, location: (1,4)-(1,12))
# | # array constructor
# | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
# | # example: [1, 2, 3]
# +- nd_alen (length): 1
# +- nd_head (element):
# | @ NODE_DASGN_CURR (line: 1, location: (1,4)-(1,12))
# | | # dynamic variable assignment (in current scope)
# | | # format: [nd_vid](current dvar) = [nd_value]
# | | # example: 1.times { x = foo }
# | +- nd_vid (local variable): :age
# | +- nd_value (rvalue):
# | @ NODE_CALL (line: 1, location: (1,4)-(1,12))
# | | # method invocation
# | | # format: [nd_recv].[nd_mid]([nd_args])
# | | # example: obj.foo(1)
# | +- nd_mid (method id): :*
# | +- nd_recv (receiver):
# | | @ NODE_DVAR (line: 1, location: (1,4)-(1,7))
# | | | # dynamic variable reference
# | | | # format: [nd_vid](dvar)
# | | | # example: 1.times { x = 1; x }
# | | +- nd_vid (local variable): :age
# | +- nd_args (arguments):
# | @ NODE_ARRAY (line: 1, location: (1,11)-(1,12))
# | | # array constructor
# | | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
# | | # example: [1, 2, 3]
# | +- nd_alen (length): 1
# | +- nd_head (element):
# | | @ NODE_LIT (line: 1, location: (1,11)-(1,12))
# | | | # literal
# | | | # format: [nd_lit]
# | | | # example: 1, /foo/
# | | +- nd_lit (literal): 2
# | +- nd_next (next element):
# | (null node)
# +- nd_next (next element):
# (null node)
如您所见,+-nd#mid(方法id):+
其中1
被视为接收方,右侧的所有内容都被视为参数。现在,它更进一步,尽最大努力评估参数
进一步支持Aleksei的伟大回答。@NODE\u DASGN\u CURR(第1行,位置:(1,4)-(1,12))
是对age
的赋值,作为一个局部变量,因为它将其解码为age=age*2
,这就是为什么+-nd\u mid(方法id):*
被视为对age
的操作作为接收器,而2
作为其参数
现在,当它继续编译时,它尝试作为操作:age*2
其中age
为nil,因为它已经将其解析为没有预先赋值的局部变量,因此引发异常未定义的方法'*',用于nil:NilClass(NoMethodError)
它的工作原理是,接收器上的任何操作都必须有来自RHO的已计算参数。检查
ruby-y
输出,您可以准确地看到发生了什么。考虑到1+age*=2
的来源,输出表明发生了这种情况(简化):
找到了tINTEGER
,识别为简单的_数值的
,它是一个数值的
,它是一个文字的
,它是一个主要的
。知道接下来是+
,primary
被识别为arg
找到了+
。还不能交易
tIDENTIFIER
已找到。知道下一个令牌是tOP\u ASGN
(操作员分配),tIDENTIFIER
被识别为user\u variable
,然后被识别为var\u lhs
tOP\u ASGN
找到。还不能交易
tINTEGER
找到。与上一个相同,它最终被识别为主
。知道下一个令牌是\n
,主
被识别为arg
此时,堆栈上有arg+var\u lhs tOP\u ASGN arg
。在此上下文中,我们将最后一个arg
识别为arg\u rhs
。现在,我们可以从堆栈中弹出var\u lhs tOP\u ASGN arg\u rhs
,并将其识别为arg
,堆栈的结尾为arg+arg
,这可以简化为arg
arg
然后被识别为expr
,stmt
,top\u stmt
,top\u stmts
<代码>\n被识别为术语
,然后是术语
,然后是选择术语
<代码>顶部搜索选项术语被识别为顶部搜索选项
,最终被识别为程序
另一方面,如果源代码
1+age*2
,则会发生以下情况:
找到了tINTEGER
,识别为简单的_数值的
,它是一个数值的
,它是一个文字的
,它是一个主要的
。知道接下来是+
,primary
被识别为arg
找到了+
。还不能交易
tIDENTIFIER
已找到。知道下一个令牌是*
,tIDENTIFIER
被识别为user\u variable
,然后是var\u ref
,然后是primary
和arg
*
已找到。还不能交易
tINTEGER
找到。与上一个相同,它最终被识别为主
。知道下一个令牌是\n
,主
被识别为arg
堆栈现在是arg+arg*arg
arg*arg
可以减少为arg
,结果arg+arg
也可以减少为arg
arg
然后被识别为expr
,stmt
,top\u stmt
,top\u stmts
<代码>\n被识别为术语
,然后是术语
,然后是选择术语
<代码>顶部搜索选项术语被识别为顶部搜索选项
,最终被识别为程序
关键的区别是什么?在第一段代码中,
age
(一个tIDENTIFIER
)被识别为var\u lhs
(赋值的左侧),但在第二段代码中,它是var\u ref
(变量引用)。为什么?因为Bison是一个LALR(1)解析器,这意味着它有一个令牌前瞻。所以age
是var\u lhs
,因为Ruby sawtOP\u ASGN
即将出现;当它看到*
时,它是var\u ref
。这是因为Ruby知道(使用Bison生成的巨大数据)一个规范