Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Alias (5735)

Alias
  (email not shown publicly)
http://ali.as/

Journal of Alias (5735)

Saturday December 12, 2009
09:21 AM

Template::Tiny 0.06 - Nesting without using regex recursion

[ #40013 ]

For an incremental cost of zero extra statements, and less than 1k of memory, I've managed to successfully implement support for nested IF/UNLESS/ELSE statements!

Starting from the premise that the regular expression engine already had support for recursion, I was somewhat dismayed to discover I didn't have access to recursion because it wasn't available in older Perls.

Suggestions from Google were that in a pinch you can do something similar using negative look-behind assertions (similar to tricks I've used before to do rather elegant and fast unescaped-quote scanning in PPI).

Alas, delving through the man pages I find that I don't have support for look-behind assertions either if I want 5.004 support (and that's always a nice-to-have in ::Tiny modules, although not essential).

So, how to do recursive conditions.

The slightly odd answer is that you can emulate the same kind of negative look-behind using a slightly crazier negative look-ahead assertion (which I DO have access to).

The new condition regex looks something like this (where $LEFT and $RIGHT are fairly complex whitespace-chomping expressions for [% and %])

my $CONDITION = qr/
        $LEFT (IF|UNLESS) \s+ ( $EXPR ) $RIGHT
        (?! .*?
                $LEFT (?: IF | UNLESS )
        )
        ( .*? )
        (?:
                $LEFT ELSE $RIGHT
                (?! .*?
                        $LEFT (?: IF | UNLESS )
                )
                ( .+? )
        )?
        $LEFT END $RIGHT /xs;

So basically, look for the IF block, which ISN'T followed by another IF block, then an option ELSE block (which isn't followed by an IF block), followed by an END block.

Superficially, this doesn't really make sense, because you only match things at the deepest level.

Actually, it's even worse, because it ONLY matches the last and/or deepest IF block. So lets first at least deal with all of them by doing multiple passes.

1 while $copy =~ s/
        $CONDITION
/
        eval {
                $1 eq 'UNLESS'
                xor
                !! # Force boolification
                $_[0]->expression($stash, $2)
        } ? $3 : $4; /gsex;

The key part there is the "1 while". This keeps passing over the string until we've run out of last and deepest conditions.

The only real problems here are that substiting in multiple passes runs the risk of an infinite loop, and we're processing the conditions from bottom to top and deepest to shallowest.

The infinite loop isn't a problem here (fortunately) because we never add new content to the file, only cut parts out. The expression evaluation stuff is done in a separate regex.

And the fact the conditions run in the opposite order isn't a problem either because we've already said that the stash MUST be immutable and side effects aren't allowed (although if anyone tries to cheat on this point, they're going to get weirded out).

So there's full (and infinitely deep) support for nested conditions, for pretty much no cost. This leaves the 60k of spare room largely free, which is good because FOREACH support is going to be much much harder than IF/UNLESS/ELSE.

The main problem here is that all the blocks end with the same terminator [% END %] (which means it's really hard to process different statements separately).

I do, however, have some interesting new ideas on how I might deal with this problem now that I have the negative-lookahead+while trick down pat.

Work continues...

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • 5.004? Are you serious? If you're doing it as an exercise in personal torture, ok, but hell, both 5.004_05 and 5.005_03 are past their 10 year "use by" date. I'd be interested to know if you've seen a 5.004 or even 5.005 in the wild that's actually being used for development, as opposed to just running some old code nobody touches anymore, and where an upgrade, or parallel Perl install, is not feasible.

    • I last used a 5.004 system for production purposes 3 years ago, but it was serious crufty and if it wasn't a charity stuck in a bad situation I would have insisted on an upgrade.

      I do know there's still a few 5.005 machines floating around, usually IRIX systems running old stuff.

      5.004 support is a nice to have, but can be sacrificed if needed. I'm more interested in the 5.005 support (although ONLY in the Tiny modules, everything else I'm happy to move to 5.6.1).

    • If you're doing it as an exercise in personal torture....

      I thought Template::Tiny was for Padre [perl.org], which I'd love to see running on IRIX with Perl 5.004.

      • The needs of Padre have created the specific demand, but all ::Tiny module gets used in a similar way. Just because it's needed for one thing, doesn't mean you make solutions only for that thing.

    • 5.004? Are you serious?

      He also said that loading the module should consume no more than 100kb. I would think 5.004 is a lesser constraint than that one; clearly he is serious. (Although combining both constraints… well, real Turing tar pits are even more compelling, so…)