Search code examples
perlwinapioperator-precedence

(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)...


Solution

  • 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.