register int n; /* The number of the next arg to substitute */
register int total; /* An estimate of the final length */
char *buf, *p;
- register unsigned char *format, *end;
+ register unsigned char *format, *end, *format_start;
int nchars;
/* Nonzero if the output should be a multibyte string,
which is true if any of the inputs is one. */
int *precision = (int *) (alloca(nargs * sizeof (int)));
int longest_format;
Lisp_Object val;
+ int arg_intervals = 0;
+
+ /* discarded[I] is 1 if byte I of the format
+ string was not copied into the output.
+ It is 2 if byte I was not the first byte of its character. */
+ char *discarded;
+
+ /* Each element records, for one argument,
+ the start and end bytepos in the output string,
+ and whether the argument is a string with intervals.
+ info[0] is unused. Unused elements have -1 for start. */
struct info
{
- int start, end;
+ int start, end, intervals;
} *info = 0;
/* It should not be necessary to GCPRO ARGS, because
This is not always right; sometimes the result needs to be multibyte
because of an object that we will pass through prin1,
and in that case, we won't know it here. */
- for (n = 0; n < nargs; n++) {
- if (STRINGP (args[n]) && STRING_MULTIBYTE (args[n]))
- multibyte = 1;
- /* Piggyback on this loop to initialize precision[N]. */
- precision[n] = -1;
- }
+ for (n = 0; n < nargs; n++)
+ {
+ if (STRINGP (args[n]) && STRING_MULTIBYTE (args[n]))
+ multibyte = 1;
+ /* Piggyback on this loop to initialize precision[N]. */
+ precision[n] = -1;
+ }
CHECK_STRING (args[0]);
/* We may have to change "%S" to "%s". */
retry:
format = SDATA (args[0]);
+ format_start = format;
end = format + SBYTES (args[0]);
longest_format = 0;
/* Make room in result for all the non-%-codes in the control string. */
total = 5 + CONVERTED_BYTE_SIZE (multibyte, args[0]);
+ /* Allocate the info and discarded tables. */
+ {
+ int nbytes = nargs * sizeof *info;
+ int i;
+ info = (struct info *) alloca (nbytes);
+ bzero (info, nbytes);
+ for (i = 0; i <= nargs; i++)
+ info[i].start = -1;
+ discarded = (char *) alloca (SBYTES (args[0]));
+ bzero (discarded, SBYTES (args[0]));
+ }
+
/* Add to TOTAL enough space to hold the converted arguments. */
n = 0;
int negative = 0;
unsigned char *this_format_start = format;
+ discarded[format - format_start] = 1;
format++;
/* Process a numeric arg and skip it. */
fixed. */
while ((*format >= '0' && *format <= '9')
|| *format == '-' || *format == ' ' || *format == '.')
- format++;
+ {
+ discarded[format - format_start] = 1;
+ format++;
+ }
if (*format++ == '%')
{
++n;
+ discarded[format - format_start - 1] = 1;
+ info[n].start = nchars;
+
if (STRINGP (args[n]))
{
/* handle case (precision[n] >= 0) */
/* If this argument has text properties, record where
in the result string it appears. */
if (STRING_INTERVALS (args[n]))
- {
- if (!info)
- {
- int nbytes = nargs * sizeof *info;
- info = (struct info *) alloca (nbytes);
- bzero (info, nbytes);
- }
-
- info[n].start = start;
- info[n].end = end;
- }
+ info[n].intervals = arg_intervals = 1;
}
else if (INTEGERP (args[n]) || FLOATP (args[n]))
{
p += this_nchars;
nchars += this_nchars;
}
+
+ info[n].end = nchars;
}
else if (STRING_MULTIBYTE (args[0]))
{
&& !CHAR_HEAD_P (*format))
maybe_combine_byte = 1;
*p++ = *format++;
- while (! CHAR_HEAD_P (*format)) *p++ = *format++;
+ while (! CHAR_HEAD_P (*format))
+ {
+ discarded[format - format_start] = 2;
+ *p++ = *format++;
+ }
nchars++;
}
else if (multibyte)
arguments has text properties, set up text properties of the
result string. */
- if (STRING_INTERVALS (args[0]) || info)
+ if (STRING_INTERVALS (args[0]) || arg_intervals)
{
Lisp_Object len, new_len, props;
struct gcpro gcpro1;
if (CONSP (props))
{
- new_len = make_number (SCHARS (val));
- extend_property_ranges (props, len, new_len);
+ int bytepos = 0, position = 0, translated = 0, argn = 1;
+ Lisp_Object list;
+
+ /* Adjust the bounds of each text property
+ to the proper start and end in the output string. */
+ /* We take advantage of the fact that the positions in PROPS
+ are in increasing order, so that we can do (effectively)
+ one scan through the position space of the format string.
+
+ BYTEPOS is the byte position in the format string,
+ POSITION is the untranslated char position in it,
+ TRANSLATED is the translated char position in BUF,
+ and ARGN is the number of the next arg we will come to. */
+ for (list = props; CONSP (list); list = XCDR (list))
+ {
+ Lisp_Object item, pos;
+
+ item = XCAR (list);
+
+ /* First adjust the property start position. */
+ pos = XINT (XCAR (item));
+
+ /* Advance BYTEPOS, POSITION, TRANSLATED and ARGN
+ up to this position. */
+ for (; position < pos; bytepos++)
+ {
+ if (! discarded[bytepos])
+ position++, translated++;
+ else if (discarded[bytepos] == 1)
+ {
+ position++;
+ if (translated == info[argn].start)
+ {
+ translated += info[argn].end - info[argn].start;
+ argn++;
+ }
+ }
+ }
+
+ XSETCAR (item, make_number (translated));
+
+ /* Likewise adjust the property end position. */
+ pos = XINT (XCAR (XCDR (item)));
+
+ for (; bytepos < pos; bytepos++)
+ {
+ if (! discarded[bytepos])
+ position++, translated++;
+ else if (discarded[bytepos] == 1)
+ {
+ position++;
+ if (translated == info[argn].start)
+ {
+ translated += info[argn].end - info[argn].start;
+ argn++;
+ }
+ }
+ }
+
+ XSETCAR (XCDR (item), make_number (translated));
+ }
+
add_text_properties_from_list (val, props, make_number (0));
}
/* Add text properties from arguments. */
- if (info)
+ if (arg_intervals)
for (n = 1; n < nargs; ++n)
- if (info[n].end)
+ if (info[n].intervals)
{
len = make_number (SCHARS (args[n]));
new_len = make_number (info[n].end - info[n].start);