use v6.e.PREVIEW; use Test; plan 13; my $ast; my $deparsed; my $raku; my @type = ; sub ast(RakuAST::Node:D $node --> Nil) { $ast := $node; $deparsed := $node.DEPARSE; $raku := 'use experimental :rakuast; ' ~ $node.raku; diag $deparsed.chomp; } subtest 'Default type for block and routine' => { # sub ($param) { } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Var.new('$param'), ), ) ) ); is-deeply $deparsed, 'sub ($param) { }', 'deparse'; ok .signature.params[0].type =:= Any, "@type[$++]: Default type of sub" for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku); } subtest 'Default type for block and routine' => { # -> $param { } ast RakuAST::PointyBlock.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Var.new('$param'), ), ) ) ); is-deeply $deparsed, '-> $param { }', 'deparse'; ok .signature.params[0].type =:= Mu, "@type[$++]: Default type of block" for EVAL($ast), EVAL($deparsed), EVAL(EVAL $raku); } subtest 'Type constraint is enforced on Scalar' => { # sub (Int $x) { } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( type => RakuAST::Type::Simple.new( RakuAST::Name.from-identifier('Int') ), target => RakuAST::ParameterTarget::Var.new('$x'), ), ) ) ); is-deeply $deparsed, 'sub (Int $x) { }', 'deparse'; for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].type =:= Int, "$type: Specified type is introspectable"; lives-ok { $sub(42) }, "$type: Passing correct type lives"; dies-ok { $sub("foo") }, "$type: Passing wrong type dies"; } } subtest 'Anonymous parameter still enforces type constraint' => { # sub (Int) { } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( type => RakuAST::Type::Simple.new( RakuAST::Name.from-identifier('Int') ) ), ) ) ); is-deeply $deparsed, 'sub (Int) { }', 'deparse'; for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].type =:= Int, "$type: Specified type is introspectable"; lives-ok { $sub(42) }, "$type: Passing correct type lives"; dies-ok { $sub("foo") }, "$type: Passing wrong type dies"; } } subtest 'Optional Scalar parameter defaults to type object' => { # sub (Int $x?) { $x } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( type => RakuAST::Type::Simple.new( RakuAST::Name.from-identifier('Int') ), target => RakuAST::ParameterTarget::Var.new('$x'), optional => True, ), ) ), 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'; sub (Int $x?) { $x } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].optional, "$type: Parameter introspects as optional"; is $sub.arity, 0, "$type: Correct arity"; is $sub.count, 1, "$type: Correct count"; is-deeply $sub(66), 66, "$type: Passing an argument gets the value"; is-deeply $sub(), Int, "$type: Passing no argument gets the type object"; } } subtest 'One required named parameter' => { # sub (:$named!) { $named } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Var.new('$named'), names => ['named'], :!optional ), ) ), body => RakuAST::Blockoid.new( RakuAST::StatementList.new( RakuAST::Statement::Expression.new( expression => RakuAST::Var::Lexical.new('$named') ) ) ) ); is-deeply $deparsed, q:to/CODE/.chomp, 'deparse'; sub (:$named!) { $named } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { is $sub.signature.params.elems, 1, "$type: Sub has one params elem"; is-deeply $sub.arity, 0, "$type: The block has 0 arity"; is-deeply $sub.count, 0, "$type: The block has 0 count"; given $sub.signature.params[0] { is-deeply .name, '$named', "$type: Correct variable name"; is-deeply .named_names, ('named',), "$type: Correct named name"; nok .optional, "$type: It is not optional"; } is $sub(named => 99), 99, "$type: Invoking the sub with a named argument works"; dies-ok { $sub() }, "$type: Invoking the sub without an argument dies"; } } subtest 'Required named parameter with alias' => { # sub (:first(:fst($var))!) { $var } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Var.new('$var'), names => ['first', 'fst'], :!optional ), ) ), body => RakuAST::Blockoid.new( RakuAST::StatementList.new( RakuAST::Statement::Expression.new( expression => RakuAST::Var::Lexical.new('$var') ) ) ) ); is-deeply $deparsed, q:to/CODE/.chomp, 'deparse'; sub (:first(:fst($var))!) { $var } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { is $sub.signature.params.elems, 1, "$type: Sub has one params elem"; is-deeply $sub.arity, 0, "$type: The block has 0 arity"; is-deeply $sub.count, 0, "$type: The block has 0 count"; given $sub.signature.params[0] { is-deeply .name, '$var', "$type: Correct variable name"; is-deeply .named_names.sort, ('first','fst'), "$type: Correct named names"; nok .optional, "$type: It is not optional"; } is $sub(first => 33), 33, "$type: Invoking the sub with first alias works"; is $sub(fst => 44), 44, "$type: Invoking the sub with second alias works"; dies-ok { $sub() }, "$type: Invoking the sub without an argument dies"; dies-ok { $sub(var => 99) }, "$type: Invoking the sub with non-named name dies"; } } subtest 'Slurpy hash parameter' => { # sub (*%h) { %h } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Var.new('%h'), slurpy => RakuAST::Parameter::Slurpy::Flattened ), ) ), body => RakuAST::Blockoid.new( RakuAST::StatementList.new( RakuAST::Statement::Expression.new( expression => RakuAST::Var::Lexical.new('%h') ) ) ) ); is-deeply $deparsed, q:to/CODE/.chomp, 'deparse'; sub (*%h) { %h } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].slurpy, "$type: Parameter introspects as slurpy"; ok $sub.signature.params[0].named, "$type: Parameter introspects as named"; is $sub.arity, 0, "$type: Correct arity"; is $sub.count, 0, "$type: Correct count"; is-deeply $sub(), {}, "$type: Passing no argument gets empty hash"; is-deeply $sub(a => 1), {a => 1}, "$type: Passing one named argument has correct hash"; is-deeply $sub(a => 1, b => 2), {a => 1, b => 2}, "$type: Passing two named arguments has correct hash"; } } subtest 'Slurpy flattening array parameter' => { # sub (*@a) { @a } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Var.new('@a'), slurpy => RakuAST::Parameter::Slurpy::Flattened ), ) ), body => RakuAST::Blockoid.new( RakuAST::StatementList.new( RakuAST::Statement::Expression.new( expression => RakuAST::Var::Lexical.new('@a') ) ) ) ); is-deeply $deparsed, q:to/CODE/.chomp, 'deparse'; sub (*@a) { @a } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].slurpy, "$type: Parameter introspects as slurpy"; nok $sub.signature.params[0].named, "$type: Parameter does not introspect as named"; is $sub.arity, 0, "$type: Correct arity"; is $sub.count, Inf, "$type: Correct count"; is-deeply $sub(), [], "$type: Passing no argument gets empty array"; is-deeply $sub(1), [1], "$type: Passing one argument has correct array"; is-deeply $sub(1, 2), [1, 2], "$type: Passing two arguments has correct array"; is-deeply $sub(1, 2, (3, 4)), [1, 2, 3, 4], "$type: Flattening happens"; } } subtest 'Slurpy non-flattening array parameter' => { # sub (**@a) { @a } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Var.new('@a'), slurpy => RakuAST::Parameter::Slurpy::Unflattened ), ) ), body => RakuAST::Blockoid.new( RakuAST::StatementList.new( RakuAST::Statement::Expression.new( expression => RakuAST::Var::Lexical.new('@a') ) ) ) ); is-deeply $deparsed, q:to/CODE/.chomp, 'deparse'; sub (**@a) { @a } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].slurpy, "$type: Parameter introspects as slurpy"; nok $sub.signature.params[0].named, "$type: Parameter does not introspect as named"; is $sub.arity, 0, "$type: Correct arity"; is $sub.count, Inf, "$type: Correct count"; is-deeply $sub(), [], "$type: Passing no argument gets empty array"; is-deeply $sub(1), [1], "$type: Passing one argument has correct array"; is-deeply $sub(1, 2), [1, 2], "$type: Passing two arguments has correct array"; is-deeply $sub(1, 2, (3, 4)), [1, 2, (3, 4)], "$type: Flattening does not happen"; is-deeply $sub((3, 4)), [(3, 4),], "$type: Passing a list results in one-element array with the list"; } } subtest 'Slurpy single arg rule array parameter' => { # sub (+@a) { @a } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Var.new('@a'), slurpy => RakuAST::Parameter::Slurpy::SingleArgument ), ) ), body => RakuAST::Blockoid.new( RakuAST::StatementList.new( RakuAST::Statement::Expression.new( expression => RakuAST::Var::Lexical.new('@a') ) ) ) ); is-deeply $deparsed, q:to/CODE/.chomp, 'deparse'; sub (+@a) { @a } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].slurpy, "$type: Parameter introspects as slurpy"; nok $sub.signature.params[0].named, "$type: Parameter does not introspect as named"; is $sub.arity, 0, "$type: Correct arity"; is $sub.count, Inf, "$type: Correct count"; is-deeply $sub(), [], "$type: Passing no argument gets empty array"; is-deeply $sub(1), [1], "$type: Passing one argument has correct array"; is-deeply $sub(1, 2), [1, 2], "$type: Passing two arguments has correct array"; is-deeply $sub(1, 2, (3, 4)), [1, 2, (3, 4)], "$type: Flattening does not happen"; is-deeply $sub((3, 4)), [3, 4], "$type: Passing a list is treated as the single argument"; } } subtest 'Sigilless parameter' => { # sub (Int \trm) { term } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( type => RakuAST::Type::Simple.new( RakuAST::Name.from-identifier('Int') ), target => RakuAST::ParameterTarget::Term.new(RakuAST::Name.from-identifier('trm')), ), ) ), body => RakuAST::Blockoid.new( RakuAST::StatementList.new( RakuAST::Statement::Expression.new( expression => RakuAST::Term::Name.new(RakuAST::Name.from-identifier('trm')) ) ) ) ); is-deeply $deparsed, q:to/CODE/.chomp, 'deparse'; sub (Int \trm) { trm } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].type =:= Int, "$type: Type is introspectable"; is $sub.signature.params[0].name, 'trm', "$type: The name matches"; is $sub(42), 42, "$type: The term can be resolved in the sub"; dies-ok { $sub("foo") }, "$type: Passing wrong type dies"; } } subtest 'Capture parameter' => { # sub (|c) { c } ast RakuAST::Sub.new( signature => RakuAST::Signature.new( parameters => ( RakuAST::Parameter.new( target => RakuAST::ParameterTarget::Term.new(RakuAST::Name.from-identifier('c')), slurpy => RakuAST::Parameter::Slurpy::Capture ), ) ), body => RakuAST::Blockoid.new( RakuAST::StatementList.new( RakuAST::Statement::Expression.new( expression => RakuAST::Term::Name.new(RakuAST::Name.from-identifier('c')) ) ) ) ); is-deeply $deparsed, q:to/CODE/.chomp, 'deparse'; sub (|c) { c } CODE for 'AST', EVAL($ast), 'Str', EVAL($deparsed), 'Raku', EVAL(EVAL $raku) -> $type, $sub { ok $sub.signature.params[0].capture, "$type: Parameter introspects as a capture"; is $sub.arity, 0, "$type: Correct arity"; is $sub.count, Inf, "$type: Correct count"; is-deeply $sub(), \(), "$type: Passing no argument gets empty capture"; is-deeply $sub(1, 2), \(1,2), "$type: Passing positional arguments gets correct capture"; is-deeply $sub(1, 2, (3, 4)), \(1, 2, (3, 4)), "$type: Flattening does not happen"; is-deeply $sub(:b, :!b), \(b => True, b => False), "$type: Passing named arguments gets correct capture"; is-deeply $sub("foo", :bar), \("foo", bar => 'baz'), "$type: Passing a mix of positional and named arguments gets correct capture"; } } # vim: expandtab shiftwidth=4