[Templates] Can there be two?

Paul Seamons mail@seamons.com
Thu, 4 May 2006 16:12:14 -0600


--Boundary-00=_/wnWEH78x/uj0hI
Content-Type: text/plain;
  charset="us-ascii"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

For a little background...

I had a module that was a simple TT style variable
replacer.  All it could handle were things like [% a %] or [% a.0 %]
or [% a.a.0 %].  It didn't allow for argument passing, or directives,
or operators.  All it did was variables.  The perldoc made clear the
limitations and said to use Template::Toolkit if anything more
complicated was desired.

Years went on.  I decided it would be useful to have basic IF
directive support as well as allowing for argument passed to functions
(such as [% a(b) %]).  I added these features, played around with
things, toyed with tag parsing methods.  At first there was no optree
- the finished document was built as the parse progressed.  But then I
discovered that blocks and meta tags can be out of order in the
document.  It became easier to build an optree.  It was relatively
easy.  It was only 1800 lines and took about 80 hours to write.

I then tried it against the TT 2.14 test suite.  It took another 20
hours to make it pass the rest of the TT test suite.  I was done
mostly around late January 2006 - but I've been waiting for the
anticipated 2.15 release so I could make sure that the new module is
fully compliant with any of the bug fixes that have gone into TT.  I'm
still waiting - but I'm getting to the point I'd like to release the
code.

So the first big question:  Is there room for two engines that implement
the Template Toolkit specification?

Obviously there are pros and cons to releasing another module that does
the same thing that Template::Toolkit does.

Next question.  If I do release it - should I go to lengths to support all
of the extended options that TT uses?  For example, there is no support
for V1DOLLAR and there won't be.  I've also removed support for the
ANYCASE option (Increased the overall string parsing from 339% listed in the
example below to 359%.  That isn't a major increase.  And removing ANYCASE has
no effect on compiled templates at all.)

Some other options to consider removing in CET:

   - Direct access to blocks located in other templates.

   - "References"

   - PERL and RAWPERL blocks (CET doesn't use perl for execution)
     I'd still leave the eval_perl option though.

The big things missing that won't be implemented are TT2 views, and I
may or may not include the new magic TT3 provider namespaces such as
"hash:" or "file:" (etc).

Whether I release a separate module or not, the TT dev team (mostly
Andy) will be welcome to cut out what ever portions of code they may
find useful.  The CET code base is somewhat different from TT in that
TT uses a separate object for each phase of execution while CET uses a
separate method.  This makes CET either easier or harder to override
functionality depending upon which programming camp you come from.

Finally - Is releasing this premature?  Will TT3's speed increase?  Is
increasing the speed a factor for anybody else?  Does anybody even
care if there is another module?

I apologize that this letter doesn't include the source code.  I'd
rather wait until I can see the general feelings of people on the
matter.  I'm sure there will be split feelings either way.

If you have something scathing to say in response, you are certainly
welcome to.  But please decide if it is appropriate for the entire
list to hear.  Otherwise just email me directly.

Paul Seamons


///----------------------------------------------------------------///

Three files are attached.
7_template_00_base.t - The file used to test most of the language constructs 
of CGI::Ex::Template.  If you uncomment the line that says $module = 
'Template' it will use Template instead.  It is useful for finding edge 
cases.

bench_template.pl - Used to test relative performance.  All benchmarks should 
be taken with a grain of salt.  The output of the run on my machine is listed 
below.  The output varies depending upon memory, disk and processor speed.

perldoc - This is perldoc CGI::Ex::Template > perldoc  - It is the unfinished 
perldoc for CGI::Ex::Template.  Did I mention that it is unfinished.

///----------------------------------------------------------------///

The following results are the output of the bench_template.pl script
included in the email.  The computer used is a 1.86 Pentium M with 1
Gig of ram running Breezy Kubuntu.  All percentages are
CGI::Ex::Template vs TT2.14.  The first column shows the "template"
that is being processed.  The second column is both engines using
compiled templates that are already in memory.  For the third column
the engines use a new object each time.  In the fourth column the
engine is using file side caches (CACHE_EXT is set) (it needs to load
a new object each time).  The fifth column is passing the template as
a scalar ref rather than loading from file.  The last column is the
number of iterations per second of CET running in memory (iterations
used for determining the first column).

Copying and pasting this into a fixed width display will make things line up a 
little better.

                                                    ### All percents are 
CGI::Ex::Template vs TT2
                                                    ### (The percent that CET 
is faster than TT)
                                                          Existing object by 
string ref #
                                                 New object with CACHE_EXT set 
#        #
                                                 New object each time #        
#        #
         This percent is compiled in memory (repeated calls) #        #        
#        #
"",                                                 #  236%  #  645%  #  337%  
#  457%  # 19787.6/s #
"[% one %]",                                        #  168%  #  592%  #  421%  
#  476%  # 14355.1/s #
"[% one %]"x100,                                    #   31%  #  341%  #   79%  
#  348%  # 940.2/s #
"[% SET one = 2 %]",                                #  161%  #  534%  #  419%  
#  406%  # 14121.9/s #
"[% SET one = 2 %]"x100,                            #   12%  #  279%  #   37%  
#  276%  # 864.5/s #
"[% SET one = [0..30] %]",                          #   42%  #  320%  #  247%  
#  211%  # 7518.1/s #
"[% hash.a %]",                                     #  173%  #  619%  #  423%  
#  494%  # 13334.4/s #
"".((" "x100)."[% one %]\n")x10,                    #   82%  #  517%  #  261%  
#  476%  # 5848.1/s #
"".((" "x10)."[% one %]\n")x100,                    #   23%  #  432%  #  116%  
#  422%  # 867.7/s #
"".("[% \"".(" "x100)."\$one\" %]\n")x10,           #  -15%  #  1415%  #  102%  
#  1467%  # 2700.5/s #
"".("[% \"".(" "x10)."\$one\" %]\n")x100,           #  -50%  #  299%  #    2%  
#  302%  # 329.3/s #
"[% 2 %]",                                          #  177%  #  602%  #  459%  
#  459%  # 15838.0/s #
"[% 1 + 2 %]",                                      #   87%  #  435%  #  334%  
#  299%  # 10746.9/s #
"[% 1 + 2 + 3 + 5 + 6 + 8 %]",                      #   67%  #  320%  #  301%  
#  218%  # 9570.1/s #
"[% c.d.0.hee.0 %]",                                #  171%  #  616%  #  416%  
#  527%  # 13717.7/s #
"[% SET c.d.0.hee.0 = 2 %]",                        #  156%  #  519%  #  364%  
#  413%  # 10628.0/s #
"[% c.d.0.hee.0 %]"x100,                            #   54%  #  464%  #   82%  
#  467%  # 752.8/s #
"[% SET c.d.0.hee.0 = 2 %]"x100,                    #  107%  #  341%  #   91%  
#  341%  # 342.0/s #
"[% t = 1 || 0 ? 0 : 1 || 2 ? 2 : 3 %][% t %]",     #   73%  #  291%  #  256%  
#  210%  # 8674.9/s #
"[% a=1 %][% IF a %]Two[% END %]",                  #  126%  #  481%  #  347%  
#  378%  # 11166.3/s #
"         [% IF a %]Two[% END %]",                  #  161%  #  596%  #  412%  
#  492%  # 13846.3/s #
"[% IF a %]A[% ELSE %]B[% END %]",                  #  141%  #  526%  #  378%  
#  422%  # 12728.4/s #
"[% IF a %]A[% ELSIF b %]B[% ELSE %]C[% END %]",    #  131%  #  519%  #  359%  
#  407%  # 11386.8/s #
"[% FOREACH i = [0..10]   ; i ; END %]",            #   16%  #  230%  #  154%  
#  155%  # 2376.1/s #
"[% FOREACH i = [0..100]  ; i ; END %]",            #  -15%  #   38%  #   13%  
#   18%  # 362.5/s #
"[% FOREACH [0..10]       ; i ; END %]",            #   23%  #  245%  #  162%  
#  169%  # 2463.0/s #
"[% FOREACH [0..100]      ; i ; END %]",            #   -4%  #   56%  #   28%  
#   32%  # 384.9/s #
"[% f = 10 %][%WHILE f%][%f=f- 1%][%f%][% END %]",  #  -14%  #  152%  #   50%  
#  102%  # 1279.5/s #
"[% f = 10; WHILE (g=f) ; f = f - 1 ; f ; END %]",  #  -18%  #  115%  #   34%  
#   75%  # 1014.6/s #
"[% f = 1;  WHILE (g=f) ; f = f - 1 ; f ; END %]",  #   38%  #  302%  #  193%  
#  223%  # 5061.4/s #
"[% PROCESS bar.tt %]",                             #  236%  #  592%  #  397%  
#  506%  # 10189.6/s #
"[% INCLUDE baz.tt %]",                             #  157%  #  427%  #  296%  
#  370%  # 6549.1/s #
"[% BLOCK foo %]Hi[% END %][% PROCESS foo %]",      #  162%  #  592%  #  422%  
#  487%  # 10030.6/s #
"[% BLOCK foo %]Hi[% END %][% INCLUDE foo %]",      #  140%  #  557%  #  387%  
#  462%  # 8555.5/s #
"[% MACRO foo BLOCK %]Hi[% END %][% foo %]",        #   79%  #  413%  #  301%  
#  309%  # 7363.9/s #
"[% MACRO foo(n) BLOCK %]Hi[%n%][%END%][%foo(2)%]", #   65%  #  296%  #  265%  
#  219%  # 6081.4/s #
"[% MACRO foo PROCESS bar;BLOCK bar%]7[%END;foo%]", #   96%  #  449%  #  316%  
#  355%  # 5962.3/s #
"[% n = 1 %][% n | repeat(2) %]",                   #  128%  #  432%  #  370%  
#  336%  # 9705.7/s #
"[% n = 1 %][% n FILTER repeat(2) %]",              #   91%  #  367%  #  325%  
#  256%  # 8025.8/s #
"[% n=1; n FILTER echo=repeat(2); n FILTER echo%]", #   36%  #  317%  #  241%  
#  243%  # 5321.4/s #
"[% constants.simple %]",                           #  176%  #  617%  #  473%  
#  448%  # 15760.8/s #
"[%one='ONE'%][% PERL %]print \"[%one%]\"[%END%]",  #   66%  #  447%  #  300%  
#  357%  # 6472.9/s #
"[% 'hi' | \$filt %]",                              #   97%  #  517%  #  348%  
#  400%  # 9500.9/s #
"[% ' ' | uri %]",                                  #  127%  #  620%  #  402%  
#  505%  # 11498.0/s #
"[% foo | eval %]",                                 #  326%  #  600%  #  498%  
#  517%  # 5022.9/s #
"[% b = \\code(1); b(2) %]",                        #   60%  #  270%  #  239%  
#  174%  # 6451.9/s #
"[% foo = BLOCK %]Hi[% END %][% foo %]",            #  105%  #  438%  #  312%  
#  326%  # 9962.1/s #
"$longer_template",                                 #   51%  #  305%  #  139%  
#  264%  # 1098.6/s #
       # overall                                    #   94%  #  439%  #  268%  
#  359%  #
       # overall (with Stash::XS)                   #   27%  #  371%  #  212%  
#  307%  #

# Note that TT is faster on for loops and while loops.  The opcode tree method 
of CET can't outperform the low level loops in perl.

--Boundary-00=_/wnWEH78x/uj0hI
Content-Type: application/x-perl;
  name="bench_template.pl"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filename="bench_template.pl"

#!/usr/bin/perl -w

=head1 NAME

bench_template.pl - Test relative performance of CGI::Ex::Template to Template::Toolkit

=cut

use strict;
use Benchmark qw(cmpthese timethese);
use POSIX qw(tmpnam);
use File::Path qw(rmtree);
use CGI::Ex::Template;
use CGI::Ex::Dump qw(debug);
use Template;
use constant test_taint => 0 && eval { require Taint::Runtime }; # s/0/1/ to check tainting

Taint::Runtime::taint_start() if test_taint;

my $tt_cache_dir = tmpnam;
END { rmtree $tt_cache_dir };
mkdir $tt_cache_dir, 0755;

my $swap = {
    one   => "ONE",
    a_var => "a",
    foo   => '[% bar %]',
    bar   => "baz",
    hash  => {a => 1, b => 2, c => { d => [{hee => ["hmm"]}] }},
    array => [qw(A B C D E a A)],
    code  => sub {"(@_)"},
    filt  => sub {sub {$_[0]x2}},
};

use Template::Stash;;
my $s = Template::Stash->new($swap);
#use Template::Stash::XS;
#$s = Template::Stash::XS->new($swap);

###----------------------------------------------------------------###
### get objects ready

my @config1 = (STASH => $s, ABSOLUTE => 1, CONSTANTS => {simple => 'var'}, EVAL_PERL => 1, INCLUDE_PATH => $tt_cache_dir);
#push @config1, (INTERPOLATE => 1);
my @config2 = (@config1, COMPILE_EXT => '.ttc');

my $tt1 = Template->new(@config1);
my $tt2 = Template->new(@config2);

my $cet = CGI::Ex::Template->new(@config1);
my $cetc = CGI::Ex::Template->new(@config2);

#$swap->{$_} = $_ for (1 .. 1000); # swap size affects benchmark speed

###----------------------------------------------------------------###
### write out some file to be used later

my $fh;
my $bar_template = "$tt_cache_dir/bar.tt";
END { unlink $bar_template };
open($fh, ">$bar_template") || die "Couldn't open $bar_template: $!";
print $fh "BAR";
close $fh;

my $baz_template = "$tt_cache_dir/baz.tt";
END { unlink $baz_template };
open($fh, ">$baz_template") || die "Couldn't open $baz_template: $!";
print $fh "[% SET baz = 42 %][% baz %][% bing %]";
close $fh;

my $longer_template = "[% INCLUDE bar.tt %]"
    ."[% array.join('|') %]"
    .("123"x200)
    ."[% FOREACH a IN array %]foobar[% IF a == 'A' %][% INCLUDE baz.tt %][% END %]bazbing[% END %]"
    .("456"x200)
    ."[% IF foo ; bar ; ELSIF baz ; bing ; ELSE ; bong ; END %]"
    .("789"x200)
    ."[% IF foo ; bar ; ELSIF baz ; bing ; ELSE ; bong ; END %]"
    .("012"x200)
    ."[% IF foo ; bar ; ELSIF baz ; bing ; ELSE ; bong ; END %]"
    ."[% array.join('|') %]"
    ."[% PROCESS bar.tt %]";

###----------------------------------------------------------------###
### set a few globals that will be available in our subs
my $show_list = grep {$_ eq '--list'} @ARGV;
my $run_all   = grep {$_ eq '--all'}  @ARGV;
my @run = $run_all ? () : @ARGV;
my $str_ref;
my $filename;

### uncomment to run a specific test - otherwise all tests run
#@run = qw(07);

#                                                                         ### All percents are CGI::Ex::Template vs TT2
#                                                                         ### (The percent that CET is faster than TT)
#                                                                               Existing object by string ref #
#                                                                      New object with CACHE_EXT set #        #
#                                       This percent is compiled in memory (repeated calls) #        #        #
#                                          New object each time (undef CACHE_SIZE) #        #        #        #
my $tests = {                                                             #        #        #        #        #
    '01_empty'     => "",                                                 #  236%  #  645%  #  337%  #  457%  # 19787.6/s #
    '02_var_sma'   => "[% one %]",                                        #  168%  #  592%  #  421%  #  476%  # 14355.1/s #
    '03_var_lar'   => "[% one %]"x100,                                    #   31%  #  341%  #   79%  #  348%  # 940.2/s #
    '04_set_sma'   => "[% SET one = 2 %]",                                #  161%  #  534%  #  419%  #  406%  # 14121.9/s #
    '05_set_lar'   => "[% SET one = 2 %]"x100,                            #   12%  #  279%  #   37%  #  276%  # 864.5/s #
    '06_set_range' => "[% SET one = [0..30] %]",                          #   42%  #  320%  #  247%  #  211%  # 7518.1/s #
    '07_chain_sm'  => "[% hash.a %]",                                     #  173%  #  619%  #  423%  #  494%  # 13334.4/s #
    '08_mixed_sma' => "".((" "x100)."[% one %]\n")x10,                    #   82%  #  517%  #  261%  #  476%  # 5848.1/s #
    '09_mixed_med' => "".((" "x10)."[% one %]\n")x100,                    #   23%  #  432%  #  116%  #  422%  # 867.7/s #
    '10_str_sma'   => "".("[% \"".(" "x100)."\$one\" %]\n")x10,           #  -15%  #  1415%  #  102%  #  1467%  # 2700.5/s #
    '11_str_lar'   => "".("[% \"".(" "x10)."\$one\" %]\n")x100,           #  -50%  #  299%  #    2%  #  302%  # 329.3/s #
    '12_num_lterl' => "[% 2 %]",                                          #  177%  #  602%  #  459%  #  459%  # 15838.0/s #
    '13_plus'      => "[% 1 + 2 %]",                                      #   87%  #  435%  #  334%  #  299%  # 10746.9/s #
    '14_plus_long' => "[% 1 + 2 + 3 + 5 + 6 + 8 %]",                      #   67%  #  320%  #  301%  #  218%  # 9570.1/s #
    '15_chained'   => "[% c.d.0.hee.0 %]",                                #  171%  #  616%  #  416%  #  527%  # 13717.7/s #
    '16_chain_set' => "[% SET c.d.0.hee.0 = 2 %]",                        #  156%  #  519%  #  364%  #  413%  # 10628.0/s #
    '17_chain_lar' => "[% c.d.0.hee.0 %]"x100,                            #   54%  #  464%  #   82%  #  467%  # 752.8/s #
    '18_chain_sl'  => "[% SET c.d.0.hee.0 = 2 %]"x100,                    #  107%  #  341%  #   91%  #  341%  # 342.0/s #
    '19_cplx_comp' => "[% t = 1 || 0 ? 0 : 1 || 2 ? 2 : 3 %][% t %]",     #   73%  #  291%  #  256%  #  210%  # 8674.9/s #
    '20_if_sim_t'  => "[% a=1 %][% IF a %]Two[% END %]",                  #  126%  #  481%  #  347%  #  378%  # 11166.3/s #
    '21_if_sim_f'  => "         [% IF a %]Two[% END %]",                  #  161%  #  596%  #  412%  #  492%  # 13846.3/s #
    '22_if_else'   => "[% IF a %]A[% ELSE %]B[% END %]",                  #  141%  #  526%  #  378%  #  422%  # 12728.4/s #
    '23_if_elsif'  => "[% IF a %]A[% ELSIF b %]B[% ELSE %]C[% END %]",    #  131%  #  519%  #  359%  #  407%  # 11386.8/s #
    '24_for_i_sml' => "[% FOREACH i = [0..10]   ; i ; END %]",            #   16%  #  230%  #  154%  #  155%  # 2376.1/s #
    '25_for_i_med' => "[% FOREACH i = [0..100]  ; i ; END %]",            #  -15%  #   38%  #   13%  #   18%  # 362.5/s #
    '26_for_sml'   => "[% FOREACH [0..10]       ; i ; END %]",            #   23%  #  245%  #  162%  #  169%  # 2463.0/s #
    '27_for_med'   => "[% FOREACH [0..100]      ; i ; END %]",            #   -4%  #   56%  #   28%  #   32%  # 384.9/s #
    '28_while'     => "[% f = 10 %][%WHILE f%][%f=f- 1%][%f%][% END %]",  #  -14%  #  152%  #   50%  #  102%  # 1279.5/s #
    '29_whl_set_l' => "[% f = 10; WHILE (g=f) ; f = f - 1 ; f ; END %]",  #  -18%  #  115%  #   34%  #   75%  # 1014.6/s #
    '30_whl_set_s' => "[% f = 1;  WHILE (g=f) ; f = f - 1 ; f ; END %]",  #   38%  #  302%  #  193%  #  223%  # 5061.4/s #
    '31_file_proc' => "[% PROCESS bar.tt %]",                             #  236%  #  592%  #  397%  #  506%  # 10189.6/s #
    '32_file_incl' => "[% INCLUDE baz.tt %]",                             #  157%  #  427%  #  296%  #  370%  # 6549.1/s #
    '33_process'   => "[% BLOCK foo %]Hi[% END %][% PROCESS foo %]",      #  162%  #  592%  #  422%  #  487%  # 10030.6/s #
    '34_include'   => "[% BLOCK foo %]Hi[% END %][% INCLUDE foo %]",      #  140%  #  557%  #  387%  #  462%  # 8555.5/s #
    '35_macro'     => "[% MACRO foo BLOCK %]Hi[% END %][% foo %]",        #   79%  #  413%  #  301%  #  309%  # 7363.9/s #
    '36_macro_arg' => "[% MACRO foo(n) BLOCK %]Hi[%n%][%END%][%foo(2)%]", #   65%  #  296%  #  265%  #  219%  # 6081.4/s #
    '37_macro_pro' => "[% MACRO foo PROCESS bar;BLOCK bar%]7[%END;foo%]", #   96%  #  449%  #  316%  #  355%  # 5962.3/s #
    '38_filter2'   => "[% n = 1 %][% n | repeat(2) %]",                   #  128%  #  432%  #  370%  #  336%  # 9705.7/s #
    '39_filter'    => "[% n = 1 %][% n FILTER repeat(2) %]",              #   91%  #  367%  #  325%  #  256%  # 8025.8/s #
    '40_fltr_name' => "[% n=1; n FILTER echo=repeat(2); n FILTER echo%]", #   36%  #  317%  #  241%  #  243%  # 5321.4/s #
    '41_constant'  => "[% constants.simple %]",                           #  176%  #  617%  #  473%  #  448%  # 15760.8/s #
    '42_perl'      => "[%one='ONE'%][% PERL %]print \"[%one%]\"[%END%]",  #   66%  #  447%  #  300%  #  357%  # 6472.9/s #
    '43_filtervar' => "[% 'hi' | \$filt %]",                              #   97%  #  517%  #  348%  #  400%  # 9500.9/s #
    '44_filteruri' => "[% ' ' | uri %]",                                  #  127%  #  620%  #  402%  #  505%  # 11498.0/s #
    '45_filterevl' => "[% foo | eval %]",                                 #  326%  #  600%  #  498%  #  517%  # 5022.9/s #
    '46_refs'      => "[% b = \\code(1); b(2) %]",                        #   60%  #  270%  #  239%  #  174%  # 6451.9/s #
    '47_capture'   => "[% foo = BLOCK %]Hi[% END %][% foo %]",            #  105%  #  438%  #  312%  #  326%  # 9962.1/s #
    '48_complex'   => "$longer_template",                                 #   51%  #  305%  #  139%  #  264%  # 1098.6/s #
    # overall                                                             #   94%  #  439%  #  268%  #  359%  #
    # overall (with stash::xs)                                            #   32%  #  505%  #  246%  #  380%  #
};

### load the code representation
my $text = {};
seek DATA, 0, 0;
my $data = do { local $/ = undef; <DATA> };
foreach my $key (keys %$tests) {
    $data =~ m/(.*\Q$key\E.*)/ || next;
    $text->{$key} = $1;
}

if ($show_list) {
    foreach my $text (sort values %$text) {
        print "$text\n";
    }
    exit;
}

my $run = join("|", @run);
@run = grep {/$run/} sort keys %$tests;

###----------------------------------------------------------------###

sub file_TT_new {
    my $out = '';
    my $t = Template->new(@config1);
    $t->process($filename, $swap, \$out);
    return $out;
}

sub str_TT_new {
    my $out = '';
    my $t = Template->new(@config1);
    $t->process($str_ref, $swap, \$out);
    return $out;
}

sub file_TT {
    my $out = '';
    $tt1->process($filename, $swap, \$out);
    return $out;
}

sub str_TT {
    my $out = '';
    $tt1->process($str_ref, $swap, \$out) || debug $tt1->error;
    return $out;
}

sub file_TT_cache_new {
    my $out = '';
    my $t = Template->new(@config2);
    $t->process($filename, $swap, \$out);
    return $out;
}

###----------------------------------------------------------------###

sub file_CET_new {
    my $out = '';
    my $t = CGI::Ex::Template->new(@config1);
    $t->process($filename, $swap, \$out);
    return $out;
}

sub str_CET_new {
    my $out = '';
    my $t = CGI::Ex::Template->new(@config1);
    $t->process($str_ref, $swap, \$out);
    return $out;
}

sub file_CET {
    my $out = '';
    $cet->process($filename, $swap, \$out);
    return $out;
}

sub str_CET {
    my $out = '';
    $cet->process($str_ref, $swap, \$out);
    return $out;
}

sub str_CET_swap {
    my $txt = $cet->swap($str_ref, $swap);
    return $txt;
}

sub file_CET_cache_new {
    my $out = '';
    my $t = CGI::Ex::Template->new(@config2);
    $t->process($filename, $swap, \$out);
    return $out;
}

###----------------------------------------------------------------###

@run = sort(keys %$tests) if $#run == -1;

my $output = '';
my %cumulative;
foreach my $test_name (@run) {
    die "Invalid test $test_name" if ! exists $tests->{$test_name};
    my $txt = $tests->{$test_name};
    my $sample =$text->{$test_name};
    $sample =~ s/^.+=>//;
    $sample =~ s/\#.+$//;
    print "-------------------------------------------------------------\n";
    print "Running test $test_name\n";
    print "Test text: $sample\n";

    ### set the global file types
    $str_ref = \$txt;
    $filename = $tt_cache_dir ."/$test_name.tt";
    open(my $fh, ">$filename") || die "Couldn't open $filename: $!";
    print $fh $txt;
    close $fh;

    #debug file_CET(), str_TT();
    #debug $cet->parse_tree($file);

    ### check out put - and also allow for caching
    for (1..2) {
        if (file_CET() ne str_TT()) {
            debug $cet->parse_tree($str_ref);
            debug file_CET(), str_TT();
            die "file_CET didn't match";
        }
        die "file_TT didn't match "            if file_TT()      ne str_TT();
        die "str_CET didn't match "            if str_CET()      ne str_TT();
#        die "str_CET_swap didn't match "       if str_CET_swap() ne str_TT();
        die "file_CET_cache_new didn't match " if file_CET_cache_new() ne str_TT();
        die "file_TT_cache_new didn't match " if file_TT_cache_new() ne str_TT();
    }

    next if test_taint;

###----------------------------------------------------------------###

    my $r = eval { timethese (-2, {
        file_TT_n   => \&file_TT_new,
#        str_TT_n    => \&str_TT_new,
        file_TT     => \&file_TT,
        str_TT      => \&str_TT,
        file_TT_c_n => \&file_TT_cache_new,

        file_CT_n   => \&file_CET_new,
#        str_CT_n    => \&str_CET_new,
        file_CT     => \&file_CET,
        str_CT      => \&str_CET,
#        str_CT_sw   => \&str_CET_swap,
        file_CT_c_n => \&file_CET_cache_new,
    }) };
    if (! $r) {
        debug "$@";
        next;
    }
    eval { cmpthese $r };

    my $copy = $text->{$test_name};
    $copy =~ s/\#.+//;
    $output .= $copy;

    eval {
        my $hash = {
            '1 cached_in_memory           ' => ['file_CT',     'file_TT'],
            '2 new_object                 ' => ['file_CT_n',   'file_TT_n'],
            '3 cached_on_file (new_object)' => ['file_CT_c_n', 'file_TT_c_n'],
            '4 string reference           ' => ['str_CT',      'str_TT'],
            '5 CT new vs TT in mem        ' => ['file_CT_n',   'file_TT'],
            '6 CT in mem vs TT new        ' => ['file_CT',     'file_TT_n'],
            '7 CT in mem vs CT new        ' => ['file_CT',     'file_CT_n'],
            '8 TT in mem vs TT new        ' => ['file_TT',     'file_TT_n'],
        };
        foreach my $type (sort keys %$hash) {
            my ($key1, $key2) = @{ $hash->{$type} };
            my $ct = $r->{$key1};
            my $tt = $r->{$key2};
            my $ct_s = $ct->iters / ($ct->cpu_a || 1);
            my $tt_s = $tt->iters / ($tt->cpu_a || 1);
            my $p = int(100 * ($ct_s - $tt_s) / ($tt_s || 1));
            print "$type - CT is $p% faster than TT\n";

            $output .= sprintf('#  %3s%%  ', $p) if $type =~ /^[1234]/;

            ### store cumulatives
            if (abs($p) < 10000) {
                $cumulative{$type} ||= [0, 0];
                $cumulative{$type}->[0] += $p;
                $cumulative{$type}->[1] ++;
            }
        }
    };
    debug "$@"
        if $@;

    $output .= "# ".sprintf("%.1f", $r->{'file_CT'}->iters / ($r->{'file_CT'}->cpu_a || 1))."/s #\n";
#    $output .= "#\n";

    foreach my $row (values %cumulative) {
        $row->[2] = sprintf('%.1f', $row->[0] / ($row->[1]||1));
    }

    if ($#run > 0) {
        foreach (sort keys %cumulative) {
            printf "Cumulative $_: %6.1f\n", $cumulative{$_}->[2];
        }
    }

}

### add the final total row
if ($#run > 0) {
    $output .= "    # overall" . (" "x61);
    foreach my $type (sort keys %cumulative) {
        $output .= sprintf('#  %3s%%  ', int $cumulative{$type}->[2]) if $type =~ /^[1234]/;
    }
    $output .= "#\n";

    print $output;
}



#print `ls -lR $tt_cache_dir`;
__DATA__

--Boundary-00=_/wnWEH78x/uj0hI
Content-Type: text/plain;
  charset="us-ascii";
  name="perldoc"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="perldoc"

### NOTE - this perldoc is not yet finished.  I include it with this
### email to provide more insight into what CET will do.


NAME

       CGI::Ex::Template - Lightweight TT2/3 engine

SYNOPSIS
         my $t =3D CGI::Ex::Template->new(
             INCLUDE_PATH =3D> [=E2=80=99/path/to/templates=E2=80=99],
         );

         my $swap =3D {
             key1 =3D> =E2=80=99val1=E2=80=99,
             key2 =3D> =E2=80=99val2=E2=80=99,
             code =3D> sub { 42 },
             hash =3D> {a =3D> =E2=80=99b=E2=80=99},
         };

         $t->process(=E2=80=99my/template.tt=E2=80=99, $swap)
             =E2=94=82=E2=94=82 die $t->error;

DESCRIPTION
       CGI::Ex::Template happened by accident.  The CGI::Ex suite included a
       base set of modules for doing anything from simple to complicated CGI
       applications.  Part of the suite was a simple variable interpolater
       that used TT2 style variables in TT2 style tags "[% foo.bar %]".  Th=
is
       was fine and dandy for a couple of years.  In winter of 2005-2006 CET
       was revamped and provided for most of the features of TT2 as well as
       some from TT3.

       CGI::Ex::Template (CET hereafter) is smaller, faster, uses less memo=
ry
       and less CPU than TT2.  However, it is most likely less portable, le=
ss
       extendable, and probably has many of the bugs that TT2 has already m=
as=E2=80=90
       saged out from years of bug reports and patches from a very active c=
om=E2=80=90
       munity and mailing list.  CET does not have a vibrant community behi=
nd
       it.  Fixes applied to TT2 will take longer to get into CET, should t=
hey
       get in at all.

PUBLIC METHODS
       new
               my $obj =3D CGI::Ex::Template->new({
                   INCLUDE_PATH =3D> [=E2=80=99/my/path/to/content=E2=80=99=
, =E2=80=99/my/path/to/content2=E2=80=99],
               });

               Arguments may be passed as a hash or as a hashref.  Returns =
a CGI::Ex::Template object.

               There are currently no errors during CGI::Ex::Template objec=
t creation.

       process
       process_simple
       error
           Should something go wrong during a "process" command, the error
           that occured can be retrieved via the error method.

               $obj->process(=E2=80=99somefile.html=E2=80=99, {a =3D> =E2=
=80=99b=E2=80=99}, \$string_ref)
                   =E2=94=82=E2=94=82 die $obj->error;

       define_vmethod
           This method is available for defining extra Virtual methods or f=
il=E2=80=90
           ters.  This method is similar to Template::Stash::define_vmethod.

SEMI PUBLIC METHODS
       The following list of methods are other interesting methods of CET t=
hat
       may be re-implemented by subclasses of CET.

       exception - Creates an exception object blessed into the package lis=
ted
       in $CGI::Ex::Template::PACKAGE_EXCEPTION.

       execute_tree - Executes a parsed tree (returned from parse_tree)

       get_variable - Turns a variable identity array into the parsed vari=
=E2=80=90
       able.  This method is also repsonsible for playing opererators and r=
un=E2=80=90
       ning virtual methods and filters.  The method could more accurately =
be
       called play_expression.

       get_variable_reference - similar to get_variable but returns a varia=
ble
       identity that can be used repeatedly to lookup the stored variable
       name.  This is different from TT2 currently in that it resolves argu=
=E2=80=90
       ments but makes no attempt to auto-vivify the structure.  The refere=
nce
       is more literally a name lookup while TT returns an actual perl refe=
r=E2=80=90
       ence to the appropriate data structure.

       include_filename - Takes a file path, and resolves it into the full
       filename using paths from INCLUDE_PATH.

       _insert - Resolves the file passed, and then returns its contents.

       list_filters - Dynamically loads the filters list from Template::Fil=
=E2=80=90
       ters when a filter is used that is not natively implemented in CET.

       list_plugins - Returns an arrayref of modules that are under a base
       Namespace.

         my @modules =3D @{ $self->list_plugins({base =3D> =E2=80=99Templat=
e::Plugins=E2=80=99}) }:

       load_parsed_tree - Given a filename or a string reference will retur=
n a
       parsed document hash that contains the parsed tree.

         my $doc =3D $self->load_parsed_tree($file) =E2=94=82=E2=94=82 $sel=
f->throw(=E2=80=99undef=E2=80=99, "Zero length content");

       parse_args - Allow for the multitudinous ways that TT parses argumen=
ts.
       This allows for positional as well as named arguments.  Named argume=
nts
       can be separated with a "=3D" or "=3D>", and positional arguments sh=
ould be
       separated by " " or ",".  This only returns an array of parsed vari=
=E2=80=90
       ables.  Use vivify_args to translate to the actual values.

       parse_tree - Used by load_parsed_tree.  This is the main grammar eng=
ine
       of the program.  It uses method in the $DIRECTIVES hashref to parse
       different DIRECTIVE TYPES.

       parse_variable - Used to parse a variable, an expression, a literal
       string, or a number.  It returns a parsed variable tree.  Samples of
       parsed variables can be found in the VARIABLE PARSE TREE section.

       set_variable - Used to set a variable.  Expects a variable identity
       array and the value to set.  It will autovifiy as necessary.

       throw - Creates an exception object from the arguments and dies.

       undefined_any - Called during get_variable if a value is returned th=
at
       is undefined.  This could be used to magically create variables on t=
he
       fly.  This is similar to Template::Stash::undefined.  It is suggested
       that undefined_get be used instead.  Default behavior returns undef.
       You may also pass a coderef via the UNDEFINED_ANY configuration vari=
=E2=80=90
       able.  Also, you can try using the DEBUG =3D> =E2=80=99undef=E2=80=
=99, configuration
       option which will throw an error on undefined variables.

       undefined_get - Called when a variable is undefined during a GET dir=
ec=E2=80=90
       tive.  This is useful to see if a value that is about to get inserted
       into the text is undefined.  undefined_any is a little too general f=
or
       most cases.  Also, you may pass a coderef via the UNDEFINED_GET
       configuration variable.

       vivify_args - Turns an arrayref of arg identities parsed by parse_ar=
gs
       and turns them into the actual values.

OTHER UTILITY METHODS
       The following is a brief list of other methods used by CET.  General=
ly,
       these shouldn=E2=80=99t be overwritten by subclasses.

       apply_precedence - allows for parsed operator array to be translated=
 to
       a tree based upon operator precedence.

       context - used to create a "pseudo" context object that allows for
       portability of TT plugins, filters, and perl blocks that need a cont=
ext
       object.

       DEBUG - TT2 Holdover that is used once for binmode setting during a =
TT2
       test.

       debug_node - used to get debug info on a directive if DEBUG_DIRS is
       set.

       filter_* - implement filters that are more than one line.

       get_line_number_by_index - used to turn string index position into l=
ine
       number

       interpolate_node - used for parsing text nodes for dollar variables
       when interpolate is on.

       parse_* - used by parse_tree to parse the template.  These are the
       grammar.

       play_* - used by execute_tree to execute the parsed tree.

       play_operator - to execute any found operators

       _process - called by process and the PROCESS, INCLUDE and other dire=
c=E2=80=90
       tives.

       slurp - reads contents of passed filename - throws file exception on
       error.

       split_paths - used to split INCLUDE_PATH or other directives if an
       arrayref is not passed.

       _vars - Return a reference to the current stash of variables.  This =
is
       currently only used by the pseudo context object and may disappear at
       some point.

       vmethod_* - implement virtual methods that are more than one line.

       weak_copy - used to create a weak reference to self to avoid circular
       references. (this is needed by macros and references.

TODO
           Add WRAPPER config item
           Add ERROR config item
           Document which TT2 test suites pass

HOW IS CGI::Ex::Template DIFFERENT
       CET uses the same template syntax and configuration items as TT2, but
       the internals of CET were written from scratch.  In addition to this,
       the following is a list of some of the ways that configuration and s=
yn=E2=80=90
       tax of CET different from that of TT.

           Numerical hash keys work [% a =3D {1 =3D> 2} %]

           Quoted hash key interpolation is fine [% a =3D {"$foo" =3D> 1} %]

           Range operator returns an arrayref [% a =3D 1 .. 10 %]

           Multiple ranges in same constructor [% a =3D [1..10, 21..30] %]

           Construtor types can call virtual methods

              [% a =3D [1..10].reverse %]

              [% "$foo".length %]

              [% 123.length %]   # =3D 3

              [% 123.4.length %]  # =3D 5

              [% -123.4.length %] # =3D -5 ("." binds more tightly than "-")

              [% (a ~ b).length %]

              [% "hi".repeat(3) %]

              [% {a =3D> b}.size %]

           Reserved names are less reserved

              [% GET GET %] # gets the variable named "GET"

              [% GET $GET %] # gets the variable who=E2=80=99s name is stor=
ed in "GET"

           Filters and SCALAR_OPS are interchangeable.

              [% a =E2=94=82 length %]

              [% b . lower %]

           Pipe "=E2=94=82" can be used anywhere dot "." can be and means t=
o call the
           virtual method.

              [% a =3D {size =3D> "foo"} %][% a.size %] # =3D foo

              [% a =3D {size =3D> "foo"} %][% a=E2=94=82size %] # =3D 1 (si=
ze of hash)

           Pipe "=E2=94=82" and "." can be mixed.

              [% "aa" =E2=94=82 repeat(2) . length %] # =3D 4

           Whitespace is less meaningful.

              [% 2-1 %] # =3D 1 (fails in TT)

           Added pow operator.

              [% 2 ** 3 %] [% 2 pow 3 %] # =3D 8 8

           FOREACH variables can be nested

              [% FOREACH f.b =3D [1..10] ; f.b ; END %]

              Note that nested variables are subject to scoping issues.
              f.b will not be reset to its value before the FOREACH.

           Post operative directives can be nested.

              [% one IF two IF three %]

              same as

              [% IF three %][% IF two %][% one %][% END %][% END %]

              [% a =3D [[1..3], [5..7]] %][% i FOREACH i =3D j FOREACH j =
=3D a %] # =3D 123567

           CATCH blocks can be empty.

           CHOMP at the end of a string.

             CET will replace "[% 1 =3D%]\n" with "1 "

             TT will replace "[% 1 =3D%]\n" with "1"

             This is an internal one-off exception in TT that may or may no=
t DWIM.
             In CET - it always means to always replace whitespace on the l=
ine with
             a space.  The template can be modified to either not have spac=
e - or
             to end with ~%] which will remove any following space.

           CET does not generate Perl code.  It generates an "opcode" tree.

           CET uses storable for its compiled templates.  If EVAL_PERL is o=
ff,
           CET will not eval_string on ANY piece of information.

           There is no context.  CET provides a context object that mimics =
the
           Template::Context interface for use by some TT filters, eval perl
           blocks, and plugins.

           There is no stash.  CET only supports the variables passed in VA=
RI=E2=80=90
           ABLES, PRE_DEFINE, and those passed to the process method.  CET
           provides a stash object that mimics the Template::Stash interface
           for use by some TT filters, eval perl blocks, and plugins.

           There is no provider.  CET uses the load_parsed_tree method to g=
et
           and cache templates.

           There is no grammar.  CET has its own built in grammar system.

           There is no service.

           References are less interpolated. TT partially resolves some of =
the
           names filter keys and other elements rather than wait until the
           reference is actually used. CET only resolves interpolated values
           and arguments to subroutines.  All other resolution is delayed
           until the reference is actually used.

           The DEBUG directive only understands DEBUG_DIRS (8) and DEBUG_UN=
DEF
           (2).

           When debug dirs is on, directives on different lines separated by
           colons show the line they are on rather than a general line rang=
e.

DIRECTIVES
       This section containts the alphabetical list of DIRECTIVES available=
 in
       the TT language.  DIRECTIVES are the "functions" that implement the
       Template Toolkit mini-language.  For further discussion and examples,
       please refer to the TT directives documentation.

       BLOCK
           Saves a block of text under a name for later use in PROCESS,
           INCLUDE, and WRAPPER directives.  Blocks may be placed anywhere
           within the template being processed including after where they a=
re
           used.

               [% BLOCK foo %]Some text[% END %]
               [% PROCESS foo %]

               Would print

               Some text

               [% INCLUDE foo %]
               [% BLOCK foo %]Some text[% END %]

               Would print

               Some text

           Anonymous BLOCKS can be used for capturing.

               [% a =3D BLOCK %]Some text[% END %][% a %]

               Would print

               Some text

           Anonymous BLOCKS can be used with macros.

       BREAK
           Alias for LAST.  Used for exiting FOREACH and WHILE loops.

       CALL
           Calls the variable (and any underlying coderefs) as in the GET
           method, but always returns an empty string.

       CASE
           Used with the SWITCH directive.  See the "SWITCH" directive.

       CATCH
           Used with the TRY directive.  See the "TRY" directive.

       CLEAR
           Clears any of the content currently generated in the innermost
           block or template.  This can be useful when used in conjuction w=
ith
           the TRY statement to clear generated content if an error occurs
           later.

       DEBUG
           Used to reset the DEBUG_FORMAT configuration variable, or to turn
           DEBUG statements on or off.  This only has effect if the DEBUG_D=
IRS
           or DEBUG_ALL flags were passed to the DEBUG configuration variab=
le.

               [% DEBUG format =E2=80=99($file) (line $line) ($text)=E2=80=
=99 %]
               [% DEBUG on %]
               [% DEBUG off %]

       DEFAULT
           Similar to SET, but only sets the value if a previous value was =
not
           defined or was zero length.

               [% DEFAULT foo =3D =E2=80=99bar=E2=80=99 %][% foo %] =3D> =
=E2=80=99bar=E2=80=99

               [% foo =3D =E2=80=99baz=E2=80=99 %][% DEFAULT foo =3D =E2=80=
=99bar=E2=80=99 %][% foo %] =3D> =E2=80=99baz=E2=80=99

       ELSE
           Used with the IF directive.  See the "IF" directive.

       ELSIF
           Used with the IF directive.  See the "IF" directive.

       END Used to end a block directive.

       FILTER
           Used to apply different treatments to blocks of text.  It may op=
er=E2=80=90
           ate as a BLOCK directive or as a post operative directive.  CET
           supports all of the filters in Template::Filters.  The lines
           between scalar virtual methods and filters is blurred (or non-ex=
is=E2=80=90
           tent) in CET.  Anything that is a scalar virtual method may be u=
sed
           as a FILTER.

           TODO - enumerate the at least 7 ways to pass and use filters.

       =E2=80=99=E2=94=82=E2=80=99 Alias for the FILTER directive.  Note th=
at =E2=94=82 is similar to the =E2=80=99.=E2=80=99
           in CGI::Ex::Template.  Therefore a pipe cannot be used directly
           after a variable name in some situations (the pipe will act only=
 on
           that variable).  This is the behavior employed by TT3.

       FINAL
           Used with the TRY directive.  See the "TRY" directive.

       FOR Alias for FOREACH

       FOREACH
       GET Return the value of a variable.

               [% GET a %]

           The GET keyword may be omitted.

               [% a %]

       IF (IF / ELSIF / ELSE)
           Allows for conditional testing.  Expects an expression as its on=
ly
           argument.  If the expression is true, the contents of its block =
are
           processed.  If false, the processor looks for an ELSIF block.  If
           an ELSIF=E2=80=99s expression is true then it is processed.  Fin=
ally it
           looks for an ELSE block which is processed if none of the IF or
           ELSIF=E2=80=99s expressions were true.

               [% IF a =3D=3D b %]A equaled B[% END %]

               [% IF a =3D=3D b -%]
                   A equaled B
               [%- ELSIF a =3D=3D c -%]
                   A equaled C
               [%- ELSE -%]
                   Couldn=E2=80=99t determine that A equaled anything.
               [%- END %]

           If may also be used as a post operative directive.

               [% =E2=80=99A equaled B=E2=80=99 IF a =3D=3D b %]

       INCLUDE
       INSERT
       LAST
           Used to exit out of a WHILE or FOREACH loop.

       MACRO
       META
       NEXT
           Used to go to the next iteration of a WHILE or FOREACH loop.

       PERL
       PROCESS
       RAWPERL
       RETURN
           Used to exit the innermost block or template and continue proces=
s=E2=80=90
           ing in the surrounding block or template.

       SET Used to set variables.

              [% SET a =3D 1 %][% a %]             =3D> "1"
              [% a =3D 1 %][% a %]                 =3D> "1"
              [% b =3D 1 %][% SET a =3D b %][% a %]  =3D> "1"
              [% a =3D 1 %][% SET a %][% a %]      =3D> ""
              [% SET a =3D [1, 2, 3] %][% a.1 %]   =3D> "2"
              [% SET a =3D {b =3D> =E2=80=99c=E2=80=99} %][% a.b %]  =3D> "=
c"

       STOP
           Used to exit the entire process method (out of all blocks and te=
m=E2=80=90
           plates).  No content will be processed beyond this point.

       SWITCH
       TAGS
           Change the type of enclosing braces used to delineate template
           tags.  This remains in effect until the end of the enclosing blo=
ck
           or template or until the next TAGS directive.  Either a named set
           of tags must be supplied, or two tags themselves must be supplie=
d.

               [% TAGS html %]

               [% TAGS <!-- --> %]

           The named tags are (duplicated from TT):

               template =3D> [=E2=80=99[%=E2=80=99,   =E2=80=99%]=E2=80=99]=
,  # default
               metatext =3D> [=E2=80=99%%=E2=80=99,   =E2=80=99%%=E2=80=99]=
,  # Text::MetaText
               star     =3D> [=E2=80=99[*=E2=80=99,   =E2=80=99*]=E2=80=99]=
,  # TT alternate
               php      =3D> [=E2=80=99<?=E2=80=99,   =E2=80=99?>=E2=80=99]=
,  # PHP
               asp      =3D> [=E2=80=99<%=E2=80=99,   =E2=80=99%>=E2=80=99]=
,  # ASP
               mason    =3D> [=E2=80=99<%=E2=80=99,   =E2=80=99>=E2=80=99 ]=
,  # HTML::Mason
               html     =3D> [=E2=80=99<!--=E2=80=99, =E2=80=99-->=E2=80=99=
], # HTML comments

       THROW
       TRY
       UNLESS
       USE
       WHILE
       WRAPPER
           Block directive.  Processes contents of its block and then passes
           them in the [% content %] variable to the block or filename list=
ed
           in the WRAPPER tag.

               [% WRAPPER foo %]
               My content to be processed.[% a =3D 2 %]
               [% END %]

               [% BLOCK foo %]
               A header ([% a %]).
               [% content %]
               A footer ([% a %]).
               [% END %]

           This would print.

               A header (2).
               My content to be processed.
               A footer (2).

           The WRAPPER directive may also be used as a post directive.

               [% BLOCK baz %]([% content %])[% END -%]
               [% "foobar" WRAPPER baz %]

           Would print

               (foobar)=E2=80=99);

OPERATORS
       The following operators are available in CGI::Ex::Template.  Except
       where noted these are the same operators available in TT.  They are
       listed in the order of their precedence (the higher the precedence t=
he
       tighter it binds).

       "." Binary.  The dot operator.  Allows for accessing sub-members, me=
th=E2=80=90
           ods, or virtual methods of nested data structures.

               my $obj->process(\$content, {a =3D> {b =3D> [0, {c =3D> [34,=
 57]}]}}, \$output);

               [% a.b.1.c.0 %] =3D> 34

           Note: on access to hashrefs, any hash keys that match the sub key
           name will be used before a virtual method of the same name.  For
           example if a passed hash contained pair with a keyname "defined"
           and a value of "2", then any calls to hash.defined(subkeyname)
           would always return 2 rather than using the vmethod named
           "defined."  To get around this limitation use the "=E2=94=82" op=
erator
           (listed next).

       "=E2=94=82" Binary.  The pipe operator.  Similar to the dot operator=
=2E  Allows
           for explicit calling of virtual methods and filters (filters are
           "merged" with virtual methods in CGI::Ex::Template and TT3) when
           accessing hashrefs.  See the note for the "." operator.

           The pipe character is similar to TT2 in that it can be used in
           place of a directive as an alias for FILTER.  It similar to TT3 =
in
           that it can be used for virtual method access.  This duality is =
one
           source of difference between CGI::Ex::Template and TT2 compatibi=
l=E2=80=90
           ity.  Templates that have directives that end with a variable na=
me
           that then use the "=E2=94=82" directive to apply a filter will b=
e broken as
           the "=E2=94=82" will be applied to the variable name.

           The following two cases will do the same thing.

               [% foo =E2=94=82 html %]

               [% foo FILTER html %]

           Though they do the same thing, internally, foo=E2=94=82html is s=
tored as a
           single variable while "foo FILTER html" is stored as the variable
           foo which is then passed to the the FILTER html.

           A TT2 sample that would break in CGI::Ex::Template or TT3 is:

               [% PROCESS foo a =3D b =E2=94=82 html %]

           Under TT2 the content returned by "PROCESS foo a =3D b" would al=
l be
           passed to the html filter.  Under CGI::Ex::Template and TT3, b
           would be passed to the html filter before assigning it to the va=
ri=E2=80=90
           able "a" before the template foo was processed.

           A simple fix is to do any of the following:

               [% PROCESS foo a =3D b FILTER html %]

               [% =E2=94=82 html %][% PROCESS foo a =3D b %][% END %]

               [% FILTER html %][% PROCESS foo a =3D b %][% END %]

           This shouldn=E2=80=99t be too much hardship and offers the great=
 return of
           disambiguating virtual method access.

       "\" Unary.  The reference operator.  Not well publicized in TT.  Sto=
res
           a reference to a variable for use later.  Can also be used to
           "alias" long names.  Note that a minimum of name resolution occu=
rs
           at reference creation time (including resolving any arguments to
           functions or variable name interpolation).

               [% f =3D 7 ; foo =3D \f ; f =3D 8 ; foo %] =3D> 8

               [% foo =3D \f.g.h.i.j.k; f.g.h.i.j.k =3D 7; foo %] =3D> 7

               [% f =3D "abcd"; foo =3D \f.replace("ab", "-AB-") ; foo %] =
=3D> -AB-cd

               [% f =3D "abcd"; foo =3D \f.replace("bc") ; foo("-BC-") %] =
=3D> a-BC-d

               [% f =3D "abcd"; foo =3D \f.replace ; foo("cd", "-CD-") %] =
=3D> ab-CD-

       "**  ^  pow"
           Binary.  X raised to the Y power.  This isn=E2=80=99t available =
in TT 2.14.

               [% 2 ** 3 %] =3D> 8

       "!" Unary not.  Negation of the value.

       "-  unary_minus"
           Unary minus.  Returns the value multiplied by -1.  The operator
           "unary_minus" is used internally by CGI::Ex::Template to provide
           for - to be listed in the precedence table twice.

               [% a =3D 1 ; b =3D -a ; b %] =3D> -1

       "*" Binary. Multiplication.

       "/  div  DIV"
           Binary. Division.  Note that / is floating point division, but d=
iv
           and DIV are integer division.

              [% 10  /  4 %] =3D> 2.5
              [% 10 div 4 %] =3D> 2

       "%  mod  MOD"
           Binary. Modulus.

              [% 15 % 8 %] =3D> 7

       "+" Binary.  Addition.

       "-" Binary.  Minus.

       "_  ~"
           Binary.  String concatenation.

               [% "a" ~ "b" %] =3D> ab

       "<  >  <=3D  >=3D"
           Binary.  Numerical comparators.

       "lt  gt  le  ge"
           Binary.  String comparators.

       "=3D=3D  eq"
           Binary.  Equality test.  TT chose to use Perl=E2=80=99s eq for b=
oth opera=E2=80=90
           tors.  There is no test for numeric equality.

       "!=3D ne"
           Binary.  Non-equality test.  TT chose to use Perl=E2=80=99s ne f=
or both
           operators.  There is no test for numeric non-equality.

       "&&"
           Multiple arity.  And.  All values must be true.  If all values a=
re
           true, the last value is returned as the truth value.

               [% 2 && 3 && 4 %] =3D> 4

       "=E2=94=82=E2=94=82"
           Multiple arity.  Or.  The first true value is returned.

               [% 0 =E2=94=82=E2=94=82 =E2=80=99=E2=80=99 =E2=94=82=E2=94=
=82 7 %] =3D> 7

       ".."
           Binary.  Range creator.  Returns an arrayref containing the valu=
es
           between and including the first and last arguments.

               [% t =3D [1 .. 5] %] =3D> variable t contains an array with =
1,2,3,4, and 5

           In CGI::Ex::Template, because .. is an operator that returns an
           array, you may also specify the previous example as the following
           (this does not work in TT):

               [% t =3D 1 .. 5 %] =3D> variable t contains an array with 1,=
2,3,4, and 5

           CGI::Ex::Template provides special functionality to allow the
           arrayref returned by .. to be expanded fully into a [] construtor
           as in "[1 .. 5]".  Because of this it is possible to place multi=
ple
           ranges in the same [] constructor.  This is not available in TT.

               [% t =3D [1..3, 6..8] %] =3D> variable t contains an array w=
ith 1,2,3,6,7,8

       "? :"
           Trinary.  Can be nested with other ?: pairs.

               [% 1 ? 2 : 3 %] =3D> 2
               [% 0 ? 2 : 3 %] =3D> 3

       "=3D" Assignment.  Sets the lefthand side to the value of the righth=
and
           side.  In order to not conflict with SET, FOREACH and other oper=
a=E2=80=90
           tions, this operator is only available in parenthesis.  Returns =
the
           value of the righthand side.

              [% (a =3D 1) %] --- [% a %] =3D> 1 --- 1

       "not  NOT"
           Lower precedence version of the =E2=80=99!=E2=80=99 operator.

       "and  AND"
           Lower precedence version of the =E2=80=99&&=E2=80=99 operator.

       "or OR"
           Lower precedence version of the =E2=80=99=E2=94=82=E2=94=82=E2=
=80=99 operator.

       "hashref"
           Multiple arity.  This operator is not used in TT.  It is used
           internally by CGI::Ex::Template to delay the creation of a hashr=
ef
           until the execution of the compiled template.

       "arrayref"
           Multiple arity.  This operator is not used in TT.  It is used
           internally by CGI::Ex::Template to delay the creation of an
           arrayref until the execution of the compiled template.

CONFIGURATION
       The following TT2 configuration variables are supported (in alphabet=
i=E2=80=90
       cal order).  Note: for further discussion you can refer to the TT co=
n=E2=80=90
       fig documentation.

       These variables should be passed to the "new" constructor.

          my $obj =3D CGI::Ex::Template->new(
              VARIABLES  =3D> \%hash_of_variables,
              AUTO_RESET =3D> 0,
              TRIM       =3D> 1,
              POST_CHOMP =3D> 2,
              PRE_CHOMP  =3D> 1,
          );

       ABSOLUTE
           Boolean.  Default false.  Are absolute paths allowed for included
           files.

       AUTO_RESET
           Boolean.  Default 1.  Clear blocks that were set during the proc=
ess
           method.

       BLOCKS
           A hashref of blocks that can be used by the process method.

              BLOCKS =3D> {
                  block_1 =3D> sub { ... }, # coderef that returns a block
                  block_2 =3D> =E2=80=99A String=E2=80=99,  # simple string
              },

           Note that a Template::Document cannot be supplied as a value (TT
           supports this).  However, it is possible to supply a value that =
is
           equal to the hashref returned by the load_parsed_tree method.

       CACHE_SIZE
           Number of compiled templates to keep in memory.  Default undef.
           Undefined means to allow all templates to cache.  A value of 0 w=
ill
           force no caching.  The cache mechanism will clear templates that
           have not been used recently.

       COMPILE_DIR
           Base directory to store compiled templates.  Default undef. Com=
=E2=80=90
           piled templates will only be stored if one of COMPILE_DIR and CO=
M=E2=80=90
           PILE_EXT is set.

       COMPILE_EXT
           Extension to add to stored compiled template filenames.  Default
           undef.

       CONSTANTS
           Hashref.  Used to define variables that will be "folded" into the
           compiled template.  Variables defined here cannot be overridden.

               CONSTANTS =3D> {my_constant =3D> 42},

               A template containing:

               [% constants.my_constant %]

               Will have the value 42 compiled in.

           Constants defined in this way can be chained as in [% con=E2=80=
=90
           stant.foo.bar.baz %] but may only interpolate values that are set
           before the compile process begins.  This goes one step beyond TT=
 in
           that any variable set in VARIABLES, or PRE_DEFINE, or passed to =
the
           process method are allowed - they are not in TT.  Variables defi=
ned
           in the template are not available during the compile process.

               GOOD:

               CONSTANTS =3D> {
                   foo  =3D> {
                       bar =3D> {baz =3D> 42},
                       bim =3D> 57,
                   },
                   bing =3D> =E2=80=99baz=E2=80=99,
                   bang =3D> =E2=80=99bim=E2=80=99,
               },
               VARIABLES =3D> {
                   bam  =3D> =E2=80=99bar=E2=80=99,
               },

               In the template

               [% constants.foo.${constants.bang} %]

               Will correctly print 57.

               GOOD (non-tt behavior)

               [% constants.foo.$bam.${constants.bing} %]

               CGI::Ex::Template will print 42 because the value of bam is
               known at compile time.  TT would print =E2=80=99=E2=80=99 be=
cause the value of $bam
               is not yet defined in the TT engine.

               BAD:

               In the template:

               [% bam =3D =E2=80=99somethingelse=E2=80=99 %]
               [% constants.foo.$bam.${constants.bing} %]

               Will still print 42 because the value of bam used comes from
               variables defined before the template was compiled.  TT will=
 still print =E2=80=99=E2=80=99.

       CONSTANT_NAMESPACE
       DEBUG
               Takes a list of constants =E2=94=82=E2=80=99ed together whic=
h enables different
               debugging modes.  Alternately the lowercase names may be use=
d (multiple
               values joined by a ",".

               The only supported TT values are:
               DEBUG_UNDEF (2)    - debug when an undefined value is used.
               DEBUG_DIRS  (8)    - debug when a directive is used.
               DEBUG_ALL   (2047) - turn on all debugging.

               Either of the following would turn on undef and directive de=
bugging:

               DEBUG =3D> =E2=80=99undef, dirs=E2=80=99,            # prefe=
rred
               DEBUG =3D> 2 =E2=94=82 8,
               DEBUG =3D> DEBUG_UNDEF =E2=94=82 DEBUG_DIRS, # constants fro=
m Template::Constants

       DEBUG_FORMAT
       DEFAULT
       DELIMITER
       END_TAG
       EVAL_PERL
           Boolean.  Default false.  If set to a true value, PERL and RAWPE=
RL
           blocks will be allowed to run.  This is a potential security hol=
e,
           as arbitrary perl can be included in the template.  If Tem=E2=80=
=90
           plate::Toolkit is installed, a true EVAL_PERL value also allows =
the
           perl and evalperl filters to be used.

       FILTERS
       INCLUDE_PATH
           A string or an array containing directories too look for files
           included by processed templates.

       INTERPOLATE
       LOAD_PERL
       NAMESPACE - no Template::Namespace::Constants support
       OUTPUT
       OUTPUT_PATH
       PLUGINS
       PLUGIN_BASE
       POST_CHOMP
       POST_PROCESS
       PRE_CHOMP
       PRE_DEFINE
       PRE_PROCESS
       PROCESS
       RECURSION
       RELATIVE
       START_TAG
       TAG_STYLE
       TRIM
           Remove leading and trailing whitespace from blocks and templates.
           This operation is performed after all enclosed template tags have
           been executed.

       UNDEFINED_ANY
           This is not a TT configuration option.  This option expects to b=
e a
           code ref that will be called if a variable is undefined during a
           call to get_variable.  It is passed the variable identity array =
as
           a single argument.  This is most similar to the "undefined" meth=
od
           of Template::Stash.  It allows for the "auto-defining" of a vari=
=E2=80=90
           able for use in the template.  It is suggested that UNDEFINED_GET
           be used instead as UNDEFINED_ANY is a little to general in defin=
ing
           variables.

           You can also sub class the module and override the undefined_any
           method.

       UNDEFINED_GET
           This is not a TT configuration option.  This option expects to b=
e a
           code ref that will be called if a variable is undefined during a
           call to GET.  It is passed the variable identity array as a sing=
le
           argument.  This is more useful than UNDEFINED_ANY in that it is
           only called during a GET directive rather than in embedded expre=
s=E2=80=90
           sions (such as [% a =E2=94=82=E2=94=82 b =E2=94=82=E2=94=82 c %]=
).

           You can also sub class the module and override the undefined_get
           method.

       VARIABLES
           A hashref of variables to initialize the template stash with.
           These variables are available for use in any of the executed tem=
=E2=80=90
           plates.

UNSUPPORTED TT CONFIGURATION
       ANYCASE
           This will not be supported.  You will have to use the full case
           directive names.  (It was in the beta code but was removed prior=
 to
           release).

       WRAPPER
           This will be supported - just not done yet.

       ERROR
           This will be supported - just not done yet.

       V1DOLLAR
           This will not be supported.

       LOAD_TEMPLATES
           CGI::Ex::Template has its own mechanism for loading and storing
           compiled templates.  TT would use a Template::Provider that would
           return a Template::Document.  The closest thing in CGI::Ex::Tem=
=E2=80=90
           plate is the load_parsed_template method.  There is no immediate
           plan to support the TT behavior.

       LOAD_PLUGINS
           CGI::Ex::Template uses its own mechanism for loading plugins.  TT
           would use a Template::Plugins object to load plugins requested v=
ia
           the USE directive.  The functionality for doing this in
           CGI::Ex::Template is contained in the list_plugins method and the
           play_USE method.  There is no immediate plan to support the TT
           behavior.

           Full support is offered for the PLUGINS and LOAD_PERL configurat=
ion
           items.

           Also note that CGI::Ex::Template only natively supports the Iter=
a=E2=80=90
           tor plugin.  Any of the other plugins requested will need to pro=
=E2=80=90
           vided by installing Template::Toolkit or the appropriate plugin
           module.

       LOAD_FILTERS
           CGI::Ex::Template uses its own mechanism for loading filters.  TT
           would use the Template::Filters object to load filters requested
           via the FILTER directive.  The functionality for doing this in
           CGI::Ex::Template is contained in the list_filters method and the
           get_variable method.

           Full support is offered for the FILTERS configuration item.

       TOLERANT
           This option is used by the LOAD_TEMPLATES and LOAD_PLUGINS optio=
ns
           and is not applicable in CGI::Ex::Template.

       SERVICE
           CGI::Ex::Template has no concept of service (theoretically the
           CGI::Ex::Template is the "service").

       CONTEXT
           CGI::Ex::Template provides its own pseudo context object to plug=
=E2=80=90
           ins, filters, and perl blocks.  The CGI::Ex::Template model does=
n=E2=80=99t
           really allow for a separate context.  CGI::Ex::Template IS the c=
on=E2=80=90
           text.

       STASH
           CGI::Ex::Template manages its own stash of variables.  A pseudo
           stash object is available via the pseudo context object for use =
in
           plugins, filters, and perl blocks.

       PARSER
           CGI::Ex::Template has its own built in parser.  The closest simi=
=E2=80=90
           larity is the parse_tree method.  The output of parse_tree is an
           optree that is later run by execute_tree.

       GRAMMAR
           CGI::Ex::Template maintains its own grammar.  The grammar is
           defined in the parse_tree method and the callbacks listed in the
           global $DIRECTIVES hashref.

VARIABLE PARSE TREE
       CGI::Ex::Template parses templates into an tree of operations.  Even
       variable access is parsed into a tree.  This is done in a manner som=
e=E2=80=90
       what similar to the way that TT operates except that nested variables
       such as foo.bar=E2=94=82baz contain the =E2=80=99.=E2=80=99 or =E2=
=80=99=E2=94=82=E2=80=99 in between each name level.
       Opererators are parsed and stored as part of the variable (it may be
       more appropriate to say we are parsing a term or an expression).

       The following table shows a variable or expression and the correspon=
d=E2=80=90
       ing parsed tree (this is what the parse_variable method would return=
).

           one                [ =E2=80=99one=E2=80=99,  0 ]
           one()              [ =E2=80=99one=E2=80=99,  [] ]
           one.two            [ =E2=80=99one=E2=80=99,  0, =E2=80=99.=E2=80=
=99, =E2=80=99two=E2=80=99,  0 ]
           one=E2=94=82two            [ =E2=80=99one=E2=80=99,  0, =E2=80=
=99=E2=94=82=E2=80=99, =E2=80=99two=E2=80=99,  0 ]
           one.$two           [ =E2=80=99one=E2=80=99,  0, =E2=80=99.=E2=80=
=99, [=E2=80=99two=E2=80=99, 0 ], 0 ]
           one(two)           [ =E2=80=99one=E2=80=99,  [ [=E2=80=99two=E2=
=80=99, 0] ] ]
           one.${two().three} [ =E2=80=99one=E2=80=99,  0, =E2=80=99.=E2=80=
=99, [=E2=80=99two=E2=80=99, [], =E2=80=99.=E2=80=99, =E2=80=99three=E2=80=
=99, 0], 0]
           2.34               2.34
           "one"              "one"
           "one"=E2=94=82length       [ \"one", 0, =E2=80=99=E2=94=82=E2=80=
=99, =E2=80=99length=E2=80=99, 0 ]
           "one $a two"       [ \ [ =E2=80=99~=E2=80=99, =E2=80=99one =E2=
=80=99, [=E2=80=99a=E2=80=99, 0], =E2=80=99 two=E2=80=99 ], 0 ]
           [0, 1, 2]          [ \ [ =E2=80=99arrayref=E2=80=99, 0, 1, 2 ], =
0 ]
           [0, 1, 2].size     [ \ [ =E2=80=99arrayref=E2=80=99, 0, 1, 2 ], =
0, =E2=80=99.=E2=80=99, =E2=80=99size=E2=80=99, 0 ]
           [=E2=80=99a=E2=80=99, a, $a ]      [ \ [ =E2=80=99arrayref=E2=80=
=99, =E2=80=99a=E2=80=99, [=E2=80=99a=E2=80=99, 0], [[=E2=80=99a=E2=80=99, =
0], 0] ], 0]
           {a  =3D> =E2=80=99b=E2=80=99}        [ \ [ =E2=80=99hashref=E2=
=80=99,  =E2=80=99a=E2=80=99, =E2=80=99b=E2=80=99 ], 0 ]
           {a  =3D> =E2=80=99b=E2=80=99}.size   [ \ [ =E2=80=99hashref=E2=
=80=99,  =E2=80=99a=E2=80=99, =E2=80=99b=E2=80=99 ], 0, =E2=80=99.=E2=80=99=
, =E2=80=99size=E2=80=99, 0 ]
           {$a =3D> b}          [ \ [ =E2=80=99hashref=E2=80=99,  [=E2=80=
=99a=E2=80=99, 0], [=E2=80=99b=E2=80=99, 0] ], 0 ]
           1 + 2 + 3 + 4      [ \ [ =E2=80=99+=E2=80=99, 1, 2, 3, 4 ], 0]
           a + b              [ \ [ =E2=80=99+=E2=80=99, [=E2=80=99a=E2=80=
=99, 0], [=E2=80=99b=E2=80=99, 0] ], 0 ]
           a * (b + c)        [ \ [ =E2=80=99*=E2=80=99, [=E2=80=99a=E2=80=
=99, 0], [ \ [=E2=80=99+=E2=80=99, [=E2=80=99b=E2=80=99, 0], [=E2=80=99c=E2=
=80=99, 0]], 0 ]], 0 ]
           (a + b)            [ \ [ =E2=80=99+=E2=80=99, [=E2=80=99a=E2=80=
=99, 0], [=E2=80=99b=E2=80=99, 0] ]], 0 ]
           (a + b) * c        [ \ [ =E2=80=99*=E2=80=99, [ \ [ =E2=80=99+=
=E2=80=99, [=E2=80=99a=E2=80=99, 0], [=E2=80=99b=E2=80=99, 0] ], 0 ], [=E2=
=80=99c=E2=80=99, 0] ], 0 ]
           a ? b : c          [ \ [ =E2=80=99?=E2=80=99, [=E2=80=99a=E2=80=
=99, 0], [=E2=80=99b=E2=80=99, 0], [=E2=80=99c=E2=80=99, 0] ], 0 ]
           a =E2=94=82=E2=94=82 b =E2=94=82=E2=94=82 c        [ \ [ =E2=80=
=99=E2=94=82=E2=94=82=E2=80=99, [=E2=80=99a=E2=80=99, 0], [=E2=80=99b=E2=80=
=99, 0], [=E2=80=99c=E2=80=99, 0] ], 0 ]
           ! a                [ \ [ =E2=80=99!=E2=80=99, [=E2=80=99a=E2=80=
=99, 0] ], 0 ]

       Some notes on the parsing.

           Operators are parsed as part of the variable and become part of =
the variable tree.

           Operators are stored in the variable tree using a reference to t=
he arrayref - which
           allows for quickly decending the parsed variable tree and determ=
ining that the next
           node is an operator.

           Parens () can be used at any point in an expression to disambigu=
ate precedence.

           "Variables" that appear to be literal strings or literal numbers
           are returned as the literal (no operator tree).

       The following perl can be typed at the command line to view the pars=
ed
       variable tree.

           perl -e =E2=80=99my $a =3D "\"one \$a two\"";
              use CGI::Ex::Template;
              use Data::Dumper;
              print Dumper(CGI::Ex::Template->new->parse_variable(\$a));=E2=
=80=99

AUTHOR
       Paul Seamons <mail at seamons dot com>



perl v5.8.7                       2006-05-04           .::CGI::Ex::Template=
(3)

--Boundary-00=_/wnWEH78x/uj0hI
Content-Type: text/plain;
  charset="us-ascii";
  name="7_template_00_base.t"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filename="7_template_00_base.t"

# -*- Mode: Perl; -*-

use vars qw($module $is_tt);
BEGIN {
    $module = 'CGI::Ex::Template'; #real    0m1.243s #user    0m0.695s #sys     0m0.018s
    #$module = 'Template';         #real    0m2.329s #user    0m1.466s #sys     0m0.021s
    $is_tt = $module eq 'Template';
};

use strict;
use Test::More tests => 474 - ($is_tt ? 58 : 0);
use Data::Dumper qw(Dumper);
use constant test_taint => 0 && eval { require Taint::Runtime };

use_ok($module);

Taint::Runtime::taint_start() if test_taint;

###----------------------------------------------------------------###

sub process_ok { # process the value and say if it was ok
    my $str  = shift;
    my $test = shift;
    my $vars = shift;
    my $obj  = shift || $module->new(@{ $vars->{tt_config} || [] }); # new object each time
    my $out  = '';

    Taint::Runtime::taint(\$str) if test_taint;

    $obj->process(\$str, $vars, \$out);
    my $ok = ref($test) ? $out =~ $test : $out eq $test;
    ok($ok, "\"$str\" => \"$out\"" . ($ok ? '' : " - should've been \"$test\""));
    my $line = (caller)[2];
    warn "#   process_ok called at line $line.\n" if ! $ok;
    print $obj->error if ! $ok && $obj->can('error');
    print Dumper $obj->parse_tree(\$str) if ! $ok && $obj->can('parse_tree');
    exit if ! $ok;
}

###----------------------------------------------------------------###

### set up some dummy packages for various tests
{
    package MyTestPlugin::Foo;
    $INC{'MyTestPlugin/Foo.pm'} = $0;
    sub load { $_[0] }
    sub new {
        my $class   = shift;
        my $context = shift;  # note the plugin style object that needs to shift off context
        my $args    = shift || {};
        return bless $args, $class;
    }
    sub bar { my $self = shift; return join('', map {"$_$self->{$_}"} sort keys %$self) }
    sub seven { 7 }
    sub many { return 1, 2, 3 }
    sub echo { my $self = shift; $_[0] }
}
{
    package Foo2;
    $INC{'Foo2.pm'} = $0;
    use base qw(MyTestPlugin::Foo);
    use vars qw($AUTOLOAD);
    sub new {
        my $class   = shift;
        my $args    = shift || {}; # note - no plugin context
        return bless $args, $class;
    }
    sub leave {}      # hacks to allow tt to do the plugins passed via PLUGINS
    sub delocalise {} # hacks to allow tt to do the plugins passed via PLUGINS
}

my $obj = Foo2->new;


###----------------------------------------------------------------###
### variable GETting

process_ok("[% foo %]" => "");
process_ok("[% foo %]" => "7",       {foo => 7});
process_ok("[% foo %]" => "7",       {tt_config => [VARIABLES => {foo => 7}]});
process_ok("[% foo %]" => "7",       {tt_config => [PRE_DEFINE => {foo => 7}]});
process_ok("[% foo %][% foo %][% foo %]" => "777", {foo => 7});
process_ok("[% foo() %]" => "7",     {foo => 7});
process_ok("[% foo.bar %]" => "");
process_ok("[% foo.bar %]" => "",    {foo => {}});
process_ok("[% foo.bar %]" => "7",   {foo => {bar => 7}});
process_ok("[% foo().bar %]" => "7", {foo => {bar => 7}});
process_ok("[% foo.0 %]" => "7",     {foo => [7, 2, 3]});
process_ok("[% foo.10 %]" => "",     {foo => [7, 2, 3]});
process_ok("[% foo %]" => 7,         {foo => sub { 7 }});
process_ok("[% foo(7) %]" => 7,      {foo => sub { $_[0] }});
process_ok("[% foo.length %]" => 1,  {foo => sub { 7 }});
process_ok("[% foo.0 %]" => 7,       {foo => sub { return 7, 2, 3 }});
process_ok("[% foo(bar) %]" => 7,    {foo => sub { $_[0] }, bar => 7});
process_ok("[% foo.seven %]" => 7,   {foo => $obj});
process_ok("[% foo.seven() %]" => 7, {foo => $obj});
process_ok("[% foo.seven.length %]" => 1, {foo => $obj});
process_ok("[% foo.echo(7) %]" => 7, {foo => $obj});
process_ok("[% foo.many.0 %]" => 1,  {foo => $obj});
process_ok("[% foo.many.10 %]" => '',{foo => $obj});
process_ok("[% foo.nomethod %]" => '',{foo => $obj});
process_ok("[% foo.nomethod.0 %]" => '',{foo => $obj});

process_ok("[% GET foo %]" => "");
process_ok("[% GET foo %]" => "7",     {foo => 7});
process_ok("[% GET foo.bar %]" => "");
process_ok("[% GET foo.bar %]" => "",  {foo => {}});
process_ok("[% GET foo.bar %]" => "7", {foo => {bar => 7}});
process_ok("[% GET foo.0 %]" => "7",   {foo => [7, 2, 3]});
process_ok("[% GET foo %]" => 7,       {foo => sub { 7 }});
process_ok("[% GET foo(7) %]" => 7,    {foo => sub { $_[0] }});

process_ok("[% \$name %]" => "",        {name => 'foo'});
process_ok("[% \$name %]" => "7",       {name => 'foo', foo => 7});
process_ok("[% \$name.bar %]" => "",    {name => 'foo'});
process_ok("[% \$name.bar %]" => "",    {name => 'foo', foo => {}});
process_ok("[% \$name.bar %]" => "7",   {name => 'foo', foo => {bar => 7}});
process_ok("[% \$name().bar %]" => "7", {name => 'foo', foo => {bar => 7}});
process_ok("[% \$name.0 %]" => "7",     {name => 'foo', foo => [7, 2, 3]});
process_ok("[% \$name %]" => 7,         {name => 'foo', foo => sub { 7 }});
process_ok("[% \$name(7) %]" => 7,      {name => 'foo', foo => sub { $_[0] }});

process_ok("[% GET \$name %]" => "",      {name => 'foo'});
process_ok("[% GET \$name %]" => "7",     {name => 'foo', foo => 7});
process_ok("[% GET \$name.bar %]" => "",  {name => 'foo'});
process_ok("[% GET \$name.bar %]" => "",  {name => 'foo', foo => {}});
process_ok("[% GET \$name.bar %]" => "7", {name => 'foo', foo => {bar => 7}});
process_ok("[% GET \$name.0 %]" => "7",   {name => 'foo', foo => [7, 2, 3]});
process_ok("[% GET \$name %]" => 7,       {name => 'foo', foo => sub { 7 }});
process_ok("[% GET \$name(7) %]" => 7,    {name => 'foo', foo => sub { $_[0] }});

process_ok("[% \$name %]" => "",     {name => 'foo foo', foo => 7});
process_ok("[% GET \$name %]" => "", {name => 'foo foo', foo => 7});

process_ok("[% \${name} %]" => "",        {name => 'foo'});
process_ok("[% \${name} %]" => "7",       {name => 'foo', foo => 7});
process_ok("[% \${name}.bar %]" => "",    {name => 'foo'});
process_ok("[% \${name}.bar %]" => "",    {name => 'foo', foo => {}});
process_ok("[% \${name}.bar %]" => "7",   {name => 'foo', foo => {bar => 7}});
process_ok("[% \${name}().bar %]" => "7", {name => 'foo', foo => {bar => 7}});
process_ok("[% \${name}.0 %]" => "7",     {name => 'foo', foo => [7, 2, 3]});
process_ok("[% \${name} %]" => 7,         {name => 'foo', foo => sub { 7 }});
process_ok("[% \${name}(7) %]" => 7,      {name => 'foo', foo => sub { $_[0] }});

process_ok("[% GET \${name} %]" => "",      {name => 'foo'});
process_ok("[% GET \${name} %]" => "7",     {name => 'foo', foo => 7});
process_ok("[% GET \${name}.bar %]" => "",  {name => 'foo'});
process_ok("[% GET \${name}.bar %]" => "",  {name => 'foo', foo => {}});
process_ok("[% GET \${name}.bar %]" => "7", {name => 'foo', foo => {bar => 7}});
process_ok("[% GET \${name}.0 %]" => "7",   {name => 'foo', foo => [7, 2, 3]});
process_ok("[% GET \${name} %]" => 7,       {name => 'foo', foo => sub { 7 }});
process_ok("[% GET \${name}(7) %]" => 7,    {name => 'foo', foo => sub { $_[0] }});

process_ok("[% \${name} %]" => "",     {name => 'foo foo', foo => 7});
process_ok("[% GET \${name} %]" => "", {name => 'foo foo', foo => 7});
process_ok("[% GET \${'foo'} %]" => 'bar', {foo => 'bar'});

process_ok("[% foo.\$name %]" => '', {name => 'bar'});
process_ok("[% foo.\$name %]" => 7, {name => 'bar', foo => {bar => 7}});
process_ok("[% foo.\$name.baz %]" => '', {name => 'bar', bar => {baz => 7}});

process_ok("[% \"hi\" %]" => 'hi');
process_ok("[% 'hi' %]" => 'hi');
process_ok("[% \"\$foo\" %]"   => '7', {foo => 7});
process_ok("[% \"hi \$foo\" %]"   => 'hi 7', {foo => 7});
process_ok("[% \"hi \${foo}\" %]" => 'hi 7', {foo => 7});
process_ok("[% 'hi \$foo' %]"   => 'hi $foo', {foo => 7});
process_ok("[% 'hi \${foo}' %]" => 'hi ${foo}', {foo => 7});

process_ok("[% \"hi \${foo.seven}\" %]"   => 'hi 7', {foo => $obj});
process_ok("[% \"hi \${foo.echo(7)}\" %]" => 'hi 7', {foo => $obj});

process_ok("[% _foo %]2" => '2', {_foo => 1});
process_ok("[% \$bar %]2" => '2', {_foo => 1, bar => '_foo'});
process_ok("[% __foo %]2" => '2', {__foo => 1});
process_ok("[% _foo = 1 %][% _foo %]2" => '2');
process_ok("[% foo._bar %]2" => '2', {foo => {_bar =>1}});

###----------------------------------------------------------------###
### variable SETting

process_ok("[% SET foo bar %][% foo %]" => '');
process_ok("[% SET foo = 1 %][% foo %]" => '1');
process_ok("[% SET foo = 1  bar = 2 %][% foo %][% bar %]" => '12');
process_ok("[% SET foo  bar = 1 %][% foo %]" => '');
process_ok("[% SET foo = 1 ; bar = 1 %][% foo %]" => '1');
process_ok("[% SET foo = 1 %][% SET foo %][% foo %]" => '');

process_ok("[% SET foo = [] %][% foo.0 %]" => "");
process_ok("[% SET foo = [1, 2, 3] %][% foo.1 %]" => 2);
process_ok("[% SET foo = {} %][% foo.0 %]" => "");
process_ok("[% SET foo = {1 => 2} %][% foo.1 %]" => "2") if ! $is_tt;
process_ok("[% SET foo = {'1' => 2} %][% foo.1 %]" => "2");

process_ok("[% SET name = 1 %][% SET foo = name %][% foo %]" => "1");
process_ok("[% SET name = 1 %][% SET foo = \$name %][% foo %]" => "");
process_ok("[% SET name = 1 %][% SET foo = \${name} %][% foo %]" => "");
process_ok("[% SET name = 1 %][% SET foo = \"\$name\" %][% foo %]" => "1");
process_ok("[% SET name = 1 foo = name %][% foo %]" => '1');
process_ok("[% SET name = 1 %][% SET foo = {\$name => 2} %][% foo.1 %]" => "2");
process_ok("[% SET name = 1 %][% SET foo = {\"\$name\" => 2} %][% foo.1 %]" => "2") if ! $is_tt;
process_ok("[% SET name = 1 %][% SET foo = {\${name} => 2} %][% foo.1 %]" => "2");

process_ok("[% SET name = 7 %][% SET foo = {'2' => name} %][% foo.2 %]" => "7");
process_ok("[% SET name = 7 %][% SET foo = {'2' => \"\$name\"} %][% foo.2 %]" => "7");

process_ok("[% SET name = 7 %][% SET foo = [1, name, 3] %][% foo.1 %]" => "7");
process_ok("[% SET name = 7 %][% SET foo = [1, \"\$name\", 3] %][% foo.1 %]" => "7");

process_ok("[% SET foo = { bar => { baz => [0, 7, 2] } } %][% foo.bar.baz.1 %]" => "7");

process_ok("[% SET foo.bar = 1 %][% foo.bar %]" => '1');
process_ok("[% SET foo.bar.baz.bing = 1 %][% foo.bar.baz.bing %]" => '1');
process_ok("[% SET foo.bar.2 = 1 %][% foo.bar.2 %] [% foo.bar.size %]" => '1 1');
process_ok("[% SET foo.bar = [] %][% SET foo.bar.2 = 1 %][% foo.bar.2 %] [% foo.bar.size %]" => '1 3');

process_ok("[% SET name = 'two' %][% SET \$name = 3 %][% two %]" => 3);
process_ok("[% SET name = 'two' %][% SET \${name} = 3 %][% two %]" => 3);
process_ok("[% SET name = 2 %][% SET foo.\$name = 3 %][% foo.2 %]" => 3);
process_ok("[% SET name = 2 %][% SET foo.\$name = 3 %][% foo.\$name %]" => 3);
process_ok("[% SET name = 2 %][% SET foo.\${name} = 3 %][% foo.2 %]" => 3);
process_ok("[% SET name = 2 %][% SET foo.\${name} = 3 %][% foo.2 %]" => 3);
process_ok("[% SET name = 'two' %][% SET \$name.foo = 3 %][% two.foo %]" => 3);
process_ok("[% SET name = 'two' %][% SET \${name}.foo = 3 %][% two.foo %]" => 3);
process_ok("[% SET name = 'two' %][% SET foo.\$name.foo = 3 %][% foo.two.foo %]" => 3);
process_ok("[% SET name = 'two' %][% SET foo.\${name}.foo = 3 %][% foo.two.foo %]" => 3);

process_ok("[% SET foo = [1..10] %][% foo.6 %]" => 7);
process_ok("[% SET foo = [10..1] %][% foo.6 %]" => '');
process_ok("[% SET foo = 1 .. 10 %][% foo.6 %]" => 7) if ! $is_tt;
process_ok("[% SET foo = [-10..-1] %][% foo.6 %]" => -4);
process_ok("[% SET foo = [1..3..10] %][% foo.6 %]" => '7') if ! $is_tt;
process_ok("[% SET foo = [1..2..10] %][% foo.6 %]" => '7') if ! $is_tt;
process_ok("[% SET foo = [1,1..0..10] %][% foo.6 %]" => '6') if ! $is_tt;
process_ok("[% SET foo = [1..10, 21..30] %][% foo.12 %]" => 23)         if ! $is_tt;
process_ok("[% SET foo = [..100] bar = 7 %][% bar %][% foo.0 %]" => '');
process_ok("[% SET foo = [100..] bar = 7 %][% bar %][% foo.0 %]" => 7)  if ! $is_tt;
process_ok("[% SET foo = ['a'..'z'] %][% foo.6 %]" => 'g');
process_ok("[% SET foo = ['z'..'a'] %][% foo.6 %]" => '');
process_ok("[% SET foo = ['a'..'z'].reverse %][% foo.6 %]" => 't')      if ! $is_tt;

process_ok("[% foo = 1 %][% foo %]" => '1');
process_ok("[% foo = 1 bar = 2 %][% foo %][% bar %]" => '12');
process_ok("[% foo = 1 ; bar = 2 %][% foo %][% bar %]" => '12');
process_ok("[% foo.bar = 2 %][% foo.bar %]" => '2');

process_ok('[% a = "a" %][% (b = a) %][% a %][% b %]' => 'aaa');
process_ok('[% a = "a" %][% (c = (b = a)) %][% a %][% b %][% c %]' => 'aaaa');

###----------------------------------------------------------------###
### Reserved words

my $vars = {
    GET => 'named_get',
    get => 'lower_named_get',
    named_get => 'value of named_get',
    hold_get => 'GET',
};
process_ok("[% GET %]" => '', $vars);
process_ok("[% GET GET %]" => 'named_get', $vars) if ! $is_tt;
process_ok("[% GET get %]" => 'lower_named_get', $vars);
process_ok("[% GET \${'GET'} %]" => 'bar', {GET => 'bar'});

process_ok("[% GET = 1 %][% GET GET %]" => '', $vars);
process_ok("[% SET GET = 1 %][% GET GET %]" => '1', $vars) if ! $is_tt;

process_ok("[% GET \$hold_get %]" => 'named_get', $vars);
process_ok("[% GET \$GET %]" => 'value of named_get', $vars) if ! $is_tt;
process_ok("[% BLOCK GET %]hi[% END %][% PROCESS GET %]" => 'hi') if ! $is_tt;
process_ok("[% BLOCK foo %]hi[% END %][% PROCESS foo a = GET %]" => 'hi', $vars) if ! $is_tt;
process_ok("[% BLOCK foo %]hi[% END %][% PROCESS foo GET = 1 %]" => '');
process_ok("[% BLOCK foo %]hi[% END %][% PROCESS foo IF GET %]" => 'hi', $vars) if ! $is_tt;

###----------------------------------------------------------------###
### CALL and DEFAULT

process_ok("[% DEFAULT foo = 7 %][% foo %]" => 7);
process_ok("[% SET foo = 5 %][% DEFAULT foo = 7 %][% foo %]" => 5);
process_ok("[% DEFAULT foo.bar.baz.bing = 6 %][% foo.bar.baz.bing %]" => 6);

my $t = 0;
process_ok("[% foo %]"      => 'hi', {foo => sub {$t++; 'hi'}});
process_ok("[% GET  foo %]" => 'hi', {foo => sub {$t++; 'hi'}});
process_ok("[% CALL foo %]" => '',   {foo => sub {$t++; 'hi'}});
ok($t == 3, "CALL method actually called var");

###----------------------------------------------------------------###
### virtual methods / filters

process_ok("[% [0 .. 10].reverse.1 %]" => 9) if ! $is_tt;
process_ok("[% {a => 'A'}.a %]" => 'A') if ! $is_tt;
process_ok("[% 'This is a string'.length %]" => 16) if ! $is_tt;
process_ok("[% 123.length %]" => 3) if ! $is_tt;
process_ok("[% 123.2.length %]" => 5) if ! $is_tt;
process_ok("[% -123.2.length %]" => -5) if ! $is_tt; # the - doesn't bind as tight as the dot methods
process_ok("[% (-123.2).length %]" => 6) if ! $is_tt;

process_ok("[% n.repeat %]" => '1',     {n => 1}) if ! $is_tt; # tt2 virtual method defaults to 0
process_ok("[% n.repeat(0) %]" => '',   {n => 1});
process_ok("[% n.repeat(1) %]" => '1',  {n => 1});
process_ok("[% n.repeat(2) %]" => '11', {n => 1});
process_ok("[% n.repeat(2,'|') %]" => '1|1', {n => 1}) if ! $is_tt;

process_ok("[% n.size %]", => 'SIZE', {n => {size => 'SIZE', a => 'A'}});
process_ok("[% n|size %]", => '2',    {n => {size => 'SIZE', a => 'A'}}) if ! $is_tt; # tt2 | is alias for FILTER

process_ok('[% foo | eval %]' => 'baz', {foo => '[% bar %]', bar => 'baz'});
process_ok('[% "1" | indent(2) %]' => '  1');

process_ok("[% n.replace('foo', 'bar') %]" => 'barbar', {n => 'foofoo'});
process_ok("[% n.replace('(foo)', 'bar\$1') %]" => 'barfoobarfoo', {n => 'foofoo'}) if ! $is_tt;
process_ok("[% n.replace('foo', 'bar', 0) %]" => 'barfoo', {n => 'foofoo'}) if ! $is_tt;

process_ok("[% n FILTER size %]", => '1', {n => {size => 'SIZE', a => 'A'}}) if ! $is_tt; # tt2 doesn't have size

process_ok("[% n FILTER repeat %]" => '1',     {n => 1});
process_ok("[% n FILTER repeat(0) %]" => '',   {n => 1});
process_ok("[% n FILTER repeat(1) %]" => '1',  {n => 1});
process_ok("[% n FILTER repeat(2) %]" => '11', {n => 1});
process_ok("[% n FILTER repeat(2,'|') %]" => '1|1', {n => 1}) if ! $is_tt;

process_ok("[% n FILTER echo = repeat(2) %][% n FILTER echo %]" => '1111', {n => 1});
process_ok("[% n FILTER echo = repeat(2) %][% n | echo %]" => '1111', {n => 1});
process_ok("[% n FILTER echo = repeat(2) %][% n|echo.length %]" => '112', {n => 1}) if ! $is_tt;
process_ok("[% n FILTER echo = repeat(2) %][% n FILTER \$foo %]" => '1111', {n => 1, foo => 'echo'});
process_ok("[% n FILTER echo = repeat(2) %][% n | \$foo %]" => '1111', {n => 1, foo => 'echo'});
process_ok("[% n FILTER echo = repeat(2) %][% n|\$foo.length %]" => '112', {n => 1, foo => 'echo'}) if ! $is_tt;

process_ok('[% "hi" FILTER $foo %]' => 'hihi', {foo => sub {sub {$_[0]x2}}}); # filter via a passed var
process_ok('[% FILTER $foo %]hi[% END %]' => 'hihi', {foo => sub {sub {$_[0]x2}}}); # filter via a passed var
process_ok('[% "hi" FILTER foo %]' => 'hihi', {tt_config => [FILTERS => {foo => sub {$_[0]x2}}]});
process_ok('[% "hi" FILTER foo %]' => 'hihi', {tt_config => [FILTERS => {foo => [sub {$_[0]x2},0]}]});
process_ok('[% "hi" FILTER foo(2) %]' => 'hihi', {tt_config => [FILTERS => {foo => [sub {my$a=$_[1];sub{$_[0]x$a}},1]}]});

### this does work - but requires that Template::Filters is installed
#process_ok("[% ' ' | uri %]" => '%20');

###----------------------------------------------------------------###
### chomping

process_ok(" [% foo %]" => ' ');
process_ok(" [%- foo %]" => '');
process_ok("\n[%- foo %]" => '');
process_ok("\n [%- foo %]" => '');
process_ok("\n\n[%- foo %]" => "\n");
process_ok(" \n\n[%- foo %]" => " \n");
process_ok(" \n[%- foo %]" => " ") if ! $is_tt;
process_ok(" \n \n[%- foo %]" => " \n ") if ! $is_tt;

process_ok("[% foo %] " => ' ');
process_ok("[% foo -%] " => ' ');
process_ok("[% foo -%]\n" => '');
process_ok("[% foo -%] \n" => '');
process_ok("[% foo -%]\n " => ' ');
process_ok("[% foo -%]\n\n\n" => "\n\n");
process_ok("[% foo -%] \n " => ' ');

###----------------------------------------------------------------###
### math operations

process_ok("[% 1 + 2 %]" => 3);
process_ok("[% 1 + 2 + 3 %]" => 6);
process_ok("[% (1 + 2) %]" => 3);
process_ok("[% 2 - 1 %]" => 1);
process_ok("[% -1 + 2 %]" => 1);
process_ok("[% -1+2 %]" => 1);
process_ok("[% 2 - 1 %]" => 1);
process_ok("[% 2-1 %]" => 1) if ! $is_tt;
process_ok("[% 2 - -1 %]" => 3);
process_ok("[% 4 * 2 %]" => 8);
process_ok("[% 4 / 2 %]" => 2);
process_ok("[% 10 / 3 %]" => qr/^3.333/);
process_ok("[% 10 div 3 %]" => '3');
process_ok("[% 2 ** 3 %]" => 8) if ! $is_tt;
process_ok("[% 1 + 2 * 3 %]" => 7);
process_ok("[% 3 * 2 + 1 %]" => 7);
process_ok("[% (1 + 2) * 3 %]" => 9);
process_ok("[% 3 * (1 + 2) %]" => 9);
process_ok("[% 1 + 2 ** 3 %]" => 9) if ! $is_tt;
process_ok("[% 2 * 2 ** 3 %]" => 16) if ! $is_tt;
process_ok("[% SET foo = 1 %][% foo + 2 %]" => 3);
process_ok("[% SET foo = 1 %][% (foo + 2) %]" => 3);

###----------------------------------------------------------------###
### boolean operations

process_ok("[% 5 && 6 %]" => 6);
process_ok("[% 5 || 6 %]" => 5);
process_ok("[% 0 || 6 %]" => 6);
process_ok("[% 0 && 6 %]" => 0);
process_ok("[% 0 && 0 %]" => 0);

process_ok("[% 5 + (0 || 5) %]" => 10);


process_ok("[% 1 ? 2 : 3 %]" => '2');
process_ok("[% 0 ? 2 : 3 %]" => '3');
process_ok("[% 0 ? (1 ? 2 : 3) : 4 %]" => '4');
process_ok("[% 0 ? 1 ? 2 : 3 : 4 %]" => '4');

process_ok("[% t = 1 || 0 ? 3 : 4 %][% t %]" => 3);
process_ok("[% t = 0 or 1 ? 3 : 4 %][% t %]" => 3);
process_ok("[% t = 1 or 0 ? 3 : 4 %][% t %]" => 1) if ! $is_tt;

process_ok("[% 0 ? 2 : 3 %]" => '3');
process_ok("[% 1 ? 2 : 3 %]" => '2');
process_ok("[% 0 ? 1 ? 2 : 3 : 4 %]" => '4');
process_ok("[% t = 0 ? 1 ? [1..4] : [2..4] : [3..4] %][% t.0 %]" => '3');
process_ok("[% t = 1 || 0 ? 0 : 1 || 2 ? 2 : 3 %][% t %]" => '0');
process_ok("[% t = 0 or 0 ? 0 : 1 or 2 ? 2 : 3 %][% t %]" => '1') if ! $is_tt;
process_ok("[% t = 0 or 0 ? 0 : 0 or 2 ? 2 : 3 %][% t %]" => '2');

process_ok("[% 0 ? 1 ? 1 + 2 * 3 : 1 + 2 * 4 : 1 + 2 * 5 %]" => '11');

###----------------------------------------------------------------###
### blocks

process_ok("[% PROCESS foo %]" => '');
process_ok("[% BLOCK foo %]" => '');
process_ok("[% BLOCK foo %][% END %]" => '');
process_ok("[% BLOCK %][% END %]one" => 'one');
process_ok("[% BLOCK foo %]hi there[% END %]" => '');
process_ok("[% BLOCK foo %][% BLOCK foo %][% END %][% END %]" => '');
process_ok("[% BLOCK foo %]hi there[% END %][% PROCESS foo %]" => 'hi there');
process_ok("[% PROCESS foo %][% BLOCK foo %]hi there[% END %]" => 'hi there');
process_ok("[% BLOCK foo %]hi [% one %] there[% END %][% PROCESS foo %]" => 'hi ONE there', {one => 'ONE'});
process_ok("[% BLOCK foo %]hi [% IF 1 %]Yes[% END %] there[% END %]<<[% PROCESS foo %]>>" => '<<hi Yes there>>');
process_ok("[% BLOCK foo %]hi [% one %] there[% END %][% PROCESS foo one = 'two' %]" => 'hi two there');
process_ok("[% BLOCK foo %]hi [% one.two %] there[% END %][% PROCESS foo one.two = 'two' %]" => 'hi two there');
process_ok("[% BLOCK foo %]hi [% one.two %] there[% END %][% PROCESS foo + foo one.two = 'two' %]" => 'hi two there'x2);

process_ok("[% BLOCK foo %]hi [% one %] there[% END %][% PROCESS foo one = 'two' %][% one %]" => 'hi two theretwo');
process_ok("[% BLOCK foo %]hi [% one %] there[% END %][% INCLUDE foo one = 'two' %][% one %]" => 'hi two there');

###----------------------------------------------------------------###
### if/unless/elsif/else

process_ok("[% IF 1 %]Yes[% END %]" => 'Yes');
process_ok("[% IF 0 %]Yes[% END %]" => '');
process_ok("[% IF 0 %]Yes[% ELSE %]No[% END %]" => 'No');
process_ok("[% IF 0 %]Yes[% ELSIF 1 %]No[% END %]" => 'No');
process_ok("[% IF 0 %]Yes[% ELSIF 0 %]No[% END %]" => '');
process_ok("[% IF 0 %]Yes[% ELSIF 0 %]No[% ELSE %]hmm[% END %]" => 'hmm');

process_ok("[% UNLESS 1 %]Yes[% END %]" => '');
process_ok("[% UNLESS 0 %]Yes[% END %]" => 'Yes');
process_ok("[% UNLESS 0 %]Yes[% ELSE %]No[% END %]" => 'Yes');
process_ok("[% UNLESS 1 %]Yes[% ELSIF 1 %]No[% END %]" => 'No');
process_ok("[% UNLESS 1 %]Yes[% ELSIF 0 %]No[% END %]" => '');
process_ok("[% UNLESS 1 %]Yes[% ELSIF 0 %]No[% ELSE %]hmm[% END %]" => 'hmm');

###----------------------------------------------------------------###
### comments

process_ok("[%# one %]" => '', {one => 'ONE'});
process_ok("[%#\n one %]" => '', {one => 'ONE'});
process_ok("[%-#\n one %]" => '', {one => 'ONE'})     if ! $is_tt;
process_ok("[% #\n one %]" => 'ONE', {one => 'ONE'});
process_ok("[%# BLOCK one %]" => '');
process_ok("[%# BLOCK one %]two" => 'two');
process_ok("[%# BLOCK one %]two[% END %]" => '');
process_ok("[%# BLOCK one %]two[% END %]three" => '');

###----------------------------------------------------------------###
### foreach, next, last

process_ok("[% FOREACH foo %]" => '');
process_ok("[% FOREACH foo %][% END %]" => '');
process_ok("[% FOREACH foo %]bar[% END %]" => '');
process_ok("[% FOREACH foo %]bar[% END %]" => 'bar', {foo => 1});
process_ok("[% FOREACH f IN foo %]bar[% f %][% END %]" => 'bar1bar2', {foo => [1,2]});
process_ok("[% FOREACH f = foo %]bar[% f %][% END %]" => 'bar1bar2', {foo => [1,2]});
process_ok("[% FOREACH f = [1,2] %]bar[% f %][% END %]" => 'bar1bar2');
process_ok("[% FOREACH f = [1..3] %]bar[% f %][% END %]" => 'bar1bar2bar3');
process_ok("[% FOREACH f = [{a=>'A'},{a=>'B'}] %]bar[% f.a %][% END %]" => 'barAbarB');
process_ok("[% FOREACH [{a=>'A'},{a=>'B'}] %]bar[% a %][% END %]" => 'barAbarB');
process_ok("[% FOREACH [{a=>'A'},{a=>'B'}] %]bar[% a %][% END %][% a %]" => 'barAbarB');
process_ok("[% FOREACH f = [1..3] %][% loop.count %]/[% loop.size %] [% END %]" => '1/3 2/3 3/3 ');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first %][% f %][% END %][% END %]" => '1');
process_ok("[% FOREACH f = [1..3] %][% IF loop.last %][% f %][% END %][% END %]" => '3');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first %][% NEXT %][% END %][% f %][% END %]" => '23');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first %][% LAST %][% END %][% f %][% END %]" => '');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF loop.first %][% NEXT %][% END %][% END %]" => '123');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF loop.first %][% LAST %][% END %][% END %]" => '1');

### TT is not consistent in what is localized - well it is documented
### if you set a variable in the FOREACH tag, then nothing in the loop gets localized
### if you don't set a variable - everything gets localized
process_ok("[% foo = 1 %][% FOREACH [1..10] %][% foo %][% foo = 2 %][% END %]" => '1222222222');
process_ok("[% f = 1 %][% FOREACH i = [1..10] %][% i %][% f = 2 %][% END %][% f %]" => '123456789102');
process_ok("[% f = 1 %][% FOREACH [1..10] %][% f = 2 %][% END %][% f %]" => '1');
process_ok("[% f = 1 %][% FOREACH f = [1..10] %][% f %][% END %][% f %]" => '1234567891010');
process_ok("[% FOREACH [1] %][% SET a = 1 %][% END %][% a %]" => '');
process_ok("[% a %][% FOREACH [1] %][% SET a = 1 %][% END %][% a %]" => '');
process_ok("[% a = 2 %][% FOREACH [1] %][% SET a = 1 %][% END %][% a %]" => '2');
process_ok("[% a = 2 %][% FOREACH [1] %][% a = 1 %][% END %][% a %]" => '2');
process_ok("[% a = 2 %][% FOREACH i = [1] %][% a = 1 %][% END %][% a %]" => '1');
process_ok("[% FOREACH i = [1] %][% SET a = 1 %][% END %][% a %]" => '1');
process_ok("[% f.b = 1 %][% FOREACH f.b = [1..10] %][% f.b %][% END %][% f.b %]" => '1234567891010') if ! $is_tt;
process_ok("[% a = 1 %][% FOREACH [{a=>'A'},{a=>'B'}] %]bar[% a %][% END %][% a %]" => 'barAbarB1');
process_ok("[% FOREACH [1..3] %][% loop.size %][% END %][% loop.size %]" => '333');
process_ok("[% FOREACH i = [1..3] %][% loop.size %][% END %][% loop.size %]" => '333') if ! $is_tt;
process_ok("[% FOREACH i = [1..3] %][% loop.size %][% END %][% loop.size %]" => '3331') if $is_tt;

###----------------------------------------------------------------###
### while

process_ok("[% WHILE foo %]" => '');
process_ok("[% WHILE foo %][% END %]" => '');
process_ok("[% WHILE (foo = foo - 1) %][% END %]" => '');
process_ok("[% WHILE (foo = foo - 1) %][% foo %][% END %]" => '21', {foo => 3});
process_ok("[% WHILE foo %][% foo %][% foo = foo - 1 %][% END %]" => '321', {foo => 3});

process_ok("[% WHILE 1 %][% foo %][% foo = foo - 1 %][% LAST IF foo == 1 %][% END %]" => '32', {foo => 3});
process_ok("[% f = 10; WHILE f; f = f - 1 ; f ; END %]" => '9876543210');
process_ok("[% f = 10; WHILE f; f = f - 1 ; f ; END ; f %]" => '98765432100');
process_ok("[% f = 10 a = 2; WHILE f; f = f - 1 ; f ; a=3; END ; a%]" => '98765432103');

process_ok("[% f = 10; WHILE (g=f); f = f - 1 ; f ; END %]" => '9876543210');
process_ok("[% f = 10; WHILE (g=f); f = f - 1 ; f ; END ; f %]" => '98765432100');
process_ok("[% f = 10 a = 2; WHILE (g=f); f = f - 1 ; f ; a=3; END ; a%]" => '98765432103');
process_ok("[% f = 10 a = 2; WHILE (a=f); f = f - 1 ; f ; a=3; END ; a%]" => '98765432100');

###----------------------------------------------------------------###
### stop, return, clear

process_ok("[% STOP %]" => '');
process_ok("One[% STOP %]Two" => 'One');
process_ok("[% BLOCK foo %]One[% STOP %]Two[% END %]First[% PROCESS foo %]Last" => 'FirstOne');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF loop.first %][% STOP %][% END %][% END %]" => '1');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first %][% STOP %][% END %][% f %][% END %]" => '');

process_ok("[% RETURN %]" => '');
process_ok("One[% RETURN %]Two" => 'One');
process_ok("[% BLOCK foo %]One[% RETURN %]Two[% END %]First[% PROCESS foo %]Last" => 'FirstOneLast');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF loop.first %][% RETURN %][% END %][% END %]" => '1');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first %][% RETURN %][% END %][% f %][% END %]" => '');

process_ok("[% CLEAR %]" => '');
process_ok("One[% CLEAR %]Two" => 'Two');
process_ok("[% BLOCK foo %]One[% CLEAR %]Two[% END %]First[% PROCESS foo %]Last" => 'FirstTwoLast');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF loop.first %][% CLEAR %][% END %][% END %]" => '23');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first %][% CLEAR %][% END %][% f %][% END %]" => '123');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF loop.last %][% CLEAR %][% END %][% END %]" => '');
process_ok("[% FOREACH f = [1..3] %][% IF loop.last %][% CLEAR %][% END %][% f %][% END %]" => '3');

###----------------------------------------------------------------###
### multiple-directives

process_ok("[% GET foo; GET foo %]" => '11', {foo => 1});
process_ok('[% FOREACH f = [1..3]; 1; END %]' => '111');
process_ok('[% FOREACH f = [1..3]; f; END %]' => '123');
process_ok('[% FOREACH f = [1..3]; "$f"; END %]' => '123');
process_ok('[% FOREACH f = [1..3]; f + 1; END %]' => '234');

###----------------------------------------------------------------###
### post opererator

process_ok("[% GET foo IF 1 %]" => '1', {foo => 1});
process_ok("[% f FOREACH f = [1..3] %]" => '123');

process_ok("2[% GET foo IF 1 IF 2 %]" => '21', {foo => 1})      if ! $is_tt;
process_ok("2[% GET foo IF 1 IF 0 %]" => '2',  {foo => 1})      if ! $is_tt;
process_ok("[% f FOREACH f = [1..3] IF 1 %]" => '123')          if ! $is_tt;
process_ok("[% f FOREACH f = [1..3] IF 0 %]" => '')             if ! $is_tt;
process_ok("[% f FOREACH f = g FOREACH g = [1..3] %]" => '123') if ! $is_tt;
process_ok("[% f FOREACH f = g.a FOREACH g = [{a=>1}, {a=>2}, {a=>3}] %]" => '123') if ! $is_tt;
process_ok("[% f FOREACH f = a FOREACH [{a=>1}, {a=>2}, {a=>3}] %]" => '123')       if ! $is_tt;

process_ok("[% FOREACH f = [1..3] IF 1 %]([% f %])[% END %]" => '(1)(2)(3)')        if ! $is_tt;
process_ok("[% FOREACH f = [1..3] IF 0 %]([% f %])[% END %]" => '')                 if ! $is_tt;

process_ok("[% BLOCK bar %][% foo %][% foo = foo - 1 %][% END %][% PROCESS bar WHILE foo %]" => '321', {foo => 3});

###----------------------------------------------------------------###
### capturing

process_ok("[% foo = BLOCK %]Hi[% END %][% foo %][% foo %]" => 'HiHi');
process_ok("[% BLOCK foo %]Hi[% END %][% bar = PROCESS foo %]-[% bar %]" => '-Hi');
process_ok("[% foo = IF 1 %]Hi[% END %][% foo %]" => 'Hi');

###----------------------------------------------------------------###
### tags

process_ok("[% TAGS html %]<!-- 1 + 2 -->" => '3');
process_ok("[% TAGS <!-- --> %]<!-- 1 + 2 -->" => '3');
process_ok("[% TAGS html %] <!--- 1 + 2 -->" => '3');
process_ok("[% TAGS html %]<!-- 1 + 2 --->" => '3') if ! $is_tt;
process_ok("[% TAGS html %]<!-- 1 + 2 --->\n" => '3');
process_ok("[% BLOCK foo %][% TAGS html %]<!-- 1 + 2 -->[% END %][% PROCESS foo %] [% 1 + 2 %]" => '');

###----------------------------------------------------------------###
### switch

process_ok("[% SWITCH 1 %][% END %]hi" => 'hi');
process_ok("[% SWITCH 1 %][% CASE %]bar[% END %]hi" => 'barhi');
process_ok("[% SWITCH 1 %]Pre[% CASE %]bar[% END %]hi" => 'barhi');
process_ok("[% SWITCH 1 %][% CASE DEFAULT %]bar[% END %]hi" => 'barhi');
process_ok("[% SWITCH 1 %][% CASE 0 %]bar[% END %]hi" => 'hi');
process_ok("[% SWITCH 1 %][% CASE 1 %]bar[% END %]hi" => 'barhi');
process_ok("[% SWITCH 1 %][% CASE foo %][% CASE 1 %]bar[% END %]hi" => 'barhi');
process_ok("[% SWITCH 1 %][% CASE [1..10] %]bar[% END %]hi" => 'barhi');
process_ok("[% SWITCH 11 %][% CASE [1..10] %]bar[% END %]hi" => 'hi');

process_ok("[% SWITCH 1.0 %][% CASE [1..10] %]bar[% END %]hi" => 'barhi');
process_ok("[% SWITCH '1.0' %][% CASE [1..10] %]bar[% END %]hi" => 'barhi') if ! $is_tt;

###----------------------------------------------------------------###
### try/throw/catch/final

process_ok("[% TRY %][% END %]hi" => 'hi');
process_ok("[% TRY %]Foo[% END %]hi" => 'Foohi');
process_ok("[% TRY %]Foo[% THROW foo 'for fun' %]bar[% END %]hi" => '');
process_ok("[% TRY %]Foo[% THROW foo 'for fun' %]bar[% CATCH %][% END %]hi" => 'Foohi') if ! $is_tt;
process_ok("[% TRY %]Foo[% THROW foo 'for fun' %]bar[% CATCH %]there[% END %]hi" => 'Footherehi');
process_ok("[% TRY %]Foo[% THROW foo 'for fun' %]bar[% CATCH foo %]there[% END %]hi" => 'Footherehi');
process_ok("[% TRY %]Foo[% TRY %]Foo[% THROW foo 'for fun' %][% CATCH bar %]one[% END %][% CATCH %]two[% END %]hi" => 'FooFootwohi');
process_ok("[% TRY %]Foo[% TRY %]Foo[% THROW foo 'for fun' %][% CATCH bar %]one[% END %][% CATCH s %]two[% END %]hi" => '');
process_ok("[% TRY %]Foo[% THROW foo.bar 'for fun' %][% CATCH foo %]one[% CATCH foo.bar %]two[% END %]hi" => 'Footwohi');

process_ok("[% TRY %]Foo[% FINAL %]Bar[% END %]hi" => 'FooBarhi');
process_ok("[% TRY %]Foo[% THROW foo %][% FINAL %]Bar[% CATCH %]one[% END %]hi" => '');
process_ok("[% TRY %]Foo[% THROW foo %][% CATCH %]one[% FINAL %]Bar[% END %]hi" => 'FoooneBarhi');
process_ok("[% TRY %]Foo[% THROW foo %][% CATCH bar %]one[% FINAL %]Bar[% END %]hi" => '');

process_ok("[% TRY %][% THROW foo 'bar' %][% CATCH %][% error %][% END %]" => 'foo error - bar');
process_ok("[% TRY %][% THROW foo 'bar' %][% CATCH %][% error.type %][% END %]" => 'foo');
process_ok("[% TRY %][% THROW foo 'bar' %][% CATCH %][% error.info %][% END %]" => 'bar');
process_ok("[% TRY %][% THROW foo %][% CATCH %][% error.type %][% END %]" => 'undef');
process_ok("[% TRY %][% THROW foo %][% CATCH %][% error.info %][% END %]" => 'foo');

###----------------------------------------------------------------###
### named args

process_ok("[% foo(bar = 'one', baz = 'two') %]" => "baronebaztwo",
               {foo=>sub{my $n=$_[-1];join('',map{"$_$n->{$_}"} sort keys %$n)}});
process_ok("[%bar='ONE'%][% foo(\$bar = 'one') %]" => "ONEone",
               {foo=>sub{my $n=$_[-1];join('',map{"$_$n->{$_}"} sort keys %$n)}});

###----------------------------------------------------------------###
### use

my @config_p = (PLUGIN_BASE => 'MyTestPlugin', LOAD_PERL => 1);
process_ok("[% USE son_of_gun_that_does_not_exist %]one" => '', {tt_config => \@config_p});
process_ok("[% USE Foo %]one" => 'one', {tt_config => \@config_p});
process_ok("[% USE Foo2 %]one" => 'one', {tt_config => \@config_p});
process_ok("[% USE Foo(bar = 'baz') %]one[% Foo.bar %]" => 'onebarbaz', {tt_config => \@config_p});
process_ok("[% USE Foo2(bar = 'baz') %]one[% Foo2.bar %]" => 'onebarbaz', {tt_config => \@config_p});
process_ok("[% USE Foo(bar = 'baz') %]one[% Foo.bar %]" => 'onebarbaz', {tt_config => \@config_p});
process_ok("[% USE d = Foo(bar = 'baz') %]one[% d.bar %]" => 'onebarbaz', {tt_config => \@config_p});
process_ok("[% USE d.d = Foo(bar = 'baz') %]one[% d.d.bar %]" => '', {tt_config => \@config_p});

process_ok("[% USE a(bar = 'baz') %]one[% a.seven %]" => '',     {tt_config => [@config_p, PLUGINS => {a=>'Foo'}, ]});
process_ok("[% USE a(bar = 'baz') %]one[% a.seven %]" => 'one7', {tt_config => [@config_p, PLUGINS => {a=>'Foo2'},]});

###----------------------------------------------------------------###
### macro

process_ok("[% MACRO foo PROCESS bar %][% BLOCK bar %]Hi[% END %][% foo %]" => 'Hi');
process_ok("[% MACRO foo BLOCK %]Hi[% END %][% foo %]" => 'Hi');
process_ok("[% MACRO foo BLOCK %]Hi[% END %][% foo %]" => 'Hi');
process_ok("[% MACRO foo(n) BLOCK %]Hi[% n %][% END %][% foo(2) %]" => 'Hi2');
process_ok("[%n=1%][% MACRO foo(n) BLOCK %]Hi[% n %][% END %][% foo(2) %][%n%]" => 'Hi21');
process_ok("[%n=1%][% MACRO foo BLOCK %]Hi[% n = 2%][% END %][% foo %][%n%]" => 'Hi1');
process_ok("[% MACRO foo(n) FOREACH i=[1..n] %][% i %][% END %][% foo(3) %]" => '123');

###----------------------------------------------------------------###
### debug;

process_ok("\n\n[% one %]" => "\n\n\n## input text line 3 : [% one %] ##\nONE", {one=>'ONE', tt_config => ['DEBUG' => 8]});
process_ok("[% one %]" => "\n## input text line 1 : [% one %] ##\nONE", {one=>'ONE', tt_config => ['DEBUG' => 8]});
process_ok("[% one %]\n\n" => "(1)ONE\n\n", {one=>'ONE', tt_config => ['DEBUG' => 8, 'DEBUG_FORMAT' => '($line)']});
process_ok("1\n2\n3[% one %]" => "1\n2\n3(3)ONE", {one=>'ONE', tt_config => ['DEBUG' => 8, 'DEBUG_FORMAT' => '($line)']});
process_ok("[% one;\n one %]" => "(1)ONE(2)ONE", {one=>'ONE', tt_config => ['DEBUG' => 8,
                                                                            'DEBUG_FORMAT' => '($line)']}) if ! $is_tt;
process_ok("[% DEBUG format '(\$line)' %][% one %]" => qr/\(1\)/, {one=>'ONE', tt_config => ['DEBUG' => 8]});

process_ok("[% TRY %][% abc %][% CATCH %][% error %][% END %]" => "undef error - abc is undefined\n", {tt_config => ['DEBUG' => 2]});
process_ok("[% TRY %][% abc.def %][% CATCH %][% error %][% END %]" => "undef error - def is undefined\n", {abc => {}, tt_config => ['DEBUG' => 2]});

###----------------------------------------------------------------###
### constants

my @config_c = (
    CONSTANTS => {
        harry => sub {'do_this_once'},
        foo  => {
            bar => {baz => 42},
            bim => 57,
        },
        bing => 'baz',
        bang => 'bim',
    },
    VARIABLES => {
        bam  => 'bar',
    },
);
process_ok("[% constants.harry %]" => 'do_this_once', {tt_config => \@config_c});
process_ok("[% constants.harry.length %]" => '12', {tt_config => \@config_c});
process_ok("[% SET constants.something = 1 %][% constants.something %]one" => '1one', {tt_config => \@config_c});
process_ok("[% SET constants.harry = 1 %][% constants.harry %]one" => 'do_this_onceone', {tt_config => \@config_c});
process_ok("[% constants.foo.\${constants.bang} %]" => '57', {tt_config => [@config_c]});
process_ok("[% constants.foo.\$bam.\${constants.bing} %]" => '42', {tt_config => [@config_c]}) if ! $is_tt;
process_ok("[% bam = 'somethingelse' %][% constants.foo.\$bam.\${constants.bing} %]" => '42', {tt_config => [@config_c]}) if ! $is_tt;

###----------------------------------------------------------------###
### interpolate / anycase / trim

process_ok("Foo \$one Bar" => 'Foo ONE Bar', {one => 'ONE', tt_config => ['INTERPOLATE' => 1]});
process_ok("[% PERL %] my \$n=7; print \$n [% END %]" => '7', {tt_config => ['INTERPOLATE' => 1, 'EVAL_PERL' => 1]});
process_ok("[% TRY ; PERL %] my \$n=7; print \$n [% END ; END %]" => '7', {tt_config => ['INTERPOLATE' => 1, 'EVAL_PERL' => 1]});

process_ok("[% GET %]" => '', {GET => 'ONE'});
process_ok("[% GET GET %]" => 'ONE', {GET => 'ONE'}) if ! $is_tt;

process_ok("[% BLOCK foo %]\nhi\n[% END %][% PROCESS foo %]" => "\nhi\n");
process_ok("[% BLOCK foo %]\nhi[% END %][% PROCESS foo %]" => "hi", {tt_config => [TRIM => 1]});
process_ok("[% BLOCK foo %]hi\n[% END %][% PROCESS foo %]" => "hi", {tt_config => [TRIM => 1]});
process_ok("[% BLOCK foo %]hi[% nl %][% END %][% PROCESS foo %]" => "hi", {nl => "\n", tt_config => [TRIM => 1]});
process_ok("[% BLOCK foo %][% nl %]hi[% END %][% PROCESS foo %]" => "hi", {nl => "\n", tt_config => [TRIM => 1]});
process_ok("A[% TRY %]\nhi\n[% END %]" => "A\nhi", {tt_config => [TRIM => 1]});

###----------------------------------------------------------------###
### perl

process_ok("[% TRY %][% PERL %][% END %][% CATCH ; error; END %]" => 'perl error - EVAL_PERL not set');
process_ok("[% PERL %] print \"[% one %]\" [% END %]" => 'ONE', {one => 'ONE', tt_config => ['EVAL_PERL' => 1]});
process_ok("[% PERL %] print \$stash->get('one') [% END %]" => 'ONE', {one => 'ONE', tt_config => ['EVAL_PERL' => 1]});
process_ok("[% PERL %] print \$stash->set('a.b.c', 7) [% END %][% a.b.c %]" => '77', {tt_config => ['EVAL_PERL' => 1]});

###----------------------------------------------------------------###
### recursion prevention

process_ok("[% BLOCK foo %][% PROCESS bar %][% END %][% BLOCK bar %][% PROCESS foo %][% END %][% PROCESS foo %]" => '') if ! $is_tt;

###----------------------------------------------------------------###
### refs

process_ok("[% b=\\a; b %]" => 'a sub []', {a => sub { return "a sub [@_]" } });
process_ok("[% \\a %]" => qr/^CODE/, {a => sub { return "a sub [@_]" } });
process_ok("[% b=\\a(1); b %]" => 'a sub [1]', {a => sub { return "a sub [@_]" } });
process_ok("[% b=\\a; b(2) %]" => 'a sub [2]', {a => sub { return "a sub [@_]" } });
process_ok("[% b=\\a(1); b(2) %]" => 'a sub [1 2]', {a => sub { return "a sub [@_]" } });
process_ok("[% f=\\j.k; j.k=7; f %]" => '7', {j => {k => 3}});

process_ok('[% a = "a" ; f = {a=>"A",b=>"B"} ; foo = \f.$a ; foo %]' => 'A');
process_ok('[% a = "a" ; f = {a=>"A",b=>"B"} ; foo = \f.$a ; a = "b" ; foo %]' => 'A');
process_ok('[% a = "ab" ; f = "abcd"; foo = \f.replace(a, "-AB-") ; a = "cd"; foo %]' => '-AB-cd');
process_ok('[% a = "ab" ; f = "abcd"; foo = \f.replace(a, "-AB-").replace("-AB-", "*") ; a = "cd"; foo %]' => '*cd');

### tt does odd things with partially vivifying methods
process_ok('[% a = "ab" ; f = "abcd"; foo = \f.replace(a, "-AB-") ; f = "ab"; foo %]' => '-AB-cd') if $is_tt;
process_ok('[% a = "ab" ; f = "abcd"; foo = \f.replace(a, "-AB-").replace("-AB-", "*") ; f = "ab"; foo %]' => '*cd') if $is_tt;
process_ok('[% a = "ab" ; f = "abcd"; foo = \f.replace(a, "-AB-") ; f = "ab"; foo %]' => '-AB-') if ! $is_tt;
process_ok('[% a = "ab" ; f = "abcd"; foo = \f.replace(a, "-AB-").replace("-AB-", "*") ; f = "ab"; foo %]' => '*') if ! $is_tt;


--Boundary-00=_/wnWEH78x/uj0hI--