Skip to content

Commit 4038f51

Browse files
committed
2 parents 0f4ee2e + 951ed9d commit 4038f51

31 files changed

Lines changed: 484 additions & 246 deletions

py/builtin.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#include "runtime0.h"
3737
#include "runtime.h"
3838
#include "builtin.h"
39+
#include "stream.h"
40+
#include "pfenv.h"
3941

4042
#if MICROPY_PY_BUILTINS_FLOAT
4143
#include <math.h>
@@ -424,13 +426,36 @@ STATIC mp_obj_t mp_builtin_print(uint n_args, const mp_obj_t *args, mp_map_t *kw
424426
if (end_elem != NULL && end_elem->value != mp_const_none) {
425427
end_data = mp_obj_str_get_data(end_elem->value, &end_len);
426428
}
429+
#if MICROPY_PY_IO
430+
mp_obj_t stream_obj = &mp_sys_stdout_obj;
431+
mp_map_elem_t *file_elem = mp_map_lookup(kwargs, MP_OBJ_NEW_QSTR(MP_QSTR_file), MP_MAP_LOOKUP);
432+
if (file_elem != NULL && file_elem->value != mp_const_none) {
433+
stream_obj = file_elem->value;
434+
}
435+
436+
pfenv_t pfenv;
437+
pfenv.data = stream_obj;
438+
pfenv.print_strn = (void (*)(void *, const char *, unsigned int))mp_stream_write;
439+
#endif
427440
for (int i = 0; i < n_args; i++) {
428441
if (i > 0) {
442+
#if MICROPY_PY_IO
443+
mp_stream_write(stream_obj, sep_data, sep_len);
444+
#else
429445
printf("%.*s", sep_len, sep_data);
446+
#endif
430447
}
448+
#if MICROPY_PY_IO
449+
mp_obj_print_helper((void (*)(void *env, const char *fmt, ...))pfenv_printf, &pfenv, args[i], PRINT_STR);
450+
#else
431451
mp_obj_print(args[i], PRINT_STR);
452+
#endif
432453
}
454+
#if MICROPY_PY_IO
455+
mp_stream_write(stream_obj, end_data, end_len);
456+
#else
433457
printf("%.*s", end_len, end_data);
458+
#endif
434459
return mp_const_none;
435460
}
436461

py/builtin.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,10 @@ extern const mp_obj_module_t mp_module_struct;
8181
extern const mp_obj_module_t mp_module_sys;
8282
extern const mp_obj_module_t mp_module_gc;
8383

84+
struct _dummy_t;
85+
extern struct _dummy_t mp_sys_stdin_obj;
86+
extern struct _dummy_t mp_sys_stdout_obj;
87+
extern struct _dummy_t mp_sys_stderr_obj;
88+
8489
// extmod modules
8590
extern const mp_obj_module_t mp_module_uctypes;

py/modsys.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@
4444
// only addresses.
4545
struct _dummy_t;
4646
extern struct _dummy_t mp_sys_exit_obj;
47-
extern struct _dummy_t mp_sys_stdin_obj;
48-
extern struct _dummy_t mp_sys_stdout_obj;
49-
extern struct _dummy_t mp_sys_stderr_obj;
5047

5148
extern mp_obj_int_t mp_maxsize_obj;
5249

py/pfenv.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,6 @@ int pfenv_print_mp_int(const pfenv_t *pfenv, mp_obj_t x, int sgn, int base, int
4949
#if MICROPY_PY_BUILTINS_FLOAT
5050
int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, char fill, int width, int prec);
5151
#endif
52+
53+
//int pfenv_vprintf(const pfenv_t *pfenv, const char *fmt, va_list args);
54+
int pfenv_printf(const pfenv_t *pfenv, const char *fmt, ...);

py/pfenv_printf.c

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2013, 2014 Damien P. George
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include <assert.h>
28+
#include <stdio.h>
29+
#include <stdint.h>
30+
#include <string.h>
31+
#include <stdarg.h>
32+
33+
#include "mpconfig.h"
34+
#include "misc.h"
35+
#include "qstr.h"
36+
#include "obj.h"
37+
#include "pfenv.h"
38+
39+
#if MICROPY_PY_BUILTINS_FLOAT
40+
#include "formatfloat.h"
41+
#endif
42+
43+
int pfenv_vprintf(const pfenv_t *pfenv, const char *fmt, va_list args) {
44+
int chrs = 0;
45+
for (;;) {
46+
{
47+
const char *f = fmt;
48+
while (*f != '\0' && *f != '%') {
49+
++f; // XXX UTF8 advance char
50+
}
51+
if (f > fmt) {
52+
pfenv->print_strn(pfenv->data, fmt, f - fmt);
53+
chrs += f - fmt;
54+
fmt = f;
55+
}
56+
}
57+
58+
if (*fmt == '\0') {
59+
break;
60+
}
61+
62+
// move past % character
63+
++fmt;
64+
65+
// parse flags, if they exist
66+
int flags = 0;
67+
char fill = ' ';
68+
while (*fmt != '\0') {
69+
if (*fmt == '-') flags |= PF_FLAG_LEFT_ADJUST;
70+
else if (*fmt == '+') flags |= PF_FLAG_SHOW_SIGN;
71+
else if (*fmt == ' ') flags |= PF_FLAG_SPACE_SIGN;
72+
else if (*fmt == '!') flags |= PF_FLAG_NO_TRAILZ;
73+
else if (*fmt == '0') {
74+
flags |= PF_FLAG_PAD_AFTER_SIGN;
75+
fill = '0';
76+
} else break;
77+
++fmt;
78+
}
79+
80+
// parse width, if it exists
81+
int width = 0;
82+
for (; '0' <= *fmt && *fmt <= '9'; ++fmt) {
83+
width = width * 10 + *fmt - '0';
84+
}
85+
86+
// parse precision, if it exists
87+
int prec = -1;
88+
if (*fmt == '.') {
89+
++fmt;
90+
if (*fmt == '*') {
91+
++fmt;
92+
prec = va_arg(args, int);
93+
} else {
94+
prec = 0;
95+
for (; '0' <= *fmt && *fmt <= '9'; ++fmt) {
96+
prec = prec * 10 + *fmt - '0';
97+
}
98+
}
99+
if (prec < 0) {
100+
prec = 0;
101+
}
102+
}
103+
104+
// parse long specifiers (current not used)
105+
//bool long_arg = false;
106+
if (*fmt == 'l') {
107+
++fmt;
108+
//long_arg = true;
109+
}
110+
111+
if (*fmt == '\0') {
112+
break;
113+
}
114+
115+
switch (*fmt) {
116+
case 'b':
117+
if (va_arg(args, int)) {
118+
chrs += pfenv_print_strn(pfenv, "true", 4, flags, fill, width);
119+
} else {
120+
chrs += pfenv_print_strn(pfenv, "false", 5, flags, fill, width);
121+
}
122+
break;
123+
case 'c':
124+
{
125+
char str = va_arg(args, int);
126+
chrs += pfenv_print_strn(pfenv, &str, 1, flags, fill, width);
127+
break;
128+
}
129+
case 's':
130+
{
131+
const char *str = va_arg(args, const char*);
132+
if (str) {
133+
if (prec < 0) {
134+
prec = strlen(str);
135+
}
136+
chrs += pfenv_print_strn(pfenv, str, prec, flags, fill, width);
137+
} else {
138+
chrs += pfenv_print_strn(pfenv, "(null)", 6, flags, fill, width);
139+
}
140+
break;
141+
}
142+
case 'u':
143+
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 10, 'a', flags, fill, width);
144+
break;
145+
case 'd':
146+
chrs += pfenv_print_int(pfenv, va_arg(args, int), 1, 10, 'a', flags, fill, width);
147+
break;
148+
case 'x':
149+
case 'p': // ?
150+
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'a', flags, fill, width);
151+
break;
152+
case 'X':
153+
case 'P': // ?
154+
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'A', flags, fill, width);
155+
break;
156+
#if MICROPY_PY_BUILTINS_FLOAT
157+
case 'e':
158+
case 'E':
159+
case 'f':
160+
case 'F':
161+
case 'g':
162+
case 'G':
163+
{
164+
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
165+
mp_float_t f = va_arg(args, double);
166+
chrs += pfenv_print_float(pfenv, f, *fmt, flags, fill, width, prec);
167+
#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
168+
// Currently pfenv_print_float uses snprintf, but snprintf
169+
// itself may be implemented in terms of pfenv_vprintf() for
170+
// some ports. So, for extra caution, this case is handled
171+
// with assert below. Note that currently ports which
172+
// use MICROPY_FLOAT_IMPL_DOUBLE, don't call pfenv_vprintf()
173+
// with float format specifier at all.
174+
// TODO: resolve this completely
175+
assert(0);
176+
//#error Calling pfenv_print_float with double not supported from within printf
177+
#else
178+
#error Unknown MICROPY FLOAT IMPL
179+
#endif
180+
break;
181+
}
182+
#endif
183+
default:
184+
pfenv->print_strn(pfenv->data, fmt, 1);
185+
chrs += 1;
186+
break;
187+
}
188+
++fmt;
189+
}
190+
return chrs;
191+
}
192+
193+
int pfenv_printf(const pfenv_t *pfenv, const char *fmt, ...) {
194+
va_list ap;
195+
va_start(ap, fmt);
196+
int ret = pfenv_vprintf(pfenv, fmt, ap);
197+
va_end(ap);
198+
return ret;
199+
}

py/py.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ PY_O_BASENAME = \
102102
repl.o \
103103
smallint.o \
104104
pfenv.o \
105+
pfenv_printf.o \
105106
../extmod/moductypes.o
106107

107108
# prepend the build destination prefix to the py object files

py/qstrdefs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ Q(TextIOWrapper)
413413
Q(StringIO)
414414
Q(BytesIO)
415415
Q(getvalue)
416+
Q(file)
416417
#endif
417418

418419
#if MICROPY_PY_GC

py/stream.c

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,100 @@ STATIC mp_obj_t stream_read(uint n_args, const mp_obj_t *args) {
6767
nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
6868
}
6969

70+
// What to do if sz < -1? Python docs don't specify this case.
71+
// CPython does a readall, but here we silently let negatives through,
72+
// and they will cause a MemoryError.
7073
mp_int_t sz;
7174
if (n_args == 1 || ((sz = mp_obj_get_int(args[1])) == -1)) {
7275
return stream_readall(args[0]);
7376
}
7477

7578
#if MICROPY_PY_BUILTINS_STR_UNICODE
7679
if (!o->type->stream_p->is_bytes) {
77-
mp_not_implemented("Reading from unicode text streams by character count");
80+
// We need to read sz number of unicode characters. Because we don't have any
81+
// buffering, and because the stream API can only read bytes, we must read here
82+
// in units of bytes and must never over read. If we want sz chars, then reading
83+
// sz bytes will never over-read, so we follow this approach, in a loop to keep
84+
// reading until we have exactly enough chars. This will be 1 read for text
85+
// with ASCII-only chars, and about 2 reads for text with a couple of non-ASCII
86+
// chars. For text with lots of non-ASCII chars, it'll be pretty inefficient
87+
// in time and memory.
88+
89+
vstr_t vstr;
90+
vstr_init(&vstr, sz);
91+
mp_uint_t more_bytes = sz;
92+
mp_uint_t last_buf_offset = 0;
93+
while (more_bytes > 0) {
94+
char *p = vstr_add_len(&vstr, more_bytes);
95+
if (p == NULL) {
96+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_MemoryError, "out of memory"));
97+
}
98+
int error;
99+
mp_int_t out_sz = o->type->stream_p->read(o, p, more_bytes, &error);
100+
if (out_sz == -1) {
101+
vstr_cut_tail_bytes(&vstr, more_bytes);
102+
if (is_nonblocking_error(error)) {
103+
// With non-blocking streams, we read as much as we can.
104+
// If we read nothing, return None, just like read().
105+
// Otherwise, return data read so far.
106+
// TODO what if we have read only half a non-ASCII char?
107+
if (vstr.len == 0) {
108+
vstr_clear(&vstr);
109+
return mp_const_none;
110+
}
111+
break;
112+
}
113+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
114+
}
115+
116+
if (out_sz == 0) {
117+
// Finish reading.
118+
// TODO what if we have read only half a non-ASCII char?
119+
vstr_cut_tail_bytes(&vstr, more_bytes);
120+
break;
121+
}
122+
123+
// count chars from bytes just read
124+
for (mp_uint_t off = last_buf_offset;;) {
125+
byte b = vstr.buf[off];
126+
int n;
127+
if (!UTF8_IS_NONASCII(b)) {
128+
// 1-byte ASCII char
129+
n = 1;
130+
} else if ((b & 0xe0) == 0xc0) {
131+
// 2-byte char
132+
n = 2;
133+
} else if ((b & 0xf0) == 0xe0) {
134+
// 3-byte char
135+
n = 3;
136+
} else if ((b & 0xf8) == 0xf0) {
137+
// 4-byte char
138+
n = 4;
139+
} else {
140+
// TODO
141+
n = 5;
142+
}
143+
if (off + n <= vstr.len) {
144+
// got a whole char in n bytes
145+
off += n;
146+
sz -= 1;
147+
last_buf_offset = off;
148+
if (off >= vstr.len) {
149+
more_bytes = sz;
150+
break;
151+
}
152+
} else {
153+
// didn't get a whole char, so work out how many extra bytes are needed for
154+
// this partial char, plus bytes for additional chars that we want
155+
more_bytes = (off + n - vstr.len) + (sz - 1);
156+
break;
157+
}
158+
}
159+
}
160+
161+
mp_obj_t ret = mp_obj_new_str_of_type(&mp_type_str, (byte*)vstr.buf, vstr.len);
162+
vstr_clear(&vstr);
163+
return ret;
78164
}
79165
#endif
80166

0 commit comments

Comments
 (0)