use v6.e.PREVIEW;
use Test;

plan 29; # Do not change this file to done-testing

my $ast;
my $deparsed;
my $raku;
my @type = <AST Str Raku>;
sub ast(RakuAST::Node:D $node --> Nil) {
    $ast      := $node;
    $deparsed := $node.DEPARSE;
    $raku     := 'use experimental :rakuast; ' ~ $node.raku;
    diag $deparsed.chomp;
}

subtest 'Statement list evaluates to its final statement' => {
    my $x = 12;
    my $y = 99;

    # ++$x; ; ++$y
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::ApplyPrefix.new(
          prefix => RakuAST::Prefix.new('++'),
          operand => RakuAST::Var::Lexical.new('$x')
        )
      ),
      RakuAST::Statement::Empty.new,
      RakuAST::Statement::Expression.new(
        expression => RakuAST::ApplyPrefix.new(
          prefix => RakuAST::Prefix.new('++'),
          operand => RakuAST::Var::Lexical.new('$y')
        )
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
++$x;
;
++$y
CODE

    my $expected = 100;
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($ast), $expected,
          "$type: Statement list evaluates to its final statement";
        is $x, $expected - 87,
          "$type: First side-effecting statement was executed";
        is $y, $expected,
          "$type: Second side-effecting statement was executed";
        ++$expected;
    }
}

subtest 'Basic if / elsif / else structure' => {
    my ($a, $b, $c);

    # if $a { 1 }
    # elsif $b { 2 }
    # elsif $c { 3 }
    # else { 4 }
    ast RakuAST::Statement::If.new(
      condition => RakuAST::Var::Lexical.new('$a'),
      then => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::IntLiteral.new(1)
            )
          )
        )
      ),
      elsifs => [
        RakuAST::Statement::Elsif.new(
          condition => RakuAST::Var::Lexical.new('$b'),
          then => RakuAST::Block.new(
            body => RakuAST::Blockoid.new(
              RakuAST::StatementList.new(
                RakuAST::Statement::Expression.new(
                  expression => RakuAST::IntLiteral.new(2)
                )
              )
            )
          )
        ),
        RakuAST::Statement::Elsif.new(
          condition => RakuAST::Var::Lexical.new('$c'),
          then => RakuAST::Block.new(
            body => RakuAST::Blockoid.new(
              RakuAST::StatementList.new(
                RakuAST::Statement::Expression.new(
                  expression => RakuAST::IntLiteral.new(3)
                )
              )
            )
          )
        )
      ],
      else => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::IntLiteral.new(4)
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
if $a {
    1
}
elsif $b {
    2
}
elsif $c {
    3
}
else {
    4
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $a = $b = $c = False;
        is-deeply EVAL($it), 4,
          "$type: When all conditions False, else is evaluated";

        $c = True;
        is-deeply EVAL($it), 3,
          "$type: Latest elsif reachable when matched";

        $b = True;
        is-deeply EVAL($it), 2,
          "$type: First elsif reachable when matched";

        $a = True;
        is-deeply EVAL($it), 1,
          "$type: When the main condition is true, the then block is picked";
    }
}

subtest 'simple if evaluation' => {
    my $a;

    # if $a { 1 }
    ast RakuAST::Statement::If.new(
      condition => RakuAST::Var::Lexical.new('$a'),
      then => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::IntLiteral.new(1)
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
if $a {
    1
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $a = True;
        is-deeply EVAL($it), 1,
          "$type: When simple if with no else has true condition";

        $a = False;
        is-deeply EVAL($it), Empty,
          "$type: When simple if with no else has false condition";
    }
}

subtest 'Basic with / orwith / else structure' => {
    my ($a, $b, $c);

    # with $a -> $x { 1 }
    # orwith -> $x { 2 }
    # orwith -> $x { 3 }
    # else -> $x { 4 }
    ast RakuAST::Statement::With.new(
      condition => RakuAST::Var::Lexical.new('$a'),
      then => RakuAST::PointyBlock.new(
        signature => RakuAST::Signature.new(
          parameters => (
            RakuAST::Parameter.new(
              target => RakuAST::ParameterTarget::Var.new(:name<$x>)
            ),
          )
        ),
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::IntLiteral.new(1)
            )
          )
        )
      ),
      elsifs => [
        RakuAST::Statement::Orwith.new(
          condition => RakuAST::Var::Lexical.new('$b'),
          then => RakuAST::PointyBlock.new(
            signature => RakuAST::Signature.new(
              parameters => (
                RakuAST::Parameter.new(
                  target => RakuAST::ParameterTarget::Var.new(:name<$x>)
                ),
              )
            ),
            body => RakuAST::Blockoid.new(
              RakuAST::StatementList.new(
                RakuAST::Statement::Expression.new(
                  expression => RakuAST::IntLiteral.new(2)
                )
              )
            )
          )
        ),
        RakuAST::Statement::Orwith.new(
          condition => RakuAST::Var::Lexical.new('$c'),
            then => RakuAST::PointyBlock.new(
              signature => RakuAST::Signature.new(
                parameters => (
                  RakuAST::Parameter.new(
                    target => RakuAST::ParameterTarget::Var.new(:name<$x>)
                  ),
                )
              ),
            body => RakuAST::Blockoid.new(
              RakuAST::StatementList.new(
                RakuAST::Statement::Expression.new(
                  expression => RakuAST::IntLiteral.new(3)
                )
              )
            )
          )
        )
      ],
      else => RakuAST::PointyBlock.new(
        signature => RakuAST::Signature.new(
          parameters => (
            RakuAST::Parameter.new(
              target => RakuAST::ParameterTarget::Var.new(:name<$x>)
            ),
          )
        ),
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::IntLiteral.new(4)
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
with $a -> $x {
    1
}
orwith $b -> $x {
    2
}
orwith $c -> $x {
    3
}
else -> $x {
    4
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $a = $b = $c = Nil;
        is-deeply EVAL($it), 4,
          "$type: When all conditions undefined, else is evaluated";

        $c = False;
        is-deeply EVAL($it), 3,
          "$type: Latest orwith reachable when matched";

        $b = False;
        is-deeply EVAL($it), 2,
          "$type: First orwith reachable when matched";

        $a = False;
        is-deeply EVAL($it), 1,
          "$type: When the main condition is defined, the then block is picked";
    }
}

subtest 'simple with evaluation' => {
    my $a;

    # with $a -> $x { 1 }
    ast RakuAST::Statement::With.new(
      condition => RakuAST::Var::Lexical.new('$a'),
      then => RakuAST::PointyBlock.new(
        signature => RakuAST::Signature.new(
          parameters => (
            RakuAST::Parameter.new(
              target => RakuAST::ParameterTarget::Var.new(:name<$x>)
            ),
          )
        ),
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::IntLiteral.new(1)
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
with $a -> $x {
    1
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $a = False;
        is-deeply EVAL($it), 1,
          "$type: When simple when with no else has defined condition";

        $a = Nil;
        is-deeply EVAL($it), Empty,
          "$type: When simple with if with no else has undefined condition";
    }
}

subtest 'with topicalizes in the body' => {
    # with $a { $_ } else { $_ }
    ast RakuAST::Statement::With.new(
      condition => RakuAST::Var::Lexical.new('$a'),
      then => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::Var::Lexical.new('$_')
            )
          )
        )
      ),
      else => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::Var::Lexical.new('$_')
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
with $a {
    $_
}
else {
    $_
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $a = 42;
        is-deeply EVAL($it), 42,
          "$type: with topicalizes in the body";

        $a = Int;
        is-deeply EVAL($it), Int,
          "$type: with topicalizes in the else body too";
    }
}

subtest 'simple unless with a false condition' => {
    my $x = False;
    my $y = 9;

    # unless $x { ++$y }
    ast RakuAST::Statement::Unless.new(
      condition => RakuAST::Var::Lexical.new('$x'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('++'),
                operand => RakuAST::Var::Lexical.new('$y')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
unless $x {
    ++$y
}
CODE

    my $expected = 10;
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), $expected,
          "$type: unless block with a false condition evaluates to its body";
        is $y, $expected, "$type: side-effect of the body was performed";
        ++$expected;
    }
}

subtest 'simple unless with a false condition' => {
    my $x = True;
    my $y = 9;

    # unless $x { ++$y }
    ast RakuAST::Statement::Unless.new(
      condition => RakuAST::Var::Lexical.new('$x'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix  => RakuAST::Prefix.new('++'),
                operand => RakuAST::Var::Lexical.new('$y')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
unless $x {
    ++$y
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), Empty,
          "$type: unless block with a false condition evaluates to Empty";
        is $y, 9, "$type: side-effect of the body was not performed";
    }
}

subtest 'simple without with an undefined condition' => {
    my $x = Nil;
    my $y = 9;

    # without $x { $y++ }
    ast RakuAST::Statement::Without.new(
      condition => RakuAST::Var::Lexical.new('$x'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPostfix.new(
                postfix => RakuAST::Postfix.new(:operator<++>),
                operand => RakuAST::Var::Lexical.new('$y')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
without $x {
    $y++
}
CODE

    my $expected = 9;
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), $expected,
          "$type: without block with an undefined object evaluates to its body";
        is $y, ++$expected, "$type: Side-effect of the body was performed";
    }
}

subtest 'simple without with a defined condition' => {
    my $x = True;
    my $y = 9;

    # without $x { ++$y }
    ast RakuAST::Statement::Without.new(
      condition => RakuAST::Var::Lexical.new('$x'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('++'),
                operand => RakuAST::Var::Lexical.new('$y')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
without $x {
    ++$y
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), Empty,
          "$type: a without block with a defined object evaluates to Empty";
        is $y, 9, "$type: side-effect of the body was not performed";
    }
}

subtest 'simple without with an undefined condition' => {
    my $x = Cool;

    # without $x { $_ }
    ast RakuAST::Statement::Without.new(
      condition => RakuAST::Var::Lexical.new('$x'),
      body => RakuAST::Block.new(body =>
        RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::Var::Lexical.new('$_')
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
without $x {
    $_
}
CODE

    for
      'AST', EVAL($ast),
      'Str', EVAL($deparsed),
      'Raku', EVAL(EVAL $raku)
    -> $type, $result {
        is-deeply $result, Cool,
          "$type: without block sets the topic";
    }
}

subtest 'While loop at statement level evaluates to Nil' => {
    my $x;

    # while $x { --$x }
    ast RakuAST::Statement::Loop::While.new(
      condition => RakuAST::Var::Lexical.new('$x'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('--'),
                operand => RakuAST::Var::Lexical.new('$x')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/.chomp, 'deparse';
while $x {
    --$x
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $x = 5;

        is-deeply EVAL($it), Nil,
          "$type: while loop at statement level evaluates to Nil";
        is-deeply $x, 0, "$type: Loop variable was decremented to zero";
    }
}

subtest 'Until loop at statement level evaluates to Nil' => {
    my $x;

    # until !$x { --$x }
    ast RakuAST::Statement::Loop::Until.new(
      condition => RakuAST::ApplyPrefix.new(
        prefix => RakuAST::Prefix.new('!'),
        operand => RakuAST::Var::Lexical.new('$x')
      ),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('--'),
                operand => RakuAST::Var::Lexical.new('$x')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/.chomp, 'deparse';
until !$x {
    --$x
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $x = 5;

        is-deeply EVAL($it), Nil,
          "$type: until loop at statement level evaluates to Nil";
        is-deeply $x, 0, "$type: Loop variable was decremented to zero";
    }
}

subtest 'Repeat while loop at statement level evaluates to Nil' => {
    my $x;

    # repeat { --$x } while $x
    ast RakuAST::Statement::Loop::RepeatWhile.new(
      condition => RakuAST::Var::Lexical.new('$x'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('--'),
                operand => RakuAST::Var::Lexical.new('$x')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/.chomp, 'deparse';
repeat {
    --$x
} while $x
CODE

    for 'AST', $ast,  'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $x = 5;

        is-deeply EVAL($it), Nil,
          "$type: repeat until loop at statement level evaluates to Nil";
        is-deeply $x, 0, "$type: loop variable decremented to 0";
    }
}

subtest 'Repeat until loop at statement level evaluates to Nil' => {
    my $x;

    # repeat { --$x } until $x
    ast RakuAST::Statement::Loop::RepeatUntil.new(
      condition => RakuAST::Var::Lexical.new('$x'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('--'),
                operand => RakuAST::Var::Lexical.new('$x')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/.chomp, 'deparse';
repeat {
    --$x
} until $x
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $x = 0;

        is-deeply EVAL($it), Nil,
          "$type: repeat until loop at statement level evaluates to Nil";
        is-deeply $x, -1, "$type: loop ran once";
    }
}

subtest 'Loop block with setup and increment expression' => {
    my $count;

    # loop (my $i = 9; $i; --$i) { ++$count }
    ast RakuAST::Statement::Loop.new(
      setup => RakuAST::VarDeclaration::Simple.new(
        sigil => '$',
        desigilname => RakuAST::Name.from-identifier('i'),
        initializer => RakuAST::Initializer::Assign.new(
          RakuAST::IntLiteral.new(9)
        )
      ),
      condition => RakuAST::Var::Lexical.new('$i'),
      increment => RakuAST::ApplyPrefix.new(
        prefix => RakuAST::Prefix.new('--'),
        operand => RakuAST::Var::Lexical.new('$i')
      ),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('++'),
                operand => RakuAST::Var::Lexical.new('$count')
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
loop (my $i = 9; $i; --$i) {
    ++$count
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $count = 0;

        is-deeply EVAL($it), Nil,
          "$type: loop with setup and increment evaluates to Nil";
        is-deeply $count, 9, "$type: loop ran as expected";
    }
}

subtest 'Statement level for loop' => {
    my $count;

    # for 2 .. 7 -> $x { ++$count }
    ast RakuAST::Statement::For.new(
      source => RakuAST::ApplyInfix.new(
        left => RakuAST::IntLiteral.new(2),
        infix => RakuAST::Infix.new('..'),
        right => RakuAST::IntLiteral.new(7)
      ),
      body => RakuAST::PointyBlock.new(
        signature => RakuAST::Signature.new(
          parameters => (
            RakuAST::Parameter.new(
              target => RakuAST::ParameterTarget::Var.new(:name<$x>)
            ),
          )
        ),
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('++'),
                operand => RakuAST::Var::Lexical.new('$count')
              )
            )
          )
        )
      )
    );

    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
for 2 .. 7 -> $x {
    ++$count
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $count = 0;

        is-deeply EVAL($it), Nil,
          "$type: for loop evaluates to Nil";
        is-deeply $count, 6, "$type: loop ran with expected number of times";
    }
}

subtest 'for loop with explicit iteration variable' => {
    my $total;

    # for 2 .. 7 -> $x { $total = $total + $x }
    ast RakuAST::Statement::For.new(
      source => RakuAST::ApplyInfix.new(
        left => RakuAST::IntLiteral.new(2),
        infix => RakuAST::Infix.new('..'),
        right => RakuAST::IntLiteral.new(7)
      ),
      body => RakuAST::PointyBlock.new(
        signature => RakuAST::Signature.new(
          parameters => (
            RakuAST::Parameter.new(
              target => RakuAST::ParameterTarget::Var.new(:name<$x>)
            ),
          )
        ),
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyInfix.new(
                left => RakuAST::Var::Lexical.new('$total'),
                infix => RakuAST::Infix.new('='),
                right => RakuAST::ApplyInfix.new(
                  left => RakuAST::Var::Lexical.new('$total'),
                  infix => RakuAST::Infix.new('+'),
                  right => RakuAST::Var::Lexical.new('$x')
                )
              )
            )
          )
        )
      )
    );

    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
for 2 .. 7 -> $x {
    $total = $total + $x
}
CODE

    my $sum = (2..7).sum;
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $total = 0;

        is-deeply EVAL($it), Nil, "$type: result value of for";
        is-deeply $total, $sum, "$type: correct value in iteration variable";
    }
}

subtest 'Statement level for loop with implicit topic' => {
    my $total = 0;

    # for 2 .. 7 { $total = $total + $_ }
    ast RakuAST::Statement::For.new(
      source => RakuAST::ApplyInfix.new(
        left => RakuAST::IntLiteral.new(2),
        infix => RakuAST::Infix.new('..'),
        right => RakuAST::IntLiteral.new(7)
      ),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::ApplyInfix.new(
                left => RakuAST::Var::Lexical.new('$total'),
                infix => RakuAST::Infix.new('='),
                right => RakuAST::ApplyInfix.new(
                  left => RakuAST::Var::Lexical.new('$total'),
                  infix => RakuAST::Infix.new('+'),
                  right => RakuAST::Var::Lexical.new('$_')
                )
              )
            )
          )
        )
      )
    );

    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
for 2 .. 7 {
    $total = $total + $_
}
CODE

    my $sum = (2..7).sum;
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        $total = 0;

        is-deeply EVAL($it), Nil, "$type: result value of for";
        is-deeply $total, $sum, "$type: correct value in implicit topic";
    }
}

subtest 'given with explicit signature' => {
    # given $a -> $x { $x }
    ast RakuAST::Statement::Given.new(
      source => RakuAST::Var::Lexical.new('$a'),
      body => RakuAST::PointyBlock.new(
        signature => RakuAST::Signature.new(
          parameters => (
            RakuAST::Parameter.new(
              target => RakuAST::ParameterTarget::Var.new(:name<$x>)
            ),
          )
        ),
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::Var::Lexical.new('$x')
            )
          )
        )
      )
    );

    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
given $a -> $x {
    $x
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $a = 'concrete';
        is-deeply EVAL($it), 'concrete',
          "$type: given topicalizes on the source (signature)";

        $a = Str;
        is-deeply EVAL($it), Str,
          "$type: given topicalizes even an undefined source (signature)";
    }
}

subtest 'given with implicit signature' => {
    # given $a { $_ }
    ast RakuAST::Statement::Given.new(
      source => RakuAST::Var::Lexical.new('$a'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::Var::Lexical.new('$_')
            )
          )
        )
      )
    );
    is-deeply $deparsed, q:to/CODE/.chomp, 'deparse';
given $a {
    $_
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $a = 'concrete';
        is-deeply EVAL($it), 'concrete',
          "$type: given topicalizes on the source (implicit)";

        $a = Str;
        is-deeply EVAL($it), Str,
          "$type: given topicalizes even an undefined source (implicit)";
    }
}

subtest 'given with when and default' => {
    # given $a { when 2 { "two" } when 3 { "three" } default { "another" } }
    ast RakuAST::Statement::Given.new(
      source => RakuAST::Var::Lexical.new('$a'),
      body => RakuAST::Block.new(
        body => RakuAST::Blockoid.new(
          RakuAST::StatementList.new(
            RakuAST::Statement::When.new(
              condition => RakuAST::IntLiteral.new(2),
              body => RakuAST::Block.new(
                body => RakuAST::Blockoid.new(
                   RakuAST::StatementList.new(
                     RakuAST::Statement::Expression.new(
                       expression => RakuAST::StrLiteral.new('two')
                    )
                  )
                )
              )
            ),
            RakuAST::Statement::When.new(
              condition => RakuAST::IntLiteral.new(3),
              body => RakuAST::Block.new(
                body => RakuAST::Blockoid.new(
                  RakuAST::StatementList.new(
                    RakuAST::Statement::Expression.new(
                      expression => RakuAST::StrLiteral.new('three')
                    )
                  )
                )
              )
            ),
            RakuAST::Statement::Default.new(
              body => RakuAST::Block.new(
                body => RakuAST::Blockoid.new(
                  RakuAST::StatementList.new(
                    RakuAST::Statement::Expression.new(
                      expression => RakuAST::StrLiteral.new('another')
                    )
                  )
                )
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/.chomp, 'deparse';
given $a {
    when 2 {
        "two"
    }
    when 3 {
        "three"
    }
    default {
        "another"
    }
}
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        my $a = 2;
        is-deeply EVAL($type eq 'AST' ?? $ast !! $deparsed), 'two',
          "$type: first when statement matching gives correct result";

        $a = 3;
        is-deeply EVAL($it), 'three',
          "$type: second when statement matching gives correct result";

        $a = 4;
        is-deeply EVAL($it), 'another',
          "$type: no when statement giving default";
    }
}

# This test calls an imported `&ok` to check the `use` works; the test plan
# verifies that it really works.
{
    sub ok(|) { die "Imported ok was not used" };

    # use Test; ok 1, "use statement works"
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Use.new(
        module-name => RakuAST::Name.from-identifier('Test')
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Call::Name.new(
          name => RakuAST::Name.from-identifier('ok'),
          args => RakuAST::ArgList.new(
            RakuAST::IntLiteral.new(1),
            RakuAST::StrLiteral.new('use statements work')
          )
        )
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
use Test;
ok(1, "use statements work")
CODE

    # EVALling produces test output
    EVAL($_) for $ast, $deparsed, EVAL($raku);
}

subtest "check parsing of 'use' pragma" => {

    # Apparently EVALling an AST does **NOT** set $*LANG yet,
    # so we fake one here for now
    my $*LANG = class { method set_pragma($,$) { } }

    # use MONKEY; nqp::const::STAT_ISDIR
    ast RakuAST::StatementList.new(
      RakuAST::Pragma.new(:name<MONKEY>),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Nqp::Const.new("STAT_ISDIR")
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
use MONKEY;
nqp::const::STAT_ISDIR
CODE

    is-deeply EVAL($_), 2, @type[$++] for $ast, $deparsed, EVAL($raku);
}

subtest "check parsing of 'no' pragma" => {

    # Apparently EVALling an AST does **NOT** set $*LANG yet,
    # so we fake one here for now
    my $*LANG = class { method set_pragma($,$) { } }

    # no trace; 42
    ast RakuAST::StatementList.new(
      RakuAST::Pragma.new(:name<trace>, :off),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::IntLiteral.new(42)
      )
    );
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
no trace;
42
CODE

    is-deeply EVAL($_), 42, @type[$++] for $ast, $deparsed, EVAL($raku);
}

subtest "check parsing of 'need' pragma" => {
    # need Test
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Need.new(
        module-names => (
          RakuAST::Name.from-identifier("Test"),
        )
      ),
      RakuAST::Statement::Import.new(
        module-name => RakuAST::Name.from-identifier("Test")
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Call::Name.new(
          name => RakuAST::Name.from-identifier("ok"),
          args => RakuAST::ArgList.new(
            RakuAST::Var::Lexical.new("\$type")
          )
        )
      )
    );

    plan 7;  # must run this number of tests
    is-deeply $deparsed, Q:to/CODE/, 'deparse';
need Test;
import Test;
ok($type)
CODE

    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), True, "$type: did eval work";
    }
}

# vim: expandtab shiftwidth=4
