Browse files

Refactor after some improvements in rakudo, and now

that a (leaky) workaround for diamond composition is available:

Make Sum::Partial do Sum so it does not have to be mixed in separately
Make Sum::Marshal::Cooked and Sum::Marshal::Pack work together
Make Sum::Marshal::Pack do Sum::Marshal::Cooked, DRY
Make the .whole method return X::Sum::Missing on Failure; easier to use.
Add a Sum::Marshal::Pack::Bits variant for bitfields.  No swabbing yet.
Improve/adjust some docs and internal comments, tests
Added Sum::Marshal::Block to drive NIST block-style backends (for SHA/MD)
Added Sum::Marshal::Method parametric role which replaces:
Removed Sum::Marshal::StrOrds and
Removed Sum::Marshal::BufValues
Require .size method in base Sum interface
Add Sum::Marshal::IO which will mix with either normal or NIST sums
Bump version because changes some API.  Not that anyone uses this stuff yet.

Note to self: commit more often.
  • Loading branch information...
1 parent a3735e2 commit b0be8ad34cb116a2c949aaf51291b8cba372d112 @skids committed Nov 15, 2012
Showing with 491 additions and 138 deletions.
  1. +1 −1 META.info
  2. +361 −99 lib/Sum.pm6
  3. +3 −1 lib/Sum/Adler.pm6
  4. +4 −2 lib/Sum/CRC.pm6
  5. +20 −1 lib/Sum/MD.pm6
  6. +17 −5 lib/Sum/SHA.pm6
  7. +4 −3 lib/Sum/SipHash.pm6
  8. +4 −3 t/adler.t
  9. +2 −1 t/crc.t
  10. +22 −3 t/md.t
  11. +6 −1 t/sha.t
  12. +3 −2 t/siphash.t
  13. +44 −16 t/sum.t
View
2 META.info
@@ -1,6 +1,6 @@
{
"name" : "Sum",
- "version" : "0.0.1",
+ "version" : "0.0.2",
"description" : "Perl6 modules for computing hashes and checksums.",
"author" : "Brian S. Julin",
"depends" : [ ],
View
460 lib/Sum.pm6
@@ -44,8 +44,10 @@ class X::Sum::Recourse is Exception {
use Sum;
# Define a very simple Sum class that just adds normally
- class MySum does Sum does Sum::Partial does Sum::Marshal::StrOrds {
+ class MySum does Sum::Partial
+ does Sum::Marshal::Method[:atype(Str), :method<ords>] {
has $.accum is rw = 0;
+ method size { Inf }
method finalize (*@addends) {
self.push(@addends);
$.accum;
@@ -71,8 +73,8 @@ class X::Sum::Recourse is Exception {
# Since it does Sum::Partial, one can generate partials as a List
$s.partials(1,1,2,1).say; # 16 17 19 20
- # Since it does Sum::Marshal::StrOrds, Str addends are exploded
- # into multiple character ordinals.
+ # Since it does Sum::Marshal::Method[:atype(Str), :method<ords>]
+ # Str addends are exploded into multiple character ordinals.
'abc'.ords.say; # 97 98 99
$s.partials(1,'abc',1).say; # 21 118 216 315 316
@@ -130,17 +132,21 @@ role Sum {
C<$checksum = MySum.new.finalize(1,3,5,7,9);>
A C<Sum> will generally provide coercion methods, such as C<.Numeric>,
- which is often simply aliases for C<.finalize()>. Which coercion methods
- are available may vary across different types of C<Sum>. In particular,
- sums will provide a C<.buf8> coercion method if their results are always
- expressed in bytes, and a C<.buf1> coercion method if their results
- may contain a number of bits that does not pack evenly into bytes. For
- convenience the latter may also provide a C<.buf8> method. The C<.Buf>
- coercion method will eventually return one of the above results as
- natural to the type of C<Sum>, but given that C<buf8> and C<buf1> are
- not implemented in the language core yet, all such methods return a
- C<Buf> at this time. As such, explicit use of the C<.buf8> and C<.buf1>
- methods is advised in the interim.
+ which is often simply an alias for C<.finalize()>. Which coercion
+ methods are available may vary across different types of C<Sum>. In
+ particular, sums will provide a C<.buf8> coercion method if their
+ results are conventionally expressed in bytes, and a C<.buf1> coercion
+ method if their results may contain a number of bits that does not
+ pack evenly into bytes. For convenience the latter may also provide
+ a C<.buf8> method. The C<.Buf> coercion method will eventually return
+ one of the above results as natural to the type of C<Sum>, but given
+ that C<buf8> and C<buf1> are not implemented in the language core yet,
+ all such methods return a C<Buf> at this time. As such, explicit use
+ of the C<.buf8> and C<.buf1> methods is advised in the interim.
+
+ A C<.Str> coercion method will also usually be made available, and
+ will finalize the sum and express the result as a string in a format
+ conventional to the particular type of sum.
=end pod
@@ -253,6 +259,23 @@ role Sum {
method add (*@addends) { ... }
+=begin pod
+
+=head3 method size ()
+
+ The C<:size> method returns the number of significant bits
+ in the result. It may be invoked on an instance or as a
+ class method. Note that in the case of a result obtained
+ from the C<.buf1> coercion method, one may also just call
+ C<.elems> on the result, but the equivalent may not be true of
+ the result of the C<.buf8> coercion method, since the
+ C<.buf8> method is available even when the number of bits
+ in the result is not a multiple of 8.
+
+=end pod
+
+ method size () { ... }
+
# The specs mention a .clear method when feeds are involved
# but do not elaborate.
#
@@ -264,33 +287,42 @@ role Sum {
=begin pod
-=head2 role Sum::Partial
+=head2 role Sum::Partial does Sum
- The C<Sum::Partial> role is used to designate types of C<Sum>
- which may produce partial results at any addend index.
+ The C<Sum::Partial> role is used (instead of C<Sum>) to indicate
+ that a sum may produce partial results at any addend index.
=end pod
-role Sum::Partial {
+role Sum::Partial does Sum {
=begin pod
=head3 method partials (*@addends --> List)
The C<.partials> method acts the same as C<.push>, but returns
a C<List> of the partial sums that result after finalizing the
- C<Sum> immediately after each element of C<@addends> is provided.
- It may be mixed into most progressively implemented C<Sum> roles.
+ C<Sum> after every addend is provided. Note that when the
+ C<@addends> list is processed by any C<Sum::Marshal> roles, the
+ number of partial sums may differ from the number of elements
+ provided in C<@addends>.
- Note that the finalization step for some types of C<Sum> may be
- computationally expensive.
+ Note also that the finalization step for some types of C<Sum> may
+ be computationally expensive.
This method may promulgate C<Failure>s that occur during
marshalling addends or adding them to the C<Sum>, by returning
them instead of the expected results.
=end pod
+ # This default method is satisfactory for sums that do not ruin their
+ # state on finalization. Sums that do, but which wish to provide
+ # Sum::Partial anyway, should define their own overriding method that
+ # clones the sum and finalizes the clone. In those cases mixing in
+ # Sum::Partial can still be done, but serves only to ensure that the
+ # role is listed for introspective purposes.
+
method partials (*@addends --> List) {
flat self.marshal(|@addends).map: {
last($^addend) if $addend ~~ Failure;
@@ -308,10 +340,17 @@ role Sum::Partial {
The C<Sum::Marshal::Raw> role is used by classes that value efficiency
over dwimmery. A class with this role mixed in never processes
- single arguments as though they may contain more than one addend.
- The class will be less convenient to use as a result. However,
- there may be less overhead involved, and it may result in easier
- code audits.
+ single arguments as though they may contain more than one addend,
+ nor combines multiple addends into a packed addend. Addends are passed
+ directly to the C<Sum>'s C<.add> method.
+
+ The class will be less convenient to use as a result: the types of
+ argument accepted by the C<.add> method vary depending on the
+ underlying algorithm, so the user must consult the documentation
+ for that particular type of sum.
+
+ However, there will be less overhead involved, and it may result in
+ easier code audits.
=end pod
@@ -324,7 +363,8 @@ role Sum::Marshal::Raw {
Failure.new(X::Sum::Push::Usage.new());
};
- multi method marshal (*@addends) { for @addends { $_ }};
+ # This allows simultaneous mixin of Sum::Partial
+ multi method marshal (*@addends) { flat @addends };
}
@@ -344,9 +384,29 @@ role Sum::Marshal::Raw {
role Sum::Marshal::Cooked {
- multi method marshal ( $addend ) { $addend }
+ # Subclasses may elect to handle finite numbers of addends in one
+ # method call. Where they do not, handle each one individually.
multi method marshal (*@addends) {
- for @addends { self.marshal($_) }
+ @addends.map: { self.marshal($_) }
+ }
+
+ # Last resort if no subclass has a handler for this specific type of
+ # addend. Pass the addend through, unless we are also one of the
+ # marshalling types that has restrictions.
+ multi method marshal ($addend) {
+ # See if we are also a Sum::Marshal::Pack
+ if self.^can('whole') and self.^can('violation') {
+
+ # If we get an addend that is not otherwise handled by a
+ # subclass, only pass it on if we are at a width boundary.
+ # Otherwise issue an exception, since we do not know how
+ # to pack the addend.
+ unless self.whole {
+ $.violation = True;
+ return Failure.new(X::Sum::Missing.new());
+ }
+ }
+ $addend;
}
# multi/constrained candidate to temporarily workaround diamond problem
@@ -359,47 +419,35 @@ role Sum::Marshal::Cooked {
};
Failure.new(X::Sum::Push::Usage.new());
}
-
-# method whole () { True; }
-
}
=begin pod
-=head2 role Sum::Marshal::StrOrds does Sum::Marshal::Cooked
-
- The C<Sum::Marshal::StrOrds> role will explode any provided C<Str>
- arguments into multiple addends by calling C<.ords> on the string.
+=head2 role Sum::Marshal::Method [ ::T :$atype, Str :$method, Bool :$remarshal = False ]
+ does Sum::Marshal::Cooked
- Other types of provided arguments will not be processed normally,
- unless additional C<Sum::Marshal> roles are mixed.
+ The C<Sum::Marshal::Method> role will substitute any provided addend
+ of type C<:atype> with the results of calling a method named C<:method>
+ on that addend. If a List results, it will be flattenned into the
+ preceding list of addends.
- One should exercise care as to the current encoding pragma
- or contents of provided strings when using this role.
+ If the C<:remarshal> flag is provided, the results will instead
+ be fed back through another marshalling level rather than being
+ passed directly to the C<Sum>'s C<.add> method. Note that care
+ must be taken to avoid marshalling loops, and that this is not
+ precisely equivalent to having the results appear in the original
+ addend list.
=end pod
-role Sum::Marshal::StrOrds does Sum::Marshal::Cooked {
- multi method marshal (Str $addend) { $addend.ords }
-}
-
-=begin pod
-
-=head2 role Sum::Marshal::BufValues does Sum::Marshal::Cooked
-
- The C<Sum::Marshal::BufValues> role will explode any provided C<Buf>
- arguments into multiple addends by calling C<.values> on the buffer.
-
- Other types of provided arguments will not be processed normally,
- unless additional C<Sum::Marshal> roles are mixed.
-
- One should excercise care as to the current encoding pragma
- or contents of provided strings when using this role.
-
-=end pod
-
-role Sum::Marshal::BufValues does Sum::Marshal::Cooked {
- multi method marshal (Str $addend) { $addend.values }
+role Sum::Marshal::Method [ ::T :$atype, Str :$method, Bool :$remarshal = False ]
+ does Sum::Marshal::Cooked {
+ multi method marshal (T $addend) {
+ given $addend."$method"() {
+ return flat $_ unless $remarshal;
+ self.marshal(flat $_);
+ }
+ }
}
=begin pod
@@ -420,9 +468,6 @@ role Sum::Marshal::BufValues does Sum::Marshal::Cooked {
If C<:reflect> is provided, the bit values are emitted least
significant bit first.
- Other types of provided arguments will not be processed normally,
- unless additional C<Sum::Marshal> roles are mixed.
-
=end pod
role Sum::Marshal::Bits [ ::AT :$accept = (Int), ::CT :$coerce = (Int),
@@ -446,24 +491,27 @@ role Sum::Marshal::Bits [ ::AT :$accept = (Int), ::CT :$coerce = (Int),
used in situations where a C<Sum> works on addends of a certain width,
but fragments of addends may be provided separately. The fragments
will be bitwise concatinated until a whole addend of C<$width> bits
- is available, and the whole addend will then be added to the C<Sum>.
+ is available, and the whole addend (expressed as an Int) will then be
+ added to the C<Sum>.
Any leftover bits will be kept to combine with further fragments.
+ However, if bits are left over when an addend is provided for
+ which there is no corresponding C<Sum::Marshal::Pack::*> role,
+ an C<X::Sum::Missing> unthrown exception will be returned.
- Classes which use this role should call the C<.whole> method when
- asked to finalize a C<Sum>, and return an unthrown C<X::Sum::Missing>
- when this method does not return a true value.
+ Classes which wish to allow these roles to be mixed in should
+ call the C<.whole> method, when it is present, when finalizing a
+ C<Sum>. This will return an unthrown C<X::Sum::Missing> unless
+ there are no leftover bits.
Note that the C<pack> function may be used to pre-pack values,
which can then be supplied to a less complicated type of C<Sum>.
- This will often be a better choice than using this role. The
- C<Sum::Marshal::Packed> role is meant for use when the amount of
- data involved is too large and eclectic to create C<Buf>s holding
- the addends.
+ This will often be a better choice than using these roles.
=end pod
-role Sum::Marshal::Pack [ :$width = 8 ] {
+role Sum::Marshal::Pack [ :$width = 8 ]
+ does Sum::Marshal::Cooked {
# To deal with diamond inheritance these attributes must be emulated for now
# has $.bitpos is rw = $width;
@@ -489,32 +537,11 @@ role Sum::Marshal::Pack [ :$width = 8 ] {
%attrs{$self}<violation>;
}
- # use multi/contrained method to workaround diamond problem
+ # use multi/constrained method to workaround diamond problem
multi method whole ($self where {True}:) {
$.bitpos == $width and not $.violation
- }
-
- multi method marshal (*@addends) {
- for @addends { self.marshal($_) }
- }
-
- multi method marshal ($addend) {
- unless self.whole {
- $.violation = True;
- return fail(X::Sum::Missing.new());
- }
- $addend;
- }
-
- # use multi/contrained method to workaround diamond problem
- multi method push ($self where {True}: *@addends --> Failure) {
- sink self.marshal(|@addends).map: {
- return $^addend if $addend ~~ Failure;
- given self.add($addend) {
- when Failure { return $_ };
- }
- };
- Failure.new(X::Sum::Push::Usage.new());
+ ?? True
+ !! Failure.new(X::Sum::Missing.new());
}
}
@@ -540,8 +567,8 @@ role Sum::Marshal::Pack [ :$width = 8 ] {
=end pod
-role Sum::Marshal::Pack::Bits[ ::AT :$accept = (Bool), ::CT :$coerce = (Bool) ]
- does Sum::Marshal::Pack[] {
+role Sum::Marshal::Pack::Bits[::AT :$accept = (Bool), ::CT :$coerce = (Bool)]
+{
multi method marshal (AT $addend) {
$.bitpos--;
@@ -555,3 +582,238 @@ role Sum::Marshal::Pack::Bits[ ::AT :$accept = (Bool), ::CT :$coerce = (Bool) ]
return;
}
}
+
+=begin pod
+
+=head2 role Sum::Marshal::Pack::Bits [ :$width!, :$accept, :$coerce = Int ]
+
+ When the C<:width> parameter is supplied, the C<Sum::Marshal::Pack::Bits>
+ role packs bitfields from provided addends. This C<:width> parameter
+ should not be confused with the C<:width> parameter of the
+ C<Sum::Marshal::Pack> base role, which defines the width of the
+ produced addends.
+
+ Any addend of the type specified by C<$accept> will be coerced into
+ the type specified by C<$coerce>. The least C<:width> significant
+ bits will be concatinated onto any leftover bits from previous results,
+ and when enough bits are collected, the resultant will be provided
+ as an C<Int> to the C<Sum>, keeping any remaining leftover bits to
+ combine with further addends.
+
+ The handling of leftover bits when a C<Sum> is finalized or when
+ an addend not handled by a C<Sum::Marshal::Pack::*> role is provided
+ proceeds as described above.
+
+ Note that this role will not be especially useful until native types
+ are available, unless the user defines an alternate integer-like type.
+
+=end pod
+
+role Sum::Marshal::Pack::Bits[:$width!, ::AT :$accept, ::CT :$coerce = (Int)]
+{
+ multi method marshal (AT $addend) {
+ my $a = CT($addend);
+ my $extrapos = max(0, $width - $.bitpos);
+ if ($extrapos) {
+ my $packed = $.packed +| ($a +> $extrapos);
+ $.packed = $extrapos +& ((1 +< $extrapos) - 1);
+ $.bitpos = $width - $extrapos;
+ return $packed;
+ }
+ else {
+ $.bitpos -= $width;
+ $.packed +|= $a +< $.bitpos;
+ unless $.bitpos {
+ my $packed = $.packed;
+ $.packed = 0;
+ $.bitpos = $.width;
+ return $packed;
+ }
+ }
+ return;
+ }
+}
+
+=begin pod
+
+=head2 role Sum::Marshal::Block [:$BufT = Buf, :$elems = 64, :$BitT = Bool]
+
+ The C<Sum::Marshal::Block> role is a base role used to interface with
+ types of sum that divide their message into NIST-style blocks.
+
+ This role is usually not included directly, but rather through subroles.
+ It is not compatible with any other C<Sum::Marshal> roles, unless
+ those roles C<:remarshal> first.
+
+ Classes which wish to allow these roles to be mixed in should
+ call the C<.drain> method during finalization, if it is present,
+ immediately after pushing any final addends. After this method
+ has been called, any further attempts to provide addends will
+ result in an unthrown C<X::Sum::Final>.
+
+ This base role contains fallback marshalling for C<BitT> addends which
+ will be treated as bits and packed, C<BufT> addends whose values
+ will be packed, and C<Any> addends which will be treated as C<Int>s
+ and whose least significant bits will be packed as per the bit width
+ of C<BufT>'s values.
+
+ Note: native typed buffers are not yet supported, but a punned C<Buf>
+ class will be treated as though it were a C<buf8> as per the spec,
+ which does adequately pretend to work.
+
+=end pod
+
+role Sum::Marshal::Block [::B :$BufT = Buf, :$elems = 64, ::b :$BitT = Bool]
+ does Sum::Marshal::Cooked
+{
+# To deal with diamond inheritance these attributes must be emulated for now
+# has @.accum is rw;
+# has @.bits is rw;
+# has Bool $.drained is rw = False;
+# Note this will leak like a sieve as the entries never get cleared
+ my %attrs;
+ multi method accum ($self where {True}:) is rw {
+ %attrs{$self}<accum> //= [];
+ %attrs{$self}<accum>;
+ }
+ multi method bits ($self where {True}:) is rw {
+ %attrs{$self}<bits> //= [];
+ %attrs{$self}<bits>;
+ }
+ multi method drained ($self where {True}:) is rw {
+ %attrs{$self}<drained> //= False;
+ %attrs{$self}<drained>;
+ }
+
+ # Allow subroles to use our parameters.
+ # use multi/constrained method to workaround diamond problem
+ multi method B_elems ($self where {True}:) { $elems }
+ multi method B ($self where {True}:) { B }
+ multi method b ($self where {True}:) { b }
+
+ my Int $bw = (given (B) {
+ # Maybe there will be an introspect for this...
+ when Buf { 8 }
+ });
+
+ multi method marshal () { }
+
+ # use multi/constrained method to workaround diamond problem
+ multi method emit ($self where {True}: *@addends) {
+
+ @.accum.push(@addends);
+
+ # Emit any completed blocks.
+ gather while (@.accum.elems > $elems) {
+ take B.new(@.accum.splice(0,$elems));
+ }
+ }
+
+ multi method marshal (b $addend) {
+
+ return fail(X::Sum::Final.new()) if $.drained;
+
+ @.bits.push($addend);
+ return unless +@.bits == $bw;
+
+ self.emit([+|] (+<<@.bits.splice(0, $bw)) Z+< (reverse ^$bw));
+ }
+
+ # Multidispatch seems to need a bit of a nudge, thus the ::?CLASS
+ multi method marshal (::?CLASS $self where { not +@.bits }: $addend) {
+
+ return fail(X::Sum::Final.new()) if $.drained;
+
+ self.emit($addend);
+
+ }
+
+ multi method marshal (::?CLASS $self where { so +@.bits }: $addend) {
+
+ return fail(X::Sum::Final.new()) if $.drained;
+
+ @.bits.push(?<<(1 X+& ($addend X+> reverse(^$bw))));
+
+ self.emit([+|] (@.bits.splice(0, $bw)) Z+< (reverse ^$bw));
+ }
+
+ multi method marshal (B $addend) {
+ # punt on this mess for now
+ self.marshal($addend.values);
+ }
+
+ multi method marshal (::?CLASS $self where {not +@.bits}: B $addend) {
+ gather do {
+ my $i = 0;
+ while ($addend.elems - $i + +@.accum >= $elems) {
+ take B.new(flat @.accum,
+ $addend[$i..^($i + $elems - +@.accum)]);
+ $i += $elems - +@.accum;
+ @.accum = ();
+ }
+ @.accum = $addend[$i ..^ $addend.elems];
+ }
+ }
+
+ # use multi/constrained method to workaround diamond problem
+ multi method drain ($self where {True}:) {
+ $.drained = True;
+ B.new(@.accum), ?<<@.bits
+ }
+
+}
+
+=begin pod
+
+=head2 role Sum::Marshal::IO
+
+ The C<Sum::Marshal::IO> role will take open C<IO> objects
+ as addends, and provide their contents in an efficient manner
+ to a C<Sum>.
+
+ Currently this is done by repeatedly calling the C<.read> method
+ of the object with a sensible chunk size, and providing the
+ resulting values. This may change as encoding and native type
+ support is improved.
+
+ This role may be mixed with C<Sum::Marshal::Block> roles in which
+ case remarshalling is done using C<Buf>s.
+
+=end pod
+
+role Sum::Marshal::IO {
+
+ # ^can("drain") is a poorman proxy for figuring out
+ # if a Sum::Marshal::Block[...] is mixed in with us.
+ multi method marshal (::?CLASS $self where { so $_.^can("drain") }:
+ IO $addend) {
+
+ gather while not $addend.eof {
+ given $addend.read($.B_elems - +@.accum) {
+ when Buf {
+ next unless $_.elems;
+ take flat self.marshal($_);
+ }
+ default {
+ take $_;
+ last;
+ }
+ }
+ }
+ }
+
+ multi method marshal (IO $addend) {
+ flat gather while not $addend.eof {
+ given $addend.read(1024) {
+ when Buf {
+ next unless $_.elems;
+ take flat $_.values;
+ }
+ default {
+ take $_;
+ last;
+ }
+ }
+ }
+ }
+}
View
4 lib/Sum/Adler.pm6
@@ -77,7 +77,7 @@ class X::Sum::CheckVals is Exception {
role Sum::Fletcher [ :$modulusA = 65535, :$modulusB = $modulusA,
:$inivA = 0, :$inivB = 0, :$finv = False,
:$columnsA = 16, :$columnsB = $columnsA ]
- does Sum {
+ does Sum::Partial {
has $!A is rw = ( ($inivA.WHAT === Bool)
?? (-$inivA +& ((1 +< $columnsA)-1))
@@ -86,6 +86,8 @@ role Sum::Fletcher [ :$modulusA = 65535, :$modulusB = $modulusA,
?? (-$inivB +& ((1 +< $columnsB)-1))
!! $inivB );
+ method size () { $columnsA + $columnsB }
+
method add (*@addends) {
# TODO: when native type support improves, use effecient
# types on the accumulators, and allow the sums
View
6 lib/Sum/CRC.pm6
@@ -30,7 +30,7 @@ Sum::CRC
=head2 role Sum::CRC [ :@header?, :@footer?, :$residual = 0,
:$iniv = Bool::False, :$finv = Bool::False,
:$columns = 8, :$poly, :$reflect = Bool::False ]
- does Sum does Sum::Partial
+ does Sum::Partial
The C<Sum::CRC> parametric role is used to create a type of C<Sum>
that calculates a particular kind of cyclic redundancy checksum
@@ -104,13 +104,15 @@ Sum::CRC
role Sum::CRC [ :@header?, :@footer?, :$residual = 0,
:$iniv = Bool::False, :$finv = Bool::False,
:$columns = 8, :$poly, :$reflect = Bool::False ]
- does Sum does Sum::Partial {
+ does Sum::Partial {
# Eventually we want $.rem's type (from :iniv when not Bool)
# to be predictable enough for optimization, and really it
# should only be rw from inside the class.
has $.rem is rw = ( ($iniv.WHAT === Bool) ?? (-$iniv +& ((1 +< $columns) - 1)) !! $iniv );
+ method size () { $columns }
+
method add (*@addends) {
for (@addends) -> $a {
my $b = $.rem +& (1 +< ($columns - 1));
View
21 lib/Sum/MD.pm6
@@ -88,6 +88,19 @@ role Sum::MD4_5 [ :$alg where { * eqv [|] <MD5 MD4 MD4ext> } = "MD5",
# MD5 table of constants (a.k.a. T[1..64] in RFC1321)
my @t = (Int(4294967296 * .sin.abs) for 1..64);
+ method size () {
+ given $alg {
+ when "MD4"|
+ "MD5"|
+ "RIPEMD-128" { 128 }
+ when "MD4ext" { 256 }
+ when "RIPEMD-256" { 256 }
+ when "RIPEMD-160" { 160 }
+ when "RIPEMD-320" { 320 }
+ default { Inf }
+ }
+ }
+
submethod BUILD () {
@!s = (0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476);
if $alg eqv "MD4ext" {
@@ -476,6 +489,8 @@ role Sum::MD4_5 [ :$alg where { * eqv [|] <MD5 MD4 MD4ext> } = "MD5",
my $bits = 0;
my $byte = 0;
+# $block.gist.say;
+
# Count how many stray bits we have and build them into a byte
( $byte +|= +$_ +< (7 - $bits++) )
if .defined for ($b7,$b6,$b5,$b4,$b3,$b2,$b1);
@@ -532,7 +547,11 @@ role Sum::MD4_5 [ :$alg where { * eqv [|] <MD5 MD4 MD4ext> } = "MD5",
method add (*@addends) { self.do_add(|@addends) }
method finalize(*@addends) {
- self.push(@addends);
+ given self.push(@addends) {
+ return $_ unless $_.exception.WHAT ~~ X::Sum::Push::Usage;
+ }
+
+ self.add(self.drain) if self.^can("drain");
self.add(Buf.new()) unless $!final;
View
22 lib/Sum/SHA.pm6
@@ -11,20 +11,20 @@ Sum::SHA
class mySHA1 does Sum::SHA1 does Sum::Marshal::Raw { }
my mySHA1 $a .= new();
$a.finalize("123456789".encode('ascii')).say;
- # 1414485752856024225500297739715962456813268251713
+ # 1414485752856024225500297739715962456813268251713
# SHA-224
class mySHA2 does Sum::SHA2[:columns(224)] does Sum::Marshal::Raw { }
my mySHA2 $b .= new();
$b.finalize("123456789".encode('ascii')).say;
- # 16349067602210014067037177823623301242625642097093531536712287864097
+ # 16349067602210014067037177823623301242625642097093531536712287864097
# When dealing with obselete systems that use SHA0
class mySHA0 does Sum::SHA1[:insecure_sha0_obselete]
does Sum::Marshal::Raw { }
my mySHA0 $c .= new();
$c.finalize("123456789".encode('ascii')).say;
- # 1371362676478658660830737973868471486175721482632
+ # 1371362676478658660830737973868471486175721482632
=end pod
@@ -96,6 +96,8 @@ role Sum::SHA1 [ :$insecure_sha0_obselete = False, :$mod8 = False ] does Sum {
has @!w is rw; # "Parsed" message gets bound here.
has @!s is rw; # Current hash state. H in specification.
+ method size () { 160 }
+
submethod BUILD () {
@!s = (0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0);
$!final = False;
@@ -201,7 +203,11 @@ role Sum::SHA1 [ :$insecure_sha0_obselete = False, :$mod8 = False ] does Sum {
method add (*@addends) { self.do_add(|@addends) }
method finalize(*@addends) {
- self.push(@addends);
+ given self.push(@addends) {
+ return $_ unless $_.exception.WHAT ~~ X::Sum::Push::Usage;
+ }
+
+ self.add(self.drain) if self.^can("drain");
self.add(Buf.new()) unless $!final;
@@ -297,6 +303,8 @@ role Sum::SHA2 [ :$columns where { * == (224|256|384|512) } = 256,
0x4cc5d4becb3e42b6,0x597f299cfc657e2a,0x5fcb6fab3ad6faec,0x6c44198c4a475817)
»+>» (64 - $rwidth);
+ method size () { $columns }
+
submethod BUILD () {
@!s =
(given $columns {
@@ -468,7 +476,11 @@ role Sum::SHA2 [ :$columns where { * == (224|256|384|512) } = 256,
method add (*@addends) { self.do_add(|@addends) }
method finalize(*@addends) {
- self.push(@addends);
+ given self.push(@addends) {
+ return $_ unless $_.exception.WHAT ~~ X::Sum::Push::Usage;
+ }
+
+ self.add(self.drain) if self.^can("drain");
self.add(Buf.new()) unless $!final;
View
7 lib/Sum/SipHash.pm6
@@ -54,8 +54,7 @@ $Sum::SipHash::Doc::synopsis = $=pod[0].content[4].content.Str;
The number of addends may be determined on the fly, and in this
implementation, finalization is performed without altering internal
- state, so the C<Sum::Partial> role may be mixed in when progressive
- hashing of a growing datum is desired.
+ state, so the C<Sum::Partial> role is available.
The C<$defkey> parameter defines a seed value that will be applied
to all instances which do not specify their own. There is an internal
@@ -97,7 +96,7 @@ $Sum::SipHash::Doc::synopsis = $=pod[0].content[4].content.Str;
use Sum;
-role SipHash [ :$c = 2, :$d = 4, Int :$defkey = 0 ] does Sum {
+role SipHash [ :$c = 2, :$d = 4, Int :$defkey = 0 ] does Sum::Partial {
my Buf $keyfrob = "somepseudorandomlygeneratedbytes".encode("ascii");
@@ -140,6 +139,8 @@ role SipHash [ :$c = 2, :$d = 4, Int :$defkey = 0 ] does Sum {
$!v3 +^= $!k1;
}
+ method size () { 64 };
+
my sub rol ($v is rw, $count) {
my $tmp = (($v +& (0xffffffffffffffff +> $count)) +< $count);
$tmp +|= ($v +> (64 - $count));
View
7 t/adler.t
@@ -3,15 +3,15 @@ BEGIN { @*INC.unshift: './lib'; }
use Test;
-plan 25;
+plan 26;
use Sum::Adler;
ok(1,'We use Sum::Adler and we are still alive');
-class S does Sum::Adler32 does Sum::Marshal::StrOrds does Sum::Partial { }
+class S does Sum::Adler32 does Sum::Marshal::Method[:atype(Str),:method<ords>] { }
my S $s .= new();
my $h = $s.finalize("Please to checksum this text");
-is $h, 0x96250a8e, "Adler32 (StrOrds) computes expected value";
+is $h, 0x96250a8e, "Adler32 (Str.ords) computes expected value";
$h = $s.finalize(".");
is $h, 0xa0e10abc, "append after finalization and get expected value";
is $s.buf8.values, (0xa0, 0xe1, 0x0a, 0xbc), 'Adler32 buf8 coerce works';
@@ -20,6 +20,7 @@ is $s.Buf.values, (0xa0, 0xe1, 0x0a, 0xbc), 'Adler32 Buf yields buf8';
class FLFoo does Sum::Fletcher[ :modulusA(17), :modulusB(13), :columnsA(8) ] does Sum::Marshal::Raw { }
my FLFoo $flfoo;
+is $flfoo.size ~ FLFoo.size, "1616", 'size method works';
$flfoo .= new();
is $flfoo.finalize(1,2,3,4,5,255), 0xb0f, 'custom Fletcher produces expected value';
is ($flfoo.checkvals),(4,15), 'custom Fletcher check values are as expected';
View
3 t/crc.t
@@ -3,7 +3,7 @@ BEGIN { @*INC.unshift: './lib'; }
use Test;
-plan 127;
+plan 128;
use Sum::CRC;
ok(1,'We use Sum::CRC and we are still alive');
@@ -12,6 +12,7 @@ my ($i, $s);
class ROHC does Sum::CRC_3_ROHC does Sum::Marshal::Bits { :reflect }
my ROHC $rohc .= new();
+is ROHC.size, 3, "CRC .size method works. And is a class method";
is $rohc.finalize(0x31..0x39), 0x6, "CRC_3_ROHC gives expected results";
ok $rohc.check(False,True,True), "CRC_3_ROHC self-verifies (0)";
View
25 t/md.t
@@ -3,7 +3,7 @@ BEGIN { @*INC.unshift: './lib'; }
use Test;
-plan 41;
+plan 51;
use Sum::MD;
ok 1,'We use Sum::MD and we are still alive';
@@ -15,9 +15,10 @@ sub hexify ($i is copy) {
class MD4t does Sum::MD4 does Sum::Marshal::Raw { };
my MD4t $s .= new();
-ok $s.WHAT === MD4t, 'We create a SHA1 class and object';
+ok $s.WHAT === MD4t, 'We create a MD4 class and object';
given (MD4t.new()) {
+is .size, 128, "MD4.size is correct";
is .finalize(Buf.new()),
0x31d6cfe0d16ae931b73c59d7e0c089c0,
"MD4 of an empty buffer is correct.";
@@ -54,8 +55,21 @@ is MD4t.new().finalize(Buf.new(97 xx 63),True,False,True,False,True,False,False)
0x0719b5d879deafc63150bc5aa45ba714,
"MD4 of a 511-bit buffer is correct.";
-todo "Need extended MD4 test vector", 1;
+class MD4dwim does Sum::MD4 does Sum::Marshal::Block[] { }
+my MD4dwim $dwim .= new();
+ok $dwim.WHAT === MD4dwim, 'We create a dwimmy Block MD4 class and object';
+
+is $dwim.finalize(('0123456789' x 100).ords), 0x895ffd5f1acfe6f760c777e7883605e9, "MD4 of 1000 Ints is correct";
+
+$dwim .= new();
+
+is $dwim.finalize(Buf.new((0x30..0x39) xx 100)), 0x895ffd5f1acfe6f760c777e7883605e9, "MD4 of 8000-bit Buf is correct";
+
+
+
class MD4ext does Sum::MD4ext[] does Sum::Marshal::Raw { };
+is MD4ext.size, 256, "extended MD4 .size is correct. And a class method.";
+todo "Need extended MD4 test vector", 1;
is hexify(MD4ext.new().finalize(Buf.new(97 xx 55))),
0x0,
"Extended MD4 works.";
@@ -65,6 +79,7 @@ my MD5t $md5 .= new();
ok $md5.WHAT === MD5t, 'We create an MD5 class and object';
given (MD5t.new()) {
+ is .size, 128, "MD5.size is correct";
is .finalize(Buf.new()),
0xd41d8cd98f00b204e9800998ecf8427e,
"MD5 of an empty buffer is correct.";
@@ -84,6 +99,7 @@ my r160t $r160 .= new();
ok $r160.WHAT === r160t, 'We create a RIPEMD-160 class and object';
given (r160t.new()) {
+ is .size, 160, "RIPEMD-160.size is correct";
is .finalize(Buf.new()),
0x9c1185a5c5e9fc54612808977ee8f548b2258d31,
"RIPEMD-160 of an empty buffer is correct.";
@@ -103,6 +119,7 @@ my r128t $r128 .= new();
ok $r128.WHAT === r128t, 'We create a RIPEMD-128 class and object';
given (r128t.new()) {
+ is .size, 128, "RIPEMD-128.size is correct";
is .finalize(Buf.new()),
0xcdf26213a150dc3ecb610f18f6b38b46,
"RIPEMD-128 of an empty buffer is correct.";
@@ -123,6 +140,7 @@ my r320t $r320 .= new();
ok $r320.WHAT === r320t, 'We create a RIPEMD-320 class and object';
given (r320t.new()) {
+ is .size, 320, "RIPEMD-320.size is correct";
is .finalize(Buf.new()),
0x22d65d5661536cdc75c1fdf5c6de7b41b9f27325ebc61e8557177d705a0ec880151c3a32a00899b8,
"RIPEMD-320 of an empty buffer is correct.";
@@ -142,6 +160,7 @@ my r256t $r256 .= new();
ok $r256.WHAT === r256t, 'We create a RIPEMD-256 class and object';
given (r256t.new()) {
+ is .size, 256, "RIPEMD-256.size is correct";
is .finalize(Buf.new()),
0x02ba4c4e5f8ecd1877fc52d64d30e37a2d9774fb1e5d026380ae0168e3c5522d,
"RIPEMD-256 of an empty buffer is correct.";
View
7 t/sha.t
@@ -3,7 +3,7 @@ BEGIN { @*INC.unshift: './lib'; }
use Test;
-plan 48;
+plan 53;
use Sum::SHA;
ok 1,'We use Sum::SHA and we are still alive';
@@ -18,6 +18,7 @@ my SHA1t $s .= new();
ok $s.WHAT === SHA1t, 'We create a SHA1 class and object';
given (SHA1t.new()) {
+is .size, 160, "SHA1.size is correct";
is .finalize(Buf.new()),
0xda39a3ee5e6b4b0d3255bfef95601890afd80709,
"SHA1 of an empty buffer is correct.";
@@ -64,6 +65,7 @@ my SHA256t $s2 .= new();
ok $s2.WHAT === SHA256t, 'We create a SHA2 (SHA-256) class and object';
given (SHA256t.new()) {
+ is .size, 256, "SHA2-256.size is correct";
is .finalize(Buf.new()),
0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,
"SHA-256 of an empty buffer is correct.";
@@ -105,6 +107,7 @@ class SHA224t does Sum::SHA2[ :columns(224) ] does Sum::Marshal::Raw { };
my SHA224t $s3 .= new();
ok $s3.WHAT === SHA224t, 'We create a SHA2 (SHA-224) class and object';
given (SHA224t.new()) {
+ is .size, 224, "SHA2-224.size is correct";
is .finalize(Buf.new(97 xx 55)),
0xfb0bd626a70c28541dfa781bb5cc4d7d7f56622a58f01a0b1ddd646f,
"SHA-224 expected result is correct.";
@@ -116,6 +119,7 @@ my SHA512t $s4 .= new();
ok $s4.WHAT === SHA512t, 'We create a SHA2 (SHA-512) class and object';
given (SHA512t.new()) {
+ is .size, 512, "SHA2-512.size is correct";
is .finalize(Buf.new()),
0xcf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e,
"SHA-512 of an empty buffer is correct.";
@@ -158,6 +162,7 @@ my SHA384t $s5 .= new();
ok $s5.WHAT === SHA384t, 'We create a SHA2 (SHA-384) class and object';
given (SHA384t.new()) {
+ is .size, 384, "SHA2-384.size is correct";
is .finalize(Buf.new(97 xx 111)),
0x3c37955051cb5c3026f94d551d5b5e2ac38d572ae4e07172085fed81f8466b8f90dc23a8ffcdea0b8d8e58e8fdacc80a,
"SHA-384 expected result is correct.";
View
5 t/siphash.t
@@ -3,13 +3,14 @@ BEGIN { @*INC.unshift: './lib'; }
use Test;
-plan 6;
+plan 7;
use Sum::SipHash;
ok(1,'We use Sum::SipHash and we are still alive');
-class S does SipHash does Sum::Marshal::StrOrds does Sum::Partial { }
+class S does SipHash does Sum::Marshal::Method[:atype(Str),:method<ords>] { }
my S $s .= new(:key(0x000102030405060708090a0b0c0d0e0f));
+is $s.size, 64, "SipHash.size works";
my $h = $s.finalize("Please to checksum this text");
is $h, 0x5cabf2fe9143a691, "SipHash (StrOrds) computes expected value";
$h = $s.finalize(".");
View
60 t/sum.t
@@ -3,7 +3,7 @@ BEGIN { @*INC.unshift: './lib'; }
use Test;
-plan 66;
+plan 71;
use Sum;
ok(1,'We use Sum and we are still alive');
@@ -12,21 +12,21 @@ lives_ok { X::Sum::Missing.new() }, 'X::Sum::Missing is available';
lives_ok { X::Sum::Spill.new() }, 'X::Sum::Spill is available';
lives_ok { X::Sum::Push::Usage.new() }, 'X::Sum::Push::Usage is available';
lives_ok { X::Sum::Recourse.new() }, 'X::Sum::Recourse is available';
-lives_ok { eval 'class foo1 does Sum { method finalize { }; method add { }; method push { }; }' }, 'Sum composes when interface is implemented';
+lives_ok { eval 'class foo1 does Sum { method size { }; method finalize { }; method add { }; method push { }; }' }, 'Sum composes when interface is implemented';
dies_ok { eval 'class fooX does Sum { }' }, 'Sum requires interface to compose';
-lives_ok { eval 'class foo2 does Sum does Sum::Marshal::Raw { method finalize { }; method add { }; }' }, 'Sum::Marshal::Raw composes and provides push';
-lives_ok { eval 'class foo3 does Sum does Sum::Marshal::Cooked { method finalize { }; method add { }; }' }, 'Sum::Marshal::Cooked composes and provides push';
-lives_ok { eval 'class foo4 does Sum does Sum::Marshal::StrOrds { method finalize { }; method add { }; }' }, 'Sum::Marshal::StrOrds composes and provides push';
-lives_ok { eval 'class foo5 does Sum does Sum::Marshal::BufValues { method finalize { }; method add { }; }' }, 'Sum::Marshal::BufValues composes and provides push';
-lives_ok { eval 'class foo6 does Sum does Sum::Marshal::Pack[] { method finalize { }; method add { }; }' }, 'Sum::Marshal::Pack composes and provides push';
-lives_ok { eval 'class foo7 does Sum::Marshal::Pack::Bits[ :accept(Int) ] { method finalize { }; method add { }; }' }, 'Sum::Marshal::Pack::Bits composes';
+lives_ok { eval 'class foo2 does Sum does Sum::Marshal::Raw { method size { }; method finalize { }; method add { }; }' }, 'Sum::Marshal::Raw composes and provides push';
+lives_ok { eval 'class foo3 does Sum does Sum::Marshal::Cooked { method size { }; method finalize { }; method add { }; }' }, 'Sum::Marshal::Cooked composes and provides push';
+lives_ok { eval 'class foo4 does Sum does Sum::Marshal::Method[:atype(Str),:method<ords>] { method size { }; method finalize { }; method add { }; }' }, 'Sum::Marshal::Method composes and provides push';
+lives_ok { eval 'class foo6 does Sum does Sum::Marshal::Pack[] { method size { }; method finalize { }; method add { }; }' }, 'Sum::Marshal::Pack composes and provides push';
+lives_ok { eval 'class foo7 does Sum::Marshal::Pack[] does Sum::Marshal::Pack::Bits[ :accept(Int) ] { method size { }; method finalize { }; method add { }; }' }, 'Sum::Marshal::Pack::Bits composes';
+lives_ok { eval 'class foo7a does Sum::Marshal::Pack[] does Sum::Marshal::Pack::Bits[ :width(4), :accept(Int) ] { method size { }; method finalize { }; method add { }; }' }, 'other Sum::Marshal::Pack::Bits composes';
-lives_ok { eval 'class fooC1 does Sum does Sum::Marshal::StrOrds does Sum::Marshal::BufValues { method finalize { }; method add { }; }' }, 'Two Sum::Marshal subroles can compose with same crony';
-lives_ok { eval 'class fooC2 does Sum does Sum::Marshal::Pack::Bits[ ] does Sum::Marshal::Pack::Bits[ :accept(Int) ] { method finalize { }; method add { }; }' }, 'Two Sum::Marshal::Pack subroles can compose with same crony';
+lives_ok { eval 'class fooC1 does Sum does Sum::Marshal::Method[:atype(Str),:method<ords>] does Sum::Marshal::Method[:atype(Buf),:method<values>] { method size { }; method finalize { }; method add { }; }' }, 'Two Sum::Marshal subroles can compose with same cronies';
lives_ok {
class Foo does Sum does Sum::Marshal::Cooked {
has $.accum is rw = 0;
+ method size () { 64 };
method finalize (*@addends) {
self.push(@addends);
$.accum;
@@ -58,6 +58,7 @@ is $f.accum, 31, "push with no arguments works(Cooked)";
lives_ok {
class Foo2 does Sum does Sum::Marshal::Raw {
has $.accum is rw = 0;
+ method size () { 64 };
method finalize (*@addends) {
self.push(@addends);
$.accum;
@@ -85,8 +86,9 @@ $g.push();
is $g.accum, 31, "push with no arguments works(Raw)";
lives_ok {
-class Foo3 does Sum does Sum::Partial does Sum::Marshal::Cooked {
+class Foo3 does Sum::Partial does Sum::Marshal::Cooked {
has $.accum is rw = 0;
+ method size () { 64 };
method finalize (*@addends) {
self.push(@addends);
$.accum;
@@ -120,8 +122,9 @@ my @d;
#is @d.join(""), "3942", "partials inserts values in a feed"
is $h.partials(4,5,Failure.new(X::AdHoc.new()),6).map({.WHAT.gist}), 'Int() Int() Failure()', "partials stops iterating on Failure (Partial,Cooked).";
-class Foo3r does Sum does Sum::Partial does Sum::Marshal::Raw {
+class Foo3r does Sum::Partial does Sum::Marshal::Raw {
has $.accum is rw = 0;
+ method size () { 64 }
method finalize (*@addends) {
self.push(@addends);
$.accum;
@@ -137,8 +140,9 @@ my Foo3r $hr .= new();
is $hr.partials(4,5,Failure.new(X::AdHoc.new()),6).map({.WHAT.gist}), 'Int() Int() Failure()', "partials stops iterating on Failure (Partial,Raw).";
lives_ok {
-class Foo4 does Sum does Sum::Partial does Sum::Marshal::StrOrds {
+class Foo4 does Sum::Partial does Sum::Marshal::Method[:atype(Str),:method<ords>] {
has $.accum is rw = 0;
+ method size () { 64 }
method finalize (*@addends) {
self.push(@addends);
$.accum;
@@ -147,7 +151,7 @@ class Foo4 does Sum does Sum::Partial does Sum::Marshal::StrOrds {
method add (*@addends) {
$.accum += [+] @addends;
};
-} }, "can compose a basic Sum class (StrOrds)";
+} }, "can compose a basic Sum class (Str.ords)";
my Foo4 $o1;
lives_ok { $o1 .= new(); }, "can instantiate a basic Cooked subclass";
@@ -161,8 +165,13 @@ $o1.push(1,"ABC",2);
is $o1.finalize, 65 + 66 + 67 + 3, "mix addends around exploding addend";
lives_ok {
-class Foo5 does Sum does Sum::Marshal::Pack::Bits[] {
+class Foo5
+ does Sum does Sum::Marshal::Pack[]
+ does Sum::Marshal::Pack::Bits[]
+ does Sum::Marshal::Pack::Bits[ :width(4), :accept(Str), :coerce(Int) ]
+{
has $.accum is rw = 0;
+ method size () { 64 }
method finalize (*@addends) {
self.push(@addends);
return fail(X::Sum::Missing.new()) unless self.whole;
@@ -175,7 +184,7 @@ class Foo5 does Sum does Sum::Marshal::Pack::Bits[] {
} }, "Can instantiate basic Pack subclass";
my Foo5 $o2;
-lives_ok { $o2 .= new(); }, "can instantiate a basic Packed subclass";
+lives_ok { $o2 .= new(); }, "can instantiate Pack subclasses";
$o2.push(True,False,False,False,True,False,True,False);
is $o2.finalize, 138, "can combine 8 bits";
$o2 .= new();
@@ -193,6 +202,25 @@ ok $o2.finalize.WHAT ~~ Failure, "Normal addend after 7 bits fails";
$o2 .= new();
$o2.push(True,False,False,False,True,False,True,8,False);
ok $o2.finalize.WHAT ~~ Failure, "Normal addend amid 8 bits fails";
+$o2 .= new();
+$o2.push("8","4");
+is $o2.finalize, 0x84, "Bitfield addend works";
+$o2 .= new();
+$o2.push(True,False,False,False,"4");
+is $o2.finalize, 0x84, "Mixed bit and bitfields works";
+$o2 .= new();
+$o2.push("4");
+ok $o2.finalize.WHAT ~~ Failure, "Short bitfield finalize fails";
+$o2 .= new();
+$o2.push("4",8,"4");
+ok $o2.finalize.WHAT ~~ Failure, "Normal addend amid bitfields fails";
+$o2 .= new();
+$o2.push(8,"4","3");
+is $o2.finalize, 8 + 0x43, "Normal addend after bitfields works";
+$o2 .= new();
+$o2.push("4","3",8);
+is $o2.finalize, 0x43 + 8, "Normal addend before bitfields works";
+
# Now grab the code in the synopsis from the POD and make sure it runs.

0 comments on commit b0be8ad

Please sign in to comment.