
# possible syntax for Signature DSL
my $Bit = new signature q|

role ANY {}

role Coerceable [::T] {
    to_bit (::T $self:) returns BIT;
    to_str (::T $self:) returns STR;  
    to_num (::T $self:) returns NUM;            
}

role BIT {        
    does Coerceable[BIT];
    
    not    (BIT $self:)              returns BIT;      
    
    and    (BIT $self: BLOCK $block) returns BIT;      
    or     (BIT $self: BLOCK $block) returns BIT;          
}

role OPAQUE {
    does Coerceable[OPAQUE];
    
    id           (OPAQUE $self:)                          returns NUM;            
    
    class        (OPAQUE $self:)                          returns OPAQUE;
    change_class (OPAQUE $self: OPAQUE $class)            returns NIL;
    
    get_attr     (OPAQUE $self: SYMBOL $name)             returns ANY;
    set_attr     (OPAQUE $self: SYMBOL $name, ANY $value) returns NIL;
    add_attr     (OPAQUE $self: SYMBOL $name)             returns NIL;    
}

class Bit does BIT { 
    map_native "Perl6::Core::Bit";
}

class Opaque does OPAQUE {
    map_native "Perl6::MM::Opaque";
}

|;

# the raw p5 code which the DSL might generate
my $Bit = module::signature->new(
    symbol->new('to_bit') => method::signature->new(
        params => method::params->new(symbol->new('$self:' => 'Bit')),
        returns => 'Bit'
    ),
    symbol->new('to_str') => method::signature->new(
        params => method::params->new(symbol->new('$self:' => 'Bit')),
        returns => 'Str'
    ),
    symbol->new('to_num') => method::signature->new(
        params => method::params->new(symbol->new('$self:' => 'Bit')),
        returns => 'Num'
    ),
    symbol->new('not') => method::signature->new(
        params => method::params->new(symbol->new('$self:' => 'Bit')),
        returns => 'Bit'
    ),      
    symbol->new('and') => method::signature->new(
        params => method::params->new(
            symbol->new('$self:' => 'Bit'),
            symbol->new('$block' => 'Block')
        ),
        returns => 'Bit'
    ),                 
    symbol->new('or') => method::signature->new(
        params => method::params->new(
            symbol->new('$self:' => 'Bit'),
            symbol->new('$block' => 'Block')
        ),
        returns => 'Bit'
    ),                             
);

# create a new module which 
*Bit = module->new(
            signature => $BIT, 
            native => 'Perl6::Core::Bit'
        );

# now we have the Bit available
Bit->new(1);


# code sketch of how we can wrap a native
# function with a signature
my $wrapped_or = module::native::wrap(
                     $Bit->get_signature(symbol->new('or'))
                     \&bit::or
                 );

sub module::native::wrap {
    my ($sig, $body) = @_;
    closure->new(
        $e,
        $sig->params,
        sub {
            my $e = shift;
            # extract the args from the environment
            my @args;
            foreach my $param ($sig->params->to_native) {
                my $name = $param->name;
                push @args => $e->get($name->to_native);
            }
            # execute the body of the method
            return $body->(@args);    
            # closure will automatically check the type of the return value ...
        }
    );
}
