micropython/py/objstr.c
Dave Hylands baf6f14deb Enhance str.format support
This adds support for almost everything (the comma isn't currently
supported).

The "unspecified" type with floats also doesn't behave exactly like
python.

Tested under unix with float and double
Spot tested on stmhal
2014-04-01 01:17:33 -07:00

1343 lines
45 KiB
C

#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include "nlr.h"
#include "misc.h"
#include "mpconfig.h"
#include "qstr.h"
#include "obj.h"
#include "runtime0.h"
#include "runtime.h"
#include "pfenv.h"
typedef struct _mp_obj_str_t {
mp_obj_base_t base;
machine_uint_t hash : 16; // XXX here we assume the hash size is 16 bits (it is at the moment; see qstr.c)
machine_uint_t len : 16; // len == number of bytes used in data, alloc = len + 1 because (at the moment) we also append a null byte
const byte *data;
} mp_obj_str_t;
STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t *args);
const mp_obj_t mp_const_empty_bytes;
// use this macro to extract the string hash
#define GET_STR_HASH(str_obj_in, str_hash) uint str_hash; if (MP_OBJ_IS_QSTR(str_obj_in)) { str_hash = qstr_hash(MP_OBJ_QSTR_VALUE(str_obj_in)); } else { str_hash = ((mp_obj_str_t*)str_obj_in)->hash; }
// use this macro to extract the string length
#define GET_STR_LEN(str_obj_in, str_len) uint str_len; if (MP_OBJ_IS_QSTR(str_obj_in)) { str_len = qstr_len(MP_OBJ_QSTR_VALUE(str_obj_in)); } else { str_len = ((mp_obj_str_t*)str_obj_in)->len; }
// use this macro to extract the string data and length
#define GET_STR_DATA_LEN(str_obj_in, str_data, str_len) const byte *str_data; uint str_len; if (MP_OBJ_IS_QSTR(str_obj_in)) { str_data = qstr_data(MP_OBJ_QSTR_VALUE(str_obj_in), &str_len); } else { str_len = ((mp_obj_str_t*)str_obj_in)->len; str_data = ((mp_obj_str_t*)str_obj_in)->data; }
STATIC mp_obj_t mp_obj_new_str_iterator(mp_obj_t str);
STATIC mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str);
STATIC mp_obj_t str_new(const mp_obj_type_t *type, const byte* data, uint len);
/******************************************************************************/
/* str */
void mp_str_print_quoted(void (*print)(void *env, const char *fmt, ...), void *env, const byte *str_data, uint str_len) {
// this escapes characters, but it will be very slow to print (calling print many times)
bool has_single_quote = false;
bool has_double_quote = false;
for (const byte *s = str_data, *top = str_data + str_len; (!has_single_quote || !has_double_quote) && s < top; s++) {
if (*s == '\'') {
has_single_quote = true;
} else if (*s == '"') {
has_double_quote = true;
}
}
int quote_char = '\'';
if (has_single_quote && !has_double_quote) {
quote_char = '"';
}
print(env, "%c", quote_char);
for (const byte *s = str_data, *top = str_data + str_len; s < top; s++) {
if (*s == quote_char) {
print(env, "\\%c", quote_char);
} else if (*s == '\\') {
print(env, "\\\\");
} else if (32 <= *s && *s <= 126) {
print(env, "%c", *s);
} else if (*s == '\n') {
print(env, "\\n");
// TODO add more escape codes here if we want to match CPython
} else {
print(env, "\\x%02x", *s);
}
}
print(env, "%c", quote_char);
}
STATIC void str_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
GET_STR_DATA_LEN(self_in, str_data, str_len);
bool is_bytes = MP_OBJ_IS_TYPE(self_in, &mp_type_bytes);
if (kind == PRINT_STR && !is_bytes) {
print(env, "%.*s", str_len, str_data);
} else {
if (is_bytes) {
print(env, "b");
}
mp_str_print_quoted(print, env, str_data, str_len);
}
}
STATIC mp_obj_t str_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
switch (n_args) {
case 0:
return MP_OBJ_NEW_QSTR(MP_QSTR_);
case 1:
{
vstr_t *vstr = vstr_new();
mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, args[0], PRINT_STR);
mp_obj_t s = mp_obj_new_str((byte*)vstr->buf, vstr->len, false);
vstr_free(vstr);
return s;
}
case 2:
case 3:
{
// TODO: validate 2nd/3rd args
if (!MP_OBJ_IS_TYPE(args[0], &mp_type_bytes)) {
nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "bytes expected"));
}
GET_STR_DATA_LEN(args[0], str_data, str_len);
GET_STR_HASH(args[0], str_hash);
mp_obj_str_t *o = str_new(&mp_type_str, NULL, str_len);
o->data = str_data;
o->hash = str_hash;
return o;
}
default:
nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "str takes at most 3 arguments"));
}
}
STATIC mp_obj_t bytes_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
if (n_args == 0) {
return mp_const_empty_bytes;
}
if (MP_OBJ_IS_STR(args[0])) {
if (n_args < 2 || n_args > 3) {
goto wrong_args;
}
GET_STR_DATA_LEN(args[0], str_data, str_len);
GET_STR_HASH(args[0], str_hash);
mp_obj_str_t *o = str_new(&mp_type_bytes, NULL, str_len);
o->data = str_data;
o->hash = str_hash;
return o;
}
if (n_args > 1) {
goto wrong_args;
}
if (MP_OBJ_IS_SMALL_INT(args[0])) {
uint len = MP_OBJ_SMALL_INT_VALUE(args[0]);
byte *data;
mp_obj_t o = mp_obj_str_builder_start(&mp_type_bytes, len, &data);
memset(data, 0, len);
return mp_obj_str_builder_end(o);
}
int len;
byte *data;
vstr_t *vstr = NULL;
mp_obj_t o = NULL;
// Try to create array of exact len if initializer len is known
mp_obj_t len_in = mp_obj_len_maybe(args[0]);
if (len_in == MP_OBJ_NULL) {
len = -1;
vstr = vstr_new();
} else {
len = MP_OBJ_SMALL_INT_VALUE(len_in);
o = mp_obj_str_builder_start(&mp_type_bytes, len, &data);
}
mp_obj_t iterable = mp_getiter(args[0]);
mp_obj_t item;
while ((item = mp_iternext(iterable)) != MP_OBJ_NULL) {
if (len == -1) {
vstr_add_char(vstr, MP_OBJ_SMALL_INT_VALUE(item));
} else {
*data++ = MP_OBJ_SMALL_INT_VALUE(item);
}
}
if (len == -1) {
vstr_shrink(vstr);
// TODO: Optimize, borrow buffer from vstr
len = vstr_len(vstr);
o = mp_obj_str_builder_start(&mp_type_bytes, len, &data);
memcpy(data, vstr_str(vstr), len);
vstr_free(vstr);
}
return mp_obj_str_builder_end(o);
wrong_args:
nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "wrong number of arguments"));
}
// like strstr but with specified length and allows \0 bytes
// TODO replace with something more efficient/standard
STATIC const byte *find_subbytes(const byte *haystack, machine_uint_t hlen, const byte *needle, machine_uint_t nlen, machine_int_t direction) {
if (hlen >= nlen) {
machine_uint_t str_index, str_index_end;
if (direction > 0) {
str_index = 0;
str_index_end = hlen - nlen;
} else {
str_index = hlen - nlen;
str_index_end = 0;
}
for (;;) {
if (memcmp(&haystack[str_index], needle, nlen) == 0) {
//found
return haystack + str_index;
}
if (str_index == str_index_end) {
//not found
break;
}
str_index += direction;
}
}
return NULL;
}
STATIC mp_obj_t str_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
GET_STR_DATA_LEN(lhs_in, lhs_data, lhs_len);
switch (op) {
case MP_BINARY_OP_SUBSCR:
// TODO: need predicate to check for int-like type (bools are such for example)
// ["no", "yes"][1 == 2] is common idiom
if (MP_OBJ_IS_SMALL_INT(rhs_in)) {
uint index = mp_get_index(mp_obj_get_type(lhs_in), lhs_len, rhs_in, false);
if (MP_OBJ_IS_TYPE(lhs_in, &mp_type_bytes)) {
return MP_OBJ_NEW_SMALL_INT((mp_small_int_t)lhs_data[index]);
} else {
return mp_obj_new_str(lhs_data + index, 1, true);
}
#if MICROPY_ENABLE_SLICE
} else if (MP_OBJ_IS_TYPE(rhs_in, &mp_type_slice)) {
machine_uint_t start, stop;
if (!m_seq_get_fast_slice_indexes(lhs_len, rhs_in, &start, &stop)) {
assert(0);
}
return mp_obj_new_str(lhs_data + start, stop - start, false);
#endif
} else {
// Message doesn't match CPython, but we don't have so much bytes as they
// to spend them on verbose wording
nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "index must be int"));
}
case MP_BINARY_OP_ADD:
case MP_BINARY_OP_INPLACE_ADD:
if (MP_OBJ_IS_STR(rhs_in)) {
// add 2 strings
GET_STR_DATA_LEN(rhs_in, rhs_data, rhs_len);
int alloc_len = lhs_len + rhs_len;
/* code for making qstr
byte *q_ptr;
byte *val = qstr_build_start(alloc_len, &q_ptr);
memcpy(val, lhs_data, lhs_len);
memcpy(val + lhs_len, rhs_data, rhs_len);
return MP_OBJ_NEW_QSTR(qstr_build_end(q_ptr));
*/
// code for non-qstr
byte *data;
mp_obj_t s = mp_obj_str_builder_start(mp_obj_get_type(lhs_in), alloc_len, &data);
memcpy(data, lhs_data, lhs_len);
memcpy(data + lhs_len, rhs_data, rhs_len);
return mp_obj_str_builder_end(s);
}
break;
case MP_BINARY_OP_IN:
/* NOTE `a in b` is `b.__contains__(a)` */
if (MP_OBJ_IS_STR(rhs_in)) {
GET_STR_DATA_LEN(rhs_in, rhs_data, rhs_len);
return MP_BOOL(find_subbytes(lhs_data, lhs_len, rhs_data, rhs_len, 1) != NULL);
}
break;
case MP_BINARY_OP_MULTIPLY:
{
if (!MP_OBJ_IS_SMALL_INT(rhs_in)) {
return NULL;
}
int n = MP_OBJ_SMALL_INT_VALUE(rhs_in);
byte *data;
mp_obj_t s = mp_obj_str_builder_start(mp_obj_get_type(lhs_in), lhs_len * n, &data);
mp_seq_multiply(lhs_data, sizeof(*lhs_data), lhs_len, n, data);
return mp_obj_str_builder_end(s);
}
case MP_BINARY_OP_MODULO: {
mp_obj_t *args;
uint n_args;
if (MP_OBJ_IS_TYPE(rhs_in, &mp_type_tuple)) {
// TODO: Support tuple subclasses?
mp_obj_tuple_get(rhs_in, &n_args, &args);
} else {
args = &rhs_in;
n_args = 1;
}
return str_modulo_format(lhs_in, n_args, args);
}
// These 2 are never passed here, dealt with as a special case in mp_binary_op().
//case MP_BINARY_OP_EQUAL:
//case MP_BINARY_OP_NOT_EQUAL:
case MP_BINARY_OP_LESS:
case MP_BINARY_OP_LESS_EQUAL:
case MP_BINARY_OP_MORE:
case MP_BINARY_OP_MORE_EQUAL:
if (MP_OBJ_IS_STR(rhs_in)) {
GET_STR_DATA_LEN(rhs_in, rhs_data, rhs_len);
return MP_BOOL(mp_seq_cmp_bytes(op, lhs_data, lhs_len, rhs_data, rhs_len));
}
}
return MP_OBJ_NULL; // op not supported
}
STATIC mp_obj_t str_join(mp_obj_t self_in, mp_obj_t arg) {
assert(MP_OBJ_IS_STR(self_in));
// get separation string
GET_STR_DATA_LEN(self_in, sep_str, sep_len);
// process args
uint seq_len;
mp_obj_t *seq_items;
if (MP_OBJ_IS_TYPE(arg, &mp_type_tuple)) {
mp_obj_tuple_get(arg, &seq_len, &seq_items);
} else if (MP_OBJ_IS_TYPE(arg, &mp_type_list)) {
mp_obj_list_get(arg, &seq_len, &seq_items);
} else {
goto bad_arg;
}
// count required length
int required_len = 0;
for (int i = 0; i < seq_len; i++) {
if (!MP_OBJ_IS_STR(seq_items[i])) {
goto bad_arg;
}
if (i > 0) {
required_len += sep_len;
}
GET_STR_LEN(seq_items[i], l);
required_len += l;
}
// make joined string
byte *data;
mp_obj_t joined_str = mp_obj_str_builder_start(mp_obj_get_type(self_in), required_len, &data);
for (int i = 0; i < seq_len; i++) {
if (i > 0) {
memcpy(data, sep_str, sep_len);
data += sep_len;
}
GET_STR_DATA_LEN(seq_items[i], s, l);
memcpy(data, s, l);
data += l;
}
// return joined string
return mp_obj_str_builder_end(joined_str);
bad_arg:
nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "?str.join expecting a list of str's"));
}
#define is_ws(c) ((c) == ' ' || (c) == '\t')
STATIC mp_obj_t str_split(uint n_args, const mp_obj_t *args) {
int splits = -1;
mp_obj_t sep = mp_const_none;
if (n_args > 1) {
sep = args[1];
if (n_args > 2) {
splits = MP_OBJ_SMALL_INT_VALUE(args[2]);
}
}
assert(sep == mp_const_none);
(void)sep; // unused; to hush compiler warning
mp_obj_t res = mp_obj_new_list(0, NULL);
GET_STR_DATA_LEN(args[0], s, len);
const byte *top = s + len;
const byte *start;
// Initial whitespace is not counted as split, so we pre-do it
while (s < top && is_ws(*s)) s++;
while (s < top && splits != 0) {
start = s;
while (s < top && !is_ws(*s)) s++;
mp_obj_list_append(res, mp_obj_new_str(start, s - start, false));
if (s >= top) {
break;
}
while (s < top && is_ws(*s)) s++;
if (splits > 0) {
splits--;
}
}
if (s < top) {
mp_obj_list_append(res, mp_obj_new_str(s, top - s, false));
}
return res;
}
STATIC mp_obj_t str_finder(uint n_args, const mp_obj_t *args, machine_int_t direction) {
assert(2 <= n_args && n_args <= 4);
assert(MP_OBJ_IS_STR(args[0]));
assert(MP_OBJ_IS_STR(args[1]));
GET_STR_DATA_LEN(args[0], haystack, haystack_len);
GET_STR_DATA_LEN(args[1], needle, needle_len);
machine_uint_t start = 0;
machine_uint_t end = haystack_len;
if (n_args >= 3 && args[2] != mp_const_none) {
start = mp_get_index(&mp_type_str, haystack_len, args[2], true);
}
if (n_args >= 4 && args[3] != mp_const_none) {
end = mp_get_index(&mp_type_str, haystack_len, args[3], true);
}
const byte *p = find_subbytes(haystack + start, end - start, needle, needle_len, direction);
if (p == NULL) {
// not found
return MP_OBJ_NEW_SMALL_INT(-1);
} else {
// found
return MP_OBJ_NEW_SMALL_INT(p - haystack);
}
}
STATIC mp_obj_t str_find(uint n_args, const mp_obj_t *args) {
return str_finder(n_args, args, 1);
}
STATIC mp_obj_t str_rfind(uint n_args, const mp_obj_t *args) {
return str_finder(n_args, args, -1);
}
// TODO: (Much) more variety in args
STATIC mp_obj_t str_startswith(mp_obj_t self_in, mp_obj_t arg) {
GET_STR_DATA_LEN(self_in, str, str_len);
GET_STR_DATA_LEN(arg, prefix, prefix_len);
if (prefix_len > str_len) {
return mp_const_false;
}
return MP_BOOL(memcmp(str, prefix, prefix_len) == 0);
}
STATIC mp_obj_t str_strip(uint n_args, const mp_obj_t *args) {
assert(1 <= n_args && n_args <= 2);
assert(MP_OBJ_IS_STR(args[0]));
const byte *chars_to_del;
uint chars_to_del_len;
static const byte whitespace[] = " \t\n\r\v\f";
if (n_args == 1) {
chars_to_del = whitespace;
chars_to_del_len = sizeof(whitespace);
} else {
assert(MP_OBJ_IS_STR(args[1]));
GET_STR_DATA_LEN(args[1], s, l);
chars_to_del = s;
chars_to_del_len = l;
}
GET_STR_DATA_LEN(args[0], orig_str, orig_str_len);
machine_uint_t first_good_char_pos = 0;
bool first_good_char_pos_set = false;
machine_uint_t last_good_char_pos = 0;
for (machine_uint_t i = 0; i < orig_str_len; i++) {
if (find_subbytes(chars_to_del, chars_to_del_len, &orig_str[i], 1, 1) == NULL) {
last_good_char_pos = i;
if (!first_good_char_pos_set) {
first_good_char_pos = i;
first_good_char_pos_set = true;
}
}
}
if (first_good_char_pos == 0 && last_good_char_pos == 0) {
// string is all whitespace, return ''
return MP_OBJ_NEW_QSTR(MP_QSTR_);
}
assert(last_good_char_pos >= first_good_char_pos);
//+1 to accomodate the last character
machine_uint_t stripped_len = last_good_char_pos - first_good_char_pos + 1;
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 = 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 == '}') {
str++;
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 {
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++);
}
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 {
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)));
}
}
}
mp_obj_t s = mp_obj_new_str((byte*)vstr->buf, vstr->len, false);
vstr_free(vstr);
return s;
}
STATIC mp_obj_t str_modulo_format(mp_obj_t pattern, uint n_args, const mp_obj_t *args) {
assert(MP_OBJ_IS_STR(pattern));
GET_STR_DATA_LEN(pattern, str, len);
int arg_i = 0;
vstr_t *vstr = vstr_new();
for (const byte *top = str + len; str < top; str++) {
if (*str == '%') {
if (++str >= top) {
break;
}
if (*str == '%') {
vstr_add_char(vstr, '%');
} else {
if (arg_i >= n_args) {
nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "not enough arguments for format string"));
}
switch (*str) {
case 's':
mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, args[arg_i], PRINT_STR);
break;
case 'r':
mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, args[arg_i], PRINT_REPR);
break;
}
arg_i++;
}
} else {
vstr_add_char(vstr, *str);
}
}
if (arg_i != n_args) {
nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "not all arguments converted during string formatting"));
}
mp_obj_t s = mp_obj_new_str((byte*)vstr->buf, vstr->len, false);
vstr_free(vstr);
return s;
}
STATIC mp_obj_t str_replace(uint n_args, const mp_obj_t *args) {
assert(MP_OBJ_IS_STR(args[0]));
assert(MP_OBJ_IS_STR(args[1]));
assert(MP_OBJ_IS_STR(args[2]));
machine_int_t max_rep = 0;
if (n_args == 4) {
assert(MP_OBJ_IS_SMALL_INT(args[3]));
max_rep = MP_OBJ_SMALL_INT_VALUE(args[3]);
if (max_rep == 0) {
return args[0];
} else if (max_rep < 0) {
max_rep = 0;
}
}
// if max_rep is still 0 by this point we will need to do all possible replacements
GET_STR_DATA_LEN(args[0], str, str_len);
GET_STR_DATA_LEN(args[1], old, old_len);
GET_STR_DATA_LEN(args[2], new, new_len);
// old won't exist in str if it's longer, so nothing to replace
if (old_len > str_len) {
return args[0];
}
// data for the replaced string
byte *data = NULL;
mp_obj_t replaced_str = MP_OBJ_NULL;
// do 2 passes over the string:
// first pass computes the required length of the replaced string
// second pass does the replacements
for (;;) {
machine_uint_t replaced_str_index = 0;
machine_uint_t num_replacements_done = 0;
const byte *old_occurrence;
const byte *offset_ptr = str;
machine_uint_t offset_num = 0;
while ((old_occurrence = find_subbytes(offset_ptr, str_len - offset_num, old, old_len, 1)) != NULL) {
// copy from just after end of last occurrence of to-be-replaced string to right before start of next occurrence
if (data != NULL) {
memcpy(data + replaced_str_index, offset_ptr, old_occurrence - offset_ptr);
}
replaced_str_index += old_occurrence - offset_ptr;
// copy the replacement string
if (data != NULL) {
memcpy(data + replaced_str_index, new, new_len);
}
replaced_str_index += new_len;
offset_ptr = old_occurrence + old_len;
offset_num = offset_ptr - str;
num_replacements_done++;
if (max_rep != 0 && num_replacements_done == max_rep){
break;
}
}
// copy from just after end of last occurrence of to-be-replaced string to end of old string
if (data != NULL) {
memcpy(data + replaced_str_index, offset_ptr, str_len - offset_num);
}
replaced_str_index += str_len - offset_num;
if (data == NULL) {
// first pass
if (num_replacements_done == 0) {
// no substr found, return original string
return args[0];
} else {
// substr found, allocate new string
replaced_str = mp_obj_str_builder_start(mp_obj_get_type(args[0]), replaced_str_index, &data);
}
} else {
// second pass, we are done
break;
}
}
return mp_obj_str_builder_end(replaced_str);
}
STATIC mp_obj_t str_count(uint n_args, const mp_obj_t *args) {
assert(2 <= n_args && n_args <= 4);
assert(MP_OBJ_IS_STR(args[0]));
assert(MP_OBJ_IS_STR(args[1]));
GET_STR_DATA_LEN(args[0], haystack, haystack_len);
GET_STR_DATA_LEN(args[1], needle, needle_len);
machine_uint_t start = 0;
machine_uint_t end = haystack_len;
if (n_args >= 3 && args[2] != mp_const_none) {
start = mp_get_index(&mp_type_str, haystack_len, args[2], true);
}
if (n_args >= 4 && args[3] != mp_const_none) {
end = mp_get_index(&mp_type_str, haystack_len, args[3], true);
}
// if needle_len is zero then we count each gap between characters as an occurrence
if (needle_len == 0) {
return MP_OBJ_NEW_SMALL_INT(end - start + 1);
}
// count the occurrences
machine_int_t num_occurrences = 0;
for (machine_uint_t haystack_index = start; haystack_index + needle_len <= end; haystack_index++) {
if (memcmp(&haystack[haystack_index], needle, needle_len) == 0) {
num_occurrences++;
haystack_index += needle_len - 1;
}
}
return MP_OBJ_NEW_SMALL_INT(num_occurrences);
}
STATIC mp_obj_t str_partitioner(mp_obj_t self_in, mp_obj_t arg, machine_int_t direction) {
assert(MP_OBJ_IS_STR(self_in));
if (!MP_OBJ_IS_STR(arg)) {
nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
"Can't convert '%s' object to str implicitly", mp_obj_get_type_str(arg)));
}
GET_STR_DATA_LEN(self_in, str, str_len);
GET_STR_DATA_LEN(arg, sep, sep_len);
if (sep_len == 0) {
nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "empty separator"));
}
mp_obj_t result[] = {MP_OBJ_NEW_QSTR(MP_QSTR_), MP_OBJ_NEW_QSTR(MP_QSTR_), MP_OBJ_NEW_QSTR(MP_QSTR_)};
if (direction > 0) {
result[0] = self_in;
} else {
result[2] = self_in;
}
const byte *position_ptr = find_subbytes(str, str_len, sep, sep_len, direction);
if (position_ptr != NULL) {
machine_uint_t position = position_ptr - str;
result[0] = mp_obj_new_str(str, position, false);
result[1] = arg;
result[2] = mp_obj_new_str(str + position + sep_len, str_len - position - sep_len, false);
}
return mp_obj_new_tuple(3, result);
}
STATIC mp_obj_t str_partition(mp_obj_t self_in, mp_obj_t arg) {
return str_partitioner(self_in, arg, 1);
}
STATIC mp_obj_t str_rpartition(mp_obj_t self_in, mp_obj_t arg) {
return str_partitioner(self_in, arg, -1);
}
STATIC machine_int_t str_get_buffer(mp_obj_t self_in, buffer_info_t *bufinfo, int flags) {
if (flags == BUFFER_READ) {
GET_STR_DATA_LEN(self_in, str_data, str_len);
bufinfo->buf = (void*)str_data;
bufinfo->len = str_len;
return 0;
} else {
// can't write to a string
bufinfo->buf = NULL;
bufinfo->len = 0;
return 1;
}
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_find_obj, 2, 4, str_find);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rfind_obj, 2, 4, str_rfind);
STATIC MP_DEFINE_CONST_FUN_OBJ_2(str_join_obj, str_join);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_split_obj, 1, 3, str_split);
STATIC MP_DEFINE_CONST_FUN_OBJ_2(str_startswith_obj, str_startswith);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_strip_obj, 1, 2, str_strip);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR(str_format_obj, 1, str_format);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_replace_obj, 3, 4, str_replace);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_count_obj, 2, 4, str_count);
STATIC MP_DEFINE_CONST_FUN_OBJ_2(str_partition_obj, str_partition);
STATIC MP_DEFINE_CONST_FUN_OBJ_2(str_rpartition_obj, str_rpartition);
STATIC const mp_map_elem_t str_locals_dict_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR_find), (mp_obj_t)&str_find_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_rfind), (mp_obj_t)&str_rfind_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_join), (mp_obj_t)&str_join_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_split), (mp_obj_t)&str_split_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_startswith), (mp_obj_t)&str_startswith_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_strip), (mp_obj_t)&str_strip_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_format), (mp_obj_t)&str_format_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_replace), (mp_obj_t)&str_replace_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_count), (mp_obj_t)&str_count_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_partition), (mp_obj_t)&str_partition_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_rpartition), (mp_obj_t)&str_rpartition_obj },
};
STATIC MP_DEFINE_CONST_DICT(str_locals_dict, str_locals_dict_table);
const mp_obj_type_t mp_type_str = {
{ &mp_type_type },
.name = MP_QSTR_str,
.print = str_print,
.make_new = str_make_new,
.binary_op = str_binary_op,
.getiter = mp_obj_new_str_iterator,
.buffer_p = { .get_buffer = str_get_buffer },
.locals_dict = (mp_obj_t)&str_locals_dict,
};
// Reuses most of methods from str
const mp_obj_type_t mp_type_bytes = {
{ &mp_type_type },
.name = MP_QSTR_bytes,
.print = str_print,
.make_new = bytes_make_new,
.binary_op = str_binary_op,
.getiter = mp_obj_new_bytes_iterator,
.locals_dict = (mp_obj_t)&str_locals_dict,
};
// the zero-length bytes
STATIC const mp_obj_str_t empty_bytes_obj = {{&mp_type_bytes}, 0, 0, NULL};
const mp_obj_t mp_const_empty_bytes = (mp_obj_t)&empty_bytes_obj;
mp_obj_t mp_obj_str_builder_start(const mp_obj_type_t *type, uint len, byte **data) {
mp_obj_str_t *o = m_new_obj(mp_obj_str_t);
o->base.type = type;
o->len = len;
byte *p = m_new(byte, len + 1);
o->data = p;
*data = p;
return o;
}
mp_obj_t mp_obj_str_builder_end(mp_obj_t o_in) {
mp_obj_str_t *o = o_in;
o->hash = qstr_compute_hash(o->data, o->len);
byte *p = (byte*)o->data;
p[o->len] = '\0'; // for now we add null for compatibility with C ASCIIZ strings
return o;
}
STATIC mp_obj_t str_new(const mp_obj_type_t *type, const byte* data, uint len) {
mp_obj_str_t *o = m_new_obj(mp_obj_str_t);
o->base.type = type;
o->len = len;
if (data) {
o->hash = qstr_compute_hash(data, len);
byte *p = m_new(byte, len + 1);
o->data = p;
memcpy(p, data, len * sizeof(byte));
p[len] = '\0'; // for now we add null for compatibility with C ASCIIZ strings
}
return o;
}
mp_obj_t mp_obj_new_str(const byte* data, uint len, bool make_qstr_if_not_already) {
qstr q = qstr_find_strn(data, len);
if (q != MP_QSTR_NULL) {
// qstr with this data already exists
return MP_OBJ_NEW_QSTR(q);
} else if (make_qstr_if_not_already) {
// no existing qstr, make a new one
return MP_OBJ_NEW_QSTR(qstr_from_strn((const char*)data, len));
} else {
// no existing qstr, don't make one
return str_new(&mp_type_str, data, len);
}
}
mp_obj_t mp_obj_new_bytes(const byte* data, uint len) {
return str_new(&mp_type_bytes, data, len);
}
bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) {
if (MP_OBJ_IS_QSTR(s1) && MP_OBJ_IS_QSTR(s2)) {
return s1 == s2;
} else {
GET_STR_HASH(s1, h1);
GET_STR_HASH(s2, h2);
if (h1 != h2) {
return false;
}
GET_STR_DATA_LEN(s1, d1, l1);
GET_STR_DATA_LEN(s2, d2, l2);
if (l1 != l2) {
return false;
}
return memcmp(d1, d2, l1) == 0;
}
}
void bad_implicit_conversion(mp_obj_t self_in) __attribute__((noreturn));
void bad_implicit_conversion(mp_obj_t self_in) {
nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "Can't convert '%s' object to str implicitly", mp_obj_get_type_str(self_in)));
}
uint mp_obj_str_get_hash(mp_obj_t self_in) {
if (MP_OBJ_IS_STR(self_in)) {
GET_STR_HASH(self_in, h);
return h;
} else {
bad_implicit_conversion(self_in);
}
}
uint mp_obj_str_get_len(mp_obj_t self_in) {
if (MP_OBJ_IS_STR(self_in)) {
GET_STR_LEN(self_in, l);
return l;
} else {
bad_implicit_conversion(self_in);
}
}
// use this if you will anyway convert the string to a qstr
// will be more efficient for the case where it's already a qstr
qstr mp_obj_str_get_qstr(mp_obj_t self_in) {
if (MP_OBJ_IS_QSTR(self_in)) {
return MP_OBJ_QSTR_VALUE(self_in);
} else if (MP_OBJ_IS_TYPE(self_in, &mp_type_str)) {
mp_obj_str_t *self = self_in;
return qstr_from_strn((char*)self->data, self->len);
} else {
bad_implicit_conversion(self_in);
}
}
// only use this function if you need the str data to be zero terminated
// at the moment all strings are zero terminated to help with C ASCIIZ compatibility
const char *mp_obj_str_get_str(mp_obj_t self_in) {
if (MP_OBJ_IS_STR(self_in)) {
GET_STR_DATA_LEN(self_in, s, l);
(void)l; // len unused
return (const char*)s;
} else {
bad_implicit_conversion(self_in);
}
}
const char *mp_obj_str_get_data(mp_obj_t self_in, uint *len) {
if (MP_OBJ_IS_STR(self_in)) {
GET_STR_DATA_LEN(self_in, s, l);
*len = l;
return (const char*)s;
} else {
bad_implicit_conversion(self_in);
}
}
/******************************************************************************/
/* str iterator */
typedef struct _mp_obj_str_it_t {
mp_obj_base_t base;
mp_obj_t str;
machine_uint_t cur;
} mp_obj_str_it_t;
STATIC mp_obj_t str_it_iternext(mp_obj_t self_in) {
mp_obj_str_it_t *self = self_in;
GET_STR_DATA_LEN(self->str, str, len);
if (self->cur < len) {
mp_obj_t o_out = mp_obj_new_str(str + self->cur, 1, true);
self->cur += 1;
return o_out;
} else {
return MP_OBJ_NULL;
}
}
STATIC const mp_obj_type_t mp_type_str_it = {
{ &mp_type_type },
.name = MP_QSTR_iterator,
.getiter = mp_identity,
.iternext = str_it_iternext,
};
STATIC mp_obj_t bytes_it_iternext(mp_obj_t self_in) {
mp_obj_str_it_t *self = self_in;
GET_STR_DATA_LEN(self->str, str, len);
if (self->cur < len) {
mp_obj_t o_out = MP_OBJ_NEW_SMALL_INT((mp_small_int_t)str[self->cur]);
self->cur += 1;
return o_out;
} else {
return MP_OBJ_NULL;
}
}
STATIC const mp_obj_type_t mp_type_bytes_it = {
{ &mp_type_type },
.name = MP_QSTR_iterator,
.getiter = mp_identity,
.iternext = bytes_it_iternext,
};
mp_obj_t mp_obj_new_str_iterator(mp_obj_t str) {
mp_obj_str_it_t *o = m_new_obj(mp_obj_str_it_t);
o->base.type = &mp_type_str_it;
o->str = str;
o->cur = 0;
return o;
}
mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str) {
mp_obj_str_it_t *o = m_new_obj(mp_obj_str_it_t);
o->base.type = &mp_type_bytes_it;
o->str = str;
o->cur = 0;
return o;
}