列表和一元运算符组合的 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 类型的新一元操作码添加到解析树。操作数是标量上下文中的第二个参数(termlistexpr)。


* 或单个术语,但它的优先级很低。

跟踪解析

您可以通过使用-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 并且由于 TERMs 具有最高优先级,因此会立即对其进行评估。然后它继续到逗号运算符。由于逗号运算符的优先级低于 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 运算符优先级的主要内容,如果未能解决你的问题,请参考以下文章

逻辑运算的优先级

javascript运算符——算术运算符

Java运算符优先级

Perl中的运算符介绍

运算符的优先级

java运算符的优先级