# Copyright (c) 2023 Yuki Kimoto
# MIT License
class Format {
use StringBuffer;
use Fn;
precompile static method sprintf : string ($format : string, $args : object[]...) {
my $format_length = length $format;
my $index = 0;
my $buffer = StringBuffer->new;
my $arg_count = 0;
my $constant_string_length = 0;
while ($index + $constant_string_length < $format_length) {
if ($format->[$index + $constant_string_length] != '%') {
# Read constant string
++$constant_string_length;
}
elsif ($index + $constant_string_length + 1 < $format_length &&
$format->[$index + $constant_string_length + 1] == '%') {
# Read %%
++$constant_string_length;
# Add constant string
if ($constant_string_length > 0) {
my $format_part = Fn->substr($format, $index, $constant_string_length);
$buffer->push($format_part);
$index += $constant_string_length;
$constant_string_length = 0;
}
# Skip second %
++$index;
}
elsif ($index + $constant_string_length + 1 >= $format_length) {
die "Invalid conversion in sprintf: end of string";
}
else {
# Add constant string
if ($constant_string_length > 0) {
my $format_part = Fn->substr($format, $index, $constant_string_length);
$buffer->push($format_part);
$index += $constant_string_length;
$constant_string_length = 0;
}
# Check the next element of @$args corresponding to the specifier
unless ($arg_count < @$args) {
die "Missing argument in sprintf";
}
# Read specifier %[flags][width][.precision][length]type
my $specifier_base_index = $index;
++$index; # '%'
# Read `flags`
my $pad_char = ' ';
my $plus_sign = 0;
my $left_justified = 0;
while ($index < $format_length) {
my $flag = (int)($format->[$index]);
switch($flag) {
case '0': {
++$index;
$pad_char = '0';
break;
}
case '+': {
++$index;
$plus_sign = 1;
break;
}
case '-': {
++$index;
$left_justified = 1;
break;
}
default: {
last;
break;
}
}
}
if ($left_justified) {
$pad_char = ' ';
}
# Width
my $width = 0;
while ($index < $format_length) {
my $c = $format->[$index];
if ($c < '0' || '9' < $c) {
last;
}
$width = $width * 10 + $c - '0';
++$index;
}
# Precision
my $precision = 0;
my $has_precision = 0;
if ($index < $format_length && $format->[$index] == '.') {
++$index;
while ($index < $format_length) {
my $c = $format->[$index];
if ($c < '0' || '9' < $c) {
last;
}
$has_precision = 1;
$precision = $precision * 10 + $c - '0';
++$index;
}
}
unless ($has_precision) {
$precision = -1;
}
unless ($index < $format_length) {
die "Invalid conversion in sprintf: \""
. Fn->substr($format, $specifier_base_index, $index - $specifier_base_index) . "\"";
}
my $specifier_first_char = $format->[$index];
$index++;
my $specifier = (string)"";
switch ((int) $specifier_first_char) {
case 'X' : {
$specifier = "X";
break;
}
case 'c' : {
$specifier = "c";
break;
}
case 'd' : {
$specifier = "d";
break;
}
case 'f' : {
$specifier = "f";
break;
}
case 'g' : {
$specifier = "g";
break;
}
case 'l' : {
if ($index < length $format) {
if ($format->[$index] == 'X') {
$index++;
$specifier = "lX";
}
elsif ($format->[$index] == 'd') {
$index++;
$specifier = "ld";
}
elsif ($format->[$index] == 'u') {
$index++;
$specifier = "lu";
}
elsif ($format->[$index] == 'x') {
$index++;
$specifier = "lx";
}
}
break;
}
case 'p' : {
$specifier = "p";
break;
}
case 's' : {
$specifier = "s";
break;
}
case 'u' : {
$specifier = "u";
break;
}
case 'x' : {
$specifier = "x";
break;
}
}
if ($specifier eq "X") {
my $arg = (Int)$args->[$arg_count];
my $formatted_value = &_native_snprintf_x($arg);
$formatted_value = Fn->uc($formatted_value);
&_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
}
elsif ($specifier eq "c") {
my $arg_value : int;
my $arg = $args->[$arg_count];
if ($arg isa Byte) {
$arg_value = $arg->(Byte)->value;
}
else {
$arg_value = $arg->(Int)->value;
}
my $formatted_value = Fn->chr($arg_value);
if (!$formatted_value) {
$formatted_value = "?";
}
&_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
}
elsif ($specifier eq "d") {
my $arg = (Int)$args->[$arg_count];
my $formatted_value = &_native_snprintf_d($arg);
&_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
}
elsif ($specifier eq "f") {
my $arg_value : double;
my $arg = $args->[$arg_count];
if ($arg isa Float) {
$arg_value = $arg->(Float)->value;
}
else {
$arg_value = $arg->(Double)->value;
}
my $formatted_value = &_native_snprintf_f($arg_value, $precision);
&_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
}
elsif ($specifier eq "g") {
my $arg_value : double;
my $arg = $args->[$arg_count];
if ($arg isa Float) {
$arg_value = $arg->(Float)->value;
}
else {
$arg_value = $arg->(Double)->value;
}
my $formatted_value = &_native_snprintf_g($arg_value, $precision);
&_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
}
elsif ($specifier eq "lX") {
my $arg = (Long)$args->[$arg_count];
my $formatted_value = &_native_snprintf_lx($arg);
$formatted_value = Fn->uc($formatted_value);
&_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
}
elsif ($specifier eq "ld") {
my $arg = (Long)$args->[$arg_count];
my $formatted_value = &_native_snprintf_ld($arg);
&_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
}
elsif ($specifier eq "lu") {
my $arg = (Long)$args->[$arg_count];
my $formatted_value = &_native_snprintf_lu($arg);
&_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
}
elsif ($specifier eq "lx") {
my $arg = (Long)$args->[$arg_count];
my $formatted_value = &_native_snprintf_lx($arg);
&_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
}
elsif ($specifier eq "p") {
my $arg = $args->[$arg_count];
my $formatted_value = &_native_snprintf_p($arg);
&_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
}
elsif ($specifier eq "s") {
my $arg = (string)$args->[$arg_count];
if ($has_precision) {
my $arg_length = length $arg;
if ($precision < $arg_length) {
$arg = Fn->substr($arg, 0, $precision);
$width = $precision;
}
}
my $formatted_value = $arg;
&_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
}
elsif ($specifier eq "u") {
my $arg = (Int)$args->[$arg_count];
my $formatted_value = &_native_snprintf_u($arg);
&_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
}
elsif ($specifier eq "x") {
my $arg = (Int)$args->[$arg_count];
my $formatted_value = &_native_snprintf_x($arg);
&_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
}
else {
die "Invalid specifier \"%$specifier\"";
}
++$arg_count;
}
}
# Add constant string
if ($constant_string_length > 0) {
my $format_part = Fn->substr($format, $index, $constant_string_length);
$buffer->push($format_part);
$index += $constant_string_length;
$constant_string_length = 0;
}
my $result = $buffer->to_string;
return $result;
}
native static method _native_snprintf_d : string ($value : int);
native static method _native_snprintf_ld : string ($value : long);
native static method _native_snprintf_lu : string ($value : long);
native static method _native_snprintf_u : string ($value : int);
native static method _native_snprintf_x : string ($value : int);
native static method _native_snprintf_lx : string ($value : long);
native static method _native_snprintf_f : string ($value : double, $precision : int);
native static method _native_snprintf_g : string ($value : double, $precision : int);
native static method _native_snprintf_p : string ($value : object);
static method _push_formatted_string_unsigned : void($buffer : StringBuffer, $formatted_value : string, $width : int, $pad_char : byte, $left_justified : int) {
# "+" sign is always ignored.
my $plus_sign = 0;
&_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
}
precompile static method _push_formatted_string_signed : void($buffer : StringBuffer, $formatted_value : string, $width : int, $pad_char : byte, $left_justified : int, $plus_sign : int) {
my $is_minus = 0;
if ($formatted_value->[0] == '-') {
$is_minus = 1;
}
my $space_count = $width - length $formatted_value;
if (!$is_minus && $plus_sign) {
--$space_count;
}
if ($left_justified) {
if (!$is_minus && $plus_sign) {
$buffer->push_char('+');
}
$buffer->push($formatted_value);
if ($space_count > 0) {
for (; $space_count > 0; --$space_count) {
$buffer->push_char($pad_char);
}
}
}
else {
if ($space_count > 0) {
for (; $space_count > 0; --$space_count) {
$buffer->push_char($pad_char);
}
}
if (!$is_minus && $plus_sign) {
$buffer->push_char('+');
}
$buffer->push($formatted_value);
}
}
}