Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

813 lines (759 sloc) 24.848 kb
/*
* MacRuby implementation of Ruby 1.9's sprintf.c.
*
* This file is covered by the Ruby license. See COPYING for more details.
*
* Copyright (C) 2007-2010, Apple Inc. All rights reserved.
* Copyright (C) 1993-2007 Yukihiro Matsumoto
* Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
* Copyright (C) 2000 Information-technology Promotion Agency, Japan
*/
#include <stdarg.h>
#include "ruby/ruby.h"
#include "ruby/encoding.h"
#include "encoding.h"
/*
* call-seq:
* format(format_string [, arguments...] ) => string
* sprintf(format_string [, arguments...] ) => string
*
* Returns the string resulting from applying <i>format_string</i> to
* any additional arguments. Within the format string, any characters
* other than format sequences are copied to the result.
*
* The syntax of a format sequence is follows.
*
* %[flags][width][.precision]type
*
* A format
* sequence consists of a percent sign, followed by optional flags,
* width, and precision indicators, then terminated with a field type
* character. The field type controls how the corresponding
* <code>sprintf</code> argument is to be interpreted, while the flags
* modify that interpretation.
*
* The field type characters are:
*
* Field | Integer Format
* ------+--------------------------------------------------------------
* b | Convert argument as a binary number.
* | Negative numbers will be displayed as a two's complement
* | prefixed with `..1'.
* B | Equivalent to `b', but uses an uppercase 0B for prefix
* | in the alternative format by #.
* d | Convert argument as a decimal number.
* i | Identical to `d'.
* o | Convert argument as an octal number.
* | Negative numbers will be displayed as a two's complement
* | prefixed with `..7'.
* u | Identical to `d'.
* x | Convert argument as a hexadecimal number.
* | Negative numbers will be displayed as a two's complement
* | prefixed with `..f' (representing an infinite string of
* | leading 'ff's).
* X | Equivalent to `x', but uses uppercase letters.
*
* Field | Float Format
* ------+--------------------------------------------------------------
* e | Convert floating point argument into exponential notation
* | with one digit before the decimal point as [-]d.dddddde[+-]dd.
* | The precision specifies the number of digits after the decimal
* | point (defaulting to six).
* E | Equivalent to `e', but uses an uppercase E to indicate
* | the exponent.
* f | Convert floating point argument as [-]ddd.dddddd,
* | where the precision specifies the number of digits after
* | the decimal point.
* g | Convert a floating point number using exponential form
* | if the exponent is less than -4 or greater than or
* | equal to the precision, or in dd.dddd form otherwise.
* | The precision specifies the number of significant digits.
* G | Equivalent to `g', but use an uppercase `E' in exponent form.
*
* Field | Other Format
* ------+--------------------------------------------------------------
* c | Argument is the numeric code for a single character or
* | a single character string itself.
* p | The valuing of argument.inspect.
* s | Argument is a string to be substituted. If the format
* | sequence contains a precision, at most that many characters
* | will be copied.
*
* The flags modifies the behavior of the formats.
* The flag characters are:
*
* Flag | Applies to | Meaning
* ---------+---------------+-----------------------------------------
* space | bBdiouxX | Leave a space at the start of
* | eEfgG | non-negative numbers.
* | (numeric fmt) | For `o', `x', `X', `b' and `B', use
* | | a minus sign with absolute value for
* | | negative values.
* ---------+---------------+-----------------------------------------
* (digit)$ | all | Specifies the absolute argument number
* | | for this field. Absolute and relative
* | | argument numbers cannot be mixed in a
* | | sprintf string.
* ---------+---------------+-----------------------------------------
* # | bBoxX | Use an alternative format.
* | eEfgG | For the conversions `o', increase the precision
* | | until the first digit will be `0' if
* | | it is not formatted as complements.
* | | For the conversions `x', `X', `b' and `B'
* | | on non-zero, prefix the result with ``0x'',
* | | ``0X'', ``0b'' and ``0B'', respectively.
* | | For `e', `E', `f', `g', and 'G',
* | | force a decimal point to be added,
* | | even if no digits follow.
* | | For `g' and 'G', do not remove trailing zeros.
* ---------+---------------+-----------------------------------------
* + | bBdiouxX | Add a leading plus sign to non-negative
* | eEfgG | numbers.
* | (numeric fmt) | For `o', `x', `X', `b' and `B', use
* | | a minus sign with absolute value for
* | | negative values.
* ---------+---------------+-----------------------------------------
* - | all | Left-justify the result of this conversion.
* ---------+---------------+-----------------------------------------
* 0 (zero) | bBdiouxX | Pad with zeros, not spaces.
* | eEfgG | For `o', `x', `X', `b' and `B', radix-1
* | (numeric fmt) | is used for negative numbers formatted as
* | | complements.
* ---------+---------------+-----------------------------------------
* * | all | Use the next argument as the field width.
* | | If negative, left-justify the result. If the
* | | asterisk is followed by a number and a dollar
* | | sign, use the indicated argument as the width.
*
* Examples of flags:
*
* # `+' and space flag specifies the sign of non-negative numbers.
* sprintf("%d", 123) #=> "123"
* sprintf("%+d", 123) #=> "+123"
* sprintf("% d", 123) #=> " 123"
*
* # `#' flag for `o' increases number of digits to show `0'.
* # `+' and space flag changes format of negative numbers.
* sprintf("%o", 123) #=> "173"
* sprintf("%#o", 123) #=> "0173"
* sprintf("%+o", -123) #=> "-173"
* sprintf("%o", -123) #=> "..7605"
* sprintf("%#o", -123) #=> "..7605"
*
* # `#' flag for `x' add a prefix `0x' for non-zero numbers.
* # `+' and space flag disables complements for negative numbers.
* sprintf("%x", 123) #=> "7b"
* sprintf("%#x", 123) #=> "0x7b"
* sprintf("%+x", -123) #=> "-7b"
* sprintf("%x", -123) #=> "..f85"
* sprintf("%#x", -123) #=> "0x..f85"
* sprintf("%#x", 0) #=> "0"
*
* # `#' for `X' uses the prefix `0X'.
* sprintf("%X", 123) #=> "7B"
* sprintf("%#X", 123) #=> "0X7B"
*
* # `#' flag for `b' add a prefix `0b' for non-zero numbers.
* # `+' and space flag disables complements for negative numbers.
* sprintf("%b", 123) #=> "1111011"
* sprintf("%#b", 123) #=> "0b1111011"
* sprintf("%+b", -123) #=> "-1111011"
* sprintf("%b", -123) #=> "..10000101"
* sprintf("%#b", -123) #=> "0b..10000101"
* sprintf("%#b", 0) #=> "0"
*
* # `#' for `B' uses the prefix `0B'.
* sprintf("%B", 123) #=> "1111011"
* sprintf("%#B", 123) #=> "0B1111011"
*
* # `#' for `e' forces to show the decimal point.
* sprintf("%.0e", 1) #=> "1e+00"
* sprintf("%#.0e", 1) #=> "1.e+00"
*
* # `#' for `f' forces to show the decimal point.
* sprintf("%.0f", 1234) #=> "1234"
* sprintf("%#.0f", 1234) #=> "1234."
*
* # `#' for `g' forces to show the decimal point.
* # It also disables stripping lowest zeros.
* sprintf("%g", 123.4) #=> "123.4"
* sprintf("%#g", 123.4) #=> "123.400"
* sprintf("%g", 123456) #=> "123456"
* sprintf("%#g", 123456) #=> "123456."
*
* The field width is an optional integer, followed optionally by a
* period and a precision. The width specifies the minimum number of
* characters that will be written to the result for this field.
*
* Examples of width:
*
* # padding is done by spaces, width=20
* # 0 or radix-1. <------------------>
* sprintf("%20d", 123) #=> " 123"
* sprintf("%+20d", 123) #=> " +123"
* sprintf("%020d", 123) #=> "00000000000000000123"
* sprintf("%+020d", 123) #=> "+0000000000000000123"
* sprintf("% 020d", 123) #=> " 0000000000000000123"
* sprintf("%-20d", 123) #=> "123 "
* sprintf("%-+20d", 123) #=> "+123 "
* sprintf("%- 20d", 123) #=> " 123 "
* sprintf("%020x", -123) #=> "..ffffffffffffffff85"
*
* For
* numeric fields, the precision controls the number of decimal places
* displayed. For string fields, the precision determines the maximum
* number of characters to be copied from the string. (Thus, the format
* sequence <code>%10.10s</code> will always contribute exactly ten
* characters to the result.)
*
* Examples of precisions:
*
* # precision for `d', 'o', 'x' and 'b' is
* # minimum number of digits <------>
* sprintf("%20.8d", 123) #=> " 00000123"
* sprintf("%20.8o", 123) #=> " 00000173"
* sprintf("%20.8x", 123) #=> " 0000007b"
* sprintf("%20.8b", 123) #=> " 01111011"
* sprintf("%20.8d", -123) #=> " -00000123"
* sprintf("%20.8o", -123) #=> " ..777605"
* sprintf("%20.8x", -123) #=> " ..ffff85"
* sprintf("%20.8b", -11) #=> " ..110101"
*
* # "0x" and "0b" for `#x' and `#b' is not counted for
* # precision but "0" for `#o' is counted. <------>
* sprintf("%#20.8d", 123) #=> " 00000123"
* sprintf("%#20.8o", 123) #=> " 00000173"
* sprintf("%#20.8x", 123) #=> " 0x0000007b"
* sprintf("%#20.8b", 123) #=> " 0b01111011"
* sprintf("%#20.8d", -123) #=> " -00000123"
* sprintf("%#20.8o", -123) #=> " ..777605"
* sprintf("%#20.8x", -123) #=> " 0x..ffff85"
* sprintf("%#20.8b", -11) #=> " 0b..110101"
*
* # precision for `e' is number of
* # digits after the decimal point <------>
* sprintf("%20.8e", 1234.56789) #=> " 1.23456789e+03"
*
* # precision for `f' is number of
* # digits after the decimal point <------>
* sprintf("%20.8f", 1234.56789) #=> " 1234.56789000"
*
* # precision for `g' is number of
* # significant digits <------->
* sprintf("%20.8g", 1234.56789) #=> " 1234.5679"
*
* # <------->
* sprintf("%20.8g", 123456789) #=> " 1.2345679e+08"
*
* # precision for `s' is
* # maximum number of characters <------>
* sprintf("%20.8s", "string test") #=> " string t"
*
* Examples:
*
* sprintf("%d %04x", 123, 123) #=> "123 007b"
* sprintf("%08b '%4s'", 123, 123) #=> "01111011 ' 123'"
* sprintf("%1$*2$s %2$d %1$s", "hello", 8) #=> " hello 8 hello"
* sprintf("%1$*2$s %2$d", "hello", -8) #=> "hello -8"
* sprintf("%+g:% g:%-g", 1.23, 1.23, 1.23) #=> "+1.23: 1.23:1.23"
* sprintf("%u", -123) #=> "-123"
*/
#define GETNTHARG(nth) \
((nth >= argc) ? (rb_raise(rb_eArgError, "too few arguments"), 0) : \
argv[nth])
VALUE
rb_f_sprintf_imp(VALUE recv, SEL sel, int argc, VALUE *argv)
{
return rb_str_format(argc - 1, argv + 1, GETNTHARG(0));
}
VALUE
rb_f_sprintf(int argc, const VALUE *argv)
{
return rb_str_format(argc - 1, argv + 1, GETNTHARG(0));
}
VALUE
rb_enc_vsprintf(rb_encoding *enc, const char *fmt, va_list ap)
{
char buffer[512];
int n;
n = vsnprintf(buffer, sizeof buffer, fmt, ap);
return rb_enc_str_new(buffer, n, enc);
}
VALUE
rb_enc_sprintf(rb_encoding *enc, const char *format, ...)
{
va_list ap;
va_start(ap, format);
VALUE result = rb_enc_vsprintf(enc, format, ap);
va_end(ap);
return result;
}
VALUE
rb_vsprintf(const char *fmt, va_list ap)
{
return rb_enc_vsprintf(NULL, fmt, ap);
}
VALUE
rb_sprintf(const char *format, ...)
{
va_list ap;
va_start(ap, format);
VALUE result = rb_vsprintf(format, ap);
va_end(ap);
return result;
}
#define IS_NEG(num) RBIGNUM_NEGATIVE_P(num)
#define REL_REF 1
#define ABS_REF 2
#define NAMED_REF 3
#define REF_NAME(type) \
((type) == REL_REF ? "relative" : (type) == ABS_REF ? "absolute" : "named")
#define SET_REF_TYPE(type) \
if (ref_type != 0 && (type) != ref_type) { \
rb_raise(rb_eArgError, "can't mix %s references with %s references", \
REF_NAME(type), REF_NAME(ref_type)); \
} \
ref_type = (type);
#define GET_ARG() \
if (arg == 0) { \
SET_REF_TYPE(REL_REF); \
arg = GETNTHARG(j); \
j++; \
}
#define isprenum(ch) ((ch) == '-' || (ch) == ' ' || (ch) == '+')
static void
pad_format_value(VALUE arg, long start, long width, VALUE pad)
{
const long slen = rb_str_chars_len(arg);
if (width <= slen) {
return;
}
if (start < 0) {
start += slen + 1;
}
width -= slen;
do {
rb_str_update(arg, start, 0, pad);
}
while (--width > 0);
}
static long
cstr_update(UChar **str, long *str_len, long start, long num, VALUE replace)
{
const long len = *str_len;
long replace_len = replace == 0 ? 0 : rb_str_chars_len(replace);
if (start + num > len) {
num = len - start;
}
if (replace_len >= num) {
*str_len = len + replace_len - num;
*str = (UChar *)xrealloc(*str,
sizeof(UChar) * (len + replace_len - num));
}
if (replace_len != num) {
bcopy(*str + start + num, *str + start + replace_len,
sizeof(UChar) * (len - start - num));
}
if (replace_len > 0) {
UChar *replace_chars = NULL;
bool need_free = false;
rb_str_get_uchars(replace, &replace_chars, &replace_len, &need_free);
assert(replace_len > 0);
bcopy(replace_chars, *str + start, sizeof(UChar) * replace_len);
if (need_free) {
free(replace_chars);
}
}
return replace_len - num;
}
static VALUE
get_named_arg(UChar *format_str, long format_len, long *i, VALUE hash)
{
if (TYPE(hash) != T_HASH) {
rb_raise(rb_eArgError,
"hash required for named references");
}
UChar closing = format_str[(*i)++] + 2;
UChar *str_ptr = &format_str[*i];
while (*i < format_len && format_str[*i] != closing) {
(*i)++;
}
if (*i == format_len) {
rb_raise(rb_eArgError,
"malformed name - unmatched parenthesis");
}
VALUE substr = rb_unicode_str_new(str_ptr, str_ptr - format_str);
hash = rb_hash_aref(hash, ID2SYM(rb_intern_str(substr)));
return hash;
}
// XXX look for arguments that are altered but not duped
VALUE
rb_str_format(int argc, const VALUE *argv, VALUE fmt)
{
bool tainted = OBJ_TAINTED(fmt);
UChar *format_str = NULL;
long format_len = 0;
bool need_free = false;
rb_str_get_uchars(fmt, &format_str, &format_len, &need_free);
if (format_len == 0) {
goto bail;
}
UChar *tmp = (UChar *)xmalloc(format_len * sizeof(UChar));
memcpy(tmp, format_str, format_len * sizeof(UChar));
if (need_free) {
free(format_str);
}
format_str = tmp;
long num, pos;
int j = 0;
int ref_type = 0;
long format_str_capa = format_len;
for (long i = 0; i < format_len; i++) {
if (format_str[i] != '%') {
continue;
}
if (format_str[i + 1] == '%') {
num = cstr_update(&format_str, &format_str_capa, i, 1, 0);
format_len += num;
continue;
}
bool sharp_flag = false;
bool space_flag = false;
bool plus_flag = false;
bool minus_flag = false;
bool zero_flag = false;
bool precision_flag = false;
bool complete = false;
VALUE arg = 0;
long width = 0;
long precision = 0;
int base = 0;
VALUE negative_pad = 0;
VALUE sharp_pad = rb_str_new2("");
const long start = i;
while (i++ < format_len) {
switch (format_str[i]) {
case '#':
sharp_flag = true;
break;
case '*':
if (format_str[++i] == '<' || format_str[i] == '{') {
SET_REF_TYPE(NAMED_REF);
width = NUM2LONG(rb_Integer(get_named_arg(format_str,
format_len, &i, GETNTHARG(0))));
}
else {
if (isprenum(format_str[i])) {
i--;
break;
}
num = rb_uchar_strtol(format_str, format_len, i, &pos);
if (pos == i--) {
SET_REF_TYPE(REL_REF);
width = NUM2LONG(rb_Integer(GETNTHARG(j)));
j++;
}
else if (format_str[pos] == '$') {
SET_REF_TYPE(ABS_REF);
width = NUM2LONG(rb_Integer(GETNTHARG(num - 1)));
i = pos;
}
}
if (width < 0) {
minus_flag = true;
width = -width;
}
break;
case ' ':
if (!plus_flag) {
space_flag = true;
}
break;
case '+':
plus_flag = true;
space_flag = false;
break;
case '-':
zero_flag = false;
minus_flag = true;
break;
case '0':
if (!precision_flag && !minus_flag) {
zero_flag = true;
}
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
num = rb_uchar_strtol(format_str, format_len, i, &pos);
i = pos;
if (format_str[pos] == '$') {
if (num == 0) {
rb_raise(rb_eArgError, "invalid absolute argument");
}
SET_REF_TYPE(ABS_REF);
arg = GETNTHARG(num - 1);
}
else {
SET_REF_TYPE(REL_REF);
width = num;
i--;
}
break;
case '.':
zero_flag = false;
precision_flag = true;
if (format_str[++i] == '*') {
if (format_str[++i] == '<' || format_str[i] == '{') {
SET_REF_TYPE(NAMED_REF);
precision = NUM2LONG(rb_Integer(get_named_arg(
format_str, format_len, &i, GETNTHARG(0))));
}
else {
if (isprenum(format_str[i])) {
i--;
break;
}
num = rb_uchar_strtol(format_str, format_len,
i, &pos);
if (num == i--) {
SET_REF_TYPE(REL_REF);
precision = NUM2LONG(rb_Integer(GETNTHARG(j)));
j++;
}
else if (format_str[pos] == '$') {
SET_REF_TYPE(ABS_REF);
precision = NUM2LONG(rb_Integer(GETNTHARG(
num - 1)));
i = pos;
}
}
}
else if (isdigit(format_str[i])) {
precision = rb_uchar_strtol(format_str, format_len,
i, &pos);
i = pos - 1;
}
else {
rb_raise(rb_eArgError, "invalid precision");
}
if (precision < 0) {
precision = 0;
}
break;
case '<':
case '{':
SET_REF_TYPE(NAMED_REF);
arg = get_named_arg(format_str, format_len, &i,
GETNTHARG(0));
break;
case 'd':
case 'D':
case 'i':
case 'u':
case 'U':
base = 10;
complete = true;
break;
case 'x':
case 'X':
base = 16;
negative_pad = rb_str_new2("f");
sharp_pad = rb_str_new2("0x");
complete = true;
break;
case 'o':
case 'O':
base = 8;
negative_pad = rb_str_new2("7");
sharp_pad = rb_str_new2("0");
complete = true;
break;
case 'B':
case 'b':
base = 2;
negative_pad = rb_str_new2("1");
sharp_pad = rb_str_new2("0b");
complete = true;
break;
case 'c':
case 'C':
GET_ARG();
if (TYPE(arg) == T_STRING) {
arg = rb_str_substr(arg, 0, 1);
}
else {
long num = NUM2LONG(arg);
if (num < 0 || i > 0xff) {
rb_raise(rb_eRangeError, "%ld out of char range",
num);
}
char c = (char)num;
arg = rb_str_new(&c, 1);
}
complete = true;
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'a':
case 'A':
{
// here we construct a new format str and then use
// c's sprintf. why? because floats are retarded
GET_ARG();
double value = RFLOAT_VALUE(rb_Float(arg));
complete = true;
if (isnan(value) || isinf(value)) {
arg = rb_str_new2((char *)(isnan(value) ? "NaN" :
value < 0 ? "-Inf" : "Inf"));
if (isnan(value) || value > 0) {
if (plus_flag) {
rb_str_update(arg, 0, 0, rb_str_new2("+"));
}
else if (space_flag) {
rb_str_update(arg, 0, 0, rb_str_new2(" "));
}
}
break;
}
arg = rb_unicode_str_new(&format_str[i], 1);
if (precision_flag) {
rb_str_update(arg, 0, 0, rb_big2str(LONG2NUM(precision),
10));
rb_str_update(arg, 0, 0, rb_str_new2("."));
}
rb_str_update(arg, 0, 0, rb_big2str(LONG2NUM(width), 10));
if (minus_flag) {
rb_str_update(arg, 0, 0, rb_str_new2("-"));
}
else if (zero_flag) {
rb_str_update(arg, 0, 0, rb_str_new2("0"));
}
if (plus_flag) {
rb_str_update(arg, 0, 0, rb_str_new2("+"));
}
else if (space_flag) {
rb_str_update(arg, 0, 0, rb_str_new2(" "));
}
if (sharp_flag) {
rb_str_update(arg, 0, 0, rb_str_new2("#"));
}
rb_str_update(arg, 0, 0, rb_str_new2("%"));
char *ptr;
asprintf(&ptr, RSTRING_PTR(arg), value);
arg = rb_str_new2(ptr);
free(ptr);
break;
}
case 's':
case 'S':
case 'p':
case '@':
GET_ARG();
arg = (tolower(format_str[i]) != 's'
? rb_inspect(arg) : TYPE(arg) == T_STRING
? rb_str_new3(arg) : rb_obj_as_string(arg));
if (precision_flag && precision < rb_str_chars_len(arg)) {
CFStringPad((CFMutableStringRef)arg, NULL, precision,
0);
}
complete = true;
break;
default:
rb_raise(rb_eArgError, "malformed format string - %%%c",
format_str[i]);
}
if (!complete) {
continue;
}
GET_ARG();
if (base != 0) {
bool sign_pad = false;
unsigned long num_index = 0;
VALUE zero_pad = rb_str_new2("0");
VALUE num = rb_Integer(arg);
if (TYPE(num) == T_FIXNUM) {
num = rb_int2big(FIX2LONG(num));
}
if (plus_flag || space_flag) {
sign_pad = 1;
}
if (IS_NEG(num)) {
num_index = 1;
if (!sign_pad && negative_pad != 0) {
zero_pad = negative_pad;
num = rb_big_clone(num);
rb_big_2comp(num);
}
}
arg = rb_big2str(num, base);
if (!sign_pad && IS_NEG(num) && negative_pad != 0) {
UChar neg = rb_str_get_uchar(negative_pad, 0);
if (base == 8) {
UChar c = rb_str_get_uchar(arg, 1);
const long len = rb_str_chars_len(arg) - 1;
c |= ((~0 << 3) >> ((3 * len)
% (sizeof(BDIGIT) * 8))) & ~(~0 << 3);
rb_str_update(arg, 1, 1, rb_unicode_str_new(&c, 1));
}
for (int i = 1, count = rb_str_chars_len(arg); i < count;
i++) {
if (rb_str_get_uchar(arg, i) != neg) {
break;
}
num_index++;
}
rb_str_update(arg, 0, num_index, negative_pad);
rb_str_update(arg, 0, 0, rb_str_new2(".."));
num_index = 2;
}
if (precision_flag) {
pad_format_value(arg, num_index,
precision + (IS_NEG(num)
&& (sign_pad || negative_pad == 0) ? 1 : 0),
zero_pad);
}
if (sharp_flag && rb_cmpint(num, Qfalse, Qfalse) != 0) {
rb_str_update(arg, sign_pad, 0, (VALUE)sharp_pad);
num_index += 2;
}
if (sign_pad && RBIGNUM_POSITIVE_P(num)) {
rb_str_update(arg, 0, 0, (VALUE)(plus_flag ?
rb_str_new2("+") : rb_str_new2(" ")));
num_index++;
}
if (zero_flag) {
pad_format_value(arg, num_index, width, zero_pad);
}
if (ISUPPER(format_str[i])) {
CFStringUppercase((CFMutableStringRef)arg, NULL);
}
}
if (OBJ_TAINTED(arg)) {
tainted = true;
}
pad_format_value(arg, minus_flag ? -1 : 0, width, rb_str_new2(" "));
num = cstr_update(&format_str, &format_str_capa, start,
i - start + 1, arg);
format_len += num;
i += num;
break;
}
}
bail:
fmt = rb_unicode_str_new(format_str, format_len);
if (tainted) {
OBJ_TAINT(fmt);
}
return fmt;
}
Jump to Line
Something went wrong with that request. Please try again.