Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(4 + sub) not equals to (sub + 4)?

(edit) TL;DR: my problem was that I though the Win32 API defines were true integer constants (as in the platform SDK headers) while the Win32 Perl wrapper defines them as subs. Thus caused the one-liner parsing misunderstood.


While testing in a one-liner a call to Win32::MsgBox, I am puzzled by the following : giving that the possible arguments for MsgBox are the message, a sum of flags to chose the kind of buttons (value 0..5) and message box icon "constants" (MB_ICONSTOP, ...) and the title

calling perl -MWin32 -e"Win32::MsgBox world, 4+MB_ICONQUESTION, hello" gives the expected result

OK

while the looking similar code perl -MWin32 -e"Win32::MsgBox world, MB_ICONQUESTION+4, hello" is wrong

NOK

I first though that it comes from my lack of parenthesis, but adding some perl -MWin32 -e"Win32::MsgBox (world, MB_ICONQUESTION+4, hello)" gives exactly the same wrong result.

I tried with a colleague to dig deeper and display the parameters that are passed to a function call (as the MB_xxx constants are actually subs) with the following code

>perl -Mstrict -w -e"sub T{print $/,'called T(#'.join(',',@_).'#)'; 42 }; print $/,'results:', join ' ,', T(1), T+1, 1+T"

that outputs

called T(#1#)
called T(##)
called T(#1,43#)
results:42 ,42

but I can't understand why in the list passed to join() the args T+1, 1+T are parsed as T(1, 43)...

like image 408
Seki Avatar asked May 13 '15 09:05

Seki


2 Answers

B::Deparse to the rescue:

C:>perl -MO=Deparse -MWin32 -e"Win32::MsgBox world, MB_ICONQUETION+4, hello"
use Win32;
Win32::MsgBox('world', MB_ICONQUESTION(4, 'hello'));
-e syntax OK

C:>perl -MO=Deparse -MWin32 -e"Win32::MsgBox world, 4+MB_ICONQESTION, hello"
use Win32;
Win32::MsgBox('world', 4 + MB_ICONQUESTION(), 'hello');
-e syntax OK

The MB_ICONQUESTION call in the first case is considered a function call with the arguments +4, 'hello'. In the second case, it is considered as a function call with no arguments, and having 4 added to it. It is not a constant, it seems, but a function.

In the source code we get this verified:

sub MB_ICONQUESTION                     { 0x00000020 }

It is a function that returns 32 (00100000 in binary, indicating a bit being set). Also as Sobrique points out, this is a flag variable, so you should not use addition, but the bitwise logical and/or operators.

In your case, it just accepts any arguments and ignores them. This is a bit confusing if you are expecting a constant.

In your experiment case, the statement

print $/,'results:', join ' ,', T(1), T+1, 1+T

Is interpreted

print $/,'results:', join ' ,', T(1), T(+1, (1+T))

Because execution from right to left goes

1+T = 43
T +1, 43 = 42
T(1) = 42

Because plus + has higher precedence than comma ,, and unary + even higher.

To disambiguate, you need to do use parentheses to clarify precedence:

print $/,'results:', join ' ,', T(1), T()+1, 1+T
#                                      ^^-- parentheses

As a general rule, one should always use parentheses with subroutine calls. In perldoc perlsub there are 4 calling notations:

NAME(LIST);    # & is optional with parentheses.
NAME LIST;     # Parentheses optional if predeclared/imported.
&NAME(LIST);   # Circumvent prototypes.
&NAME;         # Makes current @_ visible to called subroutine.

Of which in my opinion, only the first one is transparent, and the other ones a bit obscure.

like image 160
TLP Avatar answered Sep 20 '22 10:09

TLP


This is all to do with how you're invoking T and how perl is interpreting the results.

If we deparse your example we get:

BEGIN { $^W = 1; }
sub T {
    use strict;
    print $/, 'called T(#' . join(',', @_) . '#)';
    42;
}
use strict;
print $/, 'results:', join(' ,', T(1), T(1, 1 + T()));

This is clearly not what you've got in mind, but does explain why you get the result you do.

I would suggest in your original example - rather that + you may wish to consider using | as it looks very much like MB_ICONQUESTION is intended to be a flag.

So:

use strict;
use warnings;

use Win32 qw( MB_ICONQUESTION );

print MB_ICONQUESTION;

Win32::MsgBox( "world", 4 | MB_ICONQUESTION , "hello" );

Or

use strict;
use warnings;

use Win32 qw( MB_ICONQUESTION );

print MB_ICONQUESTION;

Win32::MsgBox( "world", MB_ICONQUESTION | 4 , "hello" );

Produce the same result.

This is because of precence when invoking subroutines without brackets - you can do:

print "one", "two";

And both are treated as arguments to print. Perl assumes that arguments after a sub are to be passed to it.

+4 is enumerated as an argument, and passed to T.

sub test { print @_,"\n";};

test 1;
test +1; 

If we deparse this, we see perl treats it as:

test 1;
test 1;

So ultimately - there is a bug in Win32 that you have found, that would be fixable by:

sub MB_ICONQUESTION() {0x00000020}

Win32::MsgBox "world", 4 + MB_ICONQUESTION, "hello";
Win32::MsgBox "world", MB_ICONQUESTION + 4, "hello";

Or perhaps:

use constant MB_ICONQUESTION => 0x00000020;

Or as noted - the workaround in your code - don't use + and instead use | which is going to have the same result for bit flag operations, but because of operator precedence is never going to be passed into the subroutine. (Or of course, always specify the parenthesis for your constants)

like image 22
Sobrique Avatar answered Sep 20 '22 10:09

Sobrique