tpl Perl API
============
Troy D. Hanson <troydhanson@comcast.net>
v1.1, April 2007

include::sflogo.txt[]
include::topnav.txt[]

Perl API
--------
include::toc.txt[]

The Perl API for reading and writing tpl is nearly identical to the C API. This
document will briefly explain the Perl API and provide examples. The chief
motivation for having a Perl API is to communicate with C programs that use tpl.

[TIP]
.Start with the C API
This document assumes familiarity with the C API.  The concepts of using tpl
are not explained here. For an introduction to tpl and its C API, see the
link:userguide.html[User Guide]. 

Tpl.pm
~~~~~~
The `Tpl.pm` file (in the `lang/perl`) directory contains the Perl module.  You
can copy it to another directory if you wish. Your Perl program may need to
include a `use lib` statement to find the module.

    #!/usr/bin/perl
    use lib "/some/directory";
    use Tpl;

tpl_map
~~~~~~~
This function resembles the C version, except that it's invoked via the `Tpl`
module, and it takes references to Perl variables after the format string.

    my $i;
    my $tpl = Tpl->tpl_map("A(i)",\$i);

The return value is a tpl object; all other API calls are object methods.
Incidentally, there is no `tpl_free()` method corresponding to the C API.  

Fixed-length arrays
^^^^^^^^^^^^^^^^^^^
Format strings such as `i#` denote a fixed-length array. In the Perl API,
fixed-length arrays require two arguments: a list reference, and the fixed
length. For example:

    my @x;
    my $tpl = Tpl->tpl_map("i#", \@x, 10);

When fixed-length arrays are packed or unpacked, the specified number of
elements will be copied from (or placed into) the designated list.

Structures
^^^^^^^^^^
Format strings containing `S(...)` are handled in the Perl API as if only the
interior, parenthesized part was present. (It does not work like the C API). So
simply ignore the `S(...)` and consider only its interior format characters when
constructing the argument list:
    
    my ($str, $int);
    my $tpl = Tpl->tpl_map("S(si)", \$str, \$int);

It really only makes sense to use `S(...)` in a format string in the Perl API if
you are communicating with a C program that uses structures. 

tpl_pack
~~~~~~~~
This is nearly identical to the C version. The only argument is the index
number to pack.

    $tpl->tpl_pack(1);

tpl_dump
~~~~~~~~
This method is a little different than the C version. Given no arguments, it
returns the tpl image; given one argument it writes a file with that name.

    $tpl->tpl_dump("demo.tpl");   # writes demo.tpl

Or,

    my $img = $tpl->tpl_dump();   

The tpl image is a binary buffer. You can do whatever you want with it, such as
write it to a socket or pipe (probably to C program listening on the other end),
or save it somewhere and later re-load it using `tpl_load()`.

tpl_load
~~~~~~~~
This method loads a tpl image from a file or from a Perl variable.  It takes
one argument. If it's not a reference, it's assumed to be a filename to load.

    $tpl->tpl_load("demo.tpl");

Otherwise, if the argument is a Perl reference, it's construed as a variable
containing the tpl image:

    $tpl->tpl_load(\$img);

The method will `die` if the image is invalid or the file doesn't exist. You
can wrap it with `eval` to catch such errors:

    eval { $tpl->tpl_load(\$img); };
    print "failed to load\n" if $@;

tpl_unpack
~~~~~~~~~~
This is nearly identical to the C version. The only argument is the index
number to unpack.

    $tpl->tpl_unpack(1);

Examples
--------

Integer array
~~~~~~~~~~~~~

.Packing A(i) to file
--------------------------------------------------------------------------------
#!/usr/bin/perl

use strict;
use warnings;

use Tpl;

my $i;
my $tpl = Tpl->tpl_map("A(i)",\$i);
for($i=0; $i<10; $i++) { 
    $tpl->tpl_pack(1); 
}
$tpl->tpl_dump("demo.tpl");
--------------------------------------------------------------------------------

.Unpacking A(i) from file
--------------------------------------------------------------------------------
#!/usr/bin/perl

use strict;
use warnings;

use Tpl;

my $j;
my $tpl2 = Tpl->tpl_map("A(i)",\$j);
$tpl2->tpl_load("demo.tpl");
while($tpl2->tpl_unpack(1) > 0) { 
    print "$j\n";
}
--------------------------------------------------------------------------------

Message-passing
~~~~~~~~~~~~~~~
While the bulk of this example is socket handling, it demonstrates how you can
use tpl as a message-passing format. In the real-world, you might have a C
server and a Perl client, for example. In this example, we'll code both a client
and a server in Perl.

.A server that sums integers
********************************************************************************
Programming literature is rife with contrived examples so we will follow in that
tradition. Our server will do no more than sum a list of integers. But in doing
so it will demonstrate message passing adequately. Both its input (the integer
array) and its output (an integer) are tpl images, passed over a TCP/IP socket.
********************************************************************************

Server
^^^^^^
The server waits for a connection from a client. When it gets one, it accepts
the connection and immediately forks a child process to handle it. Then it goes
back to waiting for another new connection.

The server child process handles the client by loading and unpacking the tpl
image sent by the client (containing an array of integers). It calculates their
sum and constructs a new tpl image containing the sum, which it sends back to
the client.

.Server
--------------------------------------------------------------------------------
#!/usr/bin/perl

use strict;
use warnings;

use IO::Socket::INET;
use Tpl;

$SIG{CHLD} = "IGNORE"; # don't create zombies

our $port = 2000;

sub handle_client {
    my $client = shift;

    undef $/;
    my $request = <$client>; # get request (slurp) 

    # read input array, and calculate total
    my ($i,$total);
    my $tpl = Tpl->tpl_map("A(i)", \$i);
    eval { $tpl->tpl_load(\$request); };
    die "received invalid tpl" if $@;
    $total += $i while $tpl->tpl_unpack(1) > 0;

    # formulate response and send
    my $tpl2 = Tpl->tpl_map("i", \$total);
    $tpl2->tpl_pack(0);
    my $response = $tpl2->tpl_dump();
    print $client $response;
    close $client;
}

my $server = IO::Socket::INET->new(LocalPort => $port,
                   Type => SOCK_STREAM,
                   Reuse => 1,
                   Listen => 10 )
        or die "Can't listen on port $port: $!\n";

while (1) {
    my $client = $server->accept();
    next unless $client;
    # new connection
    my $pid = fork;
    die "can't fork: $!\n" unless defined $pid;
    if ($pid > 0) {
        # parent
        close $client;
    } elsif ($pid == 0) {
        # child
        handle_client($client);
        exit(0);
    }
}
close ($server);
--------------------------------------------------------------------------------

Client
^^^^^^

The client is a simpler program. It constructs the tpl image containing the
integer array (taken from its command-line arguments), connects to the server
and sends the tpl image to it, and then awaits the response tpl. The response
containing the sum is loaded, unpacked and printed.

.Client
--------------------------------------------------------------------------------
#!/usr/bin/perl

use strict;
use warnings;

use IO::Socket::INET;
use Tpl;

our $port = 2000;

# construct tpl
my $i;
my $tpl = Tpl->tpl_map("A(i)",\$i);
$tpl->tpl_pack(1) while ($i=shift @ARGV);
my $request = $tpl->tpl_dump();

# send to server, get response
my $socket = IO::Socket::INET->new("localhost:$port") or die "can't connect";
print $socket $request;
shutdown($socket,1);	    # done writing (half-close)
undef $/;
my $response = <$socket>;	# get reply (slurp)

# decode response (or print error)
my $total;
my $tpl2 = Tpl->tpl_map("i", \$total);
eval { $tpl2->tpl_load(\$response); };
die "invalid response\n" if $@;
$tpl2->tpl_unpack(0);
print "total is $total\n";
--------------------------------------------------------------------------------

Running thise example
^^^^^^^^^^^^^^^^^^^^^
If the client and server programs are in `client.pl` and `server.pl`, then
you can run the example by starting the server in one window:

        ./server.pl

Then run the client in another window. E.g.,

        ./client.pl 1 2 3 4 5

The client runs and then exits, printing:

        total is 15

You can re-run the client with different arguments. When done, type `Ctrl-C` in
the server window to terminate it.

// vim: set tw=80 wm=2 syntax=asciidoc: 

