develooper Front page | perl.perl5.porters | Postings from December 2014

All I want for Christmas is: Destructive unpack()

Thread Next
From:
Paul "LeoNerd" Evans
Date:
December 25, 2014 15:03
Subject:
All I want for Christmas is: Destructive unpack()
Message ID:
20141225150315.13a973a0@shy.leonerd.org.uk
The pack() function is great for really easily building strings, or
outputting to files, binary structured data:

  sub strpack_elem
  {
    my $self = shift;
    my ( $key, $value ) = @_;
    $self->{str} .= pack( "N/a* N/a*", $key, $value );
  }

  sub fpack_elem
  {
    my $self = shift;
    my ( $key, $value ) = @_;
    $self->print( pack( "N/a* N/a*", $key, $value ) );
  }

This works great because the string-contact operator and $fh->print
operation both append new data to the output, and advances the position
so the next append operation will work correctly.

The same however, sadly cannot be said of unpack(). All that unpack()
can do is return values by looking at the string of input you give it.
That string MUST be long enough, or it fails in non-obvious ways [1].
Further, unpack gives no hint on how many bytes of input it wanted to
consume, requiring any of several additional hacks to get that length
somehow. About the neatest I could write the converse operations to
theabove is

  sub strunpack_elem
  {
    my $self = shift;
    length $self->{str} >= 4 or die "Need more";
    my ( $keylen ) = unpack( "N", $self->{str} );
    length $self->{str} >= 8 + $keylen or die "Need more";
    my ( $vallen ) = unpack( "N", substr $self->{str}, 4 + $keylen, 4 );
    length $self->{str} >= 8 + $keylen + $vallen or die "Need more";

    # Now committed
    substr( $self->{str}, 0, 4, "" ); # keylen
    my $key = substr( $self->{str}, 0, $keylen, "" );
    substr( $self->{str}, 0, 4, "" ); # vallen
    my $val = substr( $self->{str}, 0, $vallen, "" );

    return ( $key, $val );
  }

  sub funpack_elem
  {
    my $self = shift;
    my $buf = $self->read( 4 );
    my ( $keylen ) = unpack( "N", $buf );
    my $key = $self->read( $keylen );

    $buf = $self->read( 4 );
    my ( $vallen ) = unpack( "N", $buf );
    my $val = $self->read( $vallen );

    return ( $key, $val );
  }

I think we can all agree that the latter functions are much more
horrible and less readable than the pack versions - these make the
logic much harder to see, for all the machinery noise in the middle. 

I wrote a C library called "libpack", somewhat based on Perl's
pack/unpack functions [2]. This library has a bunch of destructive
unpack functions which consume bytes of the input string or filehandle
as they go, meaning the equivalents of the above are much neater to
write. It would be nice to have some of the neatness of that, in Perl
as well.

-----

In summary:

  I'd like Perl to provide destructive unpack()-like functions to work
  on a string buffer or a filehandle, atomically reading and consuming
  input data as they go, to either return the full result or take no
  action.

-----

[1]: consider the following:

  eval: [ unpack "N", "abcd" ]
  [ '1633837924' ]                   # OK

  eval: [ unpack "N", "abc" ]
  [  ]                               # Fair enough

  eval: [ unpack "N n", "abc" ]
  [ '24930' ]                        # Uhhmm....

  eval: [ unpack "N a2", "abc" ]
  [ 'ab' ]                           # Go home unpack(), you are drunk


[2]: http://www.leonerd.org.uk/code/libpack/doc.html


-- 
Paul "LeoNerd" Evans

leonerd@leonerd.org.uk
http://www.leonerd.org.uk/  |  https://metacpan.org/author/PEVANS

Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About