2
$all="{this, {is, some, {deeply, nested}, text}, for, you}";
while ($all=~s/{([^{}]*?)}/f($1)/seg) {}
sub f {return \{split(",",$_[0])};}
print @{$all};

I expect $all to be a listref whose list contains:

{this, [reference to array], for, you}

Instead, @{$all} is empty.

I'm sure this is something basic, but what am I doing wrong?

Note: I intentionally "golfed" the code to post here (ie, minimal code that shows the problem). A more extensive version is at: https://github.com/barrycarter/bcapps/blob/master/playground.pl

EDIT: Thanks to everyone who answered! Notes:

  • The real f() has side effects, updates dbs, etc, so I really do have to call it. It doesn't just change a list into something else. My bad for not mentioning this.

  • I was exporting from Mathematica so "{a,b,c}" is a list, not a hash. Again, mea culpa for not mentioning this.

  • I know the "normal" way to do this is recursive: process each element, and if an element is a list, call f() on the list itself. I was trying to do "unfold" the recursion to avoid splitting nested "{". If you work inside out, you never have to count "{" when parsing.

  • An interesting other application would be a one-line XML parser (almost).

  • Giving geekosaur the checkmark for pointing out what was wrong and why my approach is probably wrong.

  • I think I'll try the Parser approach or even jrey's s/{/[ approach.

3 Answers3

4

You can't have $all simultaneously be a string you're iteratively matching on and an arrayref collecting the result of the iteration. $all will end up being something like the string "thisARRAY(0xdeadbeef)foryou", and @$all will use it as a package symbol name, which almost certainly isn't defined, so it will autovivify as an empty list.

Additionally, {} is already a (HASH instead of ARRAY) reference, so you're returning a SCALAR ref to a HASH ref instead of an ARRAY ref as you apparently expect. And {} are special in regexes (foo{1,3} means 1 to 3 repetitions of foo), so you should escape them.

The correct way to do it is to collect into a result list, something like

my @res;
while ($all =~ /\G\{([^{}]*?)\}/sg) {
    push @res, f($1);
}

use warnings and use strict would have told you something was wrong, if not precisely what. Use them. Always.

geekosaur
  • 59,309
  • 11
  • 123
  • 114
2

Use a parser.

#! /usr/bin/env perl

use warnings;
use strict;

use Data::Dumper;
use Parse::RecDescent;

my $all = "{this, {is, some, {deeply, nested}, text}, for, you}";

my $p = Parse::RecDescent->new(q[
  list: '{' <commit> listitem(s /,/) '}' { $return = $item[3] }
      | <error?>
  listitem: word | list
  word: /\w+/
]);

my $l = $p->list($all);
die "$0: bad list\n" unless defined $l;

$Data::Dumper::Indent = $Data::Dumper::Terse = 1;
print Dumper $l;

Output:

[
  'this',
  [
    'is',
    'some',
    [
      'deeply',
      'nested'
    ],
    'text'
  ],
  'for',
  'you'
]
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
0

Your text format is easy to transform into Perl, after that you may eval it.

#!/usr/bin/env perl
my $all = "{this, {is, some, {deeply, nested}, text}, for, you}";

$all =~ s/\s*,\s*/','/g;
$all =~ s/'?\{/['/g;
$all =~ s/\}'?/']/g;
my $result = eval $all;

use Data::Dumper;
print Dumper $result;
jrey
  • 13
  • 2