aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Eggert2017-06-04 08:39:37 -0700
committerPaul Eggert2017-06-04 08:42:53 -0700
commitd5fcf9e458053af1c50f0d4dad4c59db056790e5 (patch)
treef6fe65eb1823c49d771d520c4e6d66b92525ac2d /src
parent6ad42aecc243d378d38468fd3efd89bc1fbfb187 (diff)
downloademacs-d5fcf9e458053af1c50f0d4dad4c59db056790e5.tar.gz
emacs-d5fcf9e458053af1c50f0d4dad4c59db056790e5.zip
Tune ‘format’ after recent fix
* doc/lispref/strings.texi (Formatting Strings): * src/editfns.c (Fformat): Format field numbers no longer need to be unique, reverting the previous doc change since that has now been fixed. Also, document that %% should not have modifiers. * src/editfns.c (styled_format): Improve performance. Remove the need for the new prepass over the format string, by using a typically-more-generous bound for the info array size. Initialize the info array lazily. Move string inspection to the same area to help caching. Avoid the need for a converted_to_string bitfield by using EQ. Cache arg in a local and avoid some potential aliasing issues to help the compiler. Info array is now 0-origin, not 1-origin.
Diffstat (limited to 'src')
-rw-r--r--src/editfns.c154
1 files changed, 66 insertions, 88 deletions
diff --git a/src/editfns.c b/src/editfns.c
index 56aa8ce1a72..43b17f9f116 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -3891,8 +3891,8 @@ the next available argument, or the argument explicitly specified:
3891The argument used for %d, %o, %x, %e, %f, %g or %c must be a number. 3891The argument used for %d, %o, %x, %e, %f, %g or %c must be a number.
3892Use %% to put a single % into the output. 3892Use %% to put a single % into the output.
3893 3893
3894A %-sequence may contain optional field number, flag, width, and 3894A %-sequence other than %% may contain optional field number, flag,
3895precision specifiers, as follows: 3895width, and precision specifiers, as follows:
3896 3896
3897 %<field><flags><width><precision>character 3897 %<field><flags><width><precision>character
3898 3898
@@ -3901,10 +3901,9 @@ where field is [0-9]+ followed by a literal dollar "$", flags is
3901followed by [0-9]+. 3901followed by [0-9]+.
3902 3902
3903If a %-sequence is numbered with a field with positive value N, the 3903If a %-sequence is numbered with a field with positive value N, the
3904Nth argument is substituted instead of the next one. A field number 3904Nth argument is substituted instead of the next one. A format can
3905should differ from the other field numbers in the same format. A 3905contain either numbered or unnumbered %-sequences but not both, except
3906format can contain either numbered or unnumbered %-sequences but not 3906that %% can be mixed with numbered %-sequences.
3907both, except that %% can be mixed with numbered %-sequences.
3908 3907
3909The + flag character inserts a + before any positive number, while a 3908The + flag character inserts a + before any positive number, while a
3910space inserts a space before any positive number; these flags only 3909space inserts a space before any positive number; these flags only
@@ -3980,49 +3979,40 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
3980 bool arg_intervals = false; 3979 bool arg_intervals = false;
3981 USE_SAFE_ALLOCA; 3980 USE_SAFE_ALLOCA;
3982 3981
3983 /* Each element records, for one field, 3982 /* Information recorded for each format spec. */
3984 the corresponding argument,
3985 the start and end bytepos in the output string,
3986 whether the argument has been converted to string (e.g., due to "%S"),
3987 and whether the argument is a string with intervals. */
3988 struct info 3983 struct info
3989 { 3984 {
3985 /* The corresponding argument, converted to string if conversion
3986 was needed. */
3990 Lisp_Object argument; 3987 Lisp_Object argument;
3988
3989 /* The start and end bytepos in the output string. */
3991 ptrdiff_t start, end; 3990 ptrdiff_t start, end;
3992 bool_bf converted_to_string : 1; 3991
3992 /* Whether the argument is a string with intervals. */
3993 bool_bf intervals : 1; 3993 bool_bf intervals : 1;
3994 } *info; 3994 } *info;
3995 3995
3996 CHECK_STRING (args[0]); 3996 CHECK_STRING (args[0]);
3997 char *format_start = SSDATA (args[0]); 3997 char *format_start = SSDATA (args[0]);
3998 bool multibyte_format = STRING_MULTIBYTE (args[0]);
3998 ptrdiff_t formatlen = SBYTES (args[0]); 3999 ptrdiff_t formatlen = SBYTES (args[0]);
3999 4000
4000 /* The number of percent characters is a safe upper bound for the 4001 /* Upper bound on number of format specs. Each uses at least 2 chars. */
4001 number of format fields. */ 4002 ptrdiff_t nspec_bound = SCHARS (args[0]) >> 1;
4002 ptrdiff_t num_percent = 0;
4003 for (ptrdiff_t i = 0; i < formatlen; ++i)
4004 if (format_start[i] == '%')
4005 ++num_percent;
4006 4003
4007 /* Allocate the info and discarded tables. */ 4004 /* Allocate the info and discarded tables. */
4008 ptrdiff_t alloca_size; 4005 ptrdiff_t alloca_size;
4009 if (INT_MULTIPLY_WRAPV (num_percent, sizeof *info, &alloca_size) 4006 if (INT_MULTIPLY_WRAPV (nspec_bound, sizeof *info, &alloca_size)
4010 || INT_ADD_WRAPV (sizeof *info, alloca_size, &alloca_size)
4011 || INT_ADD_WRAPV (formatlen, alloca_size, &alloca_size) 4007 || INT_ADD_WRAPV (formatlen, alloca_size, &alloca_size)
4012 || SIZE_MAX < alloca_size) 4008 || SIZE_MAX < alloca_size)
4013 memory_full (SIZE_MAX); 4009 memory_full (SIZE_MAX);
4014 /* info[0] is unused. Unused elements have -1 for start. */
4015 info = SAFE_ALLOCA (alloca_size); 4010 info = SAFE_ALLOCA (alloca_size);
4016 memset (info, 0, alloca_size);
4017 for (ptrdiff_t i = 0; i < num_percent + 1; i++)
4018 {
4019 info[i].argument = Qunbound;
4020 info[i].start = -1;
4021 }
4022 /* discarded[I] is 1 if byte I of the format 4011 /* discarded[I] is 1 if byte I of the format
4023 string was not copied into the output. 4012 string was not copied into the output.
4024 It is 2 if byte I was not the first byte of its character. */ 4013 It is 2 if byte I was not the first byte of its character. */
4025 char *discarded = (char *) &info[num_percent + 1]; 4014 char *discarded = (char *) &info[nspec_bound];
4015 memset (discarded, 0, formatlen);
4026 4016
4027 /* Try to determine whether the result should be multibyte. 4017 /* Try to determine whether the result should be multibyte.
4028 This is not always right; sometimes the result needs to be multibyte 4018 This is not always right; sometimes the result needs to be multibyte
@@ -4030,8 +4020,6 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4030 or because a grave accent or apostrophe is requoted, 4020 or because a grave accent or apostrophe is requoted,
4031 and in that case, we won't know it here. */ 4021 and in that case, we won't know it here. */
4032 4022
4033 /* True if the format is multibyte. */
4034 bool multibyte_format = STRING_MULTIBYTE (args[0]);
4035 /* True if the output should be a multibyte string, 4023 /* True if the output should be a multibyte string,
4036 which is true if any of the inputs is one. */ 4024 which is true if any of the inputs is one. */
4037 bool multibyte = multibyte_format; 4025 bool multibyte = multibyte_format;
@@ -4042,6 +4030,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4042 int quoting_style = message ? text_quoting_style () : -1; 4030 int quoting_style = message ? text_quoting_style () : -1;
4043 4031
4044 ptrdiff_t ispec; 4032 ptrdiff_t ispec;
4033 ptrdiff_t nspec = 0;
4045 4034
4046 /* If we start out planning a unibyte result, 4035 /* If we start out planning a unibyte result,
4047 then discover it has to be multibyte, we jump back to retry. */ 4036 then discover it has to be multibyte, we jump back to retry. */
@@ -4155,11 +4144,14 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4155 if (! (n < nargs)) 4144 if (! (n < nargs))
4156 error ("Not enough arguments for format string"); 4145 error ("Not enough arguments for format string");
4157 4146
4158 eassert (ispec < num_percent); 4147 struct info *spec = &info[ispec++];
4159 ++ispec; 4148 if (nspec < ispec)
4160 4149 {
4161 if (EQ (info[ispec].argument, Qunbound)) 4150 spec->argument = args[n];
4162 info[ispec].argument = args[n]; 4151 spec->intervals = false;
4152 nspec = ispec;
4153 }
4154 Lisp_Object arg = spec->argument;
4163 4155
4164 /* For 'S', prin1 the argument, and then treat like 's'. 4156 /* For 'S', prin1 the argument, and then treat like 's'.
4165 For 's', princ any argument that is not a string or 4157 For 's', princ any argument that is not a string or
@@ -4167,16 +4159,13 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4167 happen after retrying. */ 4159 happen after retrying. */
4168 if ((conversion == 'S' 4160 if ((conversion == 'S'
4169 || (conversion == 's' 4161 || (conversion == 's'
4170 && ! STRINGP (info[ispec].argument) 4162 && ! STRINGP (arg) && ! SYMBOLP (arg))))
4171 && ! SYMBOLP (info[ispec].argument))))
4172 { 4163 {
4173 if (! info[ispec].converted_to_string) 4164 if (EQ (arg, args[n]))
4174 { 4165 {
4175 Lisp_Object noescape = conversion == 'S' ? Qnil : Qt; 4166 Lisp_Object noescape = conversion == 'S' ? Qnil : Qt;
4176 info[ispec].argument = 4167 spec->argument = arg = Fprin1_to_string (arg, noescape);
4177 Fprin1_to_string (info[ispec].argument, noescape); 4168 if (STRING_MULTIBYTE (arg) && ! multibyte)
4178 info[ispec].converted_to_string = true;
4179 if (STRING_MULTIBYTE (info[ispec].argument) && ! multibyte)
4180 { 4169 {
4181 multibyte = true; 4170 multibyte = true;
4182 goto retry; 4171 goto retry;
@@ -4186,29 +4175,25 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4186 } 4175 }
4187 else if (conversion == 'c') 4176 else if (conversion == 'c')
4188 { 4177 {
4189 if (INTEGERP (info[ispec].argument) 4178 if (INTEGERP (arg) && ! ASCII_CHAR_P (XINT (arg)))
4190 && ! ASCII_CHAR_P (XINT (info[ispec].argument)))
4191 { 4179 {
4192 if (!multibyte) 4180 if (!multibyte)
4193 { 4181 {
4194 multibyte = true; 4182 multibyte = true;
4195 goto retry; 4183 goto retry;
4196 } 4184 }
4197 info[ispec].argument = 4185 spec->argument = arg = Fchar_to_string (arg);
4198 Fchar_to_string (info[ispec].argument);
4199 info[ispec].converted_to_string = true;
4200 } 4186 }
4201 4187
4202 if (info[ispec].converted_to_string) 4188 if (!EQ (arg, args[n]))
4203 conversion = 's'; 4189 conversion = 's';
4204 zero_flag = false; 4190 zero_flag = false;
4205 } 4191 }
4206 4192
4207 if (SYMBOLP (info[ispec].argument)) 4193 if (SYMBOLP (arg))
4208 { 4194 {
4209 info[ispec].argument = 4195 spec->argument = arg = SYMBOL_NAME (arg);
4210 SYMBOL_NAME (info[ispec].argument); 4196 if (STRING_MULTIBYTE (arg) && ! multibyte)
4211 if (STRING_MULTIBYTE (info[ispec].argument) && ! multibyte)
4212 { 4197 {
4213 multibyte = true; 4198 multibyte = true;
4214 goto retry; 4199 goto retry;
@@ -4239,12 +4224,11 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4239 else 4224 else
4240 { 4225 {
4241 ptrdiff_t nch, nby; 4226 ptrdiff_t nch, nby;
4242 width = lisp_string_width (info[ispec].argument, 4227 width = lisp_string_width (arg, prec, &nch, &nby);
4243 prec, &nch, &nby);
4244 if (prec < 0) 4228 if (prec < 0)
4245 { 4229 {
4246 nchars_string = SCHARS (info[ispec].argument); 4230 nchars_string = SCHARS (arg);
4247 nbytes = SBYTES (info[ispec].argument); 4231 nbytes = SBYTES (arg);
4248 } 4232 }
4249 else 4233 else
4250 { 4234 {
@@ -4254,11 +4238,8 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4254 } 4238 }
4255 4239
4256 convbytes = nbytes; 4240 convbytes = nbytes;
4257 if (convbytes && multibyte && 4241 if (convbytes && multibyte && ! STRING_MULTIBYTE (arg))
4258 ! STRING_MULTIBYTE (info[ispec].argument)) 4242 convbytes = count_size_as_multibyte (SDATA (arg), nbytes);
4259 convbytes =
4260 count_size_as_multibyte (SDATA (info[ispec].argument),
4261 nbytes);
4262 4243
4263 ptrdiff_t padding 4244 ptrdiff_t padding
4264 = width < field_width ? field_width - width : 0; 4245 = width < field_width ? field_width - width : 0;
@@ -4274,20 +4255,18 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4274 p += padding; 4255 p += padding;
4275 nchars += padding; 4256 nchars += padding;
4276 } 4257 }
4277 info[ispec].start = nchars; 4258 spec->start = nchars;
4278 4259
4279 if (p > buf 4260 if (p > buf
4280 && multibyte 4261 && multibyte
4281 && !ASCII_CHAR_P (*((unsigned char *) p - 1)) 4262 && !ASCII_CHAR_P (*((unsigned char *) p - 1))
4282 && STRING_MULTIBYTE (info[ispec].argument) 4263 && STRING_MULTIBYTE (arg)
4283 && !CHAR_HEAD_P (SREF (info[ispec].argument, 0))) 4264 && !CHAR_HEAD_P (SREF (arg, 0)))
4284 maybe_combine_byte = true; 4265 maybe_combine_byte = true;
4285 4266
4286 p += copy_text (SDATA (info[ispec].argument), 4267 p += copy_text (SDATA (arg), (unsigned char *) p,
4287 (unsigned char *) p,
4288 nbytes, 4268 nbytes,
4289 STRING_MULTIBYTE (info[ispec].argument), 4269 STRING_MULTIBYTE (arg), multibyte);
4290 multibyte);
4291 4270
4292 nchars += nchars_string; 4271 nchars += nchars_string;
4293 4272
@@ -4297,12 +4276,12 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4297 p += padding; 4276 p += padding;
4298 nchars += padding; 4277 nchars += padding;
4299 } 4278 }
4300 info[ispec].end = nchars; 4279 spec->end = nchars;
4301 4280
4302 /* If this argument has text properties, record where 4281 /* If this argument has text properties, record where
4303 in the result string it appears. */ 4282 in the result string it appears. */
4304 if (string_intervals (info[ispec].argument)) 4283 if (string_intervals (arg))
4305 info[ispec].intervals = arg_intervals = true; 4284 spec->intervals = arg_intervals = true;
4306 4285
4307 continue; 4286 continue;
4308 } 4287 }
@@ -4313,8 +4292,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4313 || conversion == 'X')) 4292 || conversion == 'X'))
4314 error ("Invalid format operation %%%c", 4293 error ("Invalid format operation %%%c",
4315 STRING_CHAR ((unsigned char *) format - 1)); 4294 STRING_CHAR ((unsigned char *) format - 1));
4316 else if (! (INTEGERP (info[ispec].argument) 4295 else if (! (INTEGERP (arg) || (FLOATP (arg) && conversion != 'c')))
4317 || (FLOATP (info[ispec].argument) && conversion != 'c')))
4318 error ("Format specifier doesn't match argument type"); 4296 error ("Format specifier doesn't match argument type");
4319 else 4297 else
4320 { 4298 {
@@ -4376,7 +4354,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4376 if (INT_AS_LDBL) 4354 if (INT_AS_LDBL)
4377 { 4355 {
4378 *f = 'L'; 4356 *f = 'L';
4379 f += INTEGERP (info[ispec].argument); 4357 f += INTEGERP (arg);
4380 } 4358 }
4381 } 4359 }
4382 else if (conversion != 'c') 4360 else if (conversion != 'c')
@@ -4408,22 +4386,22 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4408 ptrdiff_t sprintf_bytes; 4386 ptrdiff_t sprintf_bytes;
4409 if (float_conversion) 4387 if (float_conversion)
4410 { 4388 {
4411 if (INT_AS_LDBL && INTEGERP (info[ispec].argument)) 4389 if (INT_AS_LDBL && INTEGERP (arg))
4412 { 4390 {
4413 /* Although long double may have a rounding error if 4391 /* Although long double may have a rounding error if
4414 DIG_BITS_LBOUND * LDBL_MANT_DIG < FIXNUM_BITS - 1, 4392 DIG_BITS_LBOUND * LDBL_MANT_DIG < FIXNUM_BITS - 1,
4415 it is more accurate than plain 'double'. */ 4393 it is more accurate than plain 'double'. */
4416 long double x = XINT (info[ispec].argument); 4394 long double x = XINT (arg);
4417 sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x); 4395 sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x);
4418 } 4396 }
4419 else 4397 else
4420 sprintf_bytes = sprintf (sprintf_buf, convspec, prec, 4398 sprintf_bytes = sprintf (sprintf_buf, convspec, prec,
4421 XFLOATINT (info[ispec].argument)); 4399 XFLOATINT (arg));
4422 } 4400 }
4423 else if (conversion == 'c') 4401 else if (conversion == 'c')
4424 { 4402 {
4425 /* Don't use sprintf here, as it might mishandle prec. */ 4403 /* Don't use sprintf here, as it might mishandle prec. */
4426 sprintf_buf[0] = XINT (info[ispec].argument); 4404 sprintf_buf[0] = XINT (arg);
4427 sprintf_bytes = prec != 0; 4405 sprintf_bytes = prec != 0;
4428 } 4406 }
4429 else if (conversion == 'd' || conversion == 'i') 4407 else if (conversion == 'd' || conversion == 'i')
@@ -4432,11 +4410,11 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4432 instead so it also works for values outside 4410 instead so it also works for values outside
4433 the integer range. */ 4411 the integer range. */
4434 printmax_t x; 4412 printmax_t x;
4435 if (INTEGERP (info[ispec].argument)) 4413 if (INTEGERP (arg))
4436 x = XINT (info[ispec].argument); 4414 x = XINT (arg);
4437 else 4415 else
4438 { 4416 {
4439 double d = XFLOAT_DATA (info[ispec].argument); 4417 double d = XFLOAT_DATA (arg);
4440 if (d < 0) 4418 if (d < 0)
4441 { 4419 {
4442 x = TYPE_MINIMUM (printmax_t); 4420 x = TYPE_MINIMUM (printmax_t);
@@ -4456,11 +4434,11 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4456 { 4434 {
4457 /* Don't sign-extend for octal or hex printing. */ 4435 /* Don't sign-extend for octal or hex printing. */
4458 uprintmax_t x; 4436 uprintmax_t x;
4459 if (INTEGERP (info[ispec].argument)) 4437 if (INTEGERP (arg))
4460 x = XUINT (info[ispec].argument); 4438 x = XUINT (arg);
4461 else 4439 else
4462 { 4440 {
4463 double d = XFLOAT_DATA (info[ispec].argument); 4441 double d = XFLOAT_DATA (arg);
4464 if (d < 0) 4442 if (d < 0)
4465 x = 0; 4443 x = 0;
4466 else 4444 else
@@ -4541,7 +4519,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4541 exponent_bytes = src + sprintf_bytes - e; 4519 exponent_bytes = src + sprintf_bytes - e;
4542 } 4520 }
4543 4521
4544 info[ispec].start = nchars; 4522 spec->start = nchars;
4545 if (! minus_flag) 4523 if (! minus_flag)
4546 { 4524 {
4547 memset (p, ' ', padding); 4525 memset (p, ' ', padding);
@@ -4572,7 +4550,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4572 p += padding; 4550 p += padding;
4573 nchars += padding; 4551 nchars += padding;
4574 } 4552 }
4575 info[ispec].end = nchars; 4553 spec->end = nchars;
4576 4554
4577 continue; 4555 continue;
4578 } 4556 }
@@ -4681,7 +4659,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4681 if (CONSP (props)) 4659 if (CONSP (props))
4682 { 4660 {
4683 ptrdiff_t bytepos = 0, position = 0, translated = 0; 4661 ptrdiff_t bytepos = 0, position = 0, translated = 0;
4684 ptrdiff_t fieldn = 1; 4662 ptrdiff_t fieldn = 0;
4685 4663
4686 /* Adjust the bounds of each text property 4664 /* Adjust the bounds of each text property
4687 to the proper start and end in the output string. */ 4665 to the proper start and end in the output string. */
@@ -4747,7 +4725,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4747 4725
4748 /* Add text properties from arguments. */ 4726 /* Add text properties from arguments. */
4749 if (arg_intervals) 4727 if (arg_intervals)
4750 for (ptrdiff_t i = 1; i <= num_percent; i++) 4728 for (ptrdiff_t i = 0; i < nspec; i++)
4751 if (info[i].intervals) 4729 if (info[i].intervals)
4752 { 4730 {
4753 len = make_number (SCHARS (info[i].argument)); 4731 len = make_number (SCHARS (info[i].argument));