I would do it in a two-way approach, first splitting up the template into literal parts and TT code, and then stripping out the TT comments, maybe:
use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Useqq = 1;
my $tt = <<'TT';
[% # this is a comment to the end of line
foo = 'bar'
%]
<p>You might have an array in your TT</p>
[%
foo = bar[5];
%]
<p>bw, bliako</p>
[%# placing the '#' immediately inside the directive
tag comments out the entire directive
%]
TT
my @parts = ($tt =~ /\G(
(?:[^\\\[]+) # not a template, not a backsla
+sh
|(?:[\\].) # an escaped whatever
|(?:[\[][^%]) # not a template, [ followed by
+ whatever
|(?:\[%.*?%\]) # within a TT template
)
/msgx);
@parts =
map { s!\s+#.*$!!gm; $_ } # comments up to EOL
map { /^\[%#/ ? "" : $_ } # TT comments
@parts;
warn Dumper \@parts;
This will not deal well with templates containing code containing a literal %]. So, don't do that.