### ----------------------------------------------------
### -- Gzz::Text::Utils
### -- Licenses: LGPL-3.0-or-later
### -- Authors: Francis Grizzly Smit <grizzly@smit.id.au>
### -- File: rakulib/Gzz/Text/Utils.rakumod
### ----------------------------------------------------
### -- Chunk 3 of 8
<->> left justify, right is the default

=end item7                        


=begin item7

B<C<^>>  centre justify.

=end item7                        


=begin item7

B<C<#>> ensure the leading B<C<0>> for any octal, prefix non-zero hexadecimal
with B<C<0x>> or B<C<0X>>, prefix non-zero binary with B<C<0b>> or B<C<0B>>

=end item7                        


=begin item7

B<C<v>> vector flag (used only with d directive)

=end item7                        


=begin item7

B<C<' '>> pad with spaces.

=end item7                        


=begin item7

B<C<0>> pad with zeros.

=end item7                        


=begin item7

B«C«[ <char> ]»» pad with character char where char matches:

=end item7                        

=begin item8

B«C«<-[ <cntrl> \s \[ \] ]> || ' '»» i.e. anything except control characters, white
space (apart from the basic white space (i.e. \x20 or the one with ord 32)),
and B<C<[>> and finally B<C<]>>.

=end item8

=item7 L<Top of Document|#table-of-contents>

=begin item6

B<C<width>> is either an integer or a B<C<*>> or a B<C<*>> followed by an integer >= 1 and a '$'.

=end item6                        


=begin item6

B<C<precision>> is a B<C<.>> followed by either an positive integer or a B<C<*>> or a B<C<*>>
followed by an integer >= 1 and a '$'.

=end item6                        


=begin item6

B<C<max-width>> is a B<C<.>> followed by either an positive integer or a B<C<*>> or a B<C<*>>
followed by an integer >= 1 and a '$'.

=end item6                        

=begin item6

B<C<modifier>> These are not implemented but is one of:

=end item6                        

=begin item7

B<C<hh>> interpret integer as a type B<C<char>> or B<C<unsigned char>>.

=end item7

=begin item7

B<C<h>> interpret integer as a type B<C<short>> or B<C<unsigned short>>.

=end item7

=begin item7

B<C<j>> interpret integer as a type B<C<intmax_t>>, only with a C99 compiler (unportable).

=end item7

=begin item7

B<C<l>> interpret integer as a type B<C<long>> or B<C<unsigned long>>.

=end item7

=begin item7

B<C<ll>> interpret integer as a type B<C<long long>>, B<C<unsigned long long>>, or B<C<quad>> (typically 64-bit integers).

=end item7

=begin item7

B<C<q>> interpret integer as a type B<C<long long>>, B<C<unsigned long long>>, or B<C<quad>> (typically 64-bit integers).

=end item7

=begin item7

B<C<L>> interpret integer as a type B<C<long long>>, B<C<unsigned long long>>, or B<C<quad>> (typically 64-bit integers).

=end item7

=begin item7

B<C<t>> interpret integer as a type B<C<ptrdiff_t>>.

=end item7

=begin item7

B<C<z>> interpret integer as a type B<C<size_t>>.

=end item7

=item6 L<Top of Document|#table-of-contents>

=begin item6

B<C<spec-char>> or the conversion character is one of:

=end item6                        

=begin item7

B<C<c>> a character with the given codepoint.

=end item7                        


=begin item7

B<C<s>> a string.

=end item7                        


=begin item7

B<C<d>> a signed integer, in decimal.

=end item7                        


=begin item7

B<C<u>> an unsigned integer, in decimal.

=end item7                        


=begin item7

B<C<o>> an unsigned integer, in octal, with a B<C<0o>> prepended if the B<C<#>> flag is present.

=end item7                        


=begin item7

B<C<x>> an unsigned integer, in hexadecimal, with a B<C<0x>> prepended if the B<C<#>> flag is present.

=end item7                        


=begin item7

B<C<e>> a floating-point number, in scientific notation.

=end item7                        


=begin item7

B<C<f>> a floating-point number, in fixed decimal notation.

=end item7                        


=begin item7

B<C<g>> a floating-point number, in %e or %f notation.

=end item7                        


=begin item7

B<C<X>> like B<C<x>>, but using uppercase letters, with a B<C<0X>> prepended if the B<C<#>> flag is present.

=end item7                        


=begin item7

B<C<E>> like B<C<e>>, but using an uppercase B<C<E>>.

=end item7                        


=begin item7

B<C<G>> like B<C<g>>, but with an uppercase B<C<E>> (if applicable).

=end item7                        


=begin item7

B<C<b>> an unsigned integer, in binary, with a B<C<0b>> prepended if the B<C<#>> flag is present.

=end item7                        

=begin item7

B<C<B>> an unsigned integer, in binary, with a B<C<0B>> prepended if the B<C<#>> flag is present.

=end item7                        

=begin item7

B<C<i>> a synonym for B<C<%d>>.

=end item7                        

=begin item7

B<C<D>> a synonym for B<C<%ld>>.

=end item7                        

=begin item7

B<C<U>> a synonym for B<C<%lu>>.

=end item7                        

=begin item7

B<C<O>> a synonym for B<C<%lo>>.

=end item7                        

=begin item7

B<C<F>> a synonym for B<C<%f>>.

=end item7                        

=item4 L<Top of Document|#table-of-contents>

=begin item3

B<C<:&number-of-chars>> is an optional named argument which takes a function with a signature B<C<:(Int:D, Int:D --> Bool:D)>> if not specified it will have the value of B<C<&Sprintf-global-number-of-chars>> which is defined as:

=begin code :lang<raku>

our $Sprintf-total-number-of-chars is export = 0;
our $Sprintf-total-number-of-visible-chars is export = 0;

sub Sprintf-global-number-of-chars(Int:D $number-of-chars, Int:D $number-of-visible-chars --> Bool:D) {
    $Sprintf-total-number-of-chars         = $number-of-chars;
    $Sprintf-total-number-of-visible-chars = $number-of-visible-chars;
    return True
}

=end code

=end item3

=begin item4

This is exactly the same as the argument by the same name in B<C<centre>>, B<C<left>> and B<C<right>> above.

i.e. 

=begin code :lang<raku>
    
sub test( --> True) is export {
    ...
    ...
    ...
    my $test-number-of-chars = 0;
    my $test-number-of-visible-chars = 0;

    sub test-number-of-chars(Int:D $number-of-chars, Int:D $number-of-visible-chars --> Bool:D) {
        $test-number-of-chars         = $number-of-chars;
        $test-number-of-visible-chars = $number-of-visible-chars;
        return True
    }

    put Sprintf('%30.14.14s, %30.14.13s%N%%%N%^*.*s%3$*4$.*3$.*6$d%N%2$^[&]*3$.*4$.*6$s%T%1$[*]^100.*4$.99s',
                                        ${ arg => $highlighted, ref => $text }, $text, 30, 14, $highlighted, 13,
                                                                    :number-of-chars(&test-number-of-chars), :ellipsis('…'));
    dd $test-number-of-chars,  $test-number-of-visible-chars;
    put Sprintf('%30.14.14s,  testing %30.14.13s%N%%%N%^*.*s%3$*4$.*3$.*6$d%N%2$^[&]*3$.*4$.*6$s%T%1$[*]^100.*4$.99s',
                                $[ $highlighted, $text ], $text, 30, 14, $highlighted, 13, 13,
                                                                    :number-of-chars(&test-number-of-chars), :ellipsis('…'));
    dd $test-number-of-chars,  $test-number-of-visible-chars;
    ...
    ...
    ...
}
 
=end code

=end item4

=begin item5

B<Note: This is a closure we should always use a closure if we want to get the number of characters printed.> 

=end item5

=item4 L<Top of Document|#table-of-contents>

=begin item3

B<C<:$ellipsis>> this is an optional argument of type B<C<Str:D>> which defaults to B<C<''>>, if set will be used
to mark elided text, if the argument is truncated due to exceeding the value of B<C<max-width>>
(note B<C<max-width>> defaults to B<C<0>> which means infinity). The recommended value would be something like B<C<…>>.

=end item3

=begin item3

B<C<*@args>> is an arbitrary long list of values each argument can be either a scalar value to be printed or a Hash or an Array

=end item3

=begin item4

If a Hash then it should contain two pairs with keys: B<C<arg>> and B<C<ref>>; denoting the actual argument and a reference
argument respectively, the ref argument should be the same as B<C<arg>> but with no ANSI formatting etc to mess up the counting.
As this ruins formatting spacing. If not present will be set to B<C<strip-ansi($arg)>>, only bother with all this if
B<C<strip-ansi($arg)>> isn't good enough.

=end item4

=begin item4

If a Array then it should contain two values. The first being  B<C<arg>> and the other being B<C<ref>>; everything else is
the same as above.

=end item4

=begin item4

B<C<arg>> the actual argument.

=end item4

=begin item4

B<C<@args[$i][]>> the actual argument. Where B<C<$i>> is the current index into the array of args.

=end item4

=begin item4

B<C<@args[$i][1]>> the reference argument, as in the B<C<:$ref>> arg of the B<left>, B<right> and B<centre> functions which it uses.
It only makes sense if your talking strings possibly formatted if not present will be set to B<C<strip-ansi($arg)>> if $arg
is a Str or just $arg otherwise.

=end item4

=begin item4

If it's a scalar then it's the argument itself. And B<C<$ref>> is B<C<strip-ansi($arg)>> if $arg is a string type i.e. Str or
just B<C>$arg>> otherwise.

=end item4

=begin item5

B<C<ref>> the reference argument, as in the B<C<:$ref>> arg of the B<left>, B<right> and B<centre> functions which it uses.
It only makes sense if your talking strings possibly formatted if not present will be set to B<C<strip-ansi($arg)>> if $arg
is a Str or just $arg otherwise.

i.e.

=begin code :lang<raku>

put Sprintf('%30.14.14s, %30.14.13s%N%%%N%^*.*s%3$*4$.*3$.*6$d%N%2$^[&]*3$.*4$.*6$s%T%1$[*]^100.*4$.99s',
                            ${ arg => $highlighted, ref => $text }, $text, 30, 14, $highlighted, 13,
                                                                        :number-of-chars(&test-number-of-chars), :ellipsis('…'));
dd $test-number-of-chars,  $test-number-of-visible-chars;
put Sprintf('%30.14.14s,  testing %30.14.13s%N%%%N%^*.*s%3$*4$.*3$.*6$d%N%2$^[&]*3$.*4$.*6$s%T%1$[*]^100.*4$.99s',
                            $[ $highlighted, $text ], $text, 30, 14, $highlighted, 13, 13,
                                                                        :number-of-chars(&test-number-of-chars), :ellipsis('…'));
dd $test-number-of-chars,  $test-number-of-visible-chars;
 
=end code

=end item5


=end pod

our $Sprintf-total-number-of-chars is export = 0;
our $Sprintf-total-number-of-visible-chars is export = 0;

my subset FUInt of Int where {not .defined or $_ >= -1};

sub Sprintf-global-number-of-chars(Int:D $number-of-chars, Int:D $number-of-visible-chars --> Bool:D) {
    $Sprintf-total-number-of-chars         = $number-of-chars;
    $Sprintf-total-number-of-visible-chars = $number-of-visible-chars;
    return True
}

sub Sprintf(Str:D $format-str,
                :&number-of-chars:(Int:D, Int:D --> Bool:D) = &Sprintf-global-number-of-chars,
                                                        Str:D :$ellipsis = '', *@args --> Str) is export {
    my $actions = FormatActions;
    my @format-str = Format.parse($format-str, :enc('UTF-8'), :$actions).made;
    dd @format-str if $debug;
    FormatSpecError.new(:msg("Error bad format-str arg did not parse!")).throw if !@format-str || @format-str[0] === Any;
    my Int:D $positionals = [+] (@format-str.grep( -> %elt { %elt«type» eq 'fmt-spec' }).map( -> %e {
                                                                                                 my Int:D $n = 1;
                                                                                                 $n-- if %e«dollar-directive» > 0;
                                                                                                 $n++ if %e«width»«kind» eq 'star';
                                                                                                 $n++ if %e«precision»«kind» eq 'star';
                                                                                                 $n;
                                                                                             }));
    my %extra-args is SetHash[Int] = (^$positionals).list;
    my Int:D $dollars  = [+] (@format-str.grep( -> %elt { %elt«type» eq 'fmt-spec' }).map( -> %e {
                                                                                          my Int:D $n = 0;
                                                                                          my Int:D $dollar-directive = %e«dollar-directive»;
                                                                                          if $dollar-directive > 0
                                                                                                        && (%extra-args{$dollar-directive}:!exists) {
                                                                                                $n++;
                                                                                                #`««« add to SetHash »»»
                                                                                                %extra-args{$dollar-directive} = True;
                                                                                          }
                                                                                          my Int:D $dollar = ((%e«width»«kind» eq 'dollar') ??
                                                                                                                              %e«width»«val» !! -1);
                                                                                          if $dollar > 0 && (%extra-args{$dollar}:!exists) {
                                                                                                $n++;
                                                                                                #`««« add to SetHash »»»
                                                                                                %extra-args{$dollar} = True;
                                                                                          }
                                                                                          $dollar = ((%e«precision»«kind» eq 'dollar') ??
                                                                                                                          %e«precision»«val» !! -1);
                                                                                          if $dollar > 0 && (%extra-args{$dollar}:!exists) {
                                                                                                $n++;
                                                                                                #`««« add to SetHash »»»
                                                                                                %extra-args{$dollar} = True;
                                                                                          }
                                                                                          $n;
                                                                                      }));
    my Int:D $max = %extra-args.keys.max;
    ArgParityMissMatch.new(:msg("Error: argument parity error; expected $positionals args got {@args.elems}")).throw if %extra-args.elems == 0 &&
                                                                                                                         $positionals != @args.elems;
    ArgParityMissMatch.new(:msg("Error: argument parity error; referenced argument index: {$max + 1} outside the range 1..{@args.elems}")).throw
                                                                                                                            if $max >= @args.elems;
    $positionals += $dollars;
    my Int:D $total-number-of-chars = 0;
    my Int:D $total-number-of-visible-chars = 0;
    sub internal-number-of-chars(Int:D $number-of-chars, Int:D $number-of-visible-chars --> Bool:D) {
        $total-number-of-chars += $number-of-chars;
        $total-number-of-visible-chars += $number-of-visible-chars;
        return True;
    } # sub internal-number-of-chars(Int:D $number-of-chars, Int:D $number-of-visible-chars --> Bool:D) #
    my Str:D $result = '';
    my Int:D $cnt = 0;
    dd @format-str if $debug;
    for @format-str -> %elt {
        my Str:D $type = %elt«type»;
        dd $type, %elt if $debug;
        if $type eq 'literal' {
            my Str:D $lit = %elt«val»;
            $total-number-of-chars += $lit.chars;
            $total-number-of-visible-chars += strip-ansi($lit).chars;
            $result ~= $lit;
        } elsif $type eq 'fmt-spec' {
            #my %fmt-spec = type => 'fmt-spec', dollar-directive => -1, flags => [],
            #                   width => { kind => 'empty', val => 0, }
            #                   precision => { kind => 'empty', val => 0, },
            #                   modifier => '', spec-char => $/<spec-char>.made;
            my         %width-spec = %elt«width»;
            my     %precision-spec = %elt«precision»;
            my     %max-width-spec = %elt«max-width»;
            my FUInt:D $width      = -1; # -1 denotes not present. #
            my FUInt:D $precision  = -1; # -1 denotes not present. #
            my UInt:D $max-width   = 0;
           if %width-spec«kind» eq 'star' {
                BadArg.new(:msg("arg count out of range not enough args")).throw unless $cnt < @args.elems;
                my Str:D $name = @args[$cnt].WHAT.^name;
                my $tmp;
                if $name eq 'Hash' || $name ~~ rx/ ^ 'Hash[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp = @args[$cnt]«arg»;
                } elsif $name eq 'Array' || $name ~~ rx/ ^ 'Array[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp = @args[$cnt][0];
                } else {
                    $tmp = @args[$cnt]; # @args[$cnt] is a scalar and should be an Int #
                }
                BadArg.new(:msg("bad star argument width not an Int:D >= -1\nNB: -1 is the same as not being there.")).throw
                                                                                                                    if $tmp !~~ FUInt:D;
                $width   = $tmp;
                $cnt++;
            }elsif %width-spec«kind» eq 'dollar' {
                my Int:D $i = %width-spec«val»;
                dd $i, %width-spec if $debug;
                BadArg.new(:msg("\$ spec for width out of range")).throw unless $i ~~ 0..^@args.elems;
                my Str:D $name = @args[$i].WHAT.^name;
                dd $name if $debug;
                my $tmp;
                if $name eq 'Hash' || $name ~~ rx/ ^ 'Hash[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp = @args[$i]«arg»;
                } elsif $name eq 'Array' || $name ~~ rx/ ^ 'Array[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp = @args[$i][0];
                } else {
                    $tmp = @args[$i]; # @args[$i] is a scalar and should be an Int #
                }
                BadArg.new(:msg("bad star argument width not an Int:D >= -1\nNB: -1 is the same as not being there.")).throw
                                                                                                                    if $tmp !~~ FUInt:D;
                $width   = $tmp;
            } elsif %width-spec«kind» eq 'int' {
                my $tmp  = %width-spec«val»;
                BadArg.new(:msg("bad star argument width not an Int:D >= -1\nNB: -1 is the same as not being there.")).throw
                                                                                                                    if $tmp !~~ FUInt:D;
                $width   = $tmp;
            }
            if %precision-spec«kind» eq 'star' {
                BadArg.new(:msg("arg count out of range not enough args")).throw unless $cnt < @args.elems;
                my Str:D $name = @args[$cnt].WHAT.^name;
                my $tmp;
                if $name eq 'Hash' || $name ~~ rx/ ^ 'Hash[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp   = @args[$cnt]«arg»;
                } elsif $name eq 'Array' || $name ~~ rx/ ^ 'Array[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp   = @args[$cnt][0];
                } else {
                    $tmp   = @args[$cnt]; # @args[$cnt] is a scalar and should be an Int #
                }
                BadArg.new(:msg("bad star argument precision not an Int:D >= -1\nNB: -1 is the same as not being there.")).throw
                                                                                                                    if $tmp !~~ FUInt:D;
                $precision = $tmp;
                $cnt++;
            }elsif %precision-spec«kind» eq 'dollar' {
                my Int:D $i = %precision-spec«val»;
                BadArg.new(:msg("\$ spec for precision out of range")).throw unless $i ~~ 0..^@args.elems;
                my Str:D $name = @args[$i].WHAT.^name;
                my $tmp;
                if $name eq 'Hash' || $name ~~ rx/ ^ 'Hash[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp   = @args[$i]«arg»;
                } elsif $name eq 'Array' || $name ~~ rx/ ^ 'Array[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp   = @args[$i][0];
                } else {
                    $tmp   = @args[$i]; # @args[$i] is a scalar and should be an Int #
                }
                BadArg.new(:msg("bad star argument precision not an Int:D >= -1\nNB: -1 is the same as not being there.")).throw
                                                                                                                    if $tmp !~~ FUInt:D;
                $precision = $tmp;
            } elsif %precision-spec«kind» eq 'int' {
                my $tmp    = %precision-spec«val»;
                BadArg.new(:msg("bad star argument precision not an Int:D >= -1\nNB: -1 is the same as not being there.")).throw
                                                                                                                    if $tmp !~~ FUInt:D;
                $precision = $tmp;
            }
            if %max-width-spec«kind» eq 'star' {
                BadArg.new(:msg("arg count out of range not enough args")).throw unless $cnt < @args.elems;
                my $tmp;
                my Str:D $name = @args[$cnt].WHAT.^name;
                if $name eq 'Hash' || $name ~~ rx/ ^ 'Hash[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp   = @args[$cnt]«arg»;
                } elsif $name eq 'Array' || $name ~~ rx/ ^ 'Array[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp   = @args[$cnt][0];
                } else {
                    $tmp   = @args[$cnt]; # @args[$cnt] is a scalar and should be an Int #
                }
                BadArg.new(:msg("bad star argument max-width not an UInt:D")).throw if $tmp !~~ UInt:D;
                $max-width = $tmp;
                $cnt++;
            }elsif %max-width-spec«kind» eq 'dollar' {
                my Int:D $i = %max-width-spec«val»;
                BadArg.new(:msg("\$ spec for max-width out of range")).throw unless $i ~~ 0..^@args.elems;
                my Str:D $name = @args[$i].WHAT.^name;
                my $tmp;
                if $name eq 'Hash' || $name ~~ rx/ ^ 'Hash[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp   = @args[$i]«arg»;
                } elsif $name eq 'Array' || $name ~~ rx/ ^ 'Array[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                    $tmp   = @args[$i][0];
                } else {
                    $tmp   = @args[$i]; # @args[$i] is a scalar and should be an Int #
                }
                BadArg.new(:msg("bad dollar argument max-width not an UInt:D")).throw if $tmp !~~ UInt:D;
                $max-width = $tmp;
            } elsif %max-width-spec«kind» eq 'int' {
                my $tmp    = %max-width-spec«val»;
                BadArg.new(:msg("bad dollar argument max-width not an UInt:D")).throw if $tmp !~~ UInt:D;
                $max-width = $tmp;
            }
            my $arg;
            my $ref;
            my Int:D $dollar-directive = %elt«dollar-directive»;
            my Int:D $i = $dollar-directive;
            dd %elt, $dollar-directive, $i, $cnt if $debug;
            BadArg.new(:msg("\$ spec for arg out of range")).throw unless $i < @args.elems;
            if $dollar-directive < 0 {
                $i = $cnt;
                $cnt++;
            }
            my Str:D $name = @args[$i].WHAT.^name;
            if $name eq 'Hash' || $name ~~ rx/ ^ 'Hash[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                $arg = @args[$i]«arg»;
                $ref = ((@args[$i]«ref»:exists) ?? @args[$i]«ref» !! (($arg ~~ Str:D) ?? strip-ansi($arg) !! $arg));
            } elsif $name eq 'Array' || $name ~~ rx/ ^ 'Array[' [ \w+ [ [ '-' || '::' || ':' ] \w+ ]* ] ']' $ / {
                $arg = @args[$i][0];
                if @args[$i].elems == 1 {
                    $ref = (($arg ~~ Str:D) ?? strip-ansi($arg) !! $arg);
                } elsif @args[$i].elems > 1 {
                    $ref = @args[$i][1];
                }
            } else {
                $arg = @args[$i];
                $ref = (($arg ~~ Str:D) ?? strip-ansi($arg) !! $arg);
            }
            dd $arg, $ref, %elt, $width, $precision, $max-width if $debug;
            my        @flags       = |%elt«flags»;
            dd @flags if $debug;
            my Str:D  $padding     = '';
            my Bool:D $force-sign  = False;
            my Str:D  $justify     = '';
            my Bool:D $type-prefix = False;
            my Bool:D $vector      = False;
            #token flag              { [ <force-sign> || <justify> || <type-prefix> || <vector> || <padding> ] }
            my @force-sign         = @flags.grep: -> %flg { %flg«kind» eq 'force-sign' };
            my @justify            = @flags.grep: -> %flg { %flg«kind» eq 'justify' };
            my @type-prefix        = @flags.grep: -> %flg { %flg«kind» eq 'type-prefix' };
            my @vector             = @flags.grep: -> %flg { %flg«kind» eq 'vector' };
            my @padding            = @flags.grep: -> %flg { %flg«kind» eq 'padding' };
            my %force-sign; 
            %force-sign            = @force-sign[@force-sign.elems - 1] if @force-sign.elems > 0; 
            my %justify; 
            %justify               = @justify[@justify.elems - 1] if @justify.elems > 0; 
            my %type-prefix; 
            %type-prefix           = @type-prefix[@type-prefix.elems - 1] if @type-prefix.elems > 0; 
            my %vector; 
            %vector                = @vector[@vector.elems - 1] if @vector.elems > 0; 
            my %padding; 
            %padding               = @padding[@padding.elems - 1] if @padding.elems > 0; 
            $padding               = %padding«val»  if %padding;
            $force-sign            = True if %force-sign && %force-sign«val» eq '+';
            $justify               = '^'  if %justify && %justify«val» eq '^';
            $justify               = '-'  if %justify && %justify«val» eq '-';
            $type-prefix           = True if %type-prefix && %type-prefix«val» eq '#';
            $vector                = True if %vector && %vector«val» eq 'v';
            #my Str:D $modifier     = %elt«modifier»; # ignore these for now #
            my Str:D $spec-char    = %elt«spec-char»;
            $name = $arg.WHAT.^name;
            dd $arg, $ref, $ellipsis, $width, $precision, $max-width, $padding, $justify, $type-prefix, $spec-char if $debug;
            given $spec-char {
                when 'c' {
                             $arg .=Str;
                             $ref .=Str;
                             BadArg.new(:msg("arg should be one codepoint: {$arg.codes} found")).throw if $arg.codes != 1;
                             $max-width = max($max-width, $precision, 0) if $max-width > 0; #`« should not really have a both for this
                                                                                                so munge together.
                                                                                                Traditionally sprintf etc treat precision
                                                                                                as max-width for strings. »
                             if $padding eq '' {
                                 if $justify eq '' {
                                     $result ~=  right($arg, $width, :$ref, :number-of-chars(&internal-number-of-chars), :$max-width);
                                 } elsif $justify eq '-' {
                                     $result ~=  left($arg, $width, :$ref, :number-of-chars(&internal-number-of-chars), :$max-width);
                                 } elsif $justify eq '^' {
                                     $result ~=  centre($arg, $width, :$ref, :number-of-chars(&internal-number-of-chars), :$max-width);
                                 }
                             } else {
                                 if $justify eq '' {
                                     $result ~=  right($arg, $width, $padding, :$ref, :number-of-chars(&internal-number-of-chars), :$max-width);
                                 } elsif $justify eq '-' {
                                     $result ~=  left($arg, $width, $padding, :$ref, :number-of-chars(&internal-number-of-chars), :$max-width);
                                 } elsif $justify eq '^' {
                                     $result ~=  centre($arg, $width, $padding, :$ref, :number-of-chars(&internal-number-of-chars), :$max-width);
                                 }
                             }
                         }
                when 's' {
                             $arg .=Str;
                             $ref .=Str;
                             $max-width = max($max-width, $precision, 1) if $max-width > 0; #`« should not really have a both for this
                                                                                                so munge together.
                                                                                                Traditionally sprintf etc treat precision
                                                                                                as max-width for strings. »
                             dd $arg, $ref, $width, $precision, $max-width, $justify, $padding if $debug;
                             if $padding eq '' {
                                 if $justify eq '' {
                                     $result ~=  right($arg, $width, :number-of-chars(&internal-number-of-chars), :$ref,
                                                                                                             :$max-width, :$ellipsis);
                                 } elsif $justify eq '-' {
                                     $result ~=  left($arg, $width, :number-of-chars(&internal-number-of-chars), :$ref,
                                                                                                             :$max-width, :$ellipsis);
                                 } elsif $justify eq '^' {
                                     $result ~=  centre($arg, $width, :number-of-chars(&internal-number-of-chars), :$ref,
                                                                                                             :$max-width, :$ellipsis);
                                 }
                             } else {
                                 if $justify eq '' {
                                     $result ~=  right($arg, $width, $padding, :number-of-chars(&internal-number-of-chars), :$ref,
                                                                                                             :$max-width, :$ellipsis);
                                 } elsif $justify eq '-' {
                                     $result ~=  left($arg, $width, $padding, :number-of-chars(&internal-number-of-chars), :$ref,
                                                                                                             :$max-width, :$ellipsis);
                                 } elsif $justify eq '^' {
                                     $result ~=  centre($arg, $width, $padding, :number-of-chars(&internal-number-of-chars), :$ref,
                                                                                                             :$max-width, :$ellipsis);
                                 }
                             }
                         }
                when 'd'|'i'|'D' {
                                       $arg .=Int;
                                       $max-width = max($max-width, $precision, 0) if $max-width > 0; #`« should not really have a both for this
                                                                                                          so munge together.
                                                                                                          Traditionally sprintf etc treat precision
                                                                                                          as max-width for Ints. »
                                       my Str:D $fmt = '%';
                                       $fmt ~= '+' if $force-sign;
                                       $fmt ~= '#' if $type-prefix;
                                       $fmt ~= 'v' if $vector;
                                       if $padding eq '0' {
                                           $fmt ~= '-' if $justify eq '-';
                                           $fmt ~= $padding;
                                           if $width >= 0 { # centre etc make no sense here #
                                               if $max-width >= 0 {
                                                   $fmt ~= '*.*';
                                                   $fmt ~= $spec-char.lc;
                                                   $result ~= right(sprintf($fmt, $width, $max-width, $arg), $width, $padding,
                                                                               :number-of-chars(&internal-number-of-chars),
                                                                                                               :$max-width, :$ellipsis);
                                               } else {
                                                   $fmt ~= '*';
                                                   $fmt ~= $spec-char.lc;
                                                   $result ~= right(sprintf($fmt, $width, $arg), $width, $padding,
                                                                               :number-of-chars(&internal-number-of-chars),
                                                                                                               :$max-width, :$ellipsis);
                                               }
                                           } else {
                                               if $