package Text::Wigwam::Library::Iterators;

    use strict;
    use warnings;
    no warnings 'uninitialized';
    our $VERSION = '0.01';

    use base q/Exporter/;

    use Text::Wigwam::Const( q/:all/ );

    our %EXPORT_TAGS = (
        all => [
          qw/
            _proto_break    _break
            _proto_continue _continue
            _proto_first    _first
            _proto_foreach  _foreach
            _proto_last     _last
            _proto_repeat   _repeat
            _proto_while    _while
          /
        ],
        break    => [ qw/ _proto_break    _break / ],
        continue => [ qw/ _proto_continue _continue / ],
        first    => [ qw/ _proto_first    _first / ],
        foreach  => [ qw/ _proto_foreach  _foreach / ],
        last     => [ qw/ _proto_last     _last / ],
        repeat   => [ qw/ _proto_repeat   _repeat / ],
        while    => [ qw/ _proto_while    _while / ],
    );

    our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
    our @EXPORT = qw( );

=head1 Name

Text::Wigwam::Library::Iterators - A Wigwam directive library.

=head1 Description

This library provides directives relating to looping constructs.

=head1 Usage

The directives within this library can be imported into specific branches of
the directive tree (which transcends from  C<Text::Wigwam::Directives>) via
plug-ins or modules using one of the following techniques.

 package Text::Wigwam::Directives::some::arbitrary::branch;
 # import everything
 use Text::Wigwam::Library::Iterators( q/:all/ );

 package Text::Wigwam::Directives::another::arbitrary::branch;
 # import selectively
 use Text::Wigwam::Library::Iterators(
  qw/ :break :continue :for :foreach :repeat :while /
 );

=head1 Directives

=cut

sub _proto_break { [ ] }
sub _break { $_[0]->set_global( Break => 1 ); return }

=head2 #break

Breaks out of a C<#foreach>, C<#repeat>, or C<#while> loop.

 [!!
  #foreach var < a b 0 c d > {
      #unless var #break
     var
  }
  /* output:
   ab
  */
 !!]

=cut

sub _proto_continue { [ ] }
#sub _continue { $_[0]->set_global( Continue => 1 ); return }
sub _continue { ${$_[0]->get_global_ref( q/Continue/ )} = 1; return }

=head2 #continue

Skips the current iteration of a C<#foreach>, C<#repeat>, or C<#while> loop.

 [!!
  #foreach var < a b 0 c "" d > {
      #unless var #continue
     var
  }
  /* output:
   abcd
  */
 !!]

=cut

sub _proto_foreach { [ VAR, ARRAY, STRING|ITERATE ], [qw/Continue Break first last/] }
sub _foreach {
    my ($Api) = @_;
    my $var = $Api->get_arg;
    my @arr = @{$Api->get_arg};
    my $txt;
    my $continue = $Api->get_global_ref( q/Continue/ );
    my $first = $Api->set_global( first => \do { my $v=1 } );
    my $last =  $Api->set_global( last  => \do { my $v=1 } );
    while ( !$Api->eflag && scalar @arr ){
        $Api->set_value( $var => shift @arr );
        $$last = !scalar @arr;
        $txt .= $Api->get_arg;
        $$first = $$continue = 0;
    }
    $Api->kill_arg(1);
    return $txt;
}

=head2 #foreach I<var> I<array> I<block>

For each element of I<array>, the current element's value is stored in I<var>
and I<block> is executed. The concatenated results of each I<block> execution
is returned.

 [!!
  #define array < 3 . 1 4 1 5 9 6 >
  #foreach digit array {
     digit
  }
  /* output:
    3.141596
  */
 !!]

The loop can be terminated using the C<#break> directive within I<block>, or
an iteration can be skipped using C<#continue>.

=cut

=head2 #first

Returns true during the first iteration of a #foreach loop. Alternatively,
you could simply access the global directly via C<:first>.


 [!!
  #foreach item < one two three four >{
     #if #first "<UL>"
     "<LI>" item
     #if #last "</UL>"
  }
  /* Output:
   <UL><LI>one<LI>two<LI>three<LI>four</UL>
  */
 !!]

=cut

sub _proto_first { [] }
sub _first {
    my ($Api) = @_;
    my $first;
    $first = $_[0]->get_global(q/first/) and return $$first;
    $Api->exception( 'The #first directive must be called within a #foreach loop' );
    return;
}

=head2 #last

Returns true during the final iteration of a #foreach loop. Alternatively,
you can simply access the global directly via C<:last>.

 [!!
  #foreach item < milk cheese eggs waffles wd40 >{
     #if #first { "Ingredients: " item }
     #elsif #last { " and " item "." }
     #else { ", " item }
  }
  /* output:
   Ingredients: milk, cheese, eggs, waffles and wd40.
  */
 !!]

=cut

sub _proto_last { [] }
sub _last {
    my ($Api) = @_;
    my $last;
    $last = $_[0]->get_global(q/last/) and return $$last;
    $Api->exception( 'The #last directive must be called within a #foreach loop' );
    return;
}

sub _proto_repeat { [ NUM, ITERATE|STRING ], [ qw/Continue Break repeat/ ] }
sub _repeat {
    my ($Api) = @_;
    my $loop = int( abs( $Api->get_arg ) );
    my $text;
    my $continue = $Api->get_global_ref( q/Continue/ );
    my $repeat = $Api->set_global( repeat => \do { my $v = 0 } );
    while( !$Api->eflag && $loop > $$repeat ) {
        $text .= $Api->get_arg;
        ++$$repeat;
        $$continue = 0;
    }
    $Api->kill_arg(1);
    return $text;
}

=head2 #repeat I<num> I<block>

Begins a loop of a fixed number of iterations which is dictated by I<num>.
Supports C<#break> and C<#continue> within I<block>.

 [!!
  #repeat 20 { "-" }
  /* output:
   --------------------
  */
 !!]


The iteration count can be retrieved within the I<block> via the C<:repeat>
global.

 [!!
  #repeat 20 { :repeat "-" }
  /* output:
   0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-
  */
 !!]

=cut

sub _proto_while { [EXPR|BOOLEAN, ITERATE|STRING], [ qw/Continue Break/ ] }
sub _while {
    my ($Api) = @_;
    my $result;
    my $cond = $Api->get_arg;
    my $continue = $Api->get_global_ref( q/Continue/ );
    while( !$Api->eflag && $cond->execute ) {
        $result .= $Api->get_arg;
        $$continue = 0;
    }
    $Api->kill_arg(1);
    return $result;
}

=head2 #while I<cond> I<block>

Continuously executes I<block> so long as I<cond> evaluates to true prior to
each iteration. Returns the concatenated results of each iteration. 

 [!!
  #define array ( 0 1 2 3 4 )
  #while array #shift array
  /* output:
   01234
  */
 !!]

The C<#break> directive can be used within I<block> to end the loop, and
C<#continue> can be used to interrupt the current iteration.

 [!!
  #define array ( 4 9 3 8 2 7 1 0 6 5 )
  #while array {
     #define num #shift array
     #unless num #break
     #if #lt num 4 #continue
     num
  /* output:
   4987
  */
  }
 !!]

=cut

sub init_lib {
    $_[0]->new_eflag( LOCAL => Break => 0, );
    $_[0]->new_eflag( LOCAL => Continue => 0, );
}

1;

__END__

