1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include <inttypes.h> |
5 | #include <stdarg.h> |
6 | #include <stdbool.h> |
7 | #include <string.h> |
8 | #include <math.h> |
9 | #include <assert.h> |
10 | |
11 | #include "nvim/assert.h" |
12 | #include "nvim/vim.h" |
13 | #include "nvim/ascii.h" |
14 | #include "nvim/strings.h" |
15 | #include "nvim/file_search.h" |
16 | #include "nvim/buffer.h" |
17 | #include "nvim/charset.h" |
18 | #include "nvim/diff.h" |
19 | #include "nvim/edit.h" |
20 | #include "nvim/eval.h" |
21 | #include "nvim/ex_cmds.h" |
22 | #include "nvim/ex_docmd.h" |
23 | #include "nvim/ex_getln.h" |
24 | #include "nvim/fileio.h" |
25 | #include "nvim/func_attr.h" |
26 | #include "nvim/fold.h" |
27 | #include "nvim/func_attr.h" |
28 | #include "nvim/getchar.h" |
29 | #include "nvim/mark.h" |
30 | #include "nvim/math.h" |
31 | #include "nvim/mbyte.h" |
32 | #include "nvim/memfile.h" |
33 | #include "nvim/memline.h" |
34 | #include "nvim/memory.h" |
35 | #include "nvim/message.h" |
36 | #include "nvim/misc1.h" |
37 | #include "nvim/move.h" |
38 | #include "nvim/option.h" |
39 | #include "nvim/ops.h" |
40 | #include "nvim/os_unix.h" |
41 | #include "nvim/path.h" |
42 | #include "nvim/quickfix.h" |
43 | #include "nvim/regexp.h" |
44 | #include "nvim/screen.h" |
45 | #include "nvim/search.h" |
46 | #include "nvim/spell.h" |
47 | #include "nvim/syntax.h" |
48 | #include "nvim/tag.h" |
49 | #include "nvim/window.h" |
50 | #include "nvim/os/os.h" |
51 | #include "nvim/os/shell.h" |
52 | #include "nvim/eval/encode.h" |
53 | |
54 | /// Copy "string" into newly allocated memory. |
55 | char_u *vim_strsave(const char_u *string) |
56 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
57 | { |
58 | return (char_u *)xstrdup((char *)string); |
59 | } |
60 | |
61 | /// Copy up to `len` bytes of `string` into newly allocated memory and |
62 | /// terminate with a NUL. The allocated memory always has size `len + 1`, even |
63 | /// when `string` is shorter. |
64 | char_u *vim_strnsave(const char_u *string, size_t len) |
65 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
66 | { |
67 | // strncpy is intentional: some parts of Vim use `string` shorter than `len` |
68 | // and expect the remainder to be zeroed out. |
69 | return (char_u *)strncpy(xmallocz(len), (char *)string, len); |
70 | } |
71 | |
72 | /* |
73 | * Same as vim_strsave(), but any characters found in esc_chars are preceded |
74 | * by a backslash. |
75 | */ |
76 | char_u *vim_strsave_escaped(const char_u *string, const char_u *esc_chars) |
77 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
78 | { |
79 | return vim_strsave_escaped_ext(string, esc_chars, '\\', false); |
80 | } |
81 | |
82 | /* |
83 | * Same as vim_strsave_escaped(), but when "bsl" is true also escape |
84 | * characters where rem_backslash() would remove the backslash. |
85 | * Escape the characters with "cc". |
86 | */ |
87 | char_u *vim_strsave_escaped_ext(const char_u *string, const char_u *esc_chars, |
88 | char_u cc, bool bsl) |
89 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
90 | { |
91 | /* |
92 | * First count the number of backslashes required. |
93 | * Then allocate the memory and insert them. |
94 | */ |
95 | size_t length = 1; // count the trailing NUL |
96 | for (const char_u *p = string; *p; p++) { |
97 | size_t l; |
98 | if (has_mbyte && (l = (size_t)(*mb_ptr2len)(p)) > 1) { |
99 | length += l; // count a multibyte char |
100 | p += l - 1; |
101 | continue; |
102 | } |
103 | if (vim_strchr(esc_chars, *p) != NULL || (bsl && rem_backslash(p))) |
104 | ++length; /* count a backslash */ |
105 | ++length; /* count an ordinary char */ |
106 | } |
107 | |
108 | char_u *escaped_string = xmalloc(length); |
109 | char_u *p2 = escaped_string; |
110 | for (const char_u *p = string; *p; p++) { |
111 | size_t l; |
112 | if (has_mbyte && (l = (size_t)(*mb_ptr2len)(p)) > 1) { |
113 | memcpy(p2, p, l); |
114 | p2 += l; |
115 | p += l - 1; /* skip multibyte char */ |
116 | continue; |
117 | } |
118 | if (vim_strchr(esc_chars, *p) != NULL || (bsl && rem_backslash(p))) |
119 | *p2++ = cc; |
120 | *p2++ = *p; |
121 | } |
122 | *p2 = NUL; |
123 | |
124 | return escaped_string; |
125 | } |
126 | |
127 | /// Save a copy of an unquoted string |
128 | /// |
129 | /// Turns string like `a\bc"def\"ghi\\\n"jkl` into `a\bcdef"ghi\\njkl`, for use |
130 | /// in shell_build_argv: the only purpose of backslash is making next character |
131 | /// be treated literally inside the double quotes, if this character is |
132 | /// backslash or quote. |
133 | /// |
134 | /// @param[in] string String to copy. |
135 | /// @param[in] length Length of the string to copy. |
136 | /// |
137 | /// @return [allocated] Copy of the string. |
138 | char *vim_strnsave_unquoted(const char *const string, const size_t length) |
139 | FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL |
140 | FUNC_ATTR_NONNULL_RET |
141 | { |
142 | #define ESCAPE_COND(p, inquote, string_end) \ |
143 | (*p == '\\' && inquote && p + 1 < string_end && (p[1] == '\\' || p[1] == '"')) |
144 | size_t ret_length = 0; |
145 | bool inquote = false; |
146 | const char *const string_end = string + length; |
147 | for (const char *p = string; p < string_end; p++) { |
148 | if (*p == '"') { |
149 | inquote = !inquote; |
150 | } else if (ESCAPE_COND(p, inquote, string_end)) { |
151 | ret_length++; |
152 | p++; |
153 | } else { |
154 | ret_length++; |
155 | } |
156 | } |
157 | |
158 | char *const ret = xmallocz(ret_length); |
159 | char *rp = ret; |
160 | inquote = false; |
161 | for (const char *p = string; p < string_end; p++) { |
162 | if (*p == '"') { |
163 | inquote = !inquote; |
164 | } else if (ESCAPE_COND(p, inquote, string_end)) { |
165 | *rp++ = *(++p); |
166 | } else { |
167 | *rp++ = *p; |
168 | } |
169 | } |
170 | #undef ESCAPE_COND |
171 | |
172 | return ret; |
173 | } |
174 | |
175 | /* |
176 | * Escape "string" for use as a shell argument with system(). |
177 | * This uses single quotes, except when we know we need to use double quotes |
178 | * (MS-Windows without 'shellslash' set). |
179 | * Escape a newline, depending on the 'shell' option. |
180 | * When "do_special" is true also replace "!", "%", "#" and things starting |
181 | * with "<" like "<cfile>". |
182 | * When "do_newline" is false do not escape newline unless it is csh shell. |
183 | * Returns the result in allocated memory. |
184 | */ |
185 | char_u *vim_strsave_shellescape(const char_u *string, |
186 | bool do_special, bool do_newline) |
187 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
188 | { |
189 | char_u *d; |
190 | char_u *escaped_string; |
191 | size_t l; |
192 | int csh_like; |
193 | |
194 | /* Only csh and similar shells expand '!' within single quotes. For sh and |
195 | * the like we must not put a backslash before it, it will be taken |
196 | * literally. If do_special is set the '!' will be escaped twice. |
197 | * Csh also needs to have "\n" escaped twice when do_special is set. */ |
198 | csh_like = csh_like_shell(); |
199 | |
200 | /* First count the number of extra bytes required. */ |
201 | size_t length = STRLEN(string) + 3; // two quotes and a trailing NUL |
202 | for (const char_u *p = string; *p != NUL; MB_PTR_ADV(p)) { |
203 | #ifdef WIN32 |
204 | if (!p_ssl) { |
205 | if (*p == '"') { |
206 | length++; // " -> "" |
207 | } |
208 | } else |
209 | #endif |
210 | if (*p == '\'') { |
211 | length += 3; // ' => '\'' |
212 | } |
213 | if ((*p == '\n' && (csh_like || do_newline)) |
214 | || (*p == '!' && (csh_like || do_special))) { |
215 | ++length; /* insert backslash */ |
216 | if (csh_like && do_special) |
217 | ++length; /* insert backslash */ |
218 | } |
219 | if (do_special && find_cmdline_var(p, &l) >= 0) { |
220 | ++length; /* insert backslash */ |
221 | p += l - 1; |
222 | } |
223 | } |
224 | |
225 | /* Allocate memory for the result and fill it. */ |
226 | escaped_string = xmalloc(length); |
227 | d = escaped_string; |
228 | |
229 | // add opening quote |
230 | #ifdef WIN32 |
231 | if (!p_ssl) { |
232 | *d++ = '"'; |
233 | } else |
234 | #endif |
235 | *d++ = '\''; |
236 | |
237 | for (const char_u *p = string; *p != NUL; ) { |
238 | #ifdef WIN32 |
239 | if (!p_ssl) { |
240 | if (*p == '"') { |
241 | *d++ = '"'; |
242 | *d++ = '"'; |
243 | p++; |
244 | continue; |
245 | } |
246 | } else |
247 | #endif |
248 | if (*p == '\'') { |
249 | *d++ = '\''; |
250 | *d++ = '\\'; |
251 | *d++ = '\''; |
252 | *d++ = '\''; |
253 | ++p; |
254 | continue; |
255 | } |
256 | if ((*p == '\n' && (csh_like || do_newline)) |
257 | || (*p == '!' && (csh_like || do_special))) { |
258 | *d++ = '\\'; |
259 | if (csh_like && do_special) |
260 | *d++ = '\\'; |
261 | *d++ = *p++; |
262 | continue; |
263 | } |
264 | if (do_special && find_cmdline_var(p, &l) >= 0) { |
265 | *d++ = '\\'; /* insert backslash */ |
266 | while (--l != SIZE_MAX) /* copy the var */ |
267 | *d++ = *p++; |
268 | continue; |
269 | } |
270 | |
271 | MB_COPY_CHAR(p, d); |
272 | } |
273 | |
274 | // add terminating quote and finish with a NUL |
275 | # ifdef WIN32 |
276 | if (!p_ssl) { |
277 | *d++ = '"'; |
278 | } else |
279 | # endif |
280 | *d++ = '\''; |
281 | *d = NUL; |
282 | |
283 | return escaped_string; |
284 | } |
285 | |
286 | /* |
287 | * Like vim_strsave(), but make all characters uppercase. |
288 | * This uses ASCII lower-to-upper case translation, language independent. |
289 | */ |
290 | char_u *vim_strsave_up(const char_u *string) |
291 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
292 | { |
293 | char_u *p1; |
294 | |
295 | p1 = vim_strsave(string); |
296 | vim_strup(p1); |
297 | return p1; |
298 | } |
299 | |
300 | /* |
301 | * Like vim_strnsave(), but make all characters uppercase. |
302 | * This uses ASCII lower-to-upper case translation, language independent. |
303 | */ |
304 | char_u *vim_strnsave_up(const char_u *string, size_t len) |
305 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
306 | { |
307 | char_u *p1 = vim_strnsave(string, len); |
308 | vim_strup(p1); |
309 | return p1; |
310 | } |
311 | |
312 | /* |
313 | * ASCII lower-to-upper case translation, language independent. |
314 | */ |
315 | void vim_strup(char_u *p) |
316 | FUNC_ATTR_NONNULL_ALL |
317 | { |
318 | char_u c; |
319 | while ((c = *p) != NUL) { |
320 | *p++ = (char_u)(c < 'a' || c > 'z' ? c : c - 0x20); |
321 | } |
322 | } |
323 | |
324 | /// Make given string all upper-case or all lower-case |
325 | /// |
326 | /// Handles multi-byte characters as good as possible. |
327 | /// |
328 | /// @param[in] orig Input string. |
329 | /// @param[in] upper If true make uppercase, otherwise lowercase |
330 | /// |
331 | /// @return [allocated] upper-cased string. |
332 | char *strcase_save(const char *const orig, bool upper) |
333 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
334 | { |
335 | char *res = xstrdup(orig); |
336 | |
337 | char *p = res; |
338 | while (*p != NUL) { |
339 | int c = utf_ptr2char((const char_u *)p); |
340 | int l = utf_ptr2len((const char_u *)p); |
341 | if (c == 0) { |
342 | // overlong sequence, use only the first byte |
343 | c = *p; |
344 | l = 1; |
345 | } |
346 | int uc = upper ? mb_toupper(c) : mb_tolower(c); |
347 | |
348 | // Reallocate string when byte count changes. This is rare, |
349 | // thus it's OK to do another malloc()/free(). |
350 | int newl = utf_char2len(uc); |
351 | if (newl != l) { |
352 | // TODO(philix): use xrealloc() in strup_save() |
353 | char *s = xmalloc(STRLEN(res) + (size_t)(1 + newl - l)); |
354 | memcpy(s, res, (size_t)(p - res)); |
355 | STRCPY(s + (p - res) + newl, p + l); |
356 | p = s + (p - res); |
357 | xfree(res); |
358 | res = s; |
359 | } |
360 | |
361 | utf_char2bytes(uc, (char_u *)p); |
362 | p += newl; |
363 | } |
364 | |
365 | return res; |
366 | } |
367 | |
368 | /* |
369 | * delete spaces at the end of a string |
370 | */ |
371 | void del_trailing_spaces(char_u *ptr) |
372 | FUNC_ATTR_NONNULL_ALL |
373 | { |
374 | char_u *q; |
375 | |
376 | q = ptr + STRLEN(ptr); |
377 | while (--q > ptr && ascii_iswhite(q[0]) && q[-1] != '\\' && q[-1] != Ctrl_V) |
378 | *q = NUL; |
379 | } |
380 | |
381 | #if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP)) |
382 | /* |
383 | * Compare two strings, ignoring case, using current locale. |
384 | * Doesn't work for multi-byte characters. |
385 | * return 0 for match, < 0 for smaller, > 0 for bigger |
386 | */ |
387 | int vim_stricmp(const char *s1, const char *s2) |
388 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE |
389 | { |
390 | int i; |
391 | |
392 | for (;; ) { |
393 | i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2); |
394 | if (i != 0) |
395 | return i; /* this character different */ |
396 | if (*s1 == NUL) |
397 | break; /* strings match until NUL */ |
398 | ++s1; |
399 | ++s2; |
400 | } |
401 | return 0; /* strings match */ |
402 | } |
403 | #endif |
404 | |
405 | #if (!defined(HAVE_STRNCASECMP) && !defined(HAVE_STRNICMP)) |
406 | /* |
407 | * Compare two strings, for length "len", ignoring case, using current locale. |
408 | * Doesn't work for multi-byte characters. |
409 | * return 0 for match, < 0 for smaller, > 0 for bigger |
410 | */ |
411 | int vim_strnicmp(const char *s1, const char *s2, size_t len) |
412 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE |
413 | { |
414 | int i; |
415 | |
416 | while (len > 0) { |
417 | i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2); |
418 | if (i != 0) |
419 | return i; /* this character different */ |
420 | if (*s1 == NUL) |
421 | break; /* strings match until NUL */ |
422 | ++s1; |
423 | ++s2; |
424 | --len; |
425 | } |
426 | return 0; /* strings match */ |
427 | } |
428 | #endif |
429 | |
430 | /// strchr() version which handles multibyte strings |
431 | /// |
432 | /// @param[in] string String to search in. |
433 | /// @param[in] c Character to search for. |
434 | /// |
435 | /// @return Pointer to the first byte of the found character in string or NULL |
436 | /// if it was not found or character is invalid. NUL character is never |
437 | /// found, use `strlen()` instead. |
438 | char_u *vim_strchr(const char_u *const string, const int c) |
439 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT |
440 | { |
441 | if (c <= 0) { |
442 | return NULL; |
443 | } else if (c < 0x80) { |
444 | return (char_u *)strchr((const char *)string, c); |
445 | } else { |
446 | char u8char[MB_MAXBYTES + 1]; |
447 | const int len = utf_char2bytes(c, (char_u *)u8char); |
448 | u8char[len] = NUL; |
449 | return (char_u *)strstr((const char *)string, u8char); |
450 | } |
451 | } |
452 | |
453 | /* |
454 | * Sort an array of strings. |
455 | */ |
456 | |
457 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
458 | # include "strings.c.generated.h" |
459 | #endif |
460 | static int sort_compare(const void *s1, const void *s2) |
461 | FUNC_ATTR_NONNULL_ALL |
462 | { |
463 | return STRCMP(*(char **)s1, *(char **)s2); |
464 | } |
465 | |
466 | void sort_strings(char_u **files, int count) |
467 | { |
468 | qsort((void *)files, (size_t)count, sizeof(char_u *), sort_compare); |
469 | } |
470 | |
471 | /* |
472 | * Return true if string "s" contains a non-ASCII character (128 or higher). |
473 | * When "s" is NULL false is returned. |
474 | */ |
475 | bool has_non_ascii(const char_u *s) |
476 | FUNC_ATTR_PURE |
477 | { |
478 | const char_u *p; |
479 | |
480 | if (s != NULL) |
481 | for (p = s; *p != NUL; ++p) |
482 | if (*p >= 128) |
483 | return true; |
484 | return false; |
485 | } |
486 | |
487 | /// Return true if string "s" contains a non-ASCII character (128 or higher). |
488 | /// When "s" is NULL false is returned. |
489 | bool has_non_ascii_len(const char *const s, const size_t len) |
490 | FUNC_ATTR_PURE |
491 | { |
492 | if (s != NULL) { |
493 | for (size_t i = 0; i < len; i++) { |
494 | if ((uint8_t) s[i] >= 128) { |
495 | return true; |
496 | } |
497 | } |
498 | } |
499 | return false; |
500 | } |
501 | |
502 | /* |
503 | * Concatenate two strings and return the result in allocated memory. |
504 | */ |
505 | char_u *concat_str(const char_u *restrict str1, const char_u *restrict str2) |
506 | FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL |
507 | { |
508 | size_t l = STRLEN(str1); |
509 | char_u *dest = xmalloc(l + STRLEN(str2) + 1); |
510 | STRCPY(dest, str1); |
511 | STRCPY(dest + l, str2); |
512 | return dest; |
513 | } |
514 | |
515 | |
516 | static const char *const e_printf = |
517 | N_("E766: Insufficient arguments for printf()" ); |
518 | |
519 | /// Get number argument from idxp entry in tvs |
520 | /// |
521 | /// Will give an error message for VimL entry with invalid type or for |
522 | /// insufficient entries. |
523 | /// |
524 | /// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN |
525 | /// value. |
526 | /// @param[in,out] idxp Index in a list. Will be incremented. Indexing starts |
527 | /// at 1. |
528 | /// |
529 | /// @return Number value or 0 in case of error. |
530 | static varnumber_T tv_nr(typval_T *tvs, int *idxp) |
531 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT |
532 | { |
533 | int idx = *idxp - 1; |
534 | varnumber_T n = 0; |
535 | |
536 | if (tvs[idx].v_type == VAR_UNKNOWN) { |
537 | EMSG(_(e_printf)); |
538 | } else { |
539 | (*idxp)++; |
540 | bool err = false; |
541 | n = tv_get_number_chk(&tvs[idx], &err); |
542 | if (err) { |
543 | n = 0; |
544 | } |
545 | } |
546 | return n; |
547 | } |
548 | |
549 | /// Get string argument from idxp entry in tvs |
550 | /// |
551 | /// Will give an error message for VimL entry with invalid type or for |
552 | /// insufficient entries. |
553 | /// |
554 | /// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN |
555 | /// value. |
556 | /// @param[in,out] idxp Index in a list. Will be incremented. |
557 | /// @param[out] tofree If the idxp entry in tvs is not a String or a Number, |
558 | /// it will be converted to String in the same format |
559 | /// as ":echo" and stored in "*tofree". The caller must |
560 | /// free "*tofree". |
561 | /// |
562 | /// @return String value or NULL in case of error. |
563 | static const char *tv_str(typval_T *tvs, int *idxp, char **const tofree) |
564 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT |
565 | { |
566 | int idx = *idxp - 1; |
567 | const char *s = NULL; |
568 | |
569 | if (tvs[idx].v_type == VAR_UNKNOWN) { |
570 | EMSG(_(e_printf)); |
571 | } else { |
572 | (*idxp)++; |
573 | if (tvs[idx].v_type == VAR_STRING || tvs[idx].v_type == VAR_NUMBER) { |
574 | s = tv_get_string_chk(&tvs[idx]); |
575 | *tofree = NULL; |
576 | } else { |
577 | s = *tofree = encode_tv2echo(&tvs[idx], NULL); |
578 | } |
579 | } |
580 | return s; |
581 | } |
582 | |
583 | /// Get pointer argument from the next entry in tvs |
584 | /// |
585 | /// Will give an error message for VimL entry with invalid type or for |
586 | /// insufficient entries. |
587 | /// |
588 | /// @param[in] tvs List of typval_T values. |
589 | /// @param[in,out] idxp Pointer to the index of the current value. |
590 | /// |
591 | /// @return Pointer stored in typval_T or NULL. |
592 | static const void *tv_ptr(const typval_T *const tvs, int *const idxp) |
593 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT |
594 | { |
595 | #define OFF(attr) offsetof(union typval_vval_union, attr) |
596 | STATIC_ASSERT( |
597 | OFF(v_string) == OFF(v_list) |
598 | && OFF(v_string) == OFF(v_dict) |
599 | && OFF(v_string) == OFF(v_partial) |
600 | && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list) |
601 | && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict) |
602 | && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial), |
603 | "Strings, dictionaries, lists and partials are expected to be pointers, " |
604 | "so that all three of them can be accessed via v_string" ); |
605 | #undef OFF |
606 | const int idx = *idxp - 1; |
607 | if (tvs[idx].v_type == VAR_UNKNOWN) { |
608 | EMSG(_(e_printf)); |
609 | return NULL; |
610 | } else { |
611 | (*idxp)++; |
612 | return tvs[idx].vval.v_string; |
613 | } |
614 | } |
615 | |
616 | /// Get float argument from idxp entry in tvs |
617 | /// |
618 | /// Will give an error message for VimL entry with invalid type or for |
619 | /// insufficient entries. |
620 | /// |
621 | /// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN |
622 | /// value. |
623 | /// @param[in,out] idxp Index in a list. Will be incremented. |
624 | /// |
625 | /// @return Floating-point value or zero in case of error. |
626 | static float_T tv_float(typval_T *const tvs, int *const idxp) |
627 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT |
628 | { |
629 | int idx = *idxp - 1; |
630 | float_T f = 0; |
631 | |
632 | if (tvs[idx].v_type == VAR_UNKNOWN) { |
633 | EMSG(_(e_printf)); |
634 | } else { |
635 | (*idxp)++; |
636 | if (tvs[idx].v_type == VAR_FLOAT) { |
637 | f = tvs[idx].vval.v_float; |
638 | } else if (tvs[idx].v_type == VAR_NUMBER) { |
639 | f = (float_T)tvs[idx].vval.v_number; |
640 | } else { |
641 | EMSG(_("E807: Expected Float argument for printf()" )); |
642 | } |
643 | } |
644 | return f; |
645 | } |
646 | |
647 | // This code was included to provide a portable vsnprintf() and snprintf(). |
648 | // Some systems may provide their own, but we always use this one for |
649 | // consistency. |
650 | // |
651 | // This code is based on snprintf.c - a portable implementation of snprintf |
652 | // by Mark Martinec <mark.martinec@ijs.si>, Version 2.2, 2000-10-06. |
653 | // Included with permission. It was heavily modified to fit in Vim. |
654 | // The original code, including useful comments, can be found here: |
655 | // |
656 | // http://www.ijs.si/software/snprintf/ |
657 | // |
658 | // This snprintf() only supports the following conversion specifiers: |
659 | // s, c, b, B, d, u, o, x, X, p (and synonyms: i, D, U, O - see below) |
660 | // with flags: '-', '+', ' ', '0' and '#'. |
661 | // An asterisk is supported for field width as well as precision. |
662 | // |
663 | // Limited support for floating point was added: 'f', 'e', 'E', 'g', 'G'. |
664 | // |
665 | // Length modifiers 'h' (short int), 'l' (long int) and "ll" (long long int) are |
666 | // supported. |
667 | // |
668 | // The locale is not used, the string is used as a byte string. This is only |
669 | // relevant for double-byte encodings where the second byte may be '%'. |
670 | // |
671 | // It is permitted for "str_m" to be zero, and it is permitted to specify NULL |
672 | // pointer for resulting string argument if "str_m" is zero (as per ISO C99). |
673 | // |
674 | // The return value is the number of characters which would be generated |
675 | // for the given input, excluding the trailing NUL. If this value |
676 | // is greater or equal to "str_m", not all characters from the result |
677 | // have been stored in str, output bytes beyond the ("str_m"-1) -th character |
678 | // are discarded. If "str_m" is greater than zero it is guaranteed |
679 | // the resulting string will be NUL-terminated. |
680 | |
681 | // vim_vsnprintf_typval() can be invoked with either "va_list" or a list of |
682 | // "typval_T". When the latter is not used it must be NULL. |
683 | |
684 | /// Append a formatted value to the string |
685 | /// |
686 | /// @see vim_vsnprintf_typval(). |
687 | int vim_snprintf_add(char *str, size_t str_m, char *fmt, ...) |
688 | FUNC_ATTR_PRINTF(3, 4) |
689 | { |
690 | const size_t len = strlen(str); |
691 | size_t space; |
692 | |
693 | if (str_m <= len) { |
694 | space = 0; |
695 | } else { |
696 | space = str_m - len; |
697 | } |
698 | va_list ap; |
699 | va_start(ap, fmt); |
700 | const int str_l = vim_vsnprintf(str + len, space, fmt, ap); |
701 | va_end(ap); |
702 | return str_l; |
703 | } |
704 | |
705 | /// Write formatted value to the string |
706 | /// |
707 | /// @param[out] str String to write to. |
708 | /// @param[in] str_m String length. |
709 | /// @param[in] fmt String format. |
710 | /// |
711 | /// @return Number of bytes excluding NUL byte that would be written to the |
712 | /// string if str_m was greater or equal to the return value. |
713 | int vim_snprintf(char *str, size_t str_m, const char *fmt, ...) |
714 | FUNC_ATTR_PRINTF(3, 4) |
715 | { |
716 | va_list ap; |
717 | va_start(ap, fmt); |
718 | const int str_l = vim_vsnprintf(str, str_m, fmt, ap); |
719 | va_end(ap); |
720 | return str_l; |
721 | } |
722 | |
723 | // Return the representation of infinity for printf() function: |
724 | // "-inf", "inf", "+inf", " inf", "-INF", "INF", "+INF" or " INF". |
725 | static const char *infinity_str(bool positive, char fmt_spec, |
726 | int force_sign, int space_for_positive) |
727 | { |
728 | static const char *table[] = { |
729 | "-inf" , "inf" , "+inf" , " inf" , |
730 | "-INF" , "INF" , "+INF" , " INF" |
731 | }; |
732 | int idx = positive * (1 + force_sign + force_sign * space_for_positive); |
733 | if (ASCII_ISUPPER(fmt_spec)) { |
734 | idx += 4; |
735 | } |
736 | return table[idx]; |
737 | } |
738 | |
739 | int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) |
740 | { |
741 | return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL); |
742 | } |
743 | |
744 | /// Write formatted value to the string |
745 | /// |
746 | /// @param[out] str String to write to. |
747 | /// @param[in] str_m String length. |
748 | /// @param[in] fmt String format. |
749 | /// @param[in] ap Values that should be formatted. Ignored if tvs is not NULL. |
750 | /// @param[in] tvs Values that should be formatted, for printf() VimL |
751 | /// function. Must be NULL in other cases. |
752 | /// |
753 | /// @return Number of bytes excluding NUL byte that would be written to the |
754 | /// string if str_m was greater or equal to the return value. |
755 | int vim_vsnprintf_typval( |
756 | char *str, size_t str_m, const char *fmt, va_list ap, typval_T *const tvs) |
757 | { |
758 | size_t str_l = 0; |
759 | bool str_avail = str_l < str_m; |
760 | const char *p = fmt; |
761 | int arg_idx = 1; |
762 | |
763 | if (!p) { |
764 | p = "" ; |
765 | } |
766 | while (*p) { |
767 | if (*p != '%') { |
768 | // copy up to the next '%' or NUL without any changes |
769 | size_t n = (size_t)(xstrchrnul(p + 1, '%') - p); |
770 | if (str_avail) { |
771 | size_t avail = str_m - str_l; |
772 | memmove(str + str_l, p, MIN(n, avail)); |
773 | str_avail = n < avail; |
774 | } |
775 | p += n; |
776 | assert(n <= SIZE_MAX - str_l); |
777 | str_l += n; |
778 | } else { |
779 | size_t min_field_width = 0, precision = 0; |
780 | int zero_padding = 0, precision_specified = 0, justify_left = 0; |
781 | int alternate_form = 0, force_sign = 0; |
782 | |
783 | // if both ' ' and '+' flags appear, ' ' flag should be ignored |
784 | int space_for_positive = 1; |
785 | |
786 | // allowed values: \0, h, l, 2 (for ll), z, L |
787 | char length_modifier = '\0'; |
788 | |
789 | // temporary buffer for simple numeric->string conversion |
790 | # define TMP_LEN 350 // 1e308 seems reasonable as the maximum printable |
791 | char tmp[TMP_LEN]; |
792 | |
793 | // string address in case of string argument |
794 | const char *str_arg = NULL; |
795 | |
796 | // natural field width of arg without padding and sign |
797 | size_t str_arg_l; |
798 | |
799 | // unsigned char argument value (only defined for c conversion); |
800 | // standard explicitly states the char argument for the c |
801 | // conversion is unsigned |
802 | unsigned char uchar_arg; |
803 | |
804 | // number of zeros to be inserted for numeric conversions as |
805 | // required by the precision or minimal field width |
806 | size_t number_of_zeros_to_pad = 0; |
807 | |
808 | // index into tmp where zero padding is to be inserted |
809 | size_t zero_padding_insertion_ind = 0; |
810 | |
811 | // current conversion specifier character |
812 | char fmt_spec = '\0'; |
813 | |
814 | // buffer for 's' and 'S' specs |
815 | char *tofree = NULL; |
816 | |
817 | p++; // skip '%' |
818 | |
819 | // parse flags |
820 | while (true) { |
821 | switch (*p) { |
822 | case '0': zero_padding = 1; p++; continue; |
823 | case '-': justify_left = 1; p++; continue; |
824 | // if both '0' and '-' flags appear, '0' should be ignored |
825 | case '+': force_sign = 1; space_for_positive = 0; p++; continue; |
826 | case ' ': force_sign = 1; p++; continue; |
827 | // if both ' ' and '+' flags appear, ' ' should be ignored |
828 | case '#': alternate_form = 1; p++; continue; |
829 | case '\'': p++; continue; |
830 | default: break; |
831 | } |
832 | break; |
833 | } |
834 | |
835 | // parse field width |
836 | if (*p == '*') { |
837 | p++; |
838 | const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); |
839 | if (j >= 0) { |
840 | min_field_width = (size_t)j; |
841 | } else { |
842 | min_field_width = (size_t)-j; |
843 | justify_left = 1; |
844 | } |
845 | } else if (ascii_isdigit((int)(*p))) { |
846 | // size_t could be wider than unsigned int; make sure we treat |
847 | // argument like common implementations do |
848 | unsigned int uj = (unsigned)(*p++ - '0'); |
849 | |
850 | while (ascii_isdigit((int)(*p))) { |
851 | uj = 10 * uj + (unsigned int)(*p++ - '0'); |
852 | } |
853 | min_field_width = uj; |
854 | } |
855 | |
856 | // parse precision |
857 | if (*p == '.') { |
858 | p++; |
859 | precision_specified = 1; |
860 | if (*p == '*') { |
861 | const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); |
862 | p++; |
863 | if (j >= 0) { |
864 | precision = (size_t)j; |
865 | } else { |
866 | precision_specified = 0; |
867 | precision = 0; |
868 | } |
869 | } else if (ascii_isdigit((int)(*p))) { |
870 | // size_t could be wider than unsigned int; make sure we |
871 | // treat argument like common implementations do |
872 | unsigned int uj = (unsigned)(*p++ - '0'); |
873 | |
874 | while (ascii_isdigit((int)(*p))) { |
875 | uj = 10 * uj + (unsigned int)(*p++ - '0'); |
876 | } |
877 | precision = uj; |
878 | } |
879 | } |
880 | |
881 | // parse 'h', 'l', 'll' and 'z' length modifiers |
882 | if (*p == 'h' || *p == 'l' || *p == 'z') { |
883 | length_modifier = *p; |
884 | p++; |
885 | if (length_modifier == 'l' && *p == 'l') { // ll, encoded as 2 |
886 | length_modifier = '2'; |
887 | p++; |
888 | } |
889 | } |
890 | |
891 | fmt_spec = *p; |
892 | |
893 | // common synonyms |
894 | switch (fmt_spec) { |
895 | case 'i': fmt_spec = 'd'; break; |
896 | case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; |
897 | case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; |
898 | case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; |
899 | default: break; |
900 | } |
901 | |
902 | switch (fmt_spec) { |
903 | case 'd': case 'u': case 'o': case 'x': case 'X': |
904 | if (tvs && length_modifier == '\0') { |
905 | length_modifier = '2'; |
906 | } |
907 | } |
908 | |
909 | // get parameter value, do initial processing |
910 | switch (fmt_spec) { |
911 | // '%' and 'c' behave similar to 's' regarding flags and field widths |
912 | case '%': case 'c': case 's': case 'S': |
913 | str_arg_l = 1; |
914 | switch (fmt_spec) { |
915 | case '%': |
916 | str_arg = p; |
917 | break; |
918 | |
919 | case 'c': { |
920 | const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); |
921 | // standard demands unsigned char |
922 | uchar_arg = (unsigned char)j; |
923 | str_arg = (char *)&uchar_arg; |
924 | break; |
925 | } |
926 | |
927 | case 's': |
928 | case 'S': |
929 | str_arg = tvs ? tv_str(tvs, &arg_idx, &tofree) |
930 | : va_arg(ap, const char *); |
931 | if (!str_arg) { |
932 | str_arg = "[NULL]" ; |
933 | str_arg_l = 6; |
934 | } else if (!precision_specified) { |
935 | // make sure not to address string beyond the specified |
936 | // precision |
937 | str_arg_l = strlen(str_arg); |
938 | } else if (precision == 0) { |
939 | // truncate string if necessary as requested by precision |
940 | str_arg_l = 0; |
941 | } else { |
942 | // memchr on HP does not like n > 2^31 |
943 | // TODO(elmart): check if this still holds / is relevant |
944 | str_arg_l = (size_t)((char *)xmemscan(str_arg, |
945 | NUL, |
946 | MIN(precision, |
947 | 0x7fffffff)) |
948 | - str_arg); |
949 | } |
950 | if (fmt_spec == 'S') { |
951 | if (min_field_width != 0) { |
952 | min_field_width += (strlen(str_arg) |
953 | - mb_string2cells((char_u *)str_arg)); |
954 | } |
955 | if (precision) { |
956 | const char *p1 = str_arg; |
957 | for (size_t i = 0; i < precision && *p1; i++) { |
958 | p1 += mb_ptr2len((const char_u *)p1); |
959 | } |
960 | str_arg_l = precision = (size_t)(p1 - str_arg); |
961 | } |
962 | } |
963 | break; |
964 | |
965 | default: |
966 | break; |
967 | } |
968 | break; |
969 | |
970 | case 'd': |
971 | case 'u': |
972 | case 'b': case 'B': |
973 | case 'o': |
974 | case 'x': case 'X': |
975 | case 'p': { |
976 | // u, b, B, o, x, X and p conversion specifiers imply |
977 | // the value is unsigned; d implies a signed value |
978 | |
979 | // 0 if numeric argument is zero (or if pointer is NULL for 'p'), |
980 | // +1 if greater than zero (or non NULL for 'p'), |
981 | // -1 if negative (unsigned argument is never negative) |
982 | int arg_sign = 0; |
983 | |
984 | intmax_t arg = 0; |
985 | uintmax_t uarg = 0; |
986 | |
987 | // only defined for p conversion |
988 | const void *ptr_arg = NULL; |
989 | |
990 | if (fmt_spec == 'p') { |
991 | ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *); |
992 | if (ptr_arg) { |
993 | arg_sign = 1; |
994 | } |
995 | } else if (fmt_spec == 'd') { |
996 | // signed |
997 | switch (length_modifier) { |
998 | case '\0': { |
999 | arg = (int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int)); |
1000 | break; |
1001 | } |
1002 | case 'h': { |
1003 | // char and short arguments are passed as int16_t |
1004 | arg = (int16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int)); |
1005 | break; |
1006 | } |
1007 | case 'l': { |
1008 | arg = (tvs ? (long)tv_nr(tvs, &arg_idx) : va_arg(ap, long)); |
1009 | break; |
1010 | } |
1011 | case '2': { |
1012 | arg = ( |
1013 | tvs |
1014 | ? (long long)tv_nr(tvs, &arg_idx) // NOLINT (runtime/int) |
1015 | : va_arg(ap, long long)); // NOLINT (runtime/int) |
1016 | break; |
1017 | } |
1018 | case 'z': { |
1019 | arg = (tvs |
1020 | ? (ptrdiff_t)tv_nr(tvs, &arg_idx) |
1021 | : va_arg(ap, ptrdiff_t)); |
1022 | break; |
1023 | } |
1024 | } |
1025 | if (arg > 0) { |
1026 | arg_sign = 1; |
1027 | } else if (arg < 0) { |
1028 | arg_sign = -1; |
1029 | } |
1030 | } else { |
1031 | // unsigned |
1032 | switch (length_modifier) { |
1033 | case '\0': { |
1034 | uarg = (unsigned int)(tvs |
1035 | ? tv_nr(tvs, &arg_idx) |
1036 | : va_arg(ap, unsigned int)); |
1037 | break; |
1038 | } |
1039 | case 'h': { |
1040 | uarg = (uint16_t)(tvs |
1041 | ? tv_nr(tvs, &arg_idx) |
1042 | : va_arg(ap, unsigned int)); |
1043 | break; |
1044 | } |
1045 | case 'l': { |
1046 | uarg = (tvs |
1047 | ? (unsigned long)tv_nr(tvs, &arg_idx) |
1048 | : va_arg(ap, unsigned long)); |
1049 | break; |
1050 | } |
1051 | case '2': { |
1052 | uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int) |
1053 | tvs |
1054 | ? ((unsigned long long) // NOLINT (runtime/int) |
1055 | tv_nr(tvs, &arg_idx)) |
1056 | : va_arg(ap, unsigned long long)); // NOLINT (runtime/int) |
1057 | break; |
1058 | } |
1059 | case 'z': { |
1060 | uarg = (tvs |
1061 | ? (size_t)tv_nr(tvs, &arg_idx) |
1062 | : va_arg(ap, size_t)); |
1063 | break; |
1064 | } |
1065 | } |
1066 | arg_sign = (uarg != 0); |
1067 | } |
1068 | |
1069 | str_arg = tmp; |
1070 | str_arg_l = 0; |
1071 | |
1072 | // For d, i, u, o, x, and X conversions, if precision is specified, |
1073 | // '0' flag should be ignored. This is so with Solaris 2.6, Digital |
1074 | // UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl. |
1075 | if (precision_specified) { |
1076 | zero_padding = 0; |
1077 | } |
1078 | |
1079 | if (fmt_spec == 'd') { |
1080 | if (force_sign && arg_sign >= 0) { |
1081 | tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; |
1082 | } |
1083 | // leave negative numbers for snprintf to handle, to |
1084 | // avoid handling tricky cases like (short int)-32768 |
1085 | } else if (alternate_form) { |
1086 | if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X' |
1087 | || fmt_spec == 'b' || fmt_spec == 'B')) { |
1088 | tmp[str_arg_l++] = '0'; |
1089 | tmp[str_arg_l++] = fmt_spec; |
1090 | } |
1091 | // alternate form should have no effect for p * conversion, but ... |
1092 | } |
1093 | |
1094 | zero_padding_insertion_ind = str_arg_l; |
1095 | if (!precision_specified) { |
1096 | precision = 1; // default precision is 1 |
1097 | } |
1098 | if (precision == 0 && arg_sign == 0) { |
1099 | // when zero value is formatted with an explicit precision 0, |
1100 | // resulting formatted string is empty (d, i, u, b, B, o, x, X, p) |
1101 | } else { |
1102 | switch (fmt_spec) { |
1103 | case 'p': { // pointer |
1104 | str_arg_l += (size_t)snprintf(tmp + str_arg_l, |
1105 | sizeof(tmp) - str_arg_l, |
1106 | "%p" , ptr_arg); |
1107 | break; |
1108 | } |
1109 | case 'd': { // signed |
1110 | str_arg_l += (size_t)snprintf(tmp + str_arg_l, |
1111 | sizeof(tmp) - str_arg_l, |
1112 | "%" PRIdMAX, arg); |
1113 | break; |
1114 | } |
1115 | case 'b': case 'B': { // binary |
1116 | size_t bits = 0; |
1117 | for (bits = sizeof(uintmax_t) * 8; bits > 0; bits--) { |
1118 | if ((uarg >> (bits - 1)) & 0x1) { |
1119 | break; |
1120 | } |
1121 | } |
1122 | |
1123 | while (bits > 0) { |
1124 | tmp[str_arg_l++] = ((uarg >> --bits) & 0x1) ? '1' : '0'; |
1125 | } |
1126 | break; |
1127 | } |
1128 | default: { // unsigned |
1129 | // construct a simple format string for snprintf |
1130 | char f[] = "%" PRIuMAX; |
1131 | f[sizeof("%" PRIuMAX) - 1 - 1] = fmt_spec; |
1132 | assert(PRIuMAX[sizeof(PRIuMAX) - 1 - 1] == 'u'); |
1133 | str_arg_l += (size_t)snprintf(tmp + str_arg_l, |
1134 | sizeof(tmp) - str_arg_l, |
1135 | f, uarg); |
1136 | break; |
1137 | } |
1138 | } |
1139 | assert(str_arg_l < sizeof(tmp)); |
1140 | |
1141 | // include the optional minus sign and possible "0x" in the region |
1142 | // before the zero padding insertion point |
1143 | if (zero_padding_insertion_ind < str_arg_l |
1144 | && tmp[zero_padding_insertion_ind] == '-') { |
1145 | zero_padding_insertion_ind++; |
1146 | } |
1147 | if (zero_padding_insertion_ind + 1 < str_arg_l |
1148 | && tmp[zero_padding_insertion_ind] == '0' |
1149 | && (tmp[zero_padding_insertion_ind + 1] == 'x' |
1150 | || tmp[zero_padding_insertion_ind + 1] == 'X' |
1151 | || tmp[zero_padding_insertion_ind + 1] == 'b' |
1152 | || tmp[zero_padding_insertion_ind + 1] == 'B')) { |
1153 | zero_padding_insertion_ind += 2; |
1154 | } |
1155 | } |
1156 | |
1157 | { |
1158 | size_t num_of_digits = str_arg_l - zero_padding_insertion_ind; |
1159 | |
1160 | if (alternate_form && fmt_spec == 'o' |
1161 | // unless zero is already the first character |
1162 | && !(zero_padding_insertion_ind < str_arg_l |
1163 | && tmp[zero_padding_insertion_ind] == '0')) { |
1164 | // assure leading zero for alternate-form octal numbers |
1165 | if (!precision_specified |
1166 | || precision < num_of_digits + 1) { |
1167 | // precision is increased to force the first character to be |
1168 | // zero, except if a zero value is formatted with an explicit |
1169 | // precision of zero |
1170 | precision = num_of_digits + 1; |
1171 | } |
1172 | } |
1173 | // zero padding to specified precision? |
1174 | if (num_of_digits < precision) { |
1175 | number_of_zeros_to_pad = precision - num_of_digits; |
1176 | } |
1177 | } |
1178 | // zero padding to specified minimal field width? |
1179 | if (!justify_left && zero_padding) { |
1180 | const int n = (int)(min_field_width - (str_arg_l |
1181 | + number_of_zeros_to_pad)); |
1182 | if (n > 0) { |
1183 | number_of_zeros_to_pad += (size_t)n; |
1184 | } |
1185 | } |
1186 | break; |
1187 | } |
1188 | |
1189 | case 'f': |
1190 | case 'F': |
1191 | case 'e': |
1192 | case 'E': |
1193 | case 'g': |
1194 | case 'G': |
1195 | { |
1196 | // floating point |
1197 | char format[40]; |
1198 | int remove_trailing_zeroes = false; |
1199 | |
1200 | double f = tvs ? tv_float(tvs, &arg_idx) : va_arg(ap, double); |
1201 | double abs_f = f < 0 ? -f : f; |
1202 | |
1203 | if (fmt_spec == 'g' || fmt_spec == 'G') { |
1204 | // can't use %g directly, cause it prints "1.0" as "1" |
1205 | if ((abs_f >= 0.001 && abs_f < 10000000.0) || abs_f == 0.0) { |
1206 | fmt_spec = ASCII_ISUPPER(fmt_spec) ? 'F' : 'f'; |
1207 | } else { |
1208 | fmt_spec = fmt_spec == 'g' ? 'e' : 'E'; |
1209 | } |
1210 | remove_trailing_zeroes = true; |
1211 | } |
1212 | |
1213 | if (xisinf(f) |
1214 | || (strchr("fF" , fmt_spec) != NULL && abs_f > 1.0e307)) { |
1215 | xstrlcpy(tmp, infinity_str(f > 0.0, fmt_spec, |
1216 | force_sign, space_for_positive), |
1217 | sizeof(tmp)); |
1218 | str_arg_l = strlen(tmp); |
1219 | zero_padding = 0; |
1220 | } else if (xisnan(f)) { |
1221 | // Not a number: nan or NAN |
1222 | memmove(tmp, ASCII_ISUPPER(fmt_spec) ? "NAN" : "nan" , 4); |
1223 | str_arg_l = 3; |
1224 | zero_padding = 0; |
1225 | } else { |
1226 | // Regular float number |
1227 | format[0] = '%'; |
1228 | size_t l = 1; |
1229 | if (force_sign) { |
1230 | format[l++] = space_for_positive ? ' ' : '+'; |
1231 | } |
1232 | if (precision_specified) { |
1233 | size_t max_prec = TMP_LEN - 10; |
1234 | |
1235 | // make sure we don't get more digits than we have room for |
1236 | if ((fmt_spec == 'f' || fmt_spec == 'F') && abs_f > 1.0) { |
1237 | max_prec -= (size_t)log10(abs_f); |
1238 | } |
1239 | if (precision > max_prec) { |
1240 | precision = max_prec; |
1241 | } |
1242 | l += (size_t)snprintf(format + l, sizeof(format) - l, ".%d" , |
1243 | (int)precision); |
1244 | } |
1245 | |
1246 | // Cast to char to avoid a conversion warning on Ubuntu 12.04. |
1247 | assert(l + 1 < sizeof(format)); |
1248 | format[l] = (char)(fmt_spec == 'F' ? 'f' : fmt_spec); |
1249 | format[l + 1] = NUL; |
1250 | |
1251 | str_arg_l = (size_t)snprintf(tmp, sizeof(tmp), format, f); |
1252 | assert(str_arg_l < sizeof(tmp)); |
1253 | |
1254 | if (remove_trailing_zeroes) { |
1255 | int i; |
1256 | char *tp; |
1257 | |
1258 | // using %g or %G: remove superfluous zeroes |
1259 | if (fmt_spec == 'f' || fmt_spec == 'F') { |
1260 | tp = tmp + str_arg_l - 1; |
1261 | } else { |
1262 | tp = (char *)vim_strchr((char_u *)tmp, |
1263 | fmt_spec == 'e' ? 'e' : 'E'); |
1264 | if (tp) { |
1265 | // remove superfluous '+' and leading zeroes from exponent |
1266 | if (tp[1] == '+') { |
1267 | // change "1.0e+07" to "1.0e07" |
1268 | STRMOVE(tp + 1, tp + 2); |
1269 | str_arg_l--; |
1270 | } |
1271 | i = (tp[1] == '-') ? 2 : 1; |
1272 | while (tp[i] == '0') { |
1273 | // change "1.0e07" to "1.0e7" |
1274 | STRMOVE(tp + i, tp + i + 1); |
1275 | str_arg_l--; |
1276 | } |
1277 | tp--; |
1278 | } |
1279 | } |
1280 | |
1281 | if (tp != NULL && !precision_specified) { |
1282 | // remove trailing zeroes, but keep the one just after a dot |
1283 | while (tp > tmp + 2 && *tp == '0' && tp[-1] != '.') { |
1284 | STRMOVE(tp, tp + 1); |
1285 | tp--; |
1286 | str_arg_l--; |
1287 | } |
1288 | } |
1289 | } else { |
1290 | // Be consistent: some printf("%e") use 1.0e+12 and some |
1291 | // 1.0e+012; remove one zero in the last case. |
1292 | char *tp = (char *)vim_strchr((char_u *)tmp, |
1293 | fmt_spec == 'e' ? 'e' : 'E'); |
1294 | if (tp && (tp[1] == '+' || tp[1] == '-') && tp[2] == '0' |
1295 | && ascii_isdigit(tp[3]) && ascii_isdigit(tp[4])) { |
1296 | STRMOVE(tp + 2, tp + 3); |
1297 | str_arg_l--; |
1298 | } |
1299 | } |
1300 | } |
1301 | if (zero_padding && min_field_width > str_arg_l |
1302 | && (tmp[0] == '-' || force_sign)) { |
1303 | // Padding 0's should be inserted after the sign. |
1304 | number_of_zeros_to_pad = min_field_width - str_arg_l; |
1305 | zero_padding_insertion_ind = 1; |
1306 | } |
1307 | str_arg = tmp; |
1308 | break; |
1309 | } |
1310 | |
1311 | default: |
1312 | // unrecognized conversion specifier, keep format string as-is |
1313 | zero_padding = 0; // turn zero padding off for non-numeric conversion |
1314 | justify_left = 1; |
1315 | min_field_width = 0; // reset flags |
1316 | |
1317 | // discard the unrecognized conversion, just keep |
1318 | // the unrecognized conversion character |
1319 | str_arg = p; |
1320 | str_arg_l = 0; |
1321 | if (*p) { |
1322 | str_arg_l++; // include invalid conversion specifier |
1323 | } |
1324 | // unchanged if not at end-of-string |
1325 | break; |
1326 | } |
1327 | |
1328 | if (*p) { |
1329 | p++; // step over the just processed conversion specifier |
1330 | } |
1331 | |
1332 | // insert padding to the left as requested by min_field_width; |
1333 | // this does not include the zero padding in case of numerical conversions |
1334 | if (!justify_left) { |
1335 | assert(str_arg_l <= SIZE_MAX - number_of_zeros_to_pad); |
1336 | if (min_field_width > str_arg_l + number_of_zeros_to_pad) { |
1337 | // left padding with blank or zero |
1338 | size_t pn = min_field_width - (str_arg_l + number_of_zeros_to_pad); |
1339 | if (str_avail) { |
1340 | size_t avail = str_m - str_l; |
1341 | memset(str + str_l, zero_padding ? '0' : ' ', MIN(pn, avail)); |
1342 | str_avail = pn < avail; |
1343 | } |
1344 | assert(pn <= SIZE_MAX - str_l); |
1345 | str_l += pn; |
1346 | } |
1347 | } |
1348 | |
1349 | // zero padding as requested by the precision or by the minimal |
1350 | // field width for numeric conversions required? |
1351 | if (number_of_zeros_to_pad == 0) { |
1352 | // will not copy first part of numeric right now, |
1353 | // force it to be copied later in its entirety |
1354 | zero_padding_insertion_ind = 0; |
1355 | } else { |
1356 | // insert first part of numerics (sign or '0x') before zero padding |
1357 | if (zero_padding_insertion_ind > 0) { |
1358 | size_t zn = zero_padding_insertion_ind; |
1359 | if (str_avail) { |
1360 | size_t avail = str_m - str_l; |
1361 | memmove(str + str_l, str_arg, MIN(zn, avail)); |
1362 | str_avail = zn < avail; |
1363 | } |
1364 | assert(zn <= SIZE_MAX - str_l); |
1365 | str_l += zn; |
1366 | } |
1367 | |
1368 | // insert zero padding as requested by precision or min field width |
1369 | size_t zn = number_of_zeros_to_pad; |
1370 | if (str_avail) { |
1371 | size_t avail = str_m - str_l; |
1372 | memset(str + str_l, '0', MIN(zn, avail)); |
1373 | str_avail = zn < avail; |
1374 | } |
1375 | assert(zn <= SIZE_MAX - str_l); |
1376 | str_l += zn; |
1377 | } |
1378 | |
1379 | // insert formatted string |
1380 | // (or as-is conversion specifier for unknown conversions) |
1381 | if (str_arg_l > zero_padding_insertion_ind) { |
1382 | size_t sn = str_arg_l - zero_padding_insertion_ind; |
1383 | if (str_avail) { |
1384 | size_t avail = str_m - str_l; |
1385 | memmove(str + str_l, |
1386 | str_arg + zero_padding_insertion_ind, |
1387 | MIN(sn, avail)); |
1388 | str_avail = sn < avail; |
1389 | } |
1390 | assert(sn <= SIZE_MAX - str_l); |
1391 | str_l += sn; |
1392 | } |
1393 | |
1394 | // insert right padding |
1395 | if (justify_left) { |
1396 | assert(str_arg_l <= SIZE_MAX - number_of_zeros_to_pad); |
1397 | if (min_field_width > str_arg_l + number_of_zeros_to_pad) { |
1398 | // right blank padding to the field width |
1399 | size_t pn = min_field_width - (str_arg_l + number_of_zeros_to_pad); |
1400 | if (str_avail) { |
1401 | size_t avail = str_m - str_l; |
1402 | memset(str + str_l, ' ', MIN(pn, avail)); |
1403 | str_avail = pn < avail; |
1404 | } |
1405 | assert(pn <= SIZE_MAX - str_l); |
1406 | str_l += pn; |
1407 | } |
1408 | } |
1409 | |
1410 | xfree(tofree); |
1411 | } |
1412 | } |
1413 | |
1414 | if (str_m > 0) { |
1415 | // make sure the string is nul-terminated even at the expense of |
1416 | // overwriting the last character (shouldn't happen, but just in case) |
1417 | str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0'; |
1418 | } |
1419 | |
1420 | if (tvs && tvs[arg_idx - 1].v_type != VAR_UNKNOWN) { |
1421 | EMSG(_("E767: Too many arguments to printf()" )); |
1422 | } |
1423 | |
1424 | // return the number of characters formatted (excluding trailing nul |
1425 | // character); that is, the number of characters that would have been |
1426 | // written to the buffer if it were large enough. |
1427 | return (int)str_l; |
1428 | } |
1429 | |