Adding user mode types for Perl 6 NativeCall structs
Asked Answered
C

1

5

The Perl 6 docs list a bunch of types. Some of them, such as Str, have more complicated box/unbox behaviors.

Is it possible to define my own type, specifying my own routines for the box/unboxing? For a particular project, I have a bunch of types I'm reusing, and basically cut/pasting my accessor functions over and over.

For example, the C Struct uses a time_t, and I plug in accessor methods to go to/from a DateTime. Another example is a comma-separated list, I'd like to go to/from an Array and take care of the split/join automagically.

Is there a better way to do this?

Edit: Add Example:

constant time_t = uint64;
constant FooType_t = uint16;

enum FooType <A B C>;

class Foo is repr('CStruct') is rw
{
    has uint32    $.id;
    has Str       $.name;
    has FooType_t $.type;
    has time_t    $.time;

    method name(Str $n?) {
        $!name := $n with $n;
        $!name;
    }

    method type(FooType $t?) {
        $!type = $t with $t;
        FooType($!type);
    }

    method time(DateTime $d?) {
        $!time = .Instant.to-posix[0].Int with $d;
        DateTime.new($!time)
    }
}

my $f = Foo.new;
$f.id = 12;
$f.name('myname');
$f.type(B);
$f.time(DateTime.new('2000-01-01T12:34:56Z'));

say "$f.id() $f.name() $f.type() $f.time()";

# 12 myname B 2000-01-01T12:34:56Z

This works, I can set the various fields of the CStruct in Perl-ish ways (no lvalue, but I can pass them in as parameters).

Now I want to use time_t, FooType_t, etc. for many fields in a lot of structs and have them act the same way. Is there a better way other than to just copy those methods over and over?

Maybe macros could help here? I haven't mastered them yet.

Concordia answered 6/5, 2017 at 14:10 Comment(2)
Memory management becomes challenging as I approach some of this too. Probably best to treat the CStruct very raw and layer a smarter Class on top.Concordia
BTW: I love NativeCall. It is amazing how easy it is to interface with C libraries.Concordia
J
8

You could write a trait that handles automatic attribute conversion on fetching or storing the attribute. The following should get you started:

multi sub trait_mod:<is>(Attribute:D $attr, :$autoconv!) {
    use nqp;
    my $name := $attr.name;
    $attr.package.^add_method: $name.substr(2), do given $attr.type {
        when .REPR eq 'P6int' {
            method () is rw {
                my $self := self;
                Proxy.new:
                    FETCH => method () {
                        $autoconv.out(nqp::getattr_i($self, $self.WHAT, $name));
                    },
                    STORE => method ($_) {
                        nqp::bindattr_i($self, $self.WHAT, $name,
                            nqp::decont($autoconv.in($_)));
                    }
            }
        }

        default {
            die "FIXME: no idea how to handle {.^name}";
        }
    }
}

For example, take your use case of time_t:

constant time_t = uint64;

class CTimeConversion {
    multi method in(Int $_ --> time_t) { $_ }
    multi method in(DateTime $_ --> time_t) { .posix }
    method out(time_t $_ --> DateTime) { DateTime.new($_) }
}

class CTimeSpan is repr<CStruct> {
    has time_t $.start is autoconv(CTimeConversion);
    has time_t $.end is autoconv(CTimeConversion);
}

Finally, some example code to show it works:

my $span = CTimeSpan.new;
say $span;
say $span.end;

$span.end = DateTime.now;
say $span;
say $span.end;
Jumbo answered 7/5, 2017 at 9:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.