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

Apologies if I have my terminology wrong but I have a module that will substitute %foo with $var{'default'}{'foo'} and %foo.bar with $var{'foo'}{'bar'} doing something like
$work =~ s{%(\w+)(?:\.(\w+))?}{ defined $2 ? $var{$1}{$2} : $var{default}{$1} }eg;
I'd like to extend this to cover any level of nesting so that %a.deeply.nested.value would return $var{'a'}{'deeply'}{'nested'}{'value'} and the only thing I've come up with is a function that splits on the '.' and loops through the array to build a statement I can eval but I'm sure there has to be a more elegant way. If possible I'd like to avoid lookbehinds/lookaheads to keep the code portable to languages that don't support that. I'm also thinking I should I should be using $var{'foo'} instead of $var{'default'}{'foo'} so feel free to leave that out of any suggested solutions.

Replies are listed 'Best First'.
Re: Recursively substitute against multidimensional hash
by haukex (Archbishop) on Feb 14, 2025 at 07:50 UTC

    Something like this? No eval necessary.

    use warnings; use strict; sub dive { my ($r, @p) = @_; return unless @p; unshift @p, 'default' if @p==1; $r = ref $r eq 'HASH' && exists $$r{$_} ? $$r{$_} : return for + @p; return $r } my %var = ( default => { foo => 'Default-Foo' }, foo => { bar => 'Foo-Bar' }, a => { deeply => { nested => { value => 'A-Deeply-Nested-Value +' } } }, ); my $work = <<'EOF'; Hello, %foo Blah, %foo.bar Deep: %a.deeply.nested.value EOF print $work =~ s{ % ( \w+ (?:\.\w+)* ) }{ dive \%var, split /\./, $1 } +xegr; __END__ Hello, Default-Foo Blah, Foo-Bar Deep: A-Deeply-Nested-Value

    This is based on my node here. I also have similar code linked from my scratchpad in the bullet point "Data::Diver type code".

      Thanks for that but can I ask a hypothetical question? If the hash I was using was not %var but $self-{'VAR'} where $self is a blessed object reference and the original code which had been edited for clarity actually looked like
      $work =~ s{%(\w+)(?:\.(\w+))?}{ defined $2 ? $self->{'VARS'}{$1}{$2} : $self->{'VARS'}{default}{$1} }eg;
      How would one call your dive function in this hypothetical case? Because I've tried
      my $fml = $self->{'VARS'}; $work =~ s{ % ( \w+ (?:\.\w+)* ) }{ dive($fml, split /\./, $1) }xegr;
      and even $fml = \%{$self->{'VARS'}}; but not substitution is happening despite cut'n'pasting from your example code which is working fine in another windowd. NGL this is a humiliating way to stumble at the finish line yet here I am.
        Nevermind it wasn't the reference it was that sneaky little /r at the end of the regexp that was changing my functions behaviour.
Re: Recursively substitute against multidimensional hash
by Fletch (Bishop) on Feb 14, 2025 at 15:20 UTC

    Possibly attempting to corner an untaimed ornithoid without cause, but this feels like Template (Template Toolkit, https://template-toolkit.org/) might be of interest? That is if you're trying to do some sort of expansion of values into text.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      It would be of interest if I was trying to do some sort expansion of value into text but I'm not, I'm trying to write a template module. There's a subtle difference.