mirror of
				https://github.com/janet-lang/janet
				synced 2025-10-31 15:43:01 +00:00 
			
		
		
		
	Add buffer/format as well as string/format.
buffer/format uses the old string/format behavior. `string/format` no longer requires a buffer, and returns a string.
This commit is contained in:
		| @@ -317,6 +317,14 @@ static Janet cfun_buffer_blit(int32_t argc, Janet *argv) { | |||||||
|     return argv[0]; |     return argv[0]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static Janet cfun_buffer_format(int32_t argc, Janet *argv) { | ||||||
|  |     janet_arity(argc, 2, -1); | ||||||
|  |     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||||
|  |     const char *strfrmt = (const char *) janet_getstring(argv, 1); | ||||||
|  |     janet_buffer_format(buffer, strfrmt, 1, argc, argv); | ||||||
|  |     return argv[0]; | ||||||
|  | } | ||||||
|  |  | ||||||
| static const JanetReg buffer_cfuns[] = { | static const JanetReg buffer_cfuns[] = { | ||||||
|     {"buffer/new", cfun_buffer_new, |     {"buffer/new", cfun_buffer_new, | ||||||
|         JDOC("(buffer/new capacity)\n\n" |         JDOC("(buffer/new capacity)\n\n" | ||||||
| @@ -383,6 +391,11 @@ static const JanetReg buffer_cfuns[] = { | |||||||
|                 "indicate which part of src to copy into which part of dest. Indices can be " |                 "indicate which part of src to copy into which part of dest. Indices can be " | ||||||
|                 "negative to index from the end of src or dest. Returns dest.") |                 "negative to index from the end of src or dest. Returns dest.") | ||||||
|     }, |     }, | ||||||
|  |     {"buffer/format", cfun_buffer_format, | ||||||
|  |         JDOC("(buffer/format buffer format & args)\n\n" | ||||||
|  |                 "Snprintf like functionality for printing values into a buffer. Returns " | ||||||
|  |                 " the modified buffer.") | ||||||
|  |     }, | ||||||
|     {NULL, NULL, NULL} |     {NULL, NULL, NULL} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										146
									
								
								src/core/pp.c
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								src/core/pp.c
									
									
									
									
									
								
							| @@ -20,6 +20,9 @@ | |||||||
| * IN THE SOFTWARE. | * IN THE SOFTWARE. | ||||||
| */ | */ | ||||||
|  |  | ||||||
|  | #include <string.h> | ||||||
|  | #include <ctype.h> | ||||||
|  |  | ||||||
| #ifndef JANET_AMALG | #ifndef JANET_AMALG | ||||||
| #include <janet/janet.h> | #include <janet/janet.h> | ||||||
| #include "util.h" | #include "util.h" | ||||||
| @@ -549,3 +552,146 @@ const uint8_t *janet_formatc(const char *format, ...) { | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * code adapted from lua/lstrlib.c http://lua.org | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #define MAX_ITEM  256 | ||||||
|  | #define FMT_FLAGS "-+ #0" | ||||||
|  | #define MAX_FORMAT 32 | ||||||
|  |  | ||||||
|  | static const char *scanformat( | ||||||
|  |         const char *strfrmt, | ||||||
|  |         char *form, | ||||||
|  |         char width[3], | ||||||
|  |         char precision[3]) { | ||||||
|  |     const char *p = strfrmt; | ||||||
|  |     memset(width, '\0', 3); | ||||||
|  |     memset(precision, '\0', 3); | ||||||
|  |     while (*p != '\0' && strchr(FMT_FLAGS, *p) != NULL) | ||||||
|  |         p++; /* skip flags */ | ||||||
|  |     if ((size_t) (p - strfrmt) >= sizeof(FMT_FLAGS) / sizeof(char)) | ||||||
|  |         janet_panic("invalid format (repeated flags)"); | ||||||
|  |     if (isdigit((int) (*p))) | ||||||
|  |         width[0] = *p++; /* skip width */ | ||||||
|  |     if (isdigit((int) (*p))) | ||||||
|  |         width[1] = *p++; /* (2 digits at most) */ | ||||||
|  |     if (*p == '.') { | ||||||
|  |         p++; | ||||||
|  |         if (isdigit((int) (*p))) | ||||||
|  |             precision[0] = *p++; /* skip precision */ | ||||||
|  |         if (isdigit((int) (*p))) | ||||||
|  |             precision[1] = *p++; /* (2 digits at most) */ | ||||||
|  |     } | ||||||
|  |     if (isdigit((int) (*p))) | ||||||
|  |         janet_panic("invalid format (width or precision too long)"); | ||||||
|  |     *(form++) = '%'; | ||||||
|  |     memcpy(form, strfrmt, ((p - strfrmt) + 1) * sizeof(char)); | ||||||
|  |     form += (p - strfrmt) + 1; | ||||||
|  |     *form = '\0'; | ||||||
|  |     return p; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Shared implementation between string/format and | ||||||
|  |  * buffer/format */ | ||||||
|  | void janet_buffer_format( | ||||||
|  |         JanetBuffer *b, | ||||||
|  |         const char *strfrmt, | ||||||
|  |         int32_t argstart, | ||||||
|  |         int32_t argc, | ||||||
|  |         Janet *argv) { | ||||||
|  |     size_t sfl = strlen(strfrmt); | ||||||
|  |     const char *strfrmt_end = strfrmt + sfl; | ||||||
|  |     int32_t arg = argstart; | ||||||
|  |     while (strfrmt < strfrmt_end) { | ||||||
|  |         if (*strfrmt != '%') | ||||||
|  |             janet_buffer_push_u8(b, (uint8_t) * strfrmt++); | ||||||
|  |         else if (*++strfrmt == '%') | ||||||
|  |             janet_buffer_push_u8(b, (uint8_t) * strfrmt++); /* %% */ | ||||||
|  |         else { /* format item */ | ||||||
|  |             char form[MAX_FORMAT], item[MAX_ITEM]; | ||||||
|  |             char width[3], precision[3]; | ||||||
|  |             int nb = 0; /* number of bytes in added item */ | ||||||
|  |             if (++arg >= argc) | ||||||
|  |                 janet_panic("not enough values for format"); | ||||||
|  |             strfrmt = scanformat(strfrmt, form, width, precision); | ||||||
|  |             switch (*strfrmt++) { | ||||||
|  |                 case 'c': | ||||||
|  |                     { | ||||||
|  |                         nb = snprintf(item, MAX_ITEM, form, (int) | ||||||
|  |                                 janet_getinteger(argv, arg)); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 case 'd': | ||||||
|  |                 case 'i': | ||||||
|  |                 case 'o': | ||||||
|  |                 case 'u': | ||||||
|  |                 case 'x': | ||||||
|  |                 case 'X': | ||||||
|  |                     { | ||||||
|  |                         int32_t n = janet_getinteger(argv, arg); | ||||||
|  |                         nb = snprintf(item, MAX_ITEM, form, n); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 case 'a': | ||||||
|  |                 case 'A': | ||||||
|  |                 case 'e': | ||||||
|  |                 case 'E': | ||||||
|  |                 case 'f': | ||||||
|  |                 case 'g': | ||||||
|  |                 case 'G': | ||||||
|  |                     { | ||||||
|  |                         double d = janet_getnumber(argv, arg); | ||||||
|  |                         nb = snprintf(item, MAX_ITEM, form, d); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 case 's': | ||||||
|  |                     { | ||||||
|  |                         const uint8_t *s = janet_getstring(argv, arg); | ||||||
|  |                         size_t l = janet_string_length(s); | ||||||
|  |                         if (form[2] == '\0') | ||||||
|  |                             janet_buffer_push_bytes(b, s, l); | ||||||
|  |                         else { | ||||||
|  |                             if (l != strlen((const char *) s)) | ||||||
|  |                                 janet_panic("string contains zeros"); | ||||||
|  |                             if (!strchr(form, '.') && l >= 100) { | ||||||
|  |                                 janet_panic | ||||||
|  |                                     ("no precision and string is too long to be formatted"); | ||||||
|  |                             } else { | ||||||
|  |                                 nb = snprintf(item, MAX_ITEM, form, s); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 case 'V': | ||||||
|  |                     { | ||||||
|  |                         janet_to_string_b(b, argv[arg]); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 case 'v': | ||||||
|  |                     { | ||||||
|  |                         janet_description_b(b, argv[arg]); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 case 'p': /* janet pretty , precision = depth */ | ||||||
|  |                     { | ||||||
|  |                         int depth = atoi(precision); | ||||||
|  |                         if (depth < 1) | ||||||
|  |                             depth = 4; | ||||||
|  |                         janet_pretty(b, depth, argv[arg]); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 default: | ||||||
|  |                     { /* also treat cases 'nLlh' */ | ||||||
|  |                         janet_panicf("invalid conversion '%s' to 'format'", | ||||||
|  |                                 form); | ||||||
|  |                     } | ||||||
|  |             } | ||||||
|  |             if (nb >= MAX_ITEM) | ||||||
|  |                 janet_panicf("format buffer overflow", form); | ||||||
|  |             if (nb > 0) | ||||||
|  |                 janet_buffer_push_bytes(b, (uint8_t *) item, nb); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ | |||||||
| */ | */ | ||||||
|  |  | ||||||
| #include <string.h> | #include <string.h> | ||||||
| #include <ctype.h> |  | ||||||
|  |  | ||||||
| #ifndef JANET_AMALG | #ifndef JANET_AMALG | ||||||
| #include <janet/janet.h> | #include <janet/janet.h> | ||||||
| @@ -502,144 +501,12 @@ static Janet cfun_string_pretty(int32_t argc, Janet *argv) { | |||||||
|     return janet_wrap_buffer(buffer); |     return janet_wrap_buffer(buffer); | ||||||
| } | } | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * code adapted from lua/lstrlib.c http://lua.org |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #define MAX_ITEM  256 |  | ||||||
| #define FMT_FLAGS "-+ #0" |  | ||||||
| #define MAX_FORMAT 32 |  | ||||||
|  |  | ||||||
| static const char *scanformat( |  | ||||||
|         const char *strfrmt, |  | ||||||
|         char *form, |  | ||||||
|         char width[3], |  | ||||||
|         char precision[3]) { |  | ||||||
|     const char *p = strfrmt; |  | ||||||
|     memset(width, '\0', 3); |  | ||||||
|     memset(precision, '\0', 3); |  | ||||||
|     while (*p != '\0' && strchr(FMT_FLAGS, *p) != NULL) |  | ||||||
|         p++; /* skip flags */ |  | ||||||
|     if ((size_t) (p - strfrmt) >= sizeof(FMT_FLAGS) / sizeof(char)) |  | ||||||
|         janet_panic("invalid format (repeated flags)"); |  | ||||||
|     if (isdigit((int) (*p))) |  | ||||||
|         width[0] = *p++; /* skip width */ |  | ||||||
|     if (isdigit((int) (*p))) |  | ||||||
|         width[1] = *p++; /* (2 digits at most) */ |  | ||||||
|     if (*p == '.') { |  | ||||||
|         p++; |  | ||||||
|         if (isdigit((int) (*p))) |  | ||||||
|             precision[0] = *p++; /* skip precision */ |  | ||||||
|         if (isdigit((int) (*p))) |  | ||||||
|             precision[1] = *p++; /* (2 digits at most) */ |  | ||||||
|     } |  | ||||||
|     if (isdigit((int) (*p))) |  | ||||||
|         janet_panic("invalid format (width or precision too long)"); |  | ||||||
|     *(form++) = '%'; |  | ||||||
|     memcpy(form, strfrmt, ((p - strfrmt) + 1) * sizeof(char)); |  | ||||||
|     form += (p - strfrmt) + 1; |  | ||||||
|     *form = '\0'; |  | ||||||
|     return p; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static Janet cfun_string_format(int32_t argc, Janet *argv) { | static Janet cfun_string_format(int32_t argc, Janet *argv) { | ||||||
|     janet_arity(argc, 2, -1); |     janet_arity(argc, 1, -1); | ||||||
|     JanetBuffer *b = janet_getbuffer(argv, 0); |     JanetBuffer *buffer = janet_buffer(0); | ||||||
|     const char *strfrmt = (const char *) janet_getstring(argv, 1); |     const char *strfrmt = (const char *) janet_getstring(argv, 0); | ||||||
|     size_t sfl = strlen(strfrmt); |     janet_buffer_format(buffer, strfrmt, 0, argc, argv); | ||||||
|     const char *strfrmt_end = strfrmt + sfl; |     return janet_stringv(buffer->data, buffer->count); | ||||||
|     int32_t arg = 1; |  | ||||||
|     while (strfrmt < strfrmt_end) { |  | ||||||
|         if (*strfrmt != '%') |  | ||||||
|             janet_buffer_push_u8(b, (uint8_t) * strfrmt++); |  | ||||||
|         else if (*++strfrmt == '%') |  | ||||||
|             janet_buffer_push_u8(b, (uint8_t) * strfrmt++); /* %% */ |  | ||||||
|         else { /* format item */ |  | ||||||
|             char form[MAX_FORMAT],item[MAX_ITEM]; |  | ||||||
|             char width[3], precision[3]; |  | ||||||
|             int nb = 0; /* number of bytes in added item */ |  | ||||||
|             if (++arg >= argc) |  | ||||||
|                 janet_panic("not enough values for format"); |  | ||||||
|             strfrmt = scanformat(strfrmt, form, width, precision); |  | ||||||
|             switch (*strfrmt++) { |  | ||||||
|                 case 'c': |  | ||||||
|                     { |  | ||||||
|                         nb = snprintf(item, MAX_ITEM, form, (int) |  | ||||||
|                                 janet_getinteger(argv, arg)); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 case 'd': |  | ||||||
|                 case 'i': |  | ||||||
|                 case 'o': |  | ||||||
|                 case 'u': |  | ||||||
|                 case 'x': |  | ||||||
|                 case 'X': |  | ||||||
|                     { |  | ||||||
|                         int32_t n = janet_getinteger(argv, arg); |  | ||||||
|                         nb = snprintf(item, MAX_ITEM, form, n); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 case 'a': |  | ||||||
|                 case 'A': |  | ||||||
|                 case 'e': |  | ||||||
|                 case 'E': |  | ||||||
|                 case 'f': |  | ||||||
|                 case 'g': |  | ||||||
|                 case 'G': |  | ||||||
|                     { |  | ||||||
|                         double d = janet_getnumber(argv, arg); |  | ||||||
|                         nb = snprintf(item, MAX_ITEM, form, d); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 case 's': |  | ||||||
|                     { |  | ||||||
|                         const uint8_t *s = janet_getstring(argv, arg); |  | ||||||
|                         size_t l = janet_string_length(s); |  | ||||||
|                         if (form[2] == '\0') |  | ||||||
|                             janet_buffer_push_bytes(b, s, l); |  | ||||||
|                         else { |  | ||||||
|                             if (l != strlen((const char *) s)) |  | ||||||
|                                 janet_panic("string contains zeros"); |  | ||||||
|                             if (!strchr(form, '.') && l >= 100) { |  | ||||||
|                                 janet_panic |  | ||||||
|                                     ("no precision and string is too long to be formatted"); |  | ||||||
|                             } else { |  | ||||||
|                                 nb = snprintf(item, MAX_ITEM, form, s); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 case 'V': |  | ||||||
|                     { |  | ||||||
|                         janet_to_string_b(b, argv[arg]); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 case 'v': |  | ||||||
|                     { |  | ||||||
|                         janet_description_b(b, argv[arg]); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 case 'p': /* janet pretty , precision = depth */ |  | ||||||
|                     { |  | ||||||
|                         int depth = atoi(precision); |  | ||||||
|                         if (depth < 1) |  | ||||||
|                             depth = 4; |  | ||||||
|                         janet_pretty(b, depth, argv[arg]); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 default: |  | ||||||
|                     { /* also treat cases 'nLlh' */ |  | ||||||
|                         janet_panicf("invalid conversion '%s' to 'format'", |  | ||||||
|                                 form); |  | ||||||
|                     } |  | ||||||
|             } |  | ||||||
|             if (nb >= MAX_ITEM) |  | ||||||
|                 janet_panicf("format buffer overflow", form); |  | ||||||
|             if (nb > 0) |  | ||||||
|                 janet_buffer_push_bytes(b, (uint8_t *) item, nb); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return janet_wrap_buffer(b); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| static const JanetReg string_cfuns[] = { | static const JanetReg string_cfuns[] = { | ||||||
|   | |||||||
| @@ -52,6 +52,12 @@ const void *janet_strbinsearch( | |||||||
|         size_t tabcount, |         size_t tabcount, | ||||||
|         size_t itemsize, |         size_t itemsize, | ||||||
|         const uint8_t *key); |         const uint8_t *key); | ||||||
|  | void janet_buffer_format( | ||||||
|  |         JanetBuffer *b, | ||||||
|  |         const char *strfrmt, | ||||||
|  |         int32_t argstart, | ||||||
|  |         int32_t argc, | ||||||
|  |         Janet *argv); | ||||||
|  |  | ||||||
| /* Inside the janet core, defining globals is different | /* Inside the janet core, defining globals is different | ||||||
|  * at bootstrap time and normal runtime */ |  * at bootstrap time and normal runtime */ | ||||||
|   | |||||||
| @@ -20,15 +20,23 @@ | |||||||
|  |  | ||||||
| (import test/helper :prefix "" :exit true) | (import test/helper :prefix "" :exit true) | ||||||
| (start-suite 4) | (start-suite 4) | ||||||
| # some tests for string/format | # some tests for string/format and buffer/format | ||||||
|  |  | ||||||
| (assert (= (string (string/format @"" "pi = %6.3f" math/pi)) "pi =  3.142") "%6.3f") | (assert (= (string (buffer/format @"" "pi = %6.3f" math/pi)) "pi =  3.142") "%6.3f") | ||||||
| (assert (= (string (string/format @"" "pi = %+6.3f" math/pi)) "pi = +3.142") "%6.3f") | (assert (= (string (buffer/format @"" "pi = %+6.3f" math/pi)) "pi = +3.142") "%6.3f") | ||||||
| (assert (= (string (string/format @"" "pi = %40.20g" math/pi)) "pi =                     3.141592653589793116") "%6.3f") | (assert (= (string (buffer/format @"" "pi = %40.20g" math/pi)) "pi =                     3.141592653589793116") "%6.3f") | ||||||
|  |  | ||||||
| (assert (= (string (string/format @"" "🐼 = %6.3f" math/pi)) "🐼 =  3.142") "UTF-8") | (assert (= (string (buffer/format @"" "🐼 = %6.3f" math/pi)) "🐼 =  3.142") "UTF-8") | ||||||
| (assert (= (string (string/format @"" "π = %.8g" math/pi)) "π = 3.1415927") "π") | (assert (= (string (buffer/format @"" "π = %.8g" math/pi)) "π = 3.1415927") "π") | ||||||
| (assert (= (string (string/format @"" "\xCF\x80 = %.8g" math/pi)) "\xCF\x80 = 3.1415927") "\xCF\x80") | (assert (= (string (buffer/format @"" "\xCF\x80 = %.8g" math/pi)) "\xCF\x80 = 3.1415927") "\xCF\x80") | ||||||
|  |  | ||||||
|  | (assert (= (string/format "pi = %6.3f" math/pi) "pi =  3.142") "%6.3f") | ||||||
|  | (assert (= (string/format "pi = %+6.3f" math/pi) "pi = +3.142") "%6.3f") | ||||||
|  | (assert (= (string/format "pi = %40.20g" math/pi) "pi =                     3.141592653589793116") "%6.3f") | ||||||
|  |  | ||||||
|  | (assert (= (string/format "🐼 = %6.3f" math/pi) "🐼 =  3.142") "UTF-8") | ||||||
|  | (assert (= (string/format "π = %.8g" math/pi) "π = 3.1415927") "π") | ||||||
|  | (assert (= (string/format "\xCF\x80 = %.8g" math/pi) "\xCF\x80 = 3.1415927") "\xCF\x80") | ||||||
|  |  | ||||||
| (end-suite) | (end-suite) | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Calvin Rose
					Calvin Rose