Browse Source

Merge pull request #801 from svaarala/minimal-printf-provider

Add a minimal Duktape-optimized printf() provider to extras
pull/803/head
Sami Vaarala 9 years ago
parent
commit
53599c04e3
  1. 6
      RELEASES.rst
  2. 2
      config/other-defines/platform_functions.yaml
  3. 5
      extras/minimal-printf/Makefile
  4. 110
      extras/minimal-printf/README.rst
  5. 271
      extras/minimal-printf/duk_minimal_printf.c
  6. 12
      extras/minimal-printf/duk_minimal_printf.h
  7. 190
      extras/minimal-printf/test.c
  8. 9
      util/make_dist.py

6
RELEASES.rst

@ -1665,6 +1665,12 @@ Planned
* Add an extra module (extras/console) providing a minimal 'console' binding
(GH-767)
* Add an extra module (extras/minimal-printf) providing minimal,
Duktape-optimized sprintf(), snprintf(), vsnprintf(), and sscanf()
implementations; the extra compiles to less than 1kB of code which is
useful on bare metal platforms where an external printf() or scanf()
dependency may have a large footprint impact (often 10-30 kB) (GH-801)
* Fix a harmless compilation warning related to a shadowed variable (GH-793,
GH-794)

2
config/other-defines/platform_functions.yaml

@ -43,7 +43,7 @@
# String printing and parsing
- define: DUK_SPRINTF
- define: DUK_SNPRINTF
- define: DUK_VSNPRINT
- define: DUK_VSNPRINTF
- define: DUK_SSCANF
- define: DUK_VSSCANF

5
extras/minimal-printf/Makefile

@ -0,0 +1,5 @@
# Just for manual testing
.PHONY: test
test: duk_minimal_printf.c
gcc -fno-stack-protector -m32 -otest -Wall -Wextra -Os -fomit-frame-pointer duk_minimal_printf.c test.c
./test

110
extras/minimal-printf/README.rst

@ -0,0 +1,110 @@
==============================================
Minimal sprintf/sscanf replacement for Duktape
==============================================
The ``duk_minimal_printf.c`` provides a portable provider for sprintf()/scanf()
with a feature set matching minimally what Duktape needs. The provider
compiles to less than 1kB. The functions provided are::
sprintf()
snprintf()
vsnprintf()
sscanf()
Assumptions:
* ``sizeof(void *) <= sizeof(long)``
* ``sizeof(long) <= 8``
Note that these assumptions don't hold e.g. on 64-bit Windows. This printf
provider is mostly useful for low memory targets where these assumptions are
typically not an issue. The limitations are easy to fix if one relies more
on platform typing.
Supported formatting
====================
sprintf()
---------
Duktape relies on a ``sprintf()`` provider which supports at least the
following (this list is from Duktape 1.5.0)::
%c
%s
%p
%02d
%03d
%ld
%lld (JSON fast path only)
%lu
%lx
%02lx
%08lx
This minimal provider supports the following slightly different set:
* Character format ``%c``.
* String format ``%s``.
* Pointer format ``%p``.
* Integer formats ``%d``, ``%ld``, ``%lu`` with optional padding and
length modifiers.
* Hex formats ``%x``, ``%lx`` with optional padding and length modifiers.
The ``%lld`` format is not supported to avoid depending on the ``long long``
type; this makes the replacement incompatible with the JSON fast path which
must thus be disabled.
sscanf()
--------
There's only one call site for ``sscanf()``, for JX parsing of pointers::
duk_bi_json.c: (void) DUK_SSCANF((const char *) js_ctx->p, DUK_STR_FMT_PTR, &voidptr);
The exact format string here is ``%p`` and nothing else needs to be supported.
Further, when the minimal printf/scanf providers are used together we only
need to parse what we produce. In particular:
* Pointer prefix is ``0x``, no need to match ``0X`` for example.
* All digits are ``[0-9a-f]`` with no need to match uppercase.
Building "duk" with minimal printf/scanf
========================================
The necessary defines in ``duk_config.h`` can be given to genconfig, but you
can also just make the following manual additions to the bottom of the config
file::
#include "duk_minimal_printf.h"
#undef DUK_SPRINTF
#define DUK_SPRINTF duk_minimal_sprintf
#undef DUK_SNPRINTF
#define DUK_SNPRINTF duk_minimal_snprintf
#undef DUK_VSNPRINTF
#define DUK_VSNPRINTF duk_minimal_vsnprintf
#undef DUK_SSCANF
#define DUK_SSCANF duk_minimal_sscanf
Then just add ``duk_minimal_printf.c`` to build and compile the application.
Future work
===========
* Add support for ``%lld`` (maybe conditional) to allow JSON fast path to
be supported.
* Add support for platforms such as 64-bit Windows where
``sizeof(long) < sizeof(void *)``. This can be achieved by using a few
typedefs internally; typedef an integer type large enough to hold all
formatted types.

271
extras/minimal-printf/duk_minimal_printf.c

@ -0,0 +1,271 @@
/*
* Minimal sprintf() for Duktape.
*/
#include <stdarg.h> /* va_list etc */
#include <stddef.h> /* size_t */
#include <stdint.h> /* SIZE_MAX */
#define DUK__WRITE_CHAR(c) do { \
if (off < size) { \
str[off] = (char) c; \
} \
off++; \
} while (0)
static const char duk__fmt_nybbles[16] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
static size_t duk__format_long(char *str, size_t size, size_t off, int fixed_length, char pad, int radix, int neg_sign, unsigned long v) {
char buf[24]; /* 2^64 = 18446744073709552000, length 20 */
char *required;
char *p;
int i;
/* Format in reverse order first. Ensure at least one digit is output
* to handle '0' correctly. Note that space padding and zero padding
* handle negative sign differently:
*
* %9d and -321 => ' -321'
* %09d and -321 => '-00000321'
*/
for (i = 0; i < (int) sizeof(buf); i++) {
buf[i] = pad; /* compiles into memset() equivalent, avoid memset() dependency */
}
p = buf;
do {
*p++ = duk__fmt_nybbles[v % radix];
v /= radix;
} while (v != 0);
required = buf + fixed_length;
if (pad == (char) '0' && fixed_length > 0 /* handle "%0d" correctly, though insane */) {
/* Leave space for negative sign. */
if (p < required - neg_sign) {
p = required - neg_sign;
}
}
if (neg_sign) {
*p++ = '-';
}
if (p < required) {
p = required;
}
/* Now [buf,p[ contains the result in reverse; copy into place. */
while (p > buf) {
p--;
DUK__WRITE_CHAR(*p);
}
return off;
}
static int duk__parse_pointer(const char *str, void **out) {
const unsigned char *p;
long val; /* assume void * fits into long */
int count;
unsigned char ch;
/* We only need to parse what our minimal printf() produces, so that
* we can check for a '0x' prefix, and assume all hex digits are
* lowercase.
*/
p = (const unsigned char *) str;
if (*p++ != (unsigned char) '0') {
return 0;
}
if (*p++ != (unsigned char) 'x') {
return 0;
}
for (val = 0, count = 0; count < (int) (sizeof(void *) * 2); count++) {
ch = *p++;
val <<= 4;
if (ch >= (unsigned char) '0' && ch <= (unsigned char) '9') {
val += ch - (unsigned char) '0';
} else if (ch >= (unsigned char) 'a' && ch <= (unsigned char) 'f') {
val += ch - (unsigned char) 'a' + 0x0a;
} else {
return 0;
}
}
/* The input may end at a NUL or garbage may follow. As long as we
* parse the '%p' correctly, garbage is allowed to follow, and the
* JX pointer parsing also relies on that.
*/
*out = (void *) val;
return 1;
}
int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
size_t off = 0;
const char *p;
const char *p_tmp;
const char *p_fmt_start;
char c;
char pad;
int fixed_length;
int is_long;
/* Assume str != NULL unless size == 0.
* Assume format != NULL.
*/
p = format;
for (;;) {
c = *p++;
if (c == (char) 0) {
break;
}
if (c != (char) '%') {
DUK__WRITE_CHAR(c);
continue;
}
/* Start format sequence. Scan flags and format specifier. */
p_fmt_start = p - 1;
is_long = 0;
pad = ' ';
fixed_length = 0;
for (;;) {
c = *p++;
if (c == (char) 'l') {
is_long = 1;
} else if (c == (char) '0') {
/* Only support pad character '0'. */
pad = '0';
} else if (c >= (char) '1' && c <= (char) '9') {
/* Only support fixed lengths 1-9. */
fixed_length = (int) (c - (char) '0');
} else if (c == (char) 'd') {
long v;
int neg_sign = 0;
if (is_long) {
v = va_arg(ap, long);
} else {
v = (long) va_arg(ap, int);
}
if (v < 0) {
neg_sign = 1;
v = -v;
}
off = duk__format_long(str, size, off, fixed_length, pad, 10, neg_sign, (unsigned long) v);
break;
} else if (c == (char) 'u') {
unsigned long v;
if (is_long) {
v = va_arg(ap, unsigned long);
} else {
v = (unsigned long) va_arg(ap, unsigned int);
}
off = duk__format_long(str, size, off, fixed_length, pad, 10, 0, v);
break;
} else if (c == (char) 'x') {
unsigned long v;
if (is_long) {
v = va_arg(ap, unsigned long);
} else {
v = (unsigned long) va_arg(ap, unsigned int);
}
off = duk__format_long(str, size, off, fixed_length, pad, 16, 0, v);
break;
} else if (c == (char) 'c') {
char v;
v = va_arg(ap, int); /* intentionally not 'char' */
DUK__WRITE_CHAR(v);
break;
} else if (c == (char) 's') {
const char *v;
char c_tmp;
v = va_arg(ap, const char *);
if (v) {
for (;;) {
c_tmp = *v++;
if (c_tmp) {
DUK__WRITE_CHAR(c_tmp);
} else {
break;
}
}
}
break;
} else if (c == (char) 'p') {
/* Assume a void * can be represented by 'long'. This is not
* always the case. NULL pointer is printed out as 0x0000...
*/
void *v;
v = va_arg(ap, void *);
DUK__WRITE_CHAR('0');
DUK__WRITE_CHAR('x');
off = duk__format_long(str, size, off, sizeof(void *) * 2, '0', 16, 0, (unsigned long) v);
break;
} else {
/* Unrecognized, just copy verbatim. */
#if 0
DUK__WRITE_CHAR('!');
#endif
for (p_tmp = p_fmt_start; p_tmp != p; p_tmp++) {
DUK__WRITE_CHAR(*p_tmp);
}
break;
}
}
}
if (off < size) {
str[off] = (char) 0; /* No increment for 'off', not counted in return value. */
} else if (size > 0) {
/* Forced termination. */
str[size - 1] = 0;
}
return (int) off;
}
int duk_minimal_snprintf(char *str, size_t size, const char *format, ...) {
va_list ap;
int ret;
va_start(ap, format);
ret = duk_minimal_vsnprintf(str, size, format, ap);
va_end(ap);
return ret;
}
int duk_minimal_sprintf(char *str, const char *format, ...) {
va_list ap;
int ret;
va_start(ap, format);
ret = duk_minimal_vsnprintf(str, SIZE_MAX, format, ap);
va_end(ap);
return ret;
}
int duk_minimal_sscanf(const char *str, const char *format, ...) {
va_list ap;
int ret;
void **out;
/* Only the exact "%p" format is supported. */
if (format[0] != (char) '%' || format[1] != (char) 'p' ||
format[2] != (char) 0) {
}
va_start(ap, format);
out = va_arg(ap, void **);
ret = duk__parse_pointer(str, out);
va_end(ap);
return ret;
}
#undef DUK__WRITE_CHAR

12
extras/minimal-printf/duk_minimal_printf.h

@ -0,0 +1,12 @@
#if !defined(DUK_MINIMAL_PRINTF_H_INCLUDED)
#define DUK_MINIMAL_PRINTF_H_INCLUDED
#include <stdarg.h> /* va_list etc */
#include <stddef.h> /* size_t */
extern int duk_minimal_sprintf(char *str, const char *format, ...);
extern int duk_minimal_snprintf(char *str, size_t size, const char *format, ...);
extern int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap);
extern int duk_minimal_sscanf(const char *str, const char *format, ...);
#endif /* DUK_MINIMAL_PRINTF_H_INCLUDED */

190
extras/minimal-printf/test.c

@ -0,0 +1,190 @@
#include <stdio.h>
#include <string.h>
#include "duk_minimal_printf.h"
char buffer[32];
static void init_buffer(void) {
int i;
for (i = 0; i < (int) sizeof(buffer); i++) {
buffer[i] = 0xff;
}
}
static void dump_buffer(void) {
int i;
unsigned char c;
printf("Buffer: '");
for (i = 0; i < (int) sizeof(buffer); i++) {
c = (unsigned char) buffer[i];
if (c < 0x20 || c >= 0x7e) {
printf("<%02x>", (unsigned int) c);
} else {
printf("%c", (int) c);
}
}
printf("'");
#if 0
printf(" -> ");
printf("Buffer:");
for (i = 0; i < sizeof(buffer); i++) {
c = (unsigned char) buffer[i];
if (c <= 0x20 || c >= 0x7e) {
printf(" <%02x>", (unsigned int) c);
} else {
printf(" %c", (char) c);
}
}
#endif
printf("\n");
}
int main(int argc, char *argv[]) {
int ret;
void *voidptr;
int i;
(void) argc; (void) argv;
/* Char format. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "foo %c bar", 'Z');
dump_buffer();
/* Signed long format. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%ld %9ld", (long) 123, (long) 4321);
dump_buffer();
/* Signed long with zero padding. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%09ld", (long) 4321);
dump_buffer();
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%03ld %03ld %03ld", (long) -4321, (long) -432, (long) -43);
dump_buffer();
/* Unsigned long with zero padding. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%03lu %03lu %03lu", (long) -4321, (long) -432, (long) -43);
dump_buffer();
/* Signed integer. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%d %9d", (int) 0, (int) 4321);
dump_buffer();
/* Signed negative integer, fixed field width. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%9d", (int) -321);
dump_buffer();
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%09d", (int) -321);
dump_buffer();
printf(" -- printf comparison: %9d %09d\n", -321, -321);
/* Hex formatting. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%03x %03lx 0x%08lx", (int) 510, (long) 5105, (long) 0xdeadbeef);
dump_buffer();
/* Pointer formatting, NULL and non-NULL. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%p %p", (void *) NULL, (void *) buffer);
dump_buffer();
/* File/line like format test. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%s:%d", "foo bar quux", 123);
dump_buffer();
/* Zero size output buffer. */
init_buffer();
duk_minimal_snprintf(buffer, 0, "%s:%d", "foo bar quux", 123);
dump_buffer();
init_buffer();
duk_minimal_snprintf(buffer, 0, "");
dump_buffer();
/* NUL terminator boundary test. */
init_buffer();
duk_minimal_snprintf(buffer, 7, "foo: %s", "bar");
dump_buffer();
init_buffer();
duk_minimal_snprintf(buffer, 8, "foo: %s", "bar");
dump_buffer();
init_buffer();
duk_minimal_snprintf(buffer, 9, "foo: %s", "bar");
dump_buffer();
/* sprintf() binding, uses SIZE_MAX internally. */
init_buffer();
duk_minimal_sprintf(buffer, "unbounded print %s", "foo");
dump_buffer();
/* Pointer formatting; non-NULL and NULL. */
init_buffer();
duk_minimal_snprintf(buffer, sizeof(buffer), "%p %p", (void *) NULL, (void *) 0xdeadbeef);
dump_buffer();
/* Pointer parsing, non-NULL (32-bit) pointer. */
voidptr = (void *) 123;
ret = duk_minimal_sscanf("0xdeadbeef", "%p", &voidptr);
printf("ret=%d, void pointer: %p\n", ret, voidptr);
/* Pointer parsing, NULL (32-bit) pointer. */
voidptr = (void *) 123;
ret = duk_minimal_sscanf("0x00000000", "%p", &voidptr);
printf("ret=%d, void pointer: %p\n", ret, voidptr);
/* Pointer parsing, non-NULL (32-bit) pointer but garbage follows. */
voidptr = (void *) 123;
ret = duk_minimal_sscanf("0xdeadbeefx", "%p", &voidptr);
printf("ret=%d, void pointer: %p\n", ret, voidptr);
/* Fixed width test over a range of widths. */
for (i = 0; i <= 9; i++) {
char fmtbuf[16];
printf("--- pos/neg fixed width test, i=%d\n", i);
/* %0<i>d. %00d makes no sense, but tested anyway. */
memset((void *) fmtbuf, 0, sizeof(fmtbuf));
fmtbuf[0] = (char) '%';
fmtbuf[1] = (char) '0';
fmtbuf[2] = (char) ('0' + i);
fmtbuf[3] = 'd';
init_buffer();
duk_minimal_sprintf(buffer, (const char *) fmtbuf, 321);
dump_buffer();
init_buffer();
duk_minimal_sprintf(buffer, (const char *) fmtbuf, -321);
dump_buffer();
printf(" ==> printf: |");
printf((const char *) fmtbuf, 321);
printf("| |");
printf((const char *) fmtbuf, -321);
printf("|\n");
/* %<i>d. */
memset((void *) fmtbuf, 0, sizeof(fmtbuf));
fmtbuf[0] = (char) '%';
fmtbuf[1] = (char) ('0' + i);
fmtbuf[2] = 'd';
init_buffer();
duk_minimal_sprintf(buffer, (const char *) fmtbuf, 321);
dump_buffer();
init_buffer();
duk_minimal_sprintf(buffer, (const char *) fmtbuf, -321);
dump_buffer();
printf(" ==> printf: |");
printf((const char *) fmtbuf, 321);
printf("| |");
printf((const char *) fmtbuf, -321);
printf("|\n");
}
return 0;
}

9
util/make_dist.py

@ -158,6 +158,7 @@ def create_dist_directories(dist):
mkdir(os.path.join(dist, 'extras', 'print-alert'))
mkdir(os.path.join(dist, 'extras', 'console'))
mkdir(os.path.join(dist, 'extras', 'logging'))
mkdir(os.path.join(dist, 'extras', 'minimal-printf'))
mkdir(os.path.join(dist, 'polyfills'))
#mkdir(os.path.join(dist, 'doc')) # Empty, so omit
mkdir(os.path.join(dist, 'licenses'))
@ -608,6 +609,14 @@ copy_files([
'Makefile'
], os.path.join('extras', 'console'), os.path.join(dist, 'extras', 'console'))
copy_files([
'README.rst',
'duk_minimal_printf.c',
'duk_minimal_printf.h',
'Makefile',
'test.c'
], os.path.join('extras', 'minimal-printf'), os.path.join(dist, 'extras', 'minimal-printf'))
copy_files([
'Makefile.cmdline',
'Makefile.dukdebug',

Loading…
Cancel
Save