Merge pull request #407 from dhylands/str-format
Enhance str.format support
This commit is contained in:
commit
46330bd9b5
@ -14,6 +14,7 @@
|
||||
***********************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mpconfig.h"
|
||||
|
||||
|
4
py/obj.c
4
py/obj.c
@ -198,6 +198,10 @@ machine_int_t mp_obj_get_int(mp_obj_t arg) {
|
||||
return MP_OBJ_SMALL_INT_VALUE(arg);
|
||||
} else if (MP_OBJ_IS_TYPE(arg, &mp_type_int)) {
|
||||
return mp_obj_int_get_checked(arg);
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
} else if (MP_OBJ_IS_TYPE(arg, &mp_type_float)) {
|
||||
return mp_obj_float_get(arg);
|
||||
#endif
|
||||
} else {
|
||||
nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "can't convert %s to int", mp_obj_get_type_str(arg)));
|
||||
}
|
||||
|
384
py/objstr.c
384
py/objstr.c
@ -9,6 +9,7 @@
|
||||
#include "obj.h"
|
||||
#include "runtime0.h"
|
||||
#include "runtime.h"
|
||||
#include "pfenv.h"
|
||||
|
||||
typedef struct _mp_obj_str_t {
|
||||
mp_obj_base_t base;
|
||||
@ -492,28 +493,389 @@ STATIC mp_obj_t str_strip(uint n_args, const mp_obj_t *args) {
|
||||
return mp_obj_new_str(orig_str + first_good_char_pos, stripped_len, false);
|
||||
}
|
||||
|
||||
// Takes an int arg, but only parses unsigned numbers, and only changes
|
||||
// *num if at least one digit was parsed.
|
||||
static int str_to_int(const char *str, int *num) {
|
||||
const char *s = str;
|
||||
if (unichar_isdigit(*s)) {
|
||||
*num = 0;
|
||||
do {
|
||||
*num = *num * 10 + (*s - '0');
|
||||
s++;
|
||||
}
|
||||
while (unichar_isdigit(*s));
|
||||
}
|
||||
return s - str;
|
||||
}
|
||||
|
||||
static bool isalignment(char ch) {
|
||||
return ch && strchr("<>=^", ch) != NULL;
|
||||
}
|
||||
|
||||
static bool istype(char ch) {
|
||||
return ch && strchr("bcdeEfFgGnosxX%", ch) != NULL;
|
||||
}
|
||||
|
||||
static bool arg_looks_integer(mp_obj_t arg) {
|
||||
return MP_OBJ_IS_TYPE(arg, &mp_type_bool) || MP_OBJ_IS_INT(arg);
|
||||
}
|
||||
|
||||
static bool arg_looks_numeric(mp_obj_t arg) {
|
||||
return arg_looks_integer(arg)
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
|| MP_OBJ_IS_TYPE(arg, &mp_type_float)
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
mp_obj_t str_format(uint n_args, const mp_obj_t *args) {
|
||||
assert(MP_OBJ_IS_STR(args[0]));
|
||||
|
||||
GET_STR_DATA_LEN(args[0], str, len);
|
||||
int arg_i = 1;
|
||||
int arg_i = 0;
|
||||
vstr_t *vstr = vstr_new();
|
||||
pfenv_t pfenv_vstr;
|
||||
pfenv_vstr.data = vstr;
|
||||
pfenv_vstr.print_strn = pfenv_vstr_add_strn;
|
||||
|
||||
for (const byte *top = str + len; str < top; str++) {
|
||||
if (*str == '{') {
|
||||
if (*str == '}') {
|
||||
str++;
|
||||
if (str < top && *str == '{') {
|
||||
vstr_add_char(vstr, '{');
|
||||
if (str < top && *str == '}') {
|
||||
vstr_add_char(vstr, '}');
|
||||
continue;
|
||||
}
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "Single '}' encountered in format string"));
|
||||
}
|
||||
if (*str != '{') {
|
||||
vstr_add_char(vstr, *str);
|
||||
continue;
|
||||
}
|
||||
|
||||
str++;
|
||||
if (str < top && *str == '{') {
|
||||
vstr_add_char(vstr, '{');
|
||||
continue;
|
||||
}
|
||||
|
||||
// replacement_field ::= "{" [field_name] ["!" conversion] [":" format_spec] "}"
|
||||
|
||||
vstr_t *field_name = NULL;
|
||||
char conversion = '\0';
|
||||
vstr_t *format_spec = NULL;
|
||||
|
||||
if (str < top && *str != '}' && *str != '!' && *str != ':') {
|
||||
field_name = vstr_new();
|
||||
while (str < top && *str != '}' && *str != '!' && *str != ':') {
|
||||
vstr_add_char(field_name, *str++);
|
||||
}
|
||||
vstr_add_char(field_name, '\0');
|
||||
}
|
||||
|
||||
// conversion ::= "r" | "s"
|
||||
|
||||
if (str < top && *str == '!') {
|
||||
str++;
|
||||
if (str < top && (*str == 'r' || *str == 's')) {
|
||||
conversion = *str++;
|
||||
} else {
|
||||
while (str < top && *str != '}') str++;
|
||||
if (arg_i >= n_args) {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "tuple index out of range"));
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "end of format while looking for conversion specifier"));
|
||||
}
|
||||
}
|
||||
|
||||
if (str < top && *str == ':') {
|
||||
str++;
|
||||
// {:} is the same as {}, which is the same as {!s}
|
||||
// This makes a difference when passing in a True or False
|
||||
// '{}'.format(True) returns 'True'
|
||||
// '{:d}'.format(True) returns '1'
|
||||
// So we treat {:} as {} and this later gets treated to be {!s}
|
||||
if (*str != '}') {
|
||||
format_spec = vstr_new();
|
||||
while (str < top && *str != '}') {
|
||||
vstr_add_char(format_spec, *str++);
|
||||
}
|
||||
// TODO: may be PRINT_REPR depending on formatting code
|
||||
mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, args[arg_i], PRINT_STR);
|
||||
arg_i++;
|
||||
vstr_add_char(format_spec, '\0');
|
||||
}
|
||||
}
|
||||
if (str >= top) {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "unmatched '{' in format"));
|
||||
}
|
||||
if (*str != '}') {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "expected ':' after format specifier"));
|
||||
}
|
||||
|
||||
mp_obj_t arg = mp_const_none;
|
||||
|
||||
if (field_name) {
|
||||
if (arg_i > 0) {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "cannot switch from automatic field numbering to manual field specification"));
|
||||
}
|
||||
int index;
|
||||
if (str_to_int(vstr_str(field_name), &index) != vstr_len(field_name) - 1) {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "attributes not supported yet"));
|
||||
}
|
||||
if (index >= n_args - 1) {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "tuple index out of range"));
|
||||
}
|
||||
arg = args[index + 1];
|
||||
arg_i = -1;
|
||||
vstr_free(field_name);
|
||||
field_name = NULL;
|
||||
} else {
|
||||
if (arg_i < 0) {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "cannot switch from manual field specification to automatic field numbering"));
|
||||
}
|
||||
if (arg_i >= n_args - 1) {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "tuple index out of range"));
|
||||
}
|
||||
arg = args[arg_i + 1];
|
||||
arg_i++;
|
||||
}
|
||||
if (!format_spec && !conversion) {
|
||||
conversion = 's';
|
||||
}
|
||||
if (conversion) {
|
||||
mp_print_kind_t print_kind;
|
||||
if (conversion == 's') {
|
||||
print_kind = PRINT_STR;
|
||||
} else if (conversion == 'r') {
|
||||
print_kind = PRINT_REPR;
|
||||
} else {
|
||||
nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "Unknown conversion specifier %c", conversion));
|
||||
}
|
||||
vstr_t *arg_vstr = vstr_new();
|
||||
mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, arg_vstr, arg, print_kind);
|
||||
arg = mp_obj_new_str((const byte *)vstr_str(arg_vstr), vstr_len(arg_vstr), false);
|
||||
vstr_free(arg_vstr);
|
||||
}
|
||||
|
||||
char sign = '\0';
|
||||
char fill = '\0';
|
||||
char align = '\0';
|
||||
int width = -1;
|
||||
int precision = -1;
|
||||
char type = '\0';
|
||||
int flags = 0;
|
||||
|
||||
if (format_spec) {
|
||||
// The format specifier (from http://docs.python.org/2/library/string.html#formatspec)
|
||||
//
|
||||
// [[fill]align][sign][#][0][width][,][.precision][type]
|
||||
// fill ::= <any character>
|
||||
// align ::= "<" | ">" | "=" | "^"
|
||||
// sign ::= "+" | "-" | " "
|
||||
// width ::= integer
|
||||
// precision ::= integer
|
||||
// type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
|
||||
|
||||
const char *s = vstr_str(format_spec);
|
||||
if (isalignment(*s)) {
|
||||
align = *s++;
|
||||
} else if (*s && isalignment(s[1])) {
|
||||
fill = *s++;
|
||||
align = *s++;
|
||||
}
|
||||
if (*s == '+' || *s == '-' || *s == ' ') {
|
||||
if (*s == '+') {
|
||||
flags |= PF_FLAG_SHOW_SIGN;
|
||||
} else if (*s == ' ') {
|
||||
flags |= PF_FLAG_SPACE_SIGN;
|
||||
}
|
||||
sign = *s++;
|
||||
}
|
||||
if (*s == '#') {
|
||||
flags |= PF_FLAG_SHOW_PREFIX;
|
||||
s++;
|
||||
}
|
||||
if (*s == '0') {
|
||||
if (!align) {
|
||||
align = '=';
|
||||
}
|
||||
if (!fill) {
|
||||
fill = '0';
|
||||
}
|
||||
}
|
||||
s += str_to_int(s, &width);
|
||||
if (*s == ',') {
|
||||
flags |= PF_FLAG_SHOW_COMMA;
|
||||
s++;
|
||||
}
|
||||
if (*s == '.') {
|
||||
s++;
|
||||
s += str_to_int(s, &precision);
|
||||
}
|
||||
if (istype(*s)) {
|
||||
type = *s++;
|
||||
}
|
||||
if (*s) {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "Invalid conversion specification"));
|
||||
}
|
||||
vstr_free(format_spec);
|
||||
format_spec = NULL;
|
||||
}
|
||||
if (!align) {
|
||||
if (arg_looks_numeric(arg)) {
|
||||
align = '>';
|
||||
} else {
|
||||
align = '<';
|
||||
}
|
||||
}
|
||||
if (!fill) {
|
||||
fill = ' ';
|
||||
}
|
||||
|
||||
if (sign) {
|
||||
if (type == 's') {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "Sign not allowed in string format specifier"));
|
||||
}
|
||||
if (type == 'c') {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "Sign not allowed with integer format specifier 'c'"));
|
||||
}
|
||||
} else {
|
||||
vstr_add_char(vstr, *str);
|
||||
sign = '-';
|
||||
}
|
||||
|
||||
switch (align) {
|
||||
case '<': flags |= PF_FLAG_LEFT_ADJUST; break;
|
||||
case '=': flags |= PF_FLAG_PAD_AFTER_SIGN; break;
|
||||
case '^': flags |= PF_FLAG_CENTER_ADJUST; break;
|
||||
}
|
||||
|
||||
if (arg_looks_integer(arg)) {
|
||||
switch (type) {
|
||||
case 'b':
|
||||
pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 2, 'a', flags, fill, width);
|
||||
continue;
|
||||
|
||||
case 'c':
|
||||
{
|
||||
char ch = mp_obj_get_int(arg);
|
||||
pfenv_print_strn(&pfenv_vstr, &ch, 1, flags, fill, width);
|
||||
continue;
|
||||
}
|
||||
|
||||
case '\0': // No explicit format type implies 'd'
|
||||
case 'n': // I don't think we support locales in uPy so use 'd'
|
||||
case 'd':
|
||||
pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 10, 'a', flags, fill, width);
|
||||
continue;
|
||||
|
||||
case 'o':
|
||||
pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 8, 'a', flags, fill, width);
|
||||
continue;
|
||||
|
||||
case 'x':
|
||||
pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 16, 'a', flags, fill, width);
|
||||
continue;
|
||||
|
||||
case 'X':
|
||||
pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 16, 'A', flags, fill, width);
|
||||
continue;
|
||||
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'g':
|
||||
case 'G':
|
||||
case '%':
|
||||
// The floating point formatters all work with anything that
|
||||
// looks like an integer
|
||||
break;
|
||||
|
||||
default:
|
||||
nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError,
|
||||
"Unknown format code '%c' for object of type '%s'", type, mp_obj_get_type_str(arg)));
|
||||
}
|
||||
}
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
if (arg_looks_numeric(arg)) {
|
||||
if (!type) {
|
||||
|
||||
// Even though the docs say that an unspecified type is the same
|
||||
// as 'g', there is one subtle difference, when the exponent
|
||||
// is one less than the precision.
|
||||
//
|
||||
// '{:10.1}'.format(0.0) ==> '0e+00'
|
||||
// '{:10.1g}'.format(0.0) ==> '0'
|
||||
//
|
||||
// TODO: Figure out how to deal with this.
|
||||
//
|
||||
// A proper solution would involve adding a special flag
|
||||
// or something to format_float, and create a format_double
|
||||
// to deal with doubles. In order to fix this when using
|
||||
// sprintf, we'd need to use the e format and tweak the
|
||||
// returned result to strip trailing zeros like the g format
|
||||
// does.
|
||||
//
|
||||
// {:10.3} and {:10.2e} with 1.23e2 both produce 1.23e+02
|
||||
// but with 1.e2 you get 1e+02 and 1.00e+02
|
||||
//
|
||||
// Stripping the trailing 0's (like g) does would make the
|
||||
// e format give us the right format.
|
||||
//
|
||||
// CPython sources say:
|
||||
// Omitted type specifier. Behaves in the same way as repr(x)
|
||||
// and str(x) if no precision is given, else like 'g', but with
|
||||
// at least one digit after the decimal point. */
|
||||
|
||||
type = 'g';
|
||||
}
|
||||
if (type == 'n') {
|
||||
type = 'g';
|
||||
}
|
||||
|
||||
flags |= PF_FLAG_PAD_NAN_INF; // '{:06e}'.format(float('-inf')) should give '-00inf'
|
||||
switch (type) {
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'g':
|
||||
case 'G':
|
||||
pfenv_print_float(&pfenv_vstr, mp_obj_get_float(arg), type, flags, fill, width, precision);
|
||||
break;
|
||||
|
||||
case '%':
|
||||
flags |= PF_FLAG_ADD_PERCENT;
|
||||
pfenv_print_float(&pfenv_vstr, mp_obj_get_float(arg) * 100.0F, 'f', flags, fill, width, precision);
|
||||
break;
|
||||
|
||||
default:
|
||||
nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError,
|
||||
"Unknown format code '%c' for object of type 'float'",
|
||||
type, mp_obj_get_type_str(arg)));
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
if (align == '=') {
|
||||
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "'=' alignment not allowed in string format specifier"));
|
||||
}
|
||||
switch (type) {
|
||||
case '\0':
|
||||
mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, arg, PRINT_STR);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
{
|
||||
uint len;
|
||||
const char *s = mp_obj_str_get_data(arg, &len);
|
||||
if (precision < 0) {
|
||||
precision = len;
|
||||
}
|
||||
if (len > precision) {
|
||||
len = precision;
|
||||
}
|
||||
pfenv_print_strn(&pfenv_vstr, s, len, flags, fill, width);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError,
|
||||
"Unknown format code '%c' for object of type 'str'",
|
||||
type, mp_obj_get_type_str(arg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
208
py/pfenv.c
Normal file
208
py/pfenv.c
Normal file
@ -0,0 +1,208 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
///#include "std.h"
|
||||
#include "misc.h"
|
||||
#include "mpconfig.h"
|
||||
#include "qstr.h"
|
||||
#include "obj.h"
|
||||
#include "pfenv.h"
|
||||
|
||||
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
#include "formatfloat.h"
|
||||
#endif
|
||||
|
||||
#define PF_PAD_SIZE 16
|
||||
static const char *pad_spaces = " ";
|
||||
static const char *pad_zeroes = "0000000000000000";
|
||||
|
||||
void pfenv_vstr_add_strn(void *data, const char *str, unsigned int len){
|
||||
vstr_add_strn(data, str, len);
|
||||
}
|
||||
|
||||
int pfenv_print_strn(const pfenv_t *pfenv, const char *str, unsigned int len, int flags, char fill, int width) {
|
||||
int left_pad = 0;
|
||||
int right_pad = 0;
|
||||
int pad = width - len;
|
||||
char pad_fill[PF_PAD_SIZE];
|
||||
const char *pad_chars;
|
||||
|
||||
if (!fill || fill == ' ' ) {
|
||||
pad_chars = pad_spaces;
|
||||
} else if (fill == '0') {
|
||||
pad_chars = pad_zeroes;
|
||||
} else {
|
||||
memset(pad_fill, fill, PF_PAD_SIZE);
|
||||
pad_chars = pad_fill;
|
||||
}
|
||||
|
||||
if (flags & PF_FLAG_CENTER_ADJUST) {
|
||||
left_pad = pad / 2;
|
||||
right_pad = pad - left_pad;
|
||||
} else if (flags & PF_FLAG_LEFT_ADJUST) {
|
||||
right_pad = pad;
|
||||
} else {
|
||||
left_pad = pad;
|
||||
}
|
||||
|
||||
if (left_pad) {
|
||||
while (left_pad > 0) {
|
||||
int p = left_pad;
|
||||
if (p > PF_PAD_SIZE)
|
||||
p = PF_PAD_SIZE;
|
||||
pfenv->print_strn(pfenv->data, pad_chars, p);
|
||||
left_pad -= p;
|
||||
}
|
||||
}
|
||||
pfenv->print_strn(pfenv->data, str, len);
|
||||
if (right_pad) {
|
||||
while (right_pad > 0) {
|
||||
int p = right_pad;
|
||||
if (p > PF_PAD_SIZE)
|
||||
p = PF_PAD_SIZE;
|
||||
pfenv->print_strn(pfenv->data, pad_chars, p);
|
||||
right_pad -= p;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// enough room for 32 signed number
|
||||
#define INT_BUF_SIZE (16)
|
||||
|
||||
int pfenv_print_int(const pfenv_t *pfenv, unsigned int x, int sgn, int base, int base_char, int flags, char fill, int width) {
|
||||
char sign = 0;
|
||||
if (sgn) {
|
||||
if ((int)x < 0) {
|
||||
sign = '-';
|
||||
x = -x;
|
||||
} else if (flags & PF_FLAG_SHOW_SIGN) {
|
||||
sign = '+';
|
||||
} else if (flags & PF_FLAG_SPACE_SIGN) {
|
||||
sign = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
char buf[INT_BUF_SIZE];
|
||||
char *b = buf + INT_BUF_SIZE;
|
||||
|
||||
if (x == 0) {
|
||||
*(--b) = '0';
|
||||
} else {
|
||||
do {
|
||||
int c = x % base;
|
||||
x /= base;
|
||||
if (c >= 10) {
|
||||
c += base_char - 10;
|
||||
} else {
|
||||
c += '0';
|
||||
}
|
||||
*(--b) = c;
|
||||
} while (b > buf && x != 0);
|
||||
}
|
||||
|
||||
char prefix_char = '\0';
|
||||
|
||||
if (flags & PF_FLAG_SHOW_PREFIX) {
|
||||
if (base == 2) {
|
||||
prefix_char = base_char + 'b' - 'a';
|
||||
} else if (base == 8) {
|
||||
prefix_char = base_char + 'o' - 'a';
|
||||
} else if (base == 16) {
|
||||
prefix_char = base_char + 'x' - 'a';
|
||||
}
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
if (flags & PF_FLAG_PAD_AFTER_SIGN) {
|
||||
if (sign) {
|
||||
len += pfenv_print_strn(pfenv, &sign, 1, flags, fill, 1);
|
||||
width--;
|
||||
}
|
||||
if (prefix_char) {
|
||||
len += pfenv_print_strn(pfenv, "0", 1, flags, fill, 1);
|
||||
len += pfenv_print_strn(pfenv, &prefix_char, 1, flags, fill, 1);
|
||||
width -= 2;
|
||||
}
|
||||
} else {
|
||||
if (prefix_char && b > &buf[1]) {
|
||||
*(--b) = prefix_char;
|
||||
*(--b) = '0';
|
||||
}
|
||||
if (sign && b > buf) {
|
||||
*(--b) = sign;
|
||||
}
|
||||
}
|
||||
|
||||
len += pfenv_print_strn(pfenv, b, buf + INT_BUF_SIZE - b, flags, fill, width);
|
||||
return len;
|
||||
}
|
||||
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, char fill, int width, int prec) {
|
||||
char buf[32];
|
||||
char sign = '\0';
|
||||
int chrs = 0;
|
||||
|
||||
if (flags & PF_FLAG_SHOW_SIGN) {
|
||||
sign = '+';
|
||||
}
|
||||
else
|
||||
if (flags & PF_FLAG_SPACE_SIGN) {
|
||||
sign = ' ';
|
||||
}
|
||||
int len;
|
||||
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
|
||||
len = format_float(f, buf, sizeof(buf), fmt, prec, sign);
|
||||
#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
|
||||
char fmt_buf[6];
|
||||
char *fmt_s = fmt_buf;
|
||||
|
||||
*fmt_s++ = '%';
|
||||
if (sign) {
|
||||
*fmt_s++ = sign;
|
||||
}
|
||||
*fmt_s++ = '.';
|
||||
*fmt_s++ = '*';
|
||||
*fmt_s++ = fmt;
|
||||
*fmt_s = '\0';
|
||||
|
||||
len = snprintf(buf, sizeof(buf), fmt_buf, prec, f);
|
||||
#else
|
||||
#error Unknown MICROPY FLOAT IMPL
|
||||
#endif
|
||||
char *s = buf;
|
||||
|
||||
if ((flags & PF_FLAG_ADD_PERCENT) && (len + 1) < sizeof(buf)) {
|
||||
buf[len++] = '%';
|
||||
buf[len] = '\0';
|
||||
}
|
||||
|
||||
// buf[0] < '0' returns true if the first character is space, + or -
|
||||
if ((flags & PF_FLAG_PAD_AFTER_SIGN) && buf[0] < '0') {
|
||||
// We have a sign character
|
||||
s++;
|
||||
if (*s <= '9' || (flags & PF_FLAG_PAD_NAN_INF)) {
|
||||
// We have a number, or we have a inf/nan and PAD_NAN_INF is set
|
||||
// With '{:06e}'.format(float('-inf')) you get '-00inf'
|
||||
chrs += pfenv_print_strn(pfenv, &buf[0], 1, 0, 0, 1);
|
||||
width--;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
if (*s > 'A' && (flags & PF_FLAG_PAD_NAN_INF) == 0) {
|
||||
// We have one of the inf or nan variants, suppress zero fill.
|
||||
// With printf, if you use: printf("%06e", -inf) then you get " -inf"
|
||||
// so suppress the zero fill.
|
||||
fill = ' ';
|
||||
}
|
||||
chrs += pfenv_print_strn(pfenv, s, len, flags, fill, width);
|
||||
|
||||
return chrs;
|
||||
}
|
||||
#endif
|
23
py/pfenv.h
Normal file
23
py/pfenv.h
Normal file
@ -0,0 +1,23 @@
|
||||
#define PF_FLAG_LEFT_ADJUST (0x001)
|
||||
#define PF_FLAG_SHOW_SIGN (0x002)
|
||||
#define PF_FLAG_SPACE_SIGN (0x004)
|
||||
#define PF_FLAG_NO_TRAILZ (0x008)
|
||||
#define PF_FLAG_SHOW_PREFIX (0x010)
|
||||
#define PF_FLAG_SHOW_COMMA (0x020)
|
||||
#define PF_FLAG_PAD_AFTER_SIGN (0x040)
|
||||
#define PF_FLAG_CENTER_ADJUST (0x080)
|
||||
#define PF_FLAG_ADD_PERCENT (0x100)
|
||||
#define PF_FLAG_PAD_NAN_INF (0x200)
|
||||
|
||||
typedef struct _pfenv_t {
|
||||
void *data;
|
||||
void (*print_strn)(void *, const char *str, unsigned int len);
|
||||
} pfenv_t;
|
||||
|
||||
void pfenv_vstr_add_strn(void *data, const char *str, unsigned int len);
|
||||
|
||||
int pfenv_print_strn(const pfenv_t *pfenv, const char *str, unsigned int len, int flags, char fill, int width);
|
||||
int pfenv_print_int(const pfenv_t *pfenv, unsigned int x, int sgn, int base, int base_char, int flags, char fill, int width);
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, char fill, int width, int prec);
|
||||
#endif
|
1
py/py.mk
1
py/py.mk
@ -84,6 +84,7 @@ PY_O_BASENAME = \
|
||||
showbc.o \
|
||||
repl.o \
|
||||
intdivmod.o \
|
||||
pfenv.o \
|
||||
|
||||
# prepend the build destination prefix to the py object files
|
||||
PY_O = $(addprefix $(PY_BUILD)/, $(PY_O_BASENAME))
|
||||
|
150
stmhal/printf.c
150
stmhal/printf.c
@ -8,6 +8,7 @@
|
||||
#include "mpconfig.h"
|
||||
#include "qstr.h"
|
||||
#include "obj.h"
|
||||
#include "pfenv.h"
|
||||
#if 0
|
||||
#include "lcd.h"
|
||||
#endif
|
||||
@ -18,89 +19,6 @@
|
||||
#include "formatfloat.h"
|
||||
#endif
|
||||
|
||||
#define PF_FLAG_LEFT_ADJUST (0x01)
|
||||
#define PF_FLAG_SHOW_SIGN (0x02)
|
||||
#define PF_FLAG_SPACE_SIGN (0x04)
|
||||
#define PF_FLAG_NO_TRAILZ (0x08)
|
||||
#define PF_FLAG_ZERO_PAD (0x10)
|
||||
|
||||
// tricky; we compute pad string by: pad_chars + (flags & PF_FLAG_ZERO_PAD)
|
||||
#define PF_PAD_SIZE PF_FLAG_ZERO_PAD
|
||||
static const char *pad_chars = " 0000000000000000";
|
||||
|
||||
typedef struct _pfenv_t {
|
||||
void *data;
|
||||
void (*print_strn)(void *, const char *str, unsigned int len);
|
||||
} pfenv_t;
|
||||
|
||||
static void print_str_dummy(void *data, const char *str, unsigned int len) {
|
||||
}
|
||||
|
||||
const pfenv_t pfenv_dummy = {0, print_str_dummy};
|
||||
|
||||
static int pfenv_print_strn(const pfenv_t *pfenv, const char *str, unsigned int len, int flags, int width) {
|
||||
int pad = width - len;
|
||||
if (pad > 0 && (flags & PF_FLAG_LEFT_ADJUST) == 0) {
|
||||
while (pad > 0) {
|
||||
int p = pad;
|
||||
if (p > PF_PAD_SIZE)
|
||||
p = PF_PAD_SIZE;
|
||||
pfenv->print_strn(pfenv->data, pad_chars + (flags & PF_FLAG_ZERO_PAD), p);
|
||||
pad -= p;
|
||||
}
|
||||
}
|
||||
pfenv->print_strn(pfenv->data, str, len);
|
||||
while (pad > 0) {
|
||||
int p = pad;
|
||||
if (p > PF_PAD_SIZE)
|
||||
p = PF_PAD_SIZE;
|
||||
pfenv->print_strn(pfenv->data, pad_chars, p);
|
||||
pad -= p;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// enough room for 32 signed number
|
||||
#define INT_BUF_SIZE (12)
|
||||
|
||||
static int pfenv_print_int(const pfenv_t *pfenv, unsigned int x, int sgn, int base, int base_char, int flags, int width) {
|
||||
char sign = 0;
|
||||
if (sgn) {
|
||||
if ((int)x < 0) {
|
||||
sign = '-';
|
||||
x = -x;
|
||||
} else if (flags & PF_FLAG_SHOW_SIGN) {
|
||||
sign = '+';
|
||||
} else if (flags & PF_FLAG_SPACE_SIGN) {
|
||||
sign = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
char buf[INT_BUF_SIZE];
|
||||
char *b = buf + INT_BUF_SIZE;
|
||||
|
||||
if (x == 0) {
|
||||
*(--b) = '0';
|
||||
} else {
|
||||
do {
|
||||
int c = x % base;
|
||||
x /= base;
|
||||
if (c >= 10) {
|
||||
c += base_char - 10;
|
||||
} else {
|
||||
c += '0';
|
||||
}
|
||||
*(--b) = c;
|
||||
} while (b > buf && x != 0);
|
||||
}
|
||||
|
||||
if (b > buf && sign != 0) {
|
||||
*(--b) = sign;
|
||||
}
|
||||
|
||||
return pfenv_print_strn(pfenv, b, buf + INT_BUF_SIZE - b, flags, width);
|
||||
}
|
||||
|
||||
void pfenv_prints(const pfenv_t *pfenv, const char *str) {
|
||||
pfenv->print_strn(pfenv->data, str, strlen(str));
|
||||
}
|
||||
@ -129,13 +47,16 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
|
||||
|
||||
// parse flags, if they exist
|
||||
int flags = 0;
|
||||
char fill = ' ';
|
||||
while (*fmt != '\0') {
|
||||
if (*fmt == '-') flags |= PF_FLAG_LEFT_ADJUST;
|
||||
else if (*fmt == '+') flags |= PF_FLAG_SHOW_SIGN;
|
||||
else if (*fmt == ' ') flags |= PF_FLAG_SPACE_SIGN;
|
||||
else if (*fmt == '!') flags |= PF_FLAG_NO_TRAILZ;
|
||||
else if (*fmt == '0') flags |= PF_FLAG_ZERO_PAD;
|
||||
else break;
|
||||
else if (*fmt == '0') {
|
||||
flags |= PF_FLAG_PAD_AFTER_SIGN;
|
||||
fill = '0';
|
||||
} else break;
|
||||
++fmt;
|
||||
}
|
||||
|
||||
@ -177,15 +98,15 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
|
||||
switch (*fmt) {
|
||||
case 'b':
|
||||
if (va_arg(args, int)) {
|
||||
chrs += pfenv_print_strn(pfenv, "true", 4, flags, width);
|
||||
chrs += pfenv_print_strn(pfenv, "true", 4, flags, fill, width);
|
||||
} else {
|
||||
chrs += pfenv_print_strn(pfenv, "false", 5, flags, width);
|
||||
chrs += pfenv_print_strn(pfenv, "false", 5, flags, fill, width);
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
{
|
||||
char str = va_arg(args, int);
|
||||
chrs += pfenv_print_strn(pfenv, &str, 1, flags, width);
|
||||
chrs += pfenv_print_strn(pfenv, &str, 1, flags, fill, width);
|
||||
break;
|
||||
}
|
||||
case 's':
|
||||
@ -195,25 +116,25 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
|
||||
if (prec < 0) {
|
||||
prec = strlen(str);
|
||||
}
|
||||
chrs += pfenv_print_strn(pfenv, str, prec, flags, width);
|
||||
chrs += pfenv_print_strn(pfenv, str, prec, flags, fill, width);
|
||||
} else {
|
||||
chrs += pfenv_print_strn(pfenv, "(null)", 6, flags, width);
|
||||
chrs += pfenv_print_strn(pfenv, "(null)", 6, flags, fill, width);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'u':
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 10, 'a', flags, width);
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 10, 'a', flags, fill, width);
|
||||
break;
|
||||
case 'd':
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 1, 10, 'a', flags, width);
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 1, 10, 'a', flags, fill, width);
|
||||
break;
|
||||
case 'x':
|
||||
case 'p': // ?
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'a', flags, width);
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'a', flags, fill, width);
|
||||
break;
|
||||
case 'X':
|
||||
case 'P': // ?
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'A', flags, width);
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'A', flags, fill, width);
|
||||
break;
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
case 'e':
|
||||
@ -223,33 +144,18 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
|
||||
case 'g':
|
||||
case 'G':
|
||||
{
|
||||
char buf[32];
|
||||
char sign = '\0';
|
||||
|
||||
if (flags & PF_FLAG_SHOW_SIGN) {
|
||||
sign = '+';
|
||||
}
|
||||
else
|
||||
if (flags & PF_FLAG_SPACE_SIGN) {
|
||||
sign = ' ';
|
||||
}
|
||||
float f = va_arg(args, double);
|
||||
int len = format_float(f, buf, sizeof(buf), *fmt, prec, sign);
|
||||
char *s = buf;
|
||||
|
||||
// buf[0] < '0' returns true if the first character is space, + or -
|
||||
// buf[1] < '9' matches a digit, and doesn't match when we get back +nan or +inf
|
||||
if (buf[0] < '0' && buf[1] <= '9' && (flags & PF_FLAG_ZERO_PAD)) {
|
||||
chrs += pfenv_print_strn(pfenv, &buf[0], 1, 0, 1);
|
||||
s++;
|
||||
width--;
|
||||
len--;
|
||||
}
|
||||
if (*s < '0' || *s >= '9') {
|
||||
// For inf or nan, we don't want to zero pad.
|
||||
flags &= ~PF_FLAG_ZERO_PAD;
|
||||
}
|
||||
chrs += pfenv_print_strn(pfenv, s, len, flags, width);
|
||||
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
|
||||
mp_float_t f = va_arg(args, double);
|
||||
chrs += pfenv_print_float(pfenv, f, *fmt, flags, fill, width, prec);
|
||||
#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
|
||||
// Currently pfenv_print_float uses snprintf, so if you want
|
||||
// to use pfenv_print_float with doubles then you'll need
|
||||
// fix it to not use snprintf first. Otherwise you'll have
|
||||
// inifinite recursion.
|
||||
#error Calling pfenv_print_float with double not supported from within printf
|
||||
#else
|
||||
#error Unknown MICROPY FLOAT IMPL
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@ -338,7 +244,7 @@ void strn_print_strn(void *data, const char *str, unsigned int len) {
|
||||
strn_pfenv->cur += len;
|
||||
strn_pfenv->remain -= len;
|
||||
}
|
||||
|
||||
|
||||
int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) {
|
||||
strn_pfenv_t strn_pfenv;
|
||||
strn_pfenv.cur = str;
|
||||
|
@ -1,8 +1,138 @@
|
||||
print("{}-{}".format(1, [4, 5]))
|
||||
print("{0}-{1}".format(1, [4, 5]))
|
||||
print("{:x}".format(1))
|
||||
print("{!r}".format(2))
|
||||
# TODO
|
||||
#print("{1}-{0}".format(1, [4, 5]))
|
||||
#print("{:x}".format(0x10))
|
||||
#print("{!r}".format("foo"))
|
||||
def test(fmt, *args):
|
||||
print('{:8s}'.format(fmt) + '>' + fmt.format(*args) + '<')
|
||||
|
||||
test("{}-{}", 1, [4, 5])
|
||||
test("{0}-{1}", 1, [4, 5])
|
||||
test("{1}-{0}", 1, [4, 5])
|
||||
test("{:x}", 1)
|
||||
test("{!r}", 2)
|
||||
test("{1}-{0}", 1, [4, 5])
|
||||
test("{:x}", 0x10)
|
||||
test("{!r}", "foo")
|
||||
test("{!s}", "foo")
|
||||
|
||||
def test_fmt(conv, fill, alignment, sign, prefix, width, precision, type, arg):
|
||||
fmt = '{'
|
||||
if conv:
|
||||
fmt += '!'
|
||||
fmt += conv
|
||||
fmt += ':'
|
||||
if alignment:
|
||||
fmt += fill
|
||||
fmt += alignment
|
||||
fmt += sign
|
||||
fmt += prefix
|
||||
fmt += width
|
||||
if precision:
|
||||
fmt += '.'
|
||||
fmt += precision
|
||||
fmt += type
|
||||
fmt += '}'
|
||||
test(fmt, arg)
|
||||
if fill == '0' and alignment == '=':
|
||||
fmt = '{:'
|
||||
fmt += sign
|
||||
fmt += prefix
|
||||
fmt += width
|
||||
if precision:
|
||||
fmt += '.'
|
||||
fmt += precision
|
||||
fmt += type
|
||||
fmt += '}'
|
||||
test(fmt, arg)
|
||||
|
||||
int_nums = (-1234, -123, -12, -1, 0, 1, 12, 123, 1234, True, False)
|
||||
int_nums2 = (-12, -1, 0, 1, 12, True, False)
|
||||
|
||||
if True:
|
||||
for type in ('', 'b', 'd', 'o', 'x', 'X'):
|
||||
for width in ('', '1', '3', '5', '7'):
|
||||
for alignment in ('', '<', '>', '=', '^'):
|
||||
for fill in ('', ' ', '0', '@'):
|
||||
for sign in ('', '+', '-', ' '):
|
||||
for prefix in ('', '#'):
|
||||
for num in int_nums:
|
||||
test_fmt('', fill, alignment, sign, prefix, width, '', type, num)
|
||||
|
||||
if True:
|
||||
for width in ('', '1', '2'):
|
||||
for alignment in ('', '<', '>', '^'):
|
||||
for fill in ('', ' ', '0', '@'):
|
||||
test_fmt('', fill, alignment, '', '', width, '', 'c', 48)
|
||||
|
||||
if True:
|
||||
for conv in ('', 'r', 's'):
|
||||
for width in ('', '1', '4', '10'):
|
||||
for alignment in ('', '<', '>', '^'):
|
||||
for fill in ('', ' ', '0', '@'):
|
||||
for str in ('', 'a', 'bcd', 'This is a test with a longer string'):
|
||||
test_fmt(conv, fill, alignment, '', '', width, '', 's', str)
|
||||
|
||||
eg_nums = (0.0, -0.0, 0.1, 1.234, 12.3459, 1.23456789, 123456789.0, -0.0,
|
||||
-0.1, -1.234, -12.3459, 1e4, 1e-4, 1e5, 1e-5, 1e6, 1e-6, 1e10,
|
||||
1e37, -1e37, 1e-37, -1e-37,
|
||||
1.23456e8, 1.23456e7, 1.23456e6, 1.23456e5, 1.23456e4, 1.23456e3, 1.23456e2, 1.23456e1, 1.23456e0,
|
||||
1.23456e-1, 1.23456e-2, 1.23456e-3, 1.23456e-4, 1.23456e-5, 1.23456e-6, 1.23456e-7, 1.23456e-8,
|
||||
-1.23456e8, -1.23456e7, -1.23456e6, -1.23456e5, -1.23456e4, -1.23456e3, -1.23456e2, -1.23456e1, -1.23456e0,
|
||||
-1.23456e-1, -1.23456e-2, -1.23456e-3, -1.23456e-4, -1.23456e-5, -1.23456e-6, -1.23456e-7, -1.23456e-8)
|
||||
|
||||
if True:
|
||||
for type in ('e', 'E', 'g', 'G', 'n'):
|
||||
for width in ('', '4', '6', '8', '10'):
|
||||
for alignment in ('', '<', '>', '=', '^'):
|
||||
for fill in ('', '@', '0', ' '):
|
||||
for sign in ('', '+', '-', ' '):
|
||||
for prec in ('', '1', '3', '6'):
|
||||
for num in eg_nums:
|
||||
test_fmt('', fill, alignment, sign, '', width, prec, type, num)
|
||||
|
||||
# Note: We use 1.23459 rather than 1.2345 because '{:3f}'.format(1.2345)
|
||||
# rounds differently than print("%.3f", 1.2345);
|
||||
|
||||
f_nums = (0.0, -0.0, 0.0001, 0.001, 0.01, 0.1, 1.0, 10.0,
|
||||
0.0012, 0.0123, 0.1234, 1.23459, 12.3456,
|
||||
-0.0001, -0.001, -0.01, -0.1, -1.0, -10.0,
|
||||
-0.0012, -0.0123, -0.1234, -1.23459, -12.3456)
|
||||
|
||||
if True:
|
||||
for type in ('f', 'F'):
|
||||
for width in ('', '4', '6', '8', '10'):
|
||||
for alignment in ('', '<', '>', '=', '^'):
|
||||
for fill in ('', ' ', '0', '@'):
|
||||
for sign in ('', '+', '-', ' '):
|
||||
# An empty precision defaults to 6, but when uPy is
|
||||
# configured to use a float, we can only use a
|
||||
# precision of 6 with numbers less than 10 and still
|
||||
# get results that compare to CPython (which uses
|
||||
# long doubles).
|
||||
for prec in ('1', '2', '3'):
|
||||
for num in f_nums:
|
||||
test_fmt('', fill, alignment, sign, '', width, prec, type, num)
|
||||
for num in int_nums2:
|
||||
test_fmt('', fill, alignment, sign, '', width, '', type, num)
|
||||
|
||||
pct_nums1 = (0.1, 0.58, 0.99, -0.1, -0.58, -0.99)
|
||||
pct_nums2 = (True, False, 1, 0, -1)
|
||||
|
||||
if True:
|
||||
type = '%'
|
||||
for width in ('', '4', '6', '8', '10'):
|
||||
for alignment in ('', '<', '>', '=', '^'):
|
||||
for fill in ('', ' ', '0', '@'):
|
||||
for sign in ('', '+', '-', ' '):
|
||||
# An empty precision defaults to 6, but when uPy is
|
||||
# configured to use a float, we can only use a
|
||||
# precision of 6 with numbers less than 10 and still
|
||||
# get results that compare to CPython (which uses
|
||||
# long doubles).
|
||||
for prec in ('1', '2', '3'):
|
||||
for num in pct_nums1:
|
||||
test_fmt('', fill, alignment, sign, '', width, prec, type, num)
|
||||
for num in pct_nums2:
|
||||
test_fmt('', fill, alignment, sign, '', width, '', type, num)
|
||||
|
||||
# We don't currently test a type of '' with floats (see the detailed comment
|
||||
# in objstr.c)
|
||||
|
||||
# TODO Add tests for erroneous format strings.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user