package FFI::Platypus::Lang::Go;

use strict;
use warnings;
use 5.008001;
use File::ShareDir::Dist 0.07 qw( dist_config );

# ABSTRACT: Documentation and tools for using Platypus with Go
our $VERSION = '0.02'; # VERSION


my $config;

sub _config
{
  unless($config)
  {
    $config = dist_config 'FFI-Platypus-Lang-Go';
    # running out of an unbuilt git, probe for types on the fly
    if(!%$config && -f 'inc/mymm-build.pl')
    {
      my $clean = 0;
      if(!-f 'blib')
      {
        require Capture::Tiny;
        my($out, $exit) = Capture::Tiny::capture_merged(sub {
          my @cmd = ($^X, 'inc/mymm-build.pl');
          print "+@cmd\n";
          system @cmd;
        });
        if($exit)
        {
          require File::Path;
          File::Path::rmtree('blib',0,0);
          print STDERR $out;
          die "probe of go types failed";
        }
        else
        {
          $clean = 1;
        }
      }
      $config = do './blib/lib/auto/share/dist/FFI-Platypus-Lang-Go/config.pl';
      if($clean)
      {
        require File::Path;
        File::Path::rmtree('blib',0,0);
      }
    }
  }

  $config;
}

sub native_type_map
{
  _config->{go_types};
}

sub load_custom_types
{
  my(undef, $ffi) = @_;
  $ffi->load_custom_type('::GoString' => 'gostring');
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

FFI::Platypus::Lang::Go - Documentation and tools for using Platypus with Go

=head1 VERSION

version 0.02

=head1 SYNOPSIS

Go code:

 package main
 
 import "C"
 
 //export add
 func add(x, y int) int {
     return x + y
 }
 
 func main() {}

Perl code:

 use FFI::Platypus 2.00;
 use FFI::CheckLib qw( find_lib_or_die );
 use File::Basename qw( dirname );
 
 my $ffi = FFI::Platypus->new(
   api  => 2,
   lib  => './add.so',
   lang => 'Go',
 );
 $ffi->attach( add => ['goint', 'goint'] => 'goint' );
 
 print add(1,2), "\n";  # prints 3

=head1 DESCRIPTION

This distribution is the Go language plugin for Platypus.
It provides the definition for native Go types, like
C<goint> and C<gostring>.  It also provides a L<FFI::Build>
interface for building Perl extensions written in Go (see
L<FFI::Build::File::GoMod> for details).

=head1 EXAMPLES

The examples in this discussion are bundled with this
distribution and can be found in the C<examples> directory.

=head2 Passing and Returning Integers

=head3 Go

 package main
 
 import "C"
 
 //export add
 func add(x, y int) int {
     return x + y
 }
 
 func main() {}

=head3 Perl

 use FFI::Platypus 2.00;
 use FFI::CheckLib qw( find_lib_or_die );
 use File::Basename qw( dirname );
 
 my $ffi = FFI::Platypus->new(
   api  => 2,
   lib  => './add.so',
   lang => 'Go',
 );
 $ffi->attach( add => ['goint', 'goint'] => 'goint' );
 
 print add(1,2), "\n";  # prints 3

=head3 Execute

 $ go build -o add.so -buildmode=c-shared add.go
 $ perl add.pl
 3

=head3 Discussion

The Go code has to:

=over 4

=item 1 Import the pseudo package C<"C">

=item 2 Mark any exported function with the command C<//export>

=item 3 Include a C<main> function, even if you do not use it.

=back

From the Perl side, the Go types have a C<go> prefix, so C<int>
in Go is C<goint> in Perl.

Aside from that passing basic types like integers and floats
is trivial with FFI.

=head2 Module

=head3 Go

 /*
  * borrowed from
  * https://medium.com/learning-the-go-programming-language/calling-go-functions-from-other-languages-4c7d8bcc69bf
  */
 
 package main
 
 import "C"
 
 import (
 	"fmt"
 	"math"
 	"sort"
 	"sync"
 )
 
 var count int
 var mtx sync.Mutex
 
 //export Add
 func Add(a, b int) int { return a + b }
 
 //export Cosine
 func Cosine(x float64) float64 { return math.Cos(x) }
 
 //export Sort
 func Sort(vals []int) { sort.Ints(vals) }
 
 //export Log
 func Log(msg string) int {
 	mtx.Lock()
 	defer mtx.Unlock()
 	fmt.Println(msg)
 	count++
 	return count
 }
 
 func main() {}

=head3 Perl

Module:

 package Awesome::FFI;
 
 use strict;
 use warnings;
 use FFI::Platypus;
 use FFI::Go::String;
 use base qw( Exporter );
 
 our @EXPORT_OK = qw( Add Cosine Log );
 
 my $ffi = FFI::Platypus->new( api => 1, lang => 'Go' );
 # See FFI::Platypus::Bundle for the how and why
 # bundle works.
 $ffi->bundle;
 
 $ffi->attach( Add    => ['goint','goint'] => 'goint'     );
 $ffi->attach( Cosine => ['gofloat64'    ] => 'gofloat64' );
 $ffi->attach( Log    => ['gostring'     ] => 'goint'     );
 
 1;

Test:

 use Test2::V0 -no_srand => 1;
 use Awesome::FFI qw( Add Cosine Log );
 use Capture::Tiny qw( capture );
 use FFI::Go::String;
 
 is( Add(1,2), 3 );
 is( Cosine(0), 1.0 );
 
 is(
   [capture { Log("Hello Perl!") }],
   ["Hello Perl!\n", '', 1]
 );
 
 done_testing;

=head3 Execute

 $ prove -lvm t/awesome_ffi.t
 t/awesome_ffi.t ..
 ok 1
 ok 2
 ok 3
 1..3
 ok
 All tests successful.
 Files=1, Tests=3,  1 wallclock secs ( 0.01 usr  0.00 sys +  1.28 cusr  0.48 csys =  1.77 CPU)
 Result: PASS

=head3 Discussion

This is a full working example of a Perl distribution / module
included in the C<examples/Awesome-FFI> directory.

=head1 SEE ALSO

=over 4

=item L<FFI::Platypus>

More about FFI and Platypus itself.

=item L<FFI::Platypus::Type::GoString>

Type plugin for the go string type.

=item L<FFI::Go::String>

Low level interface to the go string type.

=item L<FFI::Build::File::GoMod>

L<FFI::Build> class for handling Go modules.

=item L<Calling Go Functions from Other Languages using C Shared Libraries|https://github.com/vladimirvivien/go-cshared-examples>

=back

=head1 AUTHOR

Graham Ollis <plicease@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2018-2022 by Graham Ollis.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut