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

I have a subroutine that is assembling strings from the contents of some hashes. I'd like to be able to pass the subroutine a single-quoted string that has variable names in it, and interpolate the variables within the context of the subroutine, sort of like using a format string for sprintf().

For instance, I want to be able to compose multiple strings like this: 'Foo_bar($a,$b,$c,,$d,,$e)'

which, because they're single-quoted, contain the literal $a etc. references, and then pass them to a subroutine that defines $a, $b, $c, $d, and $e, and returns the original string with those variables interpolated.

I'm hoping this makes sense to someone!

If so, what would be the correct syntax to use in the subroutine to evaluate and interpolate the string?

Replies are listed 'Best First'.
Re: interpolating preset strings in variable context (or something like that)
by ihb (Deacon) on Mar 14, 2005 at 21:36 UTC
Re: interpolating preset strings in variable context (or something like that)
by Mugatu (Monk) on Mar 14, 2005 at 21:29 UTC

    As Zaxo mentioned, you can use eval to do this, but it's not ideal. It can easily cause all sorts of hard to debug behavior, not to mention gaping security holes. I would rather approach it using a printf-like template. And instead of pulling the values from named variables directly, pull them from a hash. Here's an example:

    my $template = "Hi %foo, I have a %bar.\n"; print fill($template, foo => 'mickey', bar => 'robot'); sub fill { my $template = shift; my %params = @_; $template =~ s/%([a-z]+)/$params{$1}/g; return $template; }

    Update: I just noticed that the string you want to "fill in" looks like a function call. Maybe instead of a template type thing, you're actually looking to do a delayed call with different values? If that's the case, then you might be able to get away with a callback using closures.

    my ($x, $y, $z); my $callback = sub { print $x, $y, $z, "\n" }; ($x, $y, $z) = (1, 2, 3); $callback->(); ($x, $y, $z) = (4, 5, 6); $callback->();

    Update2: Another possiblity:

    my $callback = sub { my ($x, $y, $z) = @_; print $x, $y, $z, "\n"; }; $callback->(1,2,3); $callback->(4,5,6);

    I suppose, rather than randomly trying to guess exactly what you're doing, I should ask: What exactly are you doing? All you've given us to work with is the solution you've come up with, but we'd really be more help if you told us the actual problem you're trying to solve.

      Well, since you asked -- what I'm trying to do involves spidering a website to download various datasets that the website provides. The site uses several frames to allow the user to specify the desired dataset. One frame provides a cascading menu to choose the specific dataset; another frame provides a Javascript-generated set of form fields that specify the particular characteristics of the dataset chosen, and generates a code identifying the dataset; and the third form keeps a list of the dataset codes and ultimately provides the download functionality.

      I've tried doing this in several ways, but for the moment what I'm doing is looking at the source of all the variants of frame 2 for the Javascript arrays that generate the form fields and building a giant hash of all those arrays. Each of these pages generates a code that is more or less like Foo($a,$b,$c,$d), but each page has different variations, i.e. the variables are in different positions in the argument list, and there are sometimes other strings in the argument list as well.

      So I started off thinking that my giant hash would just have as another element for each of these alternate pages a string that represented the format of the code for that page -- hence my question. I've since discovered that each page might have a number of alternate code formats, so instead of a string I have a subroutine reference, that takes the variables I was trying to interpolate as arguments and returns the final code string.

      What I really want is a javascript parser that I could just get to run the javascript on the pages and tell me what the code is, and I tried to assemble one with JavaScript::SpiderMonkey, but I ended up trying to simulate a large subset of the DOM to get the JavaScript to work, which I couldn't figure out how to do and in any case didn't seem the most efficient way of doing this.

      That may or may not clarify what I'm attempting to do, but I think from the suggestions given here and what I've discovered about the problem that my original solution isn't the right one.

      Any suggestions as to how to do this without transferring the contents of the javascript hashes by hand into my perl hash, however, would be most heartily welcomed. Unfortunately it's a one-time task I have to do, so I only want to spend as long programming a solution as it would take me to do it by hand once, and so far that means I've given up trying to program it.

      Thanks to everyone for the advice on my original question.

        I would try to figure out what the JavaScript actually does before I'd try to write (or use) a general purpose JavaScript parser and interpreter. Most of the time, these type of JavaScript routines end up simply generating one URL or another, and the logic is easy enough to copy in Perl.
Re: interpolating preset strings in variable context (or something like that)
by Zaxo (Archbishop) on Mar 14, 2005 at 21:15 UTC

    eval STRING does what you want. Can you find another way to approach the problem? String eval is seldom the best way to do things.

    After Compline,
    Zaxo

      Yes, probably -- TMTOWTDI as usual.

      But can you say more about why eval STRING isn't usually the best way?

      Thanks for the help.

        Because of what eval STRING does. RTFM