use warnings; use strict; use 5.010; my ($LEFT,$RIGHT) = ('(',')'); use Regexp::Common qw/balanced/; sub parseparens { my ($str) = @_; return $str unless index($str,$LEFT)>=0; $str = substr $str,1,-1 if $str=~/^\Q$LEFT\E.*\Q$RIGHT\E$/; return [ map {parseparens($_)} split /$RE{balanced}{-parens=>$LEFT.$RIGHT}/, $str ]; } sub buildparens { my ($data) = @_; return $data unless ref $data; return join '', map {buildparens($_)} $LEFT, @$data, $RIGHT; } # $wanted gets the string elements in $_ and can manipulate them, # if it returns false the entire arrayref # which contains that string is filtered out sub filter { my ($data,$wanted) = @_; if (!ref $data) { local $_ = $data; return $wanted->() ? $_ : (); } my ($keep,@out) = (1); for my $e (@$data) { if (ref $e) { push @out, filter($e,$wanted); } else { local $_ = $e; $keep=0 unless $wanted->(); push @out, $_; } } return $keep ? \@out : (); } use Data::Dumper; $Data::Dumper::Terse=1; $Data::Dumper::Indent=0; my $input = '(E,(A,B),(C,(X,Y,Z),D),F)'; say " \$input = ", Dumper($input), ";"; my $parsed = parseparens($input); say " \$parsed = ", Dumper($parsed), ";"; warn "parse-/buildparens round trip failed: ".buildparens($parsed) unless buildparens($parsed) eq $input; my $filtered = filter($parsed, sub {!/\b(?:A|C)\b/}); say "\$filtered = ", Dumper($filtered), ";"; my $output = buildparens($filtered); $output=~s/,(?=,)//g; say " \$output = ", Dumper($output), ";";