The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# -*- Mode: Python -*-

# These are Pythonic ports of programs from MJD's HOP.

# Acme::Pythonic needs to know this prototype.
sub Iterator(&);

use Test::More 'no_plan';
use Acme::Pythonic debug => 0;

use strict
use warnings

my ($it, $computed, $expected)


#
# ---[ Chapter 1: Recursion ]-------------------------------------------
#

# This is hanoi() from page 9 plus a variation of check_move from pages
# 10 and 11 that constructs a list for testing instead of printing
# messages.

my @position = ('', ('A') x 3) # Disks are all initially on peg A

sub check_move:
    my $i
    my ($disk, $start, $end) = @_
    if $disk < 1 || $disk > $#position:
        die "Bad disk number $disk. Should be 1..$#position.\n"
    unless $position[$disk] eq $start:
        die "Tried to move disk $disk from $start, but it is on peg $position[$disk].\n"
    for $i in 1 .. $disk-1:
        if $position[$i] eq $start:
            die "Can't move disk $disk from $start because $i is on top of it.\n"
        elsif $position[$i] eq $end:
            die "Can't move disk $disk to $end because $i is already there.\n"
    push @$computed, [$disk, $start, $end]
    $position[$disk] = $end

sub hanoi:
    my ($n, $start, $end, $extra, $move_disk) = @_
    if $n == 1:
        $move_disk->(1, $start, $end)
    else:
        hanoi($n-1, $start, $extra, $end, $move_disk)
        $move_disk->($n, $start, $end)
        hanoi($n-1, $extra, $end, $start, $move_disk)

hanoi(3, 'A', 'C', 'B', \&check_move)
is_deeply($computed, [[1, 'A', 'C'],
                      [2, 'A', 'B'],
                      [1, 'C', 'B'],
                      [3, 'A', 'C'],
                      [1, 'B', 'A'],
                      [2, 'B', 'C'],
                      [1, 'A', 'C']])


#
# ---[ Chapter 4: Iterators ]-------------------------------------------
#

# Defined on page 121
sub upto:
    my ($mx, $nx) = @_
    return sub:
        return $mx <= $nx ? $mx++ : undef

$it = upto 2, 5
my $n = 2
while defined(my $val = $it->()):
    is $val, $n++
is $n, 6

# Defined on page 122
sub NEXTVAL:
    $_[0]->()

# Defined on page 123
sub Iterator(&):
    return $_[0]

# Defined on page 160
sub imap(&$):
    my ($transform, $it) = @_
    return Iterator:
        local $_ = NEXTVAL($it)
        return unless defined $_
        return $transform->()

$it = imap:
         $_ *= 2
         $_ += 1
         $_
      upto(2, 5)

$expected = [5, 7, 9, 11]
$computed = []
while my $val = NEXTVAL($it):
    push @$computed, $val
is_deeply $expected, $computed

# Defined on page 160
sub igrep(&$):
    my ($is_interesting, $it) = @_
    return Iterator:
        local $_
        while defined($_ = NEXTVAL($it)):
            return $_ if $is_interesting->()
        return

$it = igrep:
          $_ % 2
      upto(2, 11)
$expected = [3, 5, 7, 9, 11]
$computed = []
while my $val = NEXTVAL($it):
    push @$computed, $val
is_deeply $expected, $computed


# Defined on page 136
sub make_genes:
    my $pat = shift
    my @tokens = split /[()]/, $pat
    for my $i = 1; $i < @tokens; $i += 2:
        $tokens[$i] = [0, split(//, $tokens[$i])]
    my $FINISHED = 0
    return Iterator:
        return if $FINISHED
        my $finished_incrementing = 0
        my $result = ""
        for my $token in @tokens:
            if ref $token eq "":      # plain string
                $result .= $token
            else:                     # wildcard
                my ($n, @c) = @$token
                $result .= $c[$n]
                unless $finished_incrementing:
                    if $n == $#c:
                        $token->[0] = 0
                    else:
                        $token->[0]++
                        $finished_incrementing = 1
        $FINISHED = 1 unless $finished_incrementing
        return $result

my $seq = "A(CGT)CGT"
$expected = [qw(ACCGT AGCGT ATCGT)]
$computed = []
my $gene_iter = make_genes $seq
while my $g = NEXTVAL($gene_iter):
    push @$computed, $g
is_deeply($expected, $computed)

$seq = "A(CT)G(AC)"
$expected = [qw(ACGA ATGA ACGC ATGC)]
$computed = []
$gene_iter = make_genes $seq
while my $g = NEXTVAL($gene_iter):
    push @$computed, $g
is_deeply($expected, $computed)

$seq = "(abc)(de)-(12)"
$expected = [qw(ad-1 bd-1 cd-1 ae-1 be-1 ce-1 ad-2 bd-2 cd-2 ae-2 be-2 ce-2)]
$computed = []
$gene_iter = make_genes $seq
while my $g = NEXTVAL($gene_iter):
    push @$computed, $g
is_deeply($expected, $computed)


#
# --- [ Chapter 5: From Recursion to Iterators ] -----------------------
#

# Defined on page 210, with a fix for boundary conditions from page 211.
sub make_partitioner:
    my ($n, $treasures) = @_
    my @todo = $n ? [$n, $treasures, []] : [$n, [], []]
    sub:
        while @todo:
            my $cur = pop @todo
            my ($target, $pool, $share) = @$cur

            my ($first, @rest) = @$pool

            push @todo, [$target, \@rest, $share] if @rest
            if $target == $first:
                return [@$share, $first]
            elsif $target > $first && @rest:
                push @todo, [$target-$first, \@rest, [@$share, $first]]
        return undef

my $mp = make_partitioner(5, [1, 2])
$expected = []
$computed = []
while my $p = NEXTVAL($mp):
    push @$computed, $p
is_deeply($expected, $computed)

$mp = make_partitioner(5, [1, 2, 3])
$expected = [[2, 3]]
$computed = []
while my $p = NEXTVAL($mp):
    push @$computed, $p
is_deeply($expected, $computed)


# Defined on pages 252-253.
sub fib:
    my $n = shift
    my ($s1, $return)
    my $BRANCH = 0
    my @STACK
    while 1:
        if $n < 2:
            $return = $n
        else:
            if $BRANCH == 0:
                push (@STACK, [ 1, 0, $n ]), $n -= 1 while $n >= 2
                $return = $n
            elsif $BRANCH == 1:
                push @STACK, [ 2, $return, $n ]
                $n -= 2
                $BRANCH = 0
                next
            elsif $BRANCH == 2:
                $return += $s1

        return $return unless @STACK
        ($BRANCH, $s1, $n) = @{pop @STACK}

is fib(0), 0
is fib(1), 1
is fib(10), 55
is fib(21), 10946