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

How would I go about replacing an occurance of some string $a with $b.. but only if $a is not inside parentheses? For example, ($a hi) $a (($a) would end as ($a hi) $b (($a) I've tried multiple things but can't get it to behave correctly. Any help would be appreciated :)

Replies are listed 'Best First'.
Re: Replacement Regex
by tachyon (Chancellor) on Mar 20, 2002 at 18:26 UTC
    $find = '$a'; $find = quotemeta $find; $replace = '$b'; $str = '($a $a hi) $a (($a) $a'; $str =~ s[(\([^)]*$find)|$find] [$1 ? $1 : $replace ]eg; print $str;

    We find either (...$find or $find. When we find an opening parenthesis we move as far as we can to get $a (without passing a closing parenth) so that we do not substitute the second $a in the first parenth in my example. We then see if we have captured something in $1. If so we substitute what we found back in, effectively leaving the string unchanged. If not we do the replacement. You have not indicated how you want to handle mismatched or nested parentheses. This will not handle nesting 'correctly'. If you need to handle nesting you would do well to look at Text::Balanced

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

      This one will handle nested parens:

      #!/usr/bin/perl -w use strict; $_ = '$a ($a (hi) $a) $a (()$a)'; my $re; $re = qr/(?:[^\(\)]*|\((??{$re})\))+/; s/(\($re\))|\$a/$1?$1:'$b'/eg; print;

      Update: should've referred to perlre manpage instead of relying on memory, the version there makes it somewhat nicer:

      my $re; $re = qr/\((?:(?>[^()]*)|(??{$re}))*\)/; s/($re)|\$a/$1?$1:'$b'/eg;

      Update: It appears to broken in 5.6.0 (gives output as tachyon shows below), but it works for me with 5.6.1.

        <BEGIN_TEMPT_FATE>This one will handle nested parens:</END_TEMPT_FATE>

        $_ = '(($a $a hi) $a ($a) $a)'; my $re; $re = qr/\((?:(?>[^()]*)|(??{$re}))*\)/; s/($re)|\$a/$1?$1:'$b'/eg; print; __DATA__ (($a $a hi) $b ($a) $b)

        Oops!

        cheers

        tachyon

        s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

      I've already parsed the line so that any incorrect syntax (i.e. an open paren without a closing paren) is an error and will not be an issue. Also, ((dfs) is ok, the second ( is just treated as an ordinary character within the outer parentheses.. that is to say, there is no nesting. That said.. thanks very much for your help!
Re: Replacement Regex
by strat (Canon) on Mar 20, 2002 at 18:27 UTC
    I don't know how to do so with a regex; perhaps index might help you (but in a complicated way, I'm afraid), e.g
    #!perl -w use strict; my $searchString = '$a'; my $replaceString = '$b'; my $string = '$a($a hi) $a (($a))$a'; # get positions of chars to care about my @aPos = &GetCharPositions($string, $searchString);; my @lparants = &GetCharPositions($string, '('); my @rparants = &GetCharPositions($string, ')'); print "String: $string\n"; print "LeftP: @lparants\n"; print "RightP: @rparants\n"; die "Error: non equal paranthesis found\n" if ($#lparants != $#rparant +s); print "@aPos\n"; # filter stuff between parantheses foreach my $i (0..$#lparants){ @aPos = grep { $_ < $lparants[$i] or $_ > $rparants[$i] } @aPos; print "AP: @aPos\n"; } # do replacement of stuff still in @aPos foreach (@aPos){ substr($string, $_, length($searchString)) = $replaceString; print "S: $string\n"; } # ------------------------------------------------------------ sub GetCharPositions { my ($string, $subString) = @_; my @list = (); my $startPos = -1; my $pos; while ( defined ($pos = index($string, $subString, $startPos+1))){ last if $pos == -1; $startPos = $pos; push (@list, $pos); } return (@list); } # GetCharPositions # ----------------------------------------------------
    Output:
    C:\TEMP\test>parenReplace.pl String: $a($a hi) $a (($a))$a LeftP: 2 13 14 RightP: 8 17 18 0 3 10 15 19 AP: 0 10 15 19 AP: 0 10 19 AP: 0 10 19 S: $b($a hi) $a (($a))$a S: $b($a hi) $b (($a))$a S: $b($a hi) $b (($a))$b
    I haven't thoroughly tested this code, and can't tell if it is correct under each circumstances...

    But I'm looking forward getting some better solutions :-)

    Best regards,
    perl -le "s==*F=e=>y~\*martinF~stronat~=>s~[^\w]~~g=>chop,print"

Re: Replacement Regex
by impossiblerobot (Deacon) on Mar 20, 2002 at 22:57 UTC
    This isn't an answer to your question; this is just a reminder that you don't want to get into the bad habit of using $a and $b as sample variables (especially in sample code), since they have a special meaning to Perl.

    Specifically, they're built-in package globals for use by the sort function. :-)

    Impossible Robot
Re: Replacement Regex
by pizza_milkshake (Monk) on Mar 21, 2002 at 03:21 UTC
    i got this to work w/o much trouble. first time for everything.
    
    perl -le'$_="2 (2) 2 (2) 2"; s/2(?!\)|\(.*?\))/two/g; print'
    perl -MLWP::Simple -e'$_="104116116112058047047112097114115101101114114111114046099111109047112"; $s .= chr for/(...)/g;getprint $s' |more
Re: Replacement Regex
by Anonymous Monk on Mar 21, 2002 at 04:40 UTC
    You'll need code; famously, regexps won't do balance counting. Finite State Machines vs Push-down Automata.