列表和一元运算符组合的 Perl 运算符优先级
Posted
技术标签:
【中文标题】列表和一元运算符组合的 Perl 运算符优先级【英文标题】:Perl operator precendece for a combination of list and unary operators 【发布时间】:2015-12-14 14:27:13 【问题描述】:我想我遇到了一个奇怪的案例,与运算符优先级有关。考虑这个测试程序:
use strict;
use warnings;
use Test::More;
my $fn = 'dummy';
ok( ! -e $fn, 'file does not exists' );
ok( not -e $fn, 'file does not exists' );
done_testing();
输出是:
ok 1 - file does not exists
not ok 2
# Failed test at ./p.pl line 10.
1..2
# Looks like you failed 1 test of 2.
问题是:为什么第二次测试失败了? ($fn
被假定为不存在)
另请参阅:List Operator Precedence in Perl。
看完perlop,我猜这里至少可能涉及到五个运营商:
Terms and List Operators (Leftward)
List Operators (Rightward)
Named Unary Operators
Logical Not
Comma Operator
【问题讨论】:
【参考方案1】:perl -MO=Deparse
表明你的代码被解释为:
use Test::More;
use warnings;
use strict;
my $fn = 'dummy';
ok !(-e $fn), 'file does not exists';
ok !(-e $fn, 'file does not exists');
done_testing();
-e $fn
为假。
但'file does not exists'
本质上是正确的。
所以,列表(-e $fn, 'file does not exists')
是真的。
因此,!(...)
为假,测试失败。
【讨论】:
如果答案也能解释这与perlop中的文档有何关系,那就太好了。正在使用哪些运算符,首先和最后评估的内容以及原因 @HåkonHægland "正在使用哪些运算符"!
: 逻辑否定(高优先级); not
:逻辑否定(低优先级,生成与!
相同的操作码); -e
:文件测试操作符(一个命名的一元操作); ,
:逗号运算符。 “首先和最后评估的内容” perl -MO=Concise,-exec
按执行顺序转储解析树。 “以及为什么” !
> -e
> ,
> not
的优先级。【参考方案2】:
为什么第二次测试失败了?
因为 Perl 的解析器处理 !
和 not
的方式不同。您可以在 Perl 的语法中看到这一点,该语法在 Perl 源代码中的 perly.y 中定义。
只要解析器遇到 !
后跟一个术语,!
的规则就会生效:
| '!' term /* !$x */
$$ = newUNOP(OP_NOT, 0, scalar($2));
另一方面,not
的规则仅在解析器遇到 not
后跟列表表达式(以逗号* 连接的术语列表)时生效:
| NOTOP listexpr /* not $foo */
$$ = newUNOP(OP_NOT, 0, scalar($2));
请注意,两个规则的操作是相同的:将OP_NOT
类型的新一元操作码添加到解析树。操作数是标量上下文中的第二个参数(term
或 listexpr
)。
* 或单个术语,但它的优先级很低。
跟踪解析
您可以通过使用-DDEBUGGING
编译perl 并使用-Dpv
运行来查看上述规则,这将打开debug flags 以进行标记和解析。
这是解析器对!
所做的:
$ perl -Dpv -e'ok(! -e "foo", "bar")'
...
Next token is token '(' (0x1966e98)
Shifting token '(', Entering state 185
Reading a token:
Next token is token '!' (0x1966e98)
Shifting token '!', Entering state 49
Reading a token:
Next token is token UNIOP (0x110)
Shifting token UNIOP, Entering state 39
Reading a token:
Next token is token THING (0x1966e58)
Shifting token THING, Entering state 25
index: 2 3 4 5 6 7 8 9
state: 8 15 103 68 185 49 39 25
token: @1 remember stmtseq amper '(' '!' UNIOP THING
value: 0 22 (Nullop) rv2cv 26635928 26635928 272 const
Reducing stack by rule 184 (line 961), THING -> term
Entering state 128
Reading a token:
Next token is token ',' (0x1966e58)
index: 2 3 4 5 6 7 8 9
state: 8 15 103 68 185 49 39 128
token: @1 remember stmtseq amper '(' '!' UNIOP term
value: 0 22 (Nullop) rv2cv 26635928 26635928 272 const
Reducing stack by rule 199 (line 999), UNIOP term -> term
Entering state 150
Next token is token ',' (0x1966e58)
index: 1 2 3 4 5 6 7 8
state: 1 8 15 103 68 185 49 150
token: GRAMPROG @1 remember stmtseq amper '(' '!' term
value: 0 0 22 (Nullop) rv2cv 26635928 26635928 ftis
Reducing stack by rule 148 (line 829), '!' term -> termunop
Entering state 62
index: 1 2 3 4 5 6 7
state: 1 8 15 103 68 185 62
token: GRAMPROG @1 remember stmtseq amper '(' termunop
value: 0 0 22 (Nullop) rv2cv 26635928 not
...
换句话说,解析器读入
( ! -e "foo"
将-e "foo"
简化为term
,然后将逻辑否定操作码添加到解析树。在标量上下文中,操作数是 -e "foo"
。
这是解析器对not
所做的:
$ perl -Dpv -e'ok(not -e "foo", "bar")'
...
Reading a token:
Next token is token '(' (0x26afed8)
Shifting token '(', Entering state 185
Reading a token:
Next token is token NOTOP (0x26afed8)
Shifting token NOTOP, Entering state 48
Reading a token:
Next token is token UNIOP (0x110)
Shifting token UNIOP, Entering state 39
Reading a token:
Next token is token THING (0x26afe98)
Shifting token THING, Entering state 25
index: 2 3 4 5 6 7 8 9
state: 8 15 103 68 185 48 39 25
token: @1 remember stmtseq amper '(' NOTOP UNIOP THING
value: 0 22 (Nullop) rv2cv 40566488 40566488 272 const
Reducing stack by rule 184 (line 961), THING -> term
Entering state 128
Reading a token:
Next token is token ',' (0x26afe98)
index: 2 3 4 5 6 7 8 9
state: 8 15 103 68 185 48 39 128
token: @1 remember stmtseq amper '(' NOTOP UNIOP term
value: 0 22 (Nullop) rv2cv 40566488 40566488 272 const
Reducing stack by rule 199 (line 999), UNIOP term -> term
Entering state 65
Next token is token ',' (0x26afe98)
index: 1 2 3 4 5 6 7 8
state: 1 8 15 103 68 185 48 65
token: GRAMPROG @1 remember stmtseq amper '(' NOTOP term
value: 0 0 22 (Nullop) rv2cv 40566488 40566488 ftis
Reducing stack by rule 105 (line 683), term -> listexpr
Entering state 149
Next token is token ',' (0x26afe98)
Shifting token ',', Entering state 162
Reading a token:
Next token is token THING (0x26afdd8)
Shifting token THING, Entering state 25
index: 3 4 5 6 7 8 9 10
state: 15 103 68 185 48 149 162 25
token: remember stmtseq amper '(' NOTOP listexpr ',' THING
value: 22 (Nullop) rv2cv 40566488 40566488 ftis 40566424 const
Reducing stack by rule 184 (line 961), THING -> term
Entering state 249
Reading a token:
Next token is token ')' (0x26afdd8)
index: 3 4 5 6 7 8 9 10
state: 15 103 68 185 48 149 162 249
token: remember stmtseq amper '(' NOTOP listexpr ',' term
value: 22 (Nullop) rv2cv 40566488 40566488 ftis 40566424 const
Reducing stack by rule 104 (line 678), listexpr ',' term -> listexpr
Entering state 149
Next token is token ')' (0x26afdd8)
index: 1 2 3 4 5 6 7 8
state: 1 8 15 103 68 185 48 149
token: GRAMPROG @1 remember stmtseq amper '(' NOTOP listexpr
value: 0 0 22 (Nullop) rv2cv 40566488 40566488 list
Reducing stack by rule 196 (line 993), NOTOP listexpr -> term
Entering state 65
Next token is token ')' (0x26afdd8)
index: 1 2 3 4 5 6 7
state: 1 8 15 103 68 185 65
token: GRAMPROG @1 remember stmtseq amper '(' term
value: 0 0 22 (Nullop) rv2cv 40566488 not
...
换句话说,解析器读入
( not -e "foo"
将-e "foo"
减少为term
,读入
, "bar"
将term, "bar"
简化为listexpr
,然后将逻辑否定操作码添加到解析树。在标量上下文中,操作数是 -e "foo", "bar"
。
因此,即使两个逻辑否定的操作码相同,它们的操作数也不同。您可以通过检查生成的解析树来看到这一点:
$ perl -MO=Concise,-tree -e'ok(! -e "foo", "bar")'
<a>leave[1 ref]-+-<1>enter
|-<2>nextstate(main 1 -e:1)
`-<9>entersub[t1]---ex-list-+-<3>pushmark
|-<6>not---<5>ftis---<4>const(PV "foo")
|-<7>const(PV "bar")
`-ex-rv2cv---<8>gv(*ok)
-e syntax OK
$ perl -MO=Concise,-tree -e'ok(not -e "foo", "bar")'
<c>leave[1 ref]-+-<1>enter
|-<2>nextstate(main 1 -e:1)
`-<b>entersub[t1]---ex-list-+-<3>pushmark
|-<9>not---<8>list-+-<4>pushmark
| |-<6>ftis---<5>const(PV "foo")
| `-<7>const(PV "bar")
`-ex-rv2cv---<a>gv(*ok)
-e syntax OK
使用!
,否定作用于文件测试:
|-<6>not---<5>ftis
在使用not
时,否定作用于列表:
|-<9>not---<8>list
您还可以使用 B::Deparse 将解析树转储为 Perl 代码,它以不同的格式显示相同的内容:
$ perl -MO=Deparse,-p -e'ok(! -e "foo", "bar")'
ok((!(-e 'foo')), 'bar');
-e syntax OK
$ perl -MO=Deparse,-p -e'ok(not -e "foo", "bar")'
ok((!((-e 'foo'), 'bar')));
-e syntax OK
使用!
,否定作用于文件test:
!(-e 'foo')
当使用not
时,否定作用于列表:
!((-e 'foo'), 'bar')
作为toolic explained,标量上下文中的列表计算为列表中的最后一项,给出
ok( ! 'bar' );
! 'bar'
是假的。
【讨论】:
【参考方案3】:重读perlop 文档后,我认为是这样的:
ok( not -e $fn, 'file does not exists' );
Perl 从左到右解析这个语句。它遇到的第一件事是函数调用(也称为列表运算符,如果该函数是内置的或使用原型并对列表进行操作)。函数调用ok( ... )
。在文档中被描述为TERM
:
在 Perl 中,TERM 具有最高优先级。它们包括变量, 引号和类似引号的运算符,括号中的任何表达式,以及任何 函数,其参数用括号括起来。
列表运算符(在perlop 页面中没有准确定义,但在perlsub 页面中简要提及)如果后跟括号也被视为TERM
。 perlop 说:
如果有任何列表运算符(print()等)或任何一元运算符(chdir(), 等)后跟一个左括号作为下一个标记, 括号内的运算符和参数被认为是最高的 优先级,就像普通的函数调用一样。
现在解析器继续使用表达式not -e $fn, 'file does not exists'
。也就是说,它必须将参数解析为ok
函数。
它在这里遇到的第一件事是not
运算符。文档说:
一元“not”将表达式的逻辑否定返回到它的 对。相当于“!”除了非常低的优先级。
那么它必须确定"the expression to its right"
。在这里,解析器找到文件测试运算符-e
。文档说:
关于优先级,文件测试运算符,如
-f
、-M
等是 被视为命名的一元运算符,但他们不遵循这一点 函数括号规则。这意味着,例如,-f($file).".bak"
等价于-f "$file.bak"
。
和
各种命名的一元运算符被视为具有一个的函数 参数,带有可选的括号。
现在一元运算符(没有后面的括号)比not
运算符具有更高的优先级,因此解析器继续尝试确定-e
运算符的参数。它现在遇到一个新的TERM
,(我们现在正在考虑这个表达式:$fn, 'file does not exists'
)。 TERM
是 $fn
并且由于 TERM
s 具有最高优先级,因此会立即对其进行评估。然后它继续到逗号运算符。由于逗号运算符的优先级低于 filetest 运算符,并且 filetest 运算符是一元的(仅接受单个参数),因此解析器决定使用 filetest 运算符的参数完成并计算-e $fn
。然后它以逗号继续:
二进制“,”是逗号运算符。在标量上下文中,它评估其 左参数,丢弃该值,然后评估其右 参数并返回该值。这就像 C 的逗号运算符。 在列表上下文中,它只是列表参数分隔符,并插入 将其两个参数都放入列表中。这些参数也被评估 从左到右。
由于逗号运算符的优先级高于not
运算符,因此解析器发现它仍未完成not
的参数。相反,它发现not
的参数是一个列表(由于逗号运算符),它已经评估了逗号运算符-e $fn
的左参数,并丢弃该值,并继续处理逗号运算符,它是字符串'file does not exists'
。这被评估,然后解析器找到右括号)
,这意味着not
的参数是后一个字符串。否定非空字符串是错误的。
最后,解析器发现ok
函数的参数为假,并运行ok( 0 )
。
【讨论】:
【参考方案4】:要直接与 perlop 相关,您需要注意的是,!
高于 ,
在 not
之上:
right ! ~ \ and unary + and -
...
left , =>
...
right not
也就是说,逗号比not
更“紧密”地结合在一起,但比!
更“不紧密”。
【讨论】:
嗯..还有一个文件测试运算符,一个列表运算符,我想还有更多......你说"all you need to notice"
..这是为什么?您能否将其与文档更具体地联系起来以上是关于列表和一元运算符组合的 Perl 运算符优先级的主要内容,如果未能解决你的问题,请参考以下文章