1
0
mirror of https://github.com/janet-lang/janet synced 2024-11-28 19:19:53 +00:00

Merge pull request #40 from jfcap/string-format

string/format
This commit is contained in:
Calvin Rose 2019-02-16 15:02:47 -05:00 committed by GitHub
commit 44e31cac5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 220 additions and 0 deletions

View File

@ -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}
};

View File

@ -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);
}
}
}

View File

@ -501,6 +501,14 @@ static Janet cfun_string_pretty(int32_t argc, Janet *argv) {
return janet_wrap_buffer(buffer);
}
static Janet cfun_string_format(int32_t argc, Janet *argv) {
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[] = {
{
"string/slice", cfun_string_slice,
@ -614,6 +622,11 @@ static const JanetReg string_cfuns[] = {
"Pretty prints a value to a buffer. Optionally allows setting max "
"recursion depth, as well as writing to a buffer. Returns the buffer.")
},
{ "string/format", cfun_string_format,
JDOC("(string/format buffer format & values)\n\n"
"Similar to snprintf, but specialized for operating with janet"
"...")
},
{NULL, NULL, NULL}
};

View File

@ -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 */

42
test/suite4.janet Normal file
View File

@ -0,0 +1,42 @@
# Copyright (c) 2019 Calvin Rose
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
(import test/helper :prefix "" :exit true)
(start-suite 4)
# some tests for string/format and buffer/format
(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 (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)