11
method panic($msg) { nqp::die($msg) }
15
| <?[%]> [ [ <directive> | <escape> ]
16
|| <.panic("'" ~ nqp::substr(self.orig,1) ~ "' is not valid in sprintf format sequence '" ~ self.orig ~ "'")> ]
21
proto token directive { <...> }
22
token directive:sym<b> { '%' <idx>? <flags>* <size>? [ '.' <precision=.size> ]? $<sym>=<[bB]> }
23
token directive:sym<c> { '%' <idx>? <flags>* <size>? <sym> }
24
token directive:sym<d> { '%' <idx>? <flags>* <size>? [ '.' <precision=.size> ]? $<sym>=<[di]> }
25
token directive:sym<e> { '%' <idx>? <flags>* <size>? [ '.' <precision=.size> ]? $<sym>=<[eE]> }
26
token directive:sym<f> { '%' <idx>? <flags>* <size>? [ '.' <precision=.size> ]? $<sym>=<[fF]> }
27
token directive:sym<g> { '%' <idx>? <flags>* <size>? [ '.' <precision=.size> ]? $<sym>=<[gG]> }
28
token directive:sym<o> { '%' <idx>? <flags>* <size>? [ '.' <precision=.size> ]? <sym> }
29
token directive:sym<s> { '%' <idx>? <flags>* <size>? [ '.' <precision=.size> ]? <sym> }
30
token directive:sym<u> { '%' <idx>? <flags>* <size>? <sym> }
31
token directive:sym<x> { '%' <idx>? <flags>* <size>? [ '.' <precision=.size> ]? $<sym>=<[xX]> }
33
proto token escape { <...> }
34
token escape:sym<%> { '%' <flags>* <size>? <sym> }
36
token literal { <-[%]>+ }
39
$<param_index>=[\d+] '$'
56
my $knowhow := nqp::knowhow().new_type(:repr("P6bigint"));
57
my $zero := nqp::box_i(0, $knowhow);
60
@statements.push( $_.ast ) for $<statement>;
62
if $assert_used_args && $*ARGS_USED < +@*ARGS_HAVE {
63
nqp::die("Too few directives: found $*ARGS_USED,"
64
~ " fewer than the " ~ +@*ARGS_HAVE ~ " arguments after the format string")
66
if $*ARGS_USED > +@*ARGS_HAVE {
67
nqp::die("Too many directives: found $*ARGS_USED, but "
68
~ (+@*ARGS_HAVE > 0 ?? "only " ~ +@*ARGS_HAVE !! "no")
69
~ " arguments after the format string")
71
make nqp::join('', @statements);
77
@strings.push($s) while $i++ < $n;
78
nqp::join('', @strings);
81
sub next_argument($/) {
83
$assert_used_args := 0;
84
@*ARGS_HAVE[$<idx>.ast]
87
@*ARGS_HAVE[$*ARGS_USED++]
91
sub intify($number_representation) {
92
for @handlers -> $handler {
93
if $handler.mine($number_representation) {
94
return $handler.int($number_representation);
98
if nqp::isint($number_representation) {
99
nqp::box_i($number_representation, $knowhow);
101
if nqp::isnum($number_representation)
102
|| nqp::isstr($number_representation) {
103
if $number_representation > 0 {
104
nqp::fromnum_I(nqp::floor_n($number_representation), $knowhow);
107
nqp::fromnum_I(nqp::ceil_n($number_representation), $knowhow);
110
$number_representation;
115
sub padding_char($st) {
116
my $padding_char := ' ';
117
if (!$st<precision> && !has_flag($st, 'minus'))
118
|| $st<sym> ~~ /<[eEfFgG]>/ {
119
$padding_char := '0' if $_<zero> for $st<flags>;
124
sub has_flag($st, $key) {
127
$ok := 1 if $_{$key} for $st<flags>
132
method statement($/){
134
if $<directive> { $st := $<directive> }
135
elsif $<escape> { $st := $<escape> }
136
else { $st := $<literal> }
138
@pieces.push: infix_x(padding_char($st), $st<size>.ast - nqp::chars($st.ast)) if $st<size>;
139
has_flag($st, 'minus')
140
?? @pieces.unshift: $st.ast
141
!! @pieces.push: $st.ast;
142
make join('', @pieces)
145
method directive:sym<b>($/) {
146
my $int := intify(next_argument($/));
147
$int := nqp::base_I($int, 2);
148
my $pre := ($<sym> eq 'b' ?? '0b' !! '0B') if $int && has_flag($/, 'hash');
149
if nqp::chars($<precision>) {
150
$int := '' if $<precision>.ast == 0 && $int == 0;
151
$int := $pre ~ infix_x('0', $<precision>.ast - nqp::chars($int)) ~ $int;
158
method directive:sym<c>($/) {
159
make nqp::chr(next_argument($/))
162
method directive:sym<d>($/) {
163
my $int := intify(next_argument($/));
164
my $pad := padding_char($/);
165
my $sign := nqp::islt_I($int, $zero) ?? '-'
166
!! has_flag($/, 'plus')
168
$int := nqp::tostr_I(nqp::abs_I($int, $knowhow));
169
$int := nqp::substr($int, 0, $<precision>.ast) if nqp::chars($<precision>);
170
if $pad ne ' ' && $<size> {
171
$int := $sign ~ infix_x($pad, $<size>.ast - nqp::chars($int) - 1) ~ $int;
174
$int := $sign ~ $int;
179
sub pad-with-sign($sign, $num, $size, $pad) {
180
if $pad ne ' ' && $size {
181
$sign ~ infix_x($pad, $size - nqp::chars($num) - 1) ~ $num;
186
sub stringify-to-precision($float, $precision) {
187
$float := nqp::abs_n($float);
188
my $lhs := nqp::floor_n($float);
189
my $rhs := $float - $lhs;
191
my $int := nqp::fromnum_I($lhs, $knowhow);
192
$lhs := nqp::tostr_I($int);
195
$float := $float * nqp::pow_n(10, $precision);
196
$float := ~nqp::floor_n($float + 0.5);
197
$float := $float - nqp::pow_n(10, $precision);
199
$rhs := infix_x('0', $precision - nqp::chars($float)) ~ $float;
200
$rhs := nqp::substr($rhs, nqp::chars($rhs) - $precision);
204
sub stringify-to-precision2($float, $precision) {
205
my $exp := $float == 0.0 ?? 0 !! nqp::floor_n(nqp::log_n($float) / nqp::log_n(10));
206
$float := nqp::abs_n($float) * nqp::pow_n(10, $precision - ($exp + 1)) + 0.5;
207
$float := nqp::floor_n($float);
208
$float := $float / nqp::pow_n(10, $precision - ($exp + 1));
211
$float := stringify-to-precision($float, $precision + 3);
212
$float := nqp::substr($float, 0, nqp::chars($float) - 1) if nqp::chars($float) > 1 && $float ~~ /\.\d**4 0+$/;
213
$float := nqp::substr($float, 0, nqp::chars($float) - 1) if nqp::chars($float) > 1 && $float ~~ /\.\d**4 0+$/;
218
sub fixed-point($float, $precision, $size, $pad) {
219
my $sign := $float < 0 ?? '-' !! '';
220
$float := stringify-to-precision(nqp::abs_n($float), $precision);
221
pad-with-sign($sign, $float, $size, $pad);
223
sub scientific($float, $e, $precision, $size, $pad) {
224
my $sign := $float < 0 ?? '-' !! '';
225
$float := nqp::abs_n($float);
226
my $exp := $float == 0.0 ?? 0 !! nqp::floor_n(nqp::log_n($float) / nqp::log_n(10));
227
$float := $float / nqp::pow_n(10, $exp);
228
$float := stringify-to-precision($float, $precision);
231
$float := $float ~ $e ~ '-' ~ ($exp < 10 ?? '0' !! '') ~ $exp;
233
$float := $float ~ $e ~ '+' ~ ($exp < 10 ?? '0' !! '') ~ $exp;
235
pad-with-sign($sign, $float, $size, $pad);
237
sub shortest($float, $e, $precision, $size, $pad) {
238
my $sign := $float < 0 ?? '-' !! '';
239
$float := nqp::abs_n($float);
241
my $exp := $float == 0.0 ?? 0 !! nqp::floor_n(nqp::log_n($float) / nqp::log_n(10));
243
if -2 - $precision < $exp && $exp < $precision {
244
my $fixed-precision := $exp > $precision ?? 0 !! $precision - ($exp + 1);
245
my $fixed := stringify-to-precision2($float, $precision);
246
pad-with-sign($sign, $fixed, $size, $pad);
248
$float := $float / nqp::pow_n(10, $exp);
249
$float := stringify-to-precision2($float, $precision);
253
$sci := $float ~ $e ~ '-' ~ ($exp < 10 ?? '0' !! '') ~ $exp;
255
$sci := $float ~ $e ~ '+' ~ ($exp < 10 ?? '0' !! '') ~ $exp;
258
pad-with-sign($sign, $sci, $size, $pad);
262
method directive:sym<e>($/) {
263
my $float := next_argument($/);
264
my $precision := $<precision> ?? $<precision>.ast !! 6;
265
my $pad := padding_char($/);
266
my $size := $<size> ?? $<size>.ast !! 0;
267
make scientific($float, $<sym>, $precision, $size, $pad);
269
method directive:sym<f>($/) {
270
my $int := next_argument($/);
271
my $precision := $<precision> ?? $<precision>.ast !! 6;
272
my $pad := padding_char($/);
273
my $size := $<size> ?? $<size>.ast !! 0;
274
make fixed-point($int, $precision, $size, $pad);
276
method directive:sym<g>($/) {
277
my $float := next_argument($/);
278
my $precision := $<precision> ?? $<precision>.ast !! 6;
279
my $pad := padding_char($/);
280
my $size := $<size> ?? $<size>.ast !! 0;
281
make shortest($float, $<sym> eq 'G' ?? 'E' !! 'e', $precision, $size, $pad);
283
method directive:sym<o>($/) {
284
my $int := intify(next_argument($/));
285
$int := nqp::base_I($int, 8);
286
my $pre := '0' if $int && has_flag($/, 'hash');
287
if nqp::chars($<precision>) {
288
$int := '' if $<precision>.ast == 0 && $int == 0;
289
$int := $pre ~ infix_x('0', intify($<precision>.ast) - nqp::chars($int)) ~ $int;
297
method directive:sym<s>($/) {
298
my $string := next_argument($/);
299
if nqp::chars($<precision>) && nqp::chars($string) > $<precision>.ast {
300
$string := nqp::substr($string, 0, $<precision>.ast);
304
# XXX: Should we emulate an upper limit, like 2**64?
305
# XXX: Should we emulate p5 behaviour for negative values passed to %u ?
306
method directive:sym<u>($/) {
307
my $int := intify(next_argument($/));
308
if nqp::islt_I($int, $zero) {
309
my $err := nqp::getstderr();
310
nqp::printfh($err, "negative value '"
312
~ "' for %u in sprintf");
316
# Go through tostr_I to avoid scientific notation.
317
make nqp::tostr_I($int)
319
method directive:sym<x>($/) {
320
my $int := intify(next_argument($/));
321
$int := nqp::base_I($int, 16);
322
my $pre := '0X' if $int && has_flag($/, 'hash');
323
if nqp::chars($<precision>) {
324
$int := '' if $<precision>.ast == 0 && $int == 0;
325
$int := $pre ~ infix_x('0', $<precision>.ast - nqp::chars($int)) ~ $int;
330
make $<sym> eq 'x' ?? nqp::lc($int) !! $int
333
method escape:sym<%>($/) {
342
my $index := $<param_index> - 1;
343
nqp::die("Parameter index starts to count at 1 but 0 was passed") if $index < 0;
348
make $<star> ?? next_argument({}) !! ~$/
352
my $actions := Actions.new();
354
sub sprintf($format, @arguments) {
355
my @*ARGS_HAVE := @arguments;
356
$assert_used_args := 1;
357
return Syntax.parse( $format, :actions($actions) ).ast;
360
nqp::bindcurhllsym('sprintf', &sprintf);
365
$count := nqp::add_i($count, $_.ast) for $<statement>;
369
method statement($/) {
370
make $<directive> && !$<directive><idx> ?? 1 !! 0
374
my $directives := Directives.new();
376
sub sprintfdirectives($format) {
377
return Syntax.parse( $format, :actions($directives) ).ast;
380
nqp::bindcurhllsym('sprintfdirectives', &sprintfdirectives);
382
sub sprintfaddargumenthandler($interface) {
383
@handlers.push($interface);
384
"Added!"; # return meaningless string
387
nqp::bindcurhllsym('sprintfaddargumenthandler', &sprintfaddargumenthandler);