hdb has asked for the wisdom of the Perl Monks concerning the following question:

While trying to come up with an elegant solution to File Parsing and Pattern Matching I am completely stuck on something I have no explanation for. Here is the solution I came up with:

use strict; use warnings; use Data::Dumper; my %results = do { local $/ = ''; # paragraph mode map { /TYPE (\S+)/ => { 'cause' => (/CAUSE (\S+)/ ?$1:'UNDEF'), 'effect' => (/AFFECT (\S+)/?$1:'UNDEF'), } } <DATA>; }; print Dumper \%results; __DATA__ TYPE VALUE1 EQUALS MAIN CAUSE FAIL AFFECT ERROR ENDTYPE

shortening Mark.Allan's data to illustrate the point. While I am unable to spot any flaws in my logic it prints the following:

$VAR1 = { 'VALUE1' => { 'effect' => 'ERROR', 'cause' => 'ERROR' # instead of 'FAIL' } };

If I change the order of 'cause' and 'effect' in the code above I get 'FAIL' twice.

How can this be? And how can it be fixed? (Must be a case of Perl punishing my vanity...)

Replies are listed 'Best First'.
Re: Unexpected matching results
by dave_the_m (Monsignor) on Sep 06, 2013 at 07:45 UTC
    It's delayed evaluation of $1. The solution is to put it in quotes, i.e. "$1".

    He's a shorter example:

    $_ = '1a'; print( (/(\d)/ ? $1 : 0), (/(\D)/ ? $1 : 0), "\n"); #aa print( (/(\d)/ ? "$1" : 0), (/(\D)/ ? "$1" : 0), "\n"); #1a
    At run time, the variable $1 is pushed on the argument stack twice, then print is called. Print evaluates each of its arguments, getting its string value, and at this point $1 evaluates to the first capture of the last pattern match.

    Think of $1 being tied, with FETCH() being called (twice) from print.

    Dave.

      Thanks a lot! Now I (think I) understand it.

Re: Unexpected matching results
by kcott (Archbishop) on Sep 06, 2013 at 07:58 UTC

    G'day hdb,

    "How can this be?"

    The underlying problem is that "$1" can only have one value for each iteration of map.

    If you swap the CAUSE and AFFECT (__DATA__) lines, you get the same result; however, if you swap the cause and effect (map) lines, you get both values set to FAIL. Also, removing CAUSE or AFFECT from __DATA__ correctly gives a match result and an UNDEF.

    "And how can it be fixed?"

    This worked for me:

    'cause' => ((/CAUSE (\S+)/)[0] || 'UNDEF'), 'effect' => ((/AFFECT (\S+)/)[0] || 'UNDEF'),
    "(Must be a case of Perl punishing my vanity...)"

    If you say so :-)

    Update: Sorry, not sure what happened, I just typed the wrong words: s{you get the correct result.}{you get both values set to FAIL.}

    -- Ken

      Thanks for this alternative solution! The use of quotes around $1 is surely prone to getting "optimized" away. If I see something like "$1" I am always tempted to remove the quotes and thus introduce a bug in these circumstances here.

Re: Unexpected matching results (dynamic scope "$1")
by Anonymous Monk on Sep 06, 2013 at 07:37 UTC

      Thanks a lot! If I change my code to

      'cause' => (/CAUSE (\S+)/ ?"$1":'UNDEF'), 'effect' => (/AFFECT (\S+)/?"$1":'UNDEF'),

      it works as expected.