mirror of
				https://github.com/janet-lang/janet
				synced 2025-11-04 09:33:02 +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];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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[] = {
 | 
			
		||||
    {"buffer/new", cfun_buffer_new,
 | 
			
		||||
        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 "
 | 
			
		||||
                "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}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										146
									
								
								src/core/pp.c
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								src/core/pp.c
									
									
									
									
									
								
							@@ -20,6 +20,9 @@
 | 
			
		||||
* IN THE SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <ctype.h>
 | 
			
		||||
 | 
			
		||||
#ifndef JANET_AMALG
 | 
			
		||||
#include <janet/janet.h>
 | 
			
		||||
#include "util.h"
 | 
			
		||||
@@ -549,3 +552,146 @@ const uint8_t *janet_formatc(const char *format, ...) {
 | 
			
		||||
    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 <ctype.h>
 | 
			
		||||
 | 
			
		||||
#ifndef JANET_AMALG
 | 
			
		||||
#include <janet/janet.h>
 | 
			
		||||
@@ -502,144 +501,12 @@ static Janet cfun_string_pretty(int32_t argc, Janet *argv) {
 | 
			
		||||
    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) {
 | 
			
		||||
    janet_arity(argc, 2, -1);
 | 
			
		||||
    JanetBuffer *b = janet_getbuffer(argv, 0);
 | 
			
		||||
    const char *strfrmt = (const char *) janet_getstring(argv, 1);
 | 
			
		||||
    size_t sfl = strlen(strfrmt);
 | 
			
		||||
    const char *strfrmt_end = strfrmt + sfl;
 | 
			
		||||
    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);
 | 
			
		||||
    janet_arity(argc, 1, -1);
 | 
			
		||||
    JanetBuffer *buffer = janet_buffer(0);
 | 
			
		||||
    const char *strfrmt = (const char *) janet_getstring(argv, 0);
 | 
			
		||||
    janet_buffer_format(buffer, strfrmt, 0, argc, argv);
 | 
			
		||||
    return janet_stringv(buffer->data, buffer->count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const JanetReg string_cfuns[] = {
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,12 @@ const void *janet_strbinsearch(
 | 
			
		||||
        size_t tabcount,
 | 
			
		||||
        size_t itemsize,
 | 
			
		||||
        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
 | 
			
		||||
 * at bootstrap time and normal runtime */
 | 
			
		||||
 
 | 
			
		||||
@@ -20,15 +20,23 @@
 | 
			
		||||
 | 
			
		||||
(import test/helper :prefix "" :exit true)
 | 
			
		||||
(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 (string/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 = %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 (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 (string/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 @"" "🐼 = %6.3f" math/pi)) "🐼 =  3.142") "UTF-8")
 | 
			
		||||
(assert (= (string (buffer/format @"" "π = %.8g" math/pi)) "π = 3.1415927") "π")
 | 
			
		||||
(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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user