use v6.e.PREVIEW;
use Test;

plan 59;

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

# MUST be the first test to handle EVAL_n counter
todo("Depends on Raku grammar/actions") unless %*ENV<RAKUDO_RAKUAST>;
subtest 'Special compiler variable $?FILE' => {
    my $file := 'EVAL_1';
    # $?FILE
    ast RakuAST::Var::Compiler::File.new($file);

    is-deeply $deparsed, '$?FILE', 'deparse';
    is-deeply $_, $file, @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
}

my class A {
    subtest 'Special compiler variable $?FILE' => {
        # ::$?CLASS
        ast RakuAST::Var::Lexical::Constant.new('::?CLASS');

        is-deeply $deparsed, '::?CLASS', 'deparse';
        is-deeply $_, A, @type[$++]
          for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
    }
}

subtest 'Special compiler variable $?LINE' => {
    # $?LINE
    # always line 1 inside the EVAL
    ast RakuAST::Var::Compiler::Line.new(1);

    is-deeply $deparsed, '$?LINE', 'deparse';
    is-deeply $_, 1, @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
}

package Foo {
    our $foo = 42;
    subtest 'Special compiler variable $?PACKAGE' => {
        # $?PACKAGE
        ast RakuAST::Var::Compiler::Lookup.new('$?PACKAGE');

        is-deeply $deparsed, '$?PACKAGE', 'deparse';
        is-deeply $_, Foo, @type[$++]
          for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
    }

    subtest 'Generic package variable lookup' => {
        ast RakuAST::Var::Package.new(
          name  => RakuAST::Name.from-identifier-parts("Foo","foo"),
          sigil => '$'
        );

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

subtest 'Lexical variable lookup ($ sigil)' => {
    my $x = 42;

    # $x
    ast RakuAST::Var::Lexical.new('$x');
    is-deeply $deparsed, '$x', 'deparse';
    is-deeply $_, 42, @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
}

subtest 'Lexical variable lookup (& sigil)' => {

    # &plan
    ast RakuAST::Var::Lexical.new('&plan');
    is-deeply $deparsed, '&plan', 'deparse';
    is-deeply $_, &plan, @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
}

subtest 'A variable in the setting' => {
    ast RakuAST::Var::Lexical::Setting.new('&val');

    is-deeply $deparsed, 'SETTING::<&val>', 'deparse';
    is-deeply $_, &val, @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
}

subtest 'Positional capture variable lookup works' => {
    my $/;
    "abc" ~~ /(.)(.)/;

    # $0
    ast RakuAST::Var::PositionalCapture.new(0);
    is-deeply $deparsed, '$0', 'deparse';
    is-deeply .Str, "a", @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);

    # $1
    ast RakuAST::Var::PositionalCapture.new(1);
    is-deeply $deparsed, '$1', 'deparse';
    is-deeply .Str, "b", @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
}

subtest 'Named capture variable lookup works' => {
    my $/;
    "abc" ~~ /$<x>=(.)$<y>=(.)/;

    # $<y>
    ast RakuAST::Var::NamedCapture.new(
      RakuAST::QuotedString.new(
        segments => [RakuAST::StrLiteral.new('y')],
        processors => ['words','val']
      )
    );

    is-deeply $deparsed, '$<y>', 'deparse';
    is-deeply .Str, "b", @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);

    # $<x>
    ast RakuAST::Var::NamedCapture.new(
      RakuAST::QuotedString.new(
        segments => [RakuAST::StrLiteral.new('x')],
        processors => ['words','val']
      )
    );

    is-deeply $deparsed, '$<x>', 'deparse';
    is-deeply .Str, "a", @type[$++]
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
}

subtest 'variable declaration takes scope and name' => {
    for 'my', 'state' -> $scope {
        # my|state $foo
        ast RakuAST::VarDeclaration::Simple.new(
          scope => $scope,
          sigil => '$',
          desigilname => RakuAST::Name.from-identifier('foo'),
        );
        is-deeply $deparsed, $scope ~ ' $foo', 'deparse';

        is $ast.scope, $scope, "did we get scope: $scope";
        is $ast.name,  '$foo', 'did we get the right name';
    }
}

subtest 'Lexical variable my|state declarations work' => {
    for 'my', 'state' -> $scope {
        # my|state $foo = 10; $foo
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => $scope,
              sigil => '$',
              desigilname => RakuAST::Name.from-identifier('foo'),
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::ApplyInfix.new(
              left => RakuAST::Var::Lexical.new('$foo'),
              infix => RakuAST::Infix.new('='),
              right => RakuAST::IntLiteral.new(10)
            ),
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('$foo')
          )
        );

        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 $foo;
$foo = 10;
$foo
CODE
        is-deeply $_, 10, "@type[$++]: $scope"
          for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
    }
}

subtest 'Defaults of my|state untyped container' => {
    for 'my', 'state' -> $scope {
        # my|state $foo; $foo
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => $scope,
              sigil => '$',
              desigilname => RakuAST::Name.from-identifier('foo'),
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('$foo')
          )
        );

        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 $foo;
$foo
CODE
        for
          'AST', EVAL($ast),
          'Str', EVAL($deparsed),
          'Raku', EVAL(EVAL $raku)
        -> $type, \cont {
            is-deeply cont, Any, "$type: $scope default value is Any";
            ok cont.VAR.of =:= Mu, "$type: $scope Default constraint is Mu";
        }
    }
}

subtest 'Typed variable my|state declaration (type matches in assignment)' => {
    for 'my', 'state' -> $scope {
        # my|state Int $foo; $foo = 99; $foo
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => $scope,
              sigil => '$',
              desigilname => RakuAST::Name.from-identifier('foo'),
              type  => RakuAST::Type::Simple.new(
                RakuAST::Name.from-identifier('Int')
              )
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::ApplyInfix.new(
              left => RakuAST::Var::Lexical.new('$foo'),
              infix => RakuAST::Infix.new('='),
              right => RakuAST::IntLiteral.new(99)
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('$foo')
          )
        );

        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 Int $foo;
$foo = 99;
$foo
CODE
        is-deeply $_, 99, "@type[$++]: $scope"
          for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
    }
}

subtest 'Typed variable my|state declaration (type mismatch throws)' => {
    for 'my', 'state' -> $scope {
        # my|state Int $foo; $foo = 1e5; $foo
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => $scope,
              sigil => '$',
              desigilname => RakuAST::Name.from-identifier('foo'),
              type  => RakuAST::Type::Simple.new(
                RakuAST::Name.from-identifier('Int')
              )
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::ApplyInfix.new(
              left => RakuAST::Var::Lexical.new('$foo'),
              infix => RakuAST::Infix.new('='),
              right => RakuAST::NumLiteral.new(1e5)
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('$foo')
          )
        );
        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 Int $foo;
$foo = 100000e0;
$foo
CODE

        for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
            todo 'string EVAL produces different error'
              if $type eq 'Str';
            throws-like { EVAL($it) },
              X::TypeCheck::Assignment,
              expected => Int,
              got      => 1e5
            ;
        }
    }
}

subtest 'Lexical variable my|state declaration with assignment initializer' => {
    for 'my', 'state' -> $scope {
        # my|state $var = 125; $var
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope       => $scope,
              sigil       => '$',
              desigilname => RakuAST::Name.from-identifier('var'),
              initializer => RakuAST::Initializer::Assign.new(
                RakuAST::IntLiteral.new(125)
              )
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('$var')
          )
        );

        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 $var = 125;
$var
CODE
        for
          'AST', EVAL($ast),
          'Str', EVAL($deparsed),
          'Raku', EVAL(EVAL $raku)
        -> $type, \result {
            is-deeply result, 125,
              "$type: $scope variable declarations with assignment initializer";
            ok result.VAR.isa(Scalar),
              "$type: $scope was an assignment into a Scalar container";
            nok result.VAR.dynamic,
              "$type: $scope is not dynamic";
            lives-ok { result = 42 },
              "$type: $scope can update the container that was produced";
        }
    }
}

subtest 'Lexical my|state array declaration with assignment initializer' => {
    for 'my', 'state' -> $scope {
        # my|state @var = 22, 33; @var
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope       => $scope,
              sigil       => '@',
              desigilname => RakuAST::Name.from-identifier('var'),
              initializer => RakuAST::Initializer::Assign.new(
                RakuAST::ApplyListInfix.new(
                  infix => RakuAST::Infix.new(','),
                  operands => (
                    RakuAST::IntLiteral.new(22),
                    RakuAST::IntLiteral.new(33)
                  )
                )
              )
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('@var')
          ),
        );

        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 @var = 22, 33;
@var
CODE
        is-deeply $_, [22,33], "@type[$++]: $scope"
          for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
    }
}

subtest 'Lexical my|state variable declarations with bind initializer' => {
    for 'my', 'state' -> $scope {
        # my|state $var := 225; $var
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope       => $scope,
              sigil       => '$',
              desigilname => RakuAST::Name.from-identifier('var'),
              initializer => RakuAST::Initializer::Bind.new(
                RakuAST::IntLiteral.new(225)
              )
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('$var')
          ),
        );

        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 $var := 225;
$var
CODE
        for
          'AST', EVAL($ast),
          'Str', EVAL($deparsed),
          'Raku', EVAL(EVAL $raku)
        -> $type, \result {
            # state var limitation: binding (like "state $var := 225") not supported
            next if $scope eq 'state';

            is-deeply result, 225,
              "$type: $scope variable declarations with bind initializer";
            nok result.VAR.isa(Scalar),
              "$type: $scope really was bound; no Scalar container";
            dies-ok { result = 42 },
              "$type: $scope cannot assign as it is not a container";
        }
    }
}

subtest 'Anonymous state variable declaration' => {
    # -> { ++state $ }
    ast RakuAST::PointyBlock.new(
      body => RakuAST::Blockoid.new(
        RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::ApplyInfix.new(
              left => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('++'),
                operand => RakuAST::VarDeclaration::Anonymous.new(:sigil('$'), :scope('state'))
              ),
              infix => RakuAST::Infix.new('+'),
              right => RakuAST::ApplyPrefix.new(
                prefix => RakuAST::Prefix.new('++'),
                operand => RakuAST::VarDeclaration::Anonymous.new(:sigil('$'), :scope('state'))
              )
            )
          )
        )
      )
    );

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

    for
      'AST', EVAL($ast),
      'Str', EVAL($deparsed),
      'Raku', EVAL(EVAL $raku)
    -> $type, &result {
        is-deeply result() ~ result() ~ result(), '246',
            "$type: anonymous state variables works are are distinct";
    }
}

subtest 'Dynamic variable access' => {
    sub with-dyn(&test) {
        my $*dyn = 'in';
    }
    my $*dyn = 'out';

    # $*dyn
    ast RakuAST::Var::Dynamic.new('$*dyn');
    is-deeply $deparsed, '$*dyn', 'deparse';

    is-deeply $_, 'out', "@type[$++] access outside"
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);

    is-deeply $_, 'in', "@type[$++] access inside"
      for with-dyn({ EVAL($ast) }), with-dyn({ EVAL($deparsed) });

    # $*OUT
    ast RakuAST::Var::Dynamic.new('$*OUT');
    is-deeply $deparsed, '$*OUT', 'deparse';

    is-deeply $_, $*OUT, "@type[$++] checking \$*OUT"
      for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
}

subtest 'Dynamic variable declaration and assignment, dynamic lookup' => {
    # my $*var = 360;
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil       => '$',
          twigil      => '*',
          desigilname => RakuAST::Name.from-identifier('var'),
          initializer => RakuAST::Initializer::Assign.new(
            RakuAST::IntLiteral.new(360)
          )
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Var::Dynamic.new('$*var')
      ),
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
my $*var = 360;
$*var
CODE

    for
      'AST', EVAL($ast),
      'Str', EVAL($deparsed),
      'Raku', EVAL(EVAL $raku)
    -> $type, \result {
        is-deeply result, 360,
          "$type: Dynamic variable declaration and assignment, dynamic lookup";
        ok result.VAR.isa(Scalar),
          "$type: Dynamic did an assignment into a Scalar container";
        ok result.VAR.dynamic,
          "$type: Is a dynamic";
        lives-ok { result = 99 },
          "$type: Can update the container that was produced";
    }
}

subtest '@ sigil my|state var is initialized to Array' => {
    for 'my', 'state' -> $scope {
        # my|state @arr; @arr
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => $scope,
              sigil => '@',
              desigilname => RakuAST::Name.from-identifier('arr'),
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('@arr')
          ),
        );
        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 @arr;
@arr
CODE

        for
          'AST', EVAL($ast),
          'Str', EVAL($deparsed),
          'Raku', EVAL(EVAL $raku)
        -> $type, \cont {
            is-deeply cont.WHAT, Array,
              "$type: $scope @ sigil var is initialized to Array";
            is-deeply cont.VAR.WHAT, Array,
              "$type: $scope @ sigil var not wrapped in Scalar";
            ok cont.defined,
              "$type: $scope it is a defined Array instance";
            is cont.elems, 0,
              "$type: $scope it is empty";
            is-deeply cont[0].VAR.WHAT, Scalar,
              "$type: $scope element is a Scalar";
            is-deeply cont[0], Any,
              "$type: $scope contains an Any by default";
            ok cont[0].VAR.of =:= Mu,
              "$type: $scope constraint is Mu by default";
        }
    }
}

subtest '% sigil my|state var is initialized to Hash' => {
    for 'my', 'state' -> $scope {
        # my|state %hash; %hash
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => $scope,
              sigil => '%',
              desigilname => RakuAST::Name.from-identifier('hash'),
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('%hash')
          ),
        );
        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 %hash;
%hash
CODE

        for
          'AST', EVAL($ast),
          'Str', EVAL($deparsed),
          'Raku', EVAL(EVAL $raku)
        -> $type, \cont {
            is-deeply cont.WHAT, Hash,
              "$type: $scope % sigil var is initialized to Hash";
            is-deeply cont.VAR.WHAT, Hash,
              "$type: $scope % sigil var not wrapped in Scalar";
            ok cont.defined,
              "$type: $scope it is a defined Hash instance";
            is cont.elems, 0,
              "$type: $scope it is empty";
            is-deeply cont<k>.VAR.WHAT, Scalar,
              "$type: $scope element is a Scalar";
            is-deeply cont<k>, Any,
              "$type: $scope contains an Any by default";
            ok cont<k>.VAR.of =:= Mu,
              "$type: $scope constraint is Mu by default";
        }
    }
}

subtest '@ sigil my|state var with Int type is an Array' => {
    for 'my', 'state' -> $scope {
        # my|state Int @arr; @arr
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => $scope,
              sigil => '@',
              desigilname => RakuAST::Name.from-identifier('arr'),
              type  => RakuAST::Type::Simple.new(
                RakuAST::Name.from-identifier('Int')
              )
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('@arr')
          ),
        );
        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 Int @arr;
@arr
CODE

        for
          'AST', EVAL($ast),
          'Str', EVAL($deparsed),
          'Raku', EVAL(EVAL $raku)
        -> $type, \cont {
            ok cont ~~ Array,
              "$type: $scope @ sigil var with Int type is an Array";
            ok cont ~~ Positional[Int],
              "$type: $scope it does Positional[Int]";
            is-deeply cont.of, Int,
              "$type: $scope .of gives Int";
            is cont.elems, 0,
              "$type: $scope it is empty";
            is-deeply cont[0].VAR.WHAT, Scalar,
              "$type: $scope element is a Scalar";
            is-deeply cont[0], Int,
              "$type: $scope contains an Int";
            ok cont[0].VAR.of =:= Int,
              "$type: $scope constraint is Int";
        }
    }
}

subtest '% sigil my|state var with Int type is a Hash' => {
    for 'my', 'state' -> $scope {
        # my|state Int %hash; %hash
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => $scope,
              sigil => '%',
              desigilname => RakuAST::Name.from-identifier('hash'),
              type  => RakuAST::Type::Simple.new(
                RakuAST::Name.from-identifier('Int')
              )
            )
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('%hash')
          )
        );
        is-deeply $deparsed, $scope ~ q:to/CODE/, 'deparse';
 Int %hash;
%hash
CODE

        for
          'AST', EVAL($ast),
          'Str', EVAL($deparsed),
          'Raku', EVAL(EVAL $raku)
        -> $type, \cont {
            ok cont ~~ Hash,
              "$type: $scope % sigil var with Int type is a Hash";
            ok cont ~~ Associative[Int],
              "$type: $scope it does Associative[Int]";
            is-deeply cont.of, Int,
              "$type: $scope .of gives Int";
            is cont.elems, 0,
              "$type: $scope it is empty";
            is-deeply cont<k>.VAR.WHAT, Scalar,
              "$type: $scope element is a Scalar";
            is-deeply cont<k>, Int,
              "$type: $scope contains an Int";
            ok cont<k>.VAR.of =:= Int,
              "$type: $scope constraint is Int";
        }
    }
}

subtest 'Can access external native int var' => {
    my int $x = 42;

    # $x
    ast RakuAST::Var::Lexical.new('$x');
    is-deeply $deparsed, '$x', 'deparse';

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

subtest 'Can access external native num var' => {
    my num $x = 4e2;

    # $x
    ast RakuAST::Var::Lexical.new('$x');
    is-deeply $deparsed, '$x', 'deparse';

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

subtest 'Can access external native str var' => {
    my str $x = 'answer';

    # $x
    ast RakuAST::Var::Lexical.new('$x');
    is-deeply $deparsed, '$x', 'deparse';

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

subtest 'int declaration creates a native int container' => {
    # my int $native-int; $native-int
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil => '$',
          desigilname => RakuAST::Name.from-identifier('native-int'),
          type => RakuAST::Type::Simple.new(
            RakuAST::Name.from-identifier('int')
          )
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Var::Lexical.new('$native-int')
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
my int $native-int;
$native-int
CODE

    for
      'AST', EVAL($ast),
      'Str', EVAL($deparsed),
      'Raku', EVAL(EVAL $raku)
    -> $type, \cont {
        my $desc = "$type: int declaration creates a native int container";
        multi check(int $x) { pass $desc }
        multi check($x) { flunk $desc }

        check(cont);
        is-deeply cont, 0,
          "$type: Native int initialized to 0 by default";
    }
}

subtest 'num declaration creates a native num container' => {
    # my num $native-num; $native-num
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil => '$',
          desigilname => RakuAST::Name.from-identifier('native-num'),
          type => RakuAST::Type::Simple.new(
            RakuAST::Name.from-identifier('num')
          )
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Var::Lexical.new('$native-num')
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
my num $native-num;
$native-num
CODE

    for
      'AST', EVAL($ast),
      'Str', EVAL($deparsed),
      'Raku', EVAL(EVAL $raku)
    -> $type, \cont {
        my $desc = "$type: num declaration creates a native num container";
        multi check(num $x) { pass $desc }
        multi check($x) { flunk $desc }

        check(cont);
        is-deeply cont, 0e0,
          "$type: Native num initialized to 0e0 by default";
    }
}

subtest 'str declaration creates a native str container' => {
    # my str $native-str; $native-str
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil => '$',
          desigilname => RakuAST::Name.from-identifier('native-str'),
          type => RakuAST::Type::Simple.new(
            RakuAST::Name.from-identifier('str')
          )
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Var::Lexical.new('$native-str')
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
my str $native-str;
$native-str
CODE

    for
      'AST', EVAL($ast),
      'Str', EVAL($deparsed),
      'Raku', EVAL(EVAL $raku)
    -> $type, \cont {
        my $desc = "$type: str declaration creates a native str container";
        multi check(str $x) { pass $desc }
        multi check($x) { flunk $desc }

        check(cont);
        is-deeply cont, '',
          "$type: Native str initialized to empty string by default";
    }
}

subtest 'Native int assign initializer works' => {
    # my int $native-int = 963; $native-int
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil => '$',
          desigilname => RakuAST::Name.from-identifier('native-int'),
          type => RakuAST::Type::Simple.new(
            RakuAST::Name.from-identifier('int')
          ),
          initializer => RakuAST::Initializer::Assign.new(
            RakuAST::IntLiteral.new(963)
          )
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Var::Lexical.new('$native-int')
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
my int $native-int = 963;
$native-int
CODE

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

subtest 'Native num assign initializer works' => {
    # my num $native-num = 96e3; $native-num
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil => '$',
          desigilname => RakuAST::Name.from-identifier('native-num'),
          type => RakuAST::Type::Simple.new(
            RakuAST::Name.from-identifier('num')
          ),
          initializer => RakuAST::Initializer::Assign.new(
            RakuAST::NumLiteral.new(96e3)
          )
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Var::Lexical.new('$native-num')
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
my num $native-num = 96000e0;
$native-num
CODE

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

subtest 'Native str assign initializer works' => {
    # my str $native-str = 'nine six three'; $native-str
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil => '$',
          desigilname => RakuAST::Name.from-identifier('native-str'),
          type => RakuAST::Type::Simple.new(
            RakuAST::Name.from-identifier('str')
          ),
          initializer => RakuAST::Initializer::Assign.new(
            RakuAST::StrLiteral.new('nine six three')
          )
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Var::Lexical.new('$native-str')
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
my str $native-str = "nine six three";
$native-str
CODE

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

{
    my module M {
        our $var;

        # our $var; $var
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => 'our',
              sigil => '$',
              desigilname => RakuAST::Name.from-identifier('var'),
            ),
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('$var')
          ),
        );
        is-deeply $deparsed, q:to/CODE/, 'deparse';
our $var;
$var
CODE

        subtest 'AST: our-scoped variable declaration without initializer takes current value (eval mode)' => {
            $var = 66;
            is-deeply EVAL($ast), 66, 'eval AST';
            is-deeply $var, 66, 'Value intact after eval';
        }

        subtest 'Str: our-scoped variable declaration without initializer takes current value (eval mode)' => {
            $var = 99;
            is-deeply EVAL($ast), 99, 'eval Str';
            is-deeply $var, 99, 'Value intact after eval';
        }

        subtest 'Raku: our-scoped variable declaration without initializer takes current value (eval mode)' => {
            $var = 137;
            is-deeply EVAL($ast), 137, 'eval Str';
            is-deeply $var, 137, 'Value intact after eval';
        }

        # our $x = 42; $x
        ast RakuAST::StatementList.new(
          RakuAST::Statement::Expression.new(
            expression => RakuAST::VarDeclaration::Simple.new(
              scope => 'our',
              sigil => '$',
              desigilname => RakuAST::Name.from-identifier('x'),
              initializer => RakuAST::Initializer::Assign.new(
                RakuAST::IntLiteral.new(42)
              )
            ),
          ),
          RakuAST::Statement::Expression.new(
            expression => RakuAST::Var::Lexical.new('$x')
          ),
        );
        is-deeply $deparsed, q:to/CODE/, 'deparse';
our $x = 42;
$x
CODE

        subtest 'our-scoped variable declaration with initializer works (eval mode)' => {
            is-deeply $_, 42
              for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku);
        }

        # our $y = 99; $y
        ast RakuAST::CompUnit.new(
          :!eval,
          :comp-unit-name('TEST_1'),
          :statement-list(RakuAST::StatementList.new(
            RakuAST::Statement::Expression.new(
              expression => RakuAST::VarDeclaration::Simple.new(
                scope => 'our',
                sigil => '$',
                desigilname => RakuAST::Name.from-identifier('y'),
                initializer => RakuAST::Initializer::Assign.new(
                  RakuAST::IntLiteral.new(99)
                )
              ),
            ),
            RakuAST::Statement::Expression.new(
              expression => RakuAST::Var::Lexical.new('$y')
            )
          ))
        );
        is-deeply $deparsed, q:to/CODE/, 'deparse';
our $y = 99;
$y;
CODE

        # There is no equivalent string representation of a compilation unit
        # so we cannot actually test the DEPARSE of the ast, as that would
        # interfere with other tests.
        is-deeply EVAL($ast), 99,
          'our-scoped variable declaration with initializer works (top-level mode)';
    }
    is-deeply $M::x, 42, 'our variable set in eval mode is installed into the current package';
    ok $M::x.VAR ~~ Scalar, 'It is a bound scalar';
    todo "NYI";
    nok M.WHO<$y>:exists, 'our-scoped variable declaration in top-level comp unit does not leak out';
}

subtest 'A pointy block node with a state variable' => {
    # -> { state $foo = 42; $foo++ }
    ast RakuAST::PointyBlock.new(
      signature => RakuAST::Signature.new(
        parameters => ()
      ),
      body => RakuAST::StatementList.new(
        RakuAST::Statement::Expression.new(
          expression => RakuAST::Blockoid.new(
            RakuAST::StatementList.new(
              RakuAST::Statement::Expression.new(
                expression => RakuAST::VarDeclaration::Simple.new(
                  scope       => 'state',
                  sigil       => '$',
                  desigilname => RakuAST::Name.from-identifier('foo'),
                  initializer => RakuAST::Initializer::Assign.new(
                    RakuAST::IntLiteral.new(42)
                  )
                )
              ),
              RakuAST::Statement::Expression.new(
                expression => RakuAST::ApplyPostfix.new(
                  postfix => RakuAST::Postfix.new(:operator<++>),
                  operand => RakuAST::Var::Lexical.new('$foo')
                )
              )
            )
          )
        )
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
-> {
    state $foo = 42;
    $foo++
}
CODE

    for
      'AST', EVAL($ast),
      'Str', EVAL($deparsed),
      'Raku', EVAL(EVAL $raku)
    -> $type, $block {
        is $block(), 42, "$type: state variable initialized";
        is $block(), 43, "$type: state variable kept value";
    }
}

subtest 'Term (sigilless) variable declaration' => {
    # my \foo = 111; foo
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Term.new(
          scope => 'my',
          name  => RakuAST::Name.from-identifier('foo'),
          initializer => RakuAST::Initializer::Assign.new(
            RakuAST::IntLiteral.new(111)
          )
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::Term::Name.new(RakuAST::Name.from-identifier('foo'))
      )
    );
    is-deeply $deparsed, q:to/CODE/, 'deparse';
my \foo = 111;
foo
CODE

    for
      'AST', EVAL($ast),
      'Str', EVAL($deparsed),
      'Raku', EVAL(EVAL $raku)
    -> $type, \value {
        is-deeply value, 111, "$type: sigilless variable initialized with correct value";
        ok value.VAR =:= value, "$type: no container produced";
    }
}

subtest 'Lexical constant' => {
    # constant foo = "bar"
    ast RakuAST::Statement::Expression.new(
      expression => RakuAST::VarDeclaration::Constant.new(
        name        => "foo",
        initializer => RakuAST::Initializer::Assign.new(
          RakuAST::StrLiteral.new("bar")
        )
      )
    );
    is-deeply $deparsed, 'constant foo = "bar"', 'deparse';
    my package one {
        is-deeply EVAL($ast), "bar", "AST: did it produce 'bar'";
        is-deeply OUR::<foo>, "bar", "AST: was the constant installed";
    }
    my package two {
        is-deeply EVAL($deparsed), "bar", "Str: did it produce 'bar'";
        is-deeply OUR::<foo>, "bar", "Str: was the constant installed";
    }
    my package three {
        is-deeply EVAL(EVAL $raku), "bar", "Raku: did it produce 'bar'";
        is-deeply OUR::<foo>, "bar", "Raku: was the constant installed";
    }
}

subtest 'Lexical my constant' => {
    # my constant foo = "bar"
    ast RakuAST::Statement::Expression.new(
      expression => RakuAST::VarDeclaration::Constant.new(
        scope       => "my",
        name        => "foo",
        initializer => RakuAST::Initializer::Assign.new(
          RakuAST::StrLiteral.new("bar")
        )
      )
    );
    is-deeply $deparsed, 'my constant foo = "bar"', 'deparse';
    for "AST", $ast, "Str", $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), "bar", "$type: did it produce 'bar'";
        nok OUR::<foo>:exists, "$type: was the constant *not* installed";
    }
}

subtest 'Lexical my constant with expression' => {
    # my constant foo = now
    ast RakuAST::Statement::Expression.new(
      expression => RakuAST::VarDeclaration::Constant.new(
        scope       => "my",
        name        => "foo",
        initializer => RakuAST::Initializer::Assign.new(
          RakuAST::Term::Named.new("now")
        )
      )
    );
    is-deeply $deparsed, 'my constant foo = now', 'deparse';
    for
      'AST', (try EVAL $ast),
      'Str', EVAL($deparsed),
      'Raku', (try EVAL(EVAL $raku))
    -> $type, $it {
        todo "Unknown compilation input 'qast'"
          if $type eq 'AST' | 'Raku' && !%*ENV<RAKUDO_RAKUAST>;
        isa-ok $it, Instant, "$type: did it produce an Instant";
        nok $it == now, "$type: check that time has passed";
        nok OUR::<foo>:exists, "$type: was the constant *not* installed";
    }
}

subtest 'Special compiler variable $=pod' => {
    # $=pod
    ast RakuAST::Var::Doc.new('pod');

    is-deeply $deparsed, '$=pod', 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), [], "$type: EVAL";
    }
}

subtest 'Special compiler variable $=finish' => {
    # $=finish
    ast RakuAST::Var::Doc.new('finish');

    is-deeply $deparsed, '$=finish', 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), Mu, "$type: EVAL";
    }
}

subtest 'variable with a trait' => {
    # my $a is default(42)
    ast RakuAST::VarDeclaration::Simple.new(
      scope       => "my",
      sigil       => "\$",
      desigilname => RakuAST::Name.from-identifier("a"),
      traits      => (
        RakuAST::Trait::Is.new(
          name     => RakuAST::Name.from-identifier("default"),
          argument => RakuAST::Circumfix::Parentheses.new(
            RakuAST::SemiList.new(
              RakuAST::Statement::Expression.new(
                expression => RakuAST::IntLiteral.new(42)
              )
            )
          )
        ),
      )
    );

    is-deeply $deparsed, 'my $a is default(42)', 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), 42, "$type: EVAL";
    }
}

subtest 'scalar variable with a where guard (passing initializer)' => {
    ast RakuAST::VarDeclaration::Simple.new(
      sigil       => "\$",
      desigilname => RakuAST::Name.from-identifier("f"),
      initializer => RakuAST::Initializer::Assign.new(
        RakuAST::IntLiteral.new(5)
      ),
      where       => RakuAST::IntLiteral.new(5)
    );

    is-deeply $deparsed, 'my $f where 5 = 5', 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), 5, "$type: EVAL";
    }
}

subtest 'scalar variable with a where guard (failing initializer)' => {
    ast RakuAST::VarDeclaration::Simple.new(
      sigil       => "\$",
      desigilname => RakuAST::Name.from-identifier("f"),
      initializer => RakuAST::Initializer::Assign.new(
       RakuAST::IntLiteral.new(6)
      ),
      where       => RakuAST::IntLiteral.new(5)
    );

    is-deeply $deparsed, 'my $f where 5 = 6', 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        throws-like { EVAL($it) }, X::TypeCheck::Assignment, "$type: EVAL";
    }
}

subtest 'array variable with a where guard (passing initializer)' => {
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil       => "\@",
          desigilname => RakuAST::Name.from-identifier("h"),
          initializer => RakuAST::Initializer::Assign.new(
            RakuAST::IntLiteral.new(5)
          ),
          where       => RakuAST::IntLiteral.new(5)
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::ApplyPostfix.new(
          operand => RakuAST::Var::Lexical.new("\@h"),
          postfix => RakuAST::Postcircumfix::ArrayIndex.new(
            index => RakuAST::SemiList.new(
              RakuAST::Statement::Expression.new(
                expression => RakuAST::IntLiteral.new(0)
              )
            )
          )
        )
      )
    );
    my $output = Q:to/END/;
my @h where 5 = 5;
@h[0]
END
    is-deeply $deparsed, $output, 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), 5, "$type: EVAL";
    }
}

subtest 'array variable with a where guard (failing initializer)' => {
    ast RakuAST::VarDeclaration::Simple.new(
      sigil       => "\@",
      desigilname => RakuAST::Name.from-identifier("h"),
      initializer => RakuAST::Initializer::Assign.new(
        RakuAST::IntLiteral.new(6)
      ),
      where       => RakuAST::IntLiteral.new(5)
    );

    is-deeply $deparsed, 'my @h where 5 = 6', 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        throws-like { EVAL($it) }, X::TypeCheck::Assignment, "$type: EVAL";
    }
}

subtest 'hash variable with a where guard (passing initializer)' => {
    ast RakuAST::StatementList.new(
      RakuAST::Statement::Expression.new(
        expression => RakuAST::VarDeclaration::Simple.new(
          sigil       => "\%",
          desigilname => RakuAST::Name.from-identifier("h"),
          initializer => RakuAST::Initializer::Assign.new(
            RakuAST::ColonPair::Number.new(
              key   => "s",
              value => RakuAST::IntLiteral.new(5)
            )
          ),
          where       => RakuAST::IntLiteral.new(5)
        )
      ),
      RakuAST::Statement::Expression.new(
        expression => RakuAST::ApplyPostfix.new(
          operand => RakuAST::Var::Lexical.new("\%h"),
          postfix => RakuAST::Postcircumfix::LiteralHashIndex.new(
            index => RakuAST::QuotedString.new(
              processors => <words val>,
              segments   => (
                RakuAST::StrLiteral.new("s"),
              )
            )
          )
        )
      )
    );

    my $output = Q:to/END/;
my %h where 5 = :5s;
%h<s>
END
    is-deeply $deparsed, $output, 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        is-deeply EVAL($it), 5, "$type: EVAL";
    }
}

subtest 'hash variable with a where guard (failing initializer)' => {
    ast RakuAST::VarDeclaration::Simple.new(
      sigil       => "\%",
      desigilname => RakuAST::Name.from-identifier("h"),
      initializer => RakuAST::Initializer::Assign.new(
        RakuAST::ColonPair::Number.new(
          key   => "s",
          value => RakuAST::IntLiteral.new(6)
        )
      ),
      where       => RakuAST::IntLiteral.new(5)
    );

    is-deeply $deparsed, 'my %h where 5 = :6s', 'deparse';
    for 'AST', $ast, 'Str', $deparsed, 'Raku', EVAL($raku) -> $type, $it {
        throws-like { EVAL($it) }, X::TypeCheck::Assignment, "$type: EVAL";
    }
}
# vim: expandtab shiftwidth=4
