diff --git a/src/ciabatta.c b/src/ciabatta.c index fef13f1..c38a1ab 100644 --- a/src/ciabatta.c +++ b/src/ciabatta.c @@ -50,7 +50,7 @@ #include "conv/strpfx.c" #include "conv/int.c" #include "conv/float.c" -#include "fmt/gen_fmt.c" +#include "fmt/fmt.c" #include "stdlib/algorithm.c" #include "stdlib/multibyte.c" #include "stdlib/random.c" diff --git a/src/fmt/fmt.c b/src/fmt/fmt.c new file mode 100644 index 0000000..ace1a88 --- /dev/null +++ b/src/fmt/fmt.c @@ -0,0 +1,164 @@ + +#define FLAG_ZERO 0x0001 // "0" +#define FLAG_LEFT 0x0002 // "-" +#define FLAG_PLUS 0x0004 // "+" +#define FLAG_SPACE 0x0008 // " " +#define FLAG_HASH 0x0010 // "#" +#define FLAG_PREC 0x0020 // ".precision" +#define FLAG_LONG 0x0040 // "l" +#define FLAG_LLONG 0x0080 // "ll" +#define FLAG_SHORT 0x0100 // "h" +#define FLAG_CHAR 0x0200 // "hh" +#define FLAG_UPPER 0x0400 // for hex digits 0xa-0xf + +struct fmt_t typedef fmt_t; +struct fmt_t { + int prec; + int width; + int flags; +}; + +#define ctype char +#define pfx(f) f +#include "fmt_string.h" +#include "fmt_stream.h" +#include "ints.h" +#include "fmt.h" +#undef ctype +#undef pfx + +#define ctype wchar_t +#define pfx(f) w ## f +#include "fmt_string.h" +#include "fmt_stream.h" +#include "ints.h" +#include "fmt.h" +#undef ctype +#undef pfx + +// fprintf family + +static inline int fstream_outc(void *ctx, char c) { + FILE *file = ctx; + int result = fputc(c, file); + if(result == EOF) { + return -1; + } + return 1; +} + +static inline int fstream_outw(void *ctx, wchar c) { + FILE *file = ctx; + int result = fputc((char)c, file); // TODO: utf8 + if(result == EOF) { + return -1; + } + return 1; +} + +static inline fmt_stream_t fstream_new(FILE *file) { + fmt_stream_t stream; + stream.w = 0; + stream.ctx = file; + stream.out_ctype = fstream_outc; + stream.out_wchar = fstream_outw; + stream.out_char = fstream_outc; + return stream; +} + +int vfprintf(FILE *restrict file, const char *restrict string, va_list va) { + fmt_stream_t stream = fstream_new(file); + return fmt(&stream, string, va); +} + +int vprintf(const char *restrict string, va_list va) { + return vfprintf(stdout, string, va); +} + +int fprintf(FILE *restrict file, const char *restrict string, ...) { + va_list va; + va_start(va, string); + int result = vfprintf(file, string, va); + va_end(va); + return result; +} + +int printf(const char *restrict string, ...) { + va_list va; + va_start(va, string); + int result = vfprintf(stdout, string, va); + va_end(va); + return result; +} + +// s(n)printf family + +struct str_slice_t typedef str_slice_t; +struct str_slice_t { + char *str; + size_t max_size; + size_t written; +}; + +static inline int sstream_outc(void *ctx, char c) { + str_slice_t *slice = ctx; + if(slice->written+1 < slice->max_size) { + return -1; + } + slice->str[slice->written++] = c; + return 1; +} + +static inline int sstream_outw(void *ctx, wchar c) { + str_slice_t *slice = ctx; + // TODO: utf8 + if(slice->written+1 < slice->max_size) { + return -1; + } + slice->str[slice->written++] = c; + return 1; +} + +static inline fmt_stream_t sstream_new(str_slice_t *slice) { + fmt_stream_t stream; + stream.w = 0; + stream.ctx = slice; + stream.out_ctype = sstream_outc; + stream.out_wchar = sstream_outw; + stream.out_char = sstream_outc; + return stream; +} + +int vsnprintf(char *restrict buf, size_t cbbuf, const char *restrict str, va_list va) { + str_slice_t slice; + slice.str = buf; + slice.max_size = cbbuf; + slice.written = 0; + fmt_stream_t stream = sstream_new(&slice); + return fmt(&stream, str, va); +} + +int vsprintf(char *restrict buf, const char *restrict str, va_list va) { + str_slice_t slice; + slice.str = buf; + slice.max_size = SIZE_MAX; + slice.written = 0; + fmt_stream_t stream = sstream_new(&slice); + return fmt(&stream, str, va); +} + +int snprintf(char *restrict buf, size_t cbbuf, const char *restrict str, ...) { + va_list va; + va_start(va, str); + int result = vsnprintf(buf, cbbuf, str, va); + va_end(va); + return result; +} + +int sprintf(char *restrict buf, const char *restrict str, ...) { + va_list va; + va_start(va, str); + int result = vsprintf(buf, str, va); + va_end(va); + return result; +} diff --git a/src/fmt/fmt.h b/src/fmt/fmt.h new file mode 100644 index 0000000..6d263c7 --- /dev/null +++ b/src/fmt/fmt.h @@ -0,0 +1,44 @@ + +static inline int pfx(fmt_arg)(fmt_stream_t *stream, ctype const **strp, va_list va) { + ctype const *str = *strp; + fmt_t fmt = {0}; + if(!pfx(fmt_read)(str, &fmt, va)) { + return 0; + } + switch(*str) { + case 'i': + case 'u': + case 'b': + case 'o': + case 'd': + case 'x': + case 'X': + { + int base; + int sign; + u64 abs = pfx(fmt_int_arg)(stream, &fmt, &str, &sign, &base, va); + if(pfx(fmt_int)(stream, fmt, sign, abs, base) == -1) { + return -1; + } + } break; + } + *strp = str; + return 1; +} + +static inline int pfx(fmt)(fmt_stream_t *stream, ctype const *str, va_list va) { + stream->w = 0; + while(*str) { + if(*str == '%') { + ++str; + int ok = pfx(fmt_arg)(stream, &str, va); + if(!ok) { + return -1; + } + } + else { + stream_out(stream, *str++); + } + } + return stream->w; +} diff --git a/src/fmt/fmt_stream.h b/src/fmt/fmt_stream.h new file mode 100644 index 0000000..22e9a5d --- /dev/null +++ b/src/fmt/fmt_stream.h @@ -0,0 +1,13 @@ + +struct pfx(fmt_stream_t) typedef pfx(fmt_stream_t); +struct pfx(fmt_stream_t) { + int w; + void *ctx; + int (*pfx(out_ctype))(void *ctx, ctype ch); + int (*pfx(out_wchar))(void *ctx, wchar ch); + int (*pfx(out_char)) (void *ctx, char ch); +}; + +#define stream_out(s, ch) if((s->w++, !s->out_ctype(s->ctx, ch))) return -1 +#define stream_outc(s, ch) if((s->w++, !s->out_char (s->ctx, ch))) return -1 +#define stream_outw(s, ch) if((s->w++, !s->out_wchar(s->ctx, ch))) return -1 diff --git a/src/fmt/fmt_string.h b/src/fmt/fmt_string.h new file mode 100644 index 0000000..1c0eb8f --- /dev/null +++ b/src/fmt/fmt_string.h @@ -0,0 +1,74 @@ + +static inline int pfx(fmt_isdigit)(ctype ch) { + return '0' <= ch && ch <= '9'; +} + +static inline int pfx(fmt_atoi)(ctype const **sp, int *err) { + int i = 0; + ctype const *s = *sp; + while(pfx(fmt_isdigit(*s))) { + int d = *s-'0'; + if(i > (INT_MAX - d)/10) { + *err = 1; + return 0; + } + i = 10*i + d; + ++s; + } + *sp = s; + return i; +} + +static inline int pfx(fmt_read)(const ctype *str, fmt_t *fmt, va_list va) { + int flags = 0; + // Parse flag field + { + int isflag = 0; + do { + isflag = 1; + switch(*str) { + case '0': flags |= FLAG_ZERO; break; + case '-': flags |= FLAG_LEFT; break; + case '+': flags |= FLAG_PLUS; break; + case ' ': flags |= FLAG_SPACE; break; + case '#': flags |= FLAG_HASH; break; + default: isflag = 0; + } + if(isflag) ++str; + } while(isflag); + // If '-' and '0' are together we just discard '0' + if(flags & FLAG_LEFT) flags &= ~FLAG_ZERO; + } + // Parse width field + int width = 0; + if(pfx(fmt_isdigit(*str))) { + int err = 0; + width = pfx(fmt_atoi(&str, &err)); + if(err) return 0; + } + else if(*str == '*') { + ++str; + width = va_arg(va, int); + if(width < 0) { + width = -width; + flags |= FLAG_LEFT; + } + } + // If present, parse the precision field + int prec = 0; + if(*str == '.') { + flags |= FLAG_PREC; + ++str; + if(pfx(fmt_isdigit(*str))) { + int err = 0; + prec = pfx(fmt_atoi(&str, &err)); + if(err) return -1; + } + else if(*str == '*') { + ++str; + prec = va_arg(va, int); + prec = (prec > 0? prec : 0); + } + } + return 1; +} diff --git a/src/fmt/ints.h b/src/fmt/ints.h new file mode 100644 index 0000000..2a72d73 --- /dev/null +++ b/src/fmt/ints.h @@ -0,0 +1,150 @@ + +static inline u64 pfx(fmt_int_arg)( + fmt_stream_t *stream, + fmt_t *fmt, + ctype const **strp, + int *signp, + int *basep, + va_list va +) { + ctype const *str = *strp; + // Get integer properties + int base; + int is_signed; + switch(*str) { + case 'o': base = 8; break; + case 'b': base = 2; break; + case 'x': + case 'X': base = 16; break; + default: base = 10; + } + switch(*str) { + case 'd': is_signed = 1; break; + case 'i': is_signed = 1; break; + default: is_signed = 0; + } + if(*str == 'X') { + fmt->flags |= FLAG_UPPER; + } + // Discard some flags + if(base == 10) { + fmt->flags &= ~FLAG_HASH; + } + if(!is_signed) { + fmt->flags &= ~(FLAG_PLUS|FLAG_SPACE); + } + if(fmt->flags & FLAG_PREC) { + fmt->flags &= ~FLAG_ZERO; + } + // Parse sign and abs value of an integer + int sign = 0; + u64 abs_value = 0; + if(is_signed) { + i64 num; + if(fmt->flags & FLAG_LLONG) { + num = (i64)va_arg(va, intll); + } + else if(fmt->flags & FLAG_LONG) { + num = (i64)va_arg(va, intl); + } + else { + num = (i64)va_arg(va, int); + } + // Handle overflow on '-' + if(num == LLONG_MIN) { + sign = 1; + abs_value = (u64)LLONG_MAX + 1; + } + else { + sign = num >= 0? 0 : 1; + num = num >= 0? num : -num; + abs_value = (u64)num; + } + } + else { + if(fmt->flags & FLAG_LLONG) { + abs_value = (u64)va_arg(va, intull); + } + else if(fmt->flags & FLAG_LONG) { + abs_value = (u64)va_arg(va, intul); + } + else { + abs_value = (u64)va_arg(va, intu); + } + } + ++str; + *strp = str; + *basep = base; + *signp = sign; + return abs_value; +} + +static inline int pfx(fmt_int)( + fmt_stream_t *stream, + fmt_t fmt, + int neg, + u64 value, + int base) { + // We'll Write digits into buf in reverse and then call _out_rbuf + int ndigits = 0; + ctype digits[64] = {0}; + // Remove hash flag for 0 values + if(value == 0) fmt.flags &= ~FLAG_HASH; + // Write digits to buffer in reverse (starting from least significant) + do { + int d = (int)(value%base); + value /= base; + if(d < 10) + d += '0'; + else if(fmt.flags & FLAG_UPPER) + d += 'A' - 10; + else d += 'a' - 10; + digits[ndigits++] = d; + } while(value); + // Figure out the length of the prefix (part of number before digit stirng) + int pref_len = 0; + if(fmt.flags & FLAG_HASH) pref_len = base == 8? 1 : 2; + else if(neg) pref_len = 1; + else if(fmt.flags & FLAG_PLUS) pref_len = 1; + else if(fmt.flags & FLAG_SPACE) pref_len = 1; + if(ndigits > fmt.prec) fmt.prec = ndigits; + int num_len = pref_len + fmt.prec; + int pad_len = fmt.width - num_len; + if(!(fmt.flags & FLAG_LEFT)) { + ctype pad_ch = ' '; + if(fmt.flags & FLAG_ZERO) pad_ch = '0'; + while(pad_len-- > 0) { + stream_out(stream, pad_ch); + } + } + // Print width left-pad if it's made out of space + if(!(fmt.flags & FLAG_LEFT) && !(fmt.flags & FLAG_ZERO)) while(pad_len-- > 0) { + stream_out(stream, ' '); + } + // Print prefix + if(fmt.flags & FLAG_HASH) { + stream_out(stream, '0'); + if(base == 16) { stream_out(stream, 'x'); } + else if(base == 2) { stream_out(stream, 'b'); } + } + else if(neg) { stream_out(stream, '-'); } + else if(fmt.flags & FLAG_PLUS) { stream_out(stream, '+'); } + else if(fmt.flags & FLAG_SPACE) { stream_out(stream, ' '); } + // Print width left-pad if it's made out of zero + if(!(fmt.flags & FLAG_LEFT) && (fmt.flags & FLAG_ZERO)) while(pad_len-- > 0) { + stream_out(stream, '0'); + } + // Print zero-pad due to precision + for(int i = ndigits; i < fmt.prec; ++i) { + stream_out(stream, '0'); + } + // Output buffer in reverse + if(ndigits) while(ndigits--) { + stream_out(stream, digits[ndigits]); + } + // Print right-pad + if(fmt.flags & FLAG_LEFT) while(pad_len-- > 0) { + stream_out(stream, ' '); + } + return stream->w; +} diff --git a/test.cmd b/test.cmd deleted file mode 100644 index c543659..0000000 --- a/test.cmd +++ /dev/null @@ -1,2 +0,0 @@ - -clang -std=c11 test\%1 utf8.obj -Iinc -g -lciabatta.lib -nostdlib -fstack-protector \ No newline at end of file diff --git a/test/printf.c b/test/printf.c new file mode 100644 index 0000000..7910aaf --- /dev/null +++ b/test/printf.c @@ -0,0 +1,12 @@ + +#include +#include +#include + +int main() { + printf("%i\n", INT_MAX); + printf("%i\n", INT_MIN); + printf("%x\n", UINT_MAX); + printf("%x\n", 0); + return 0; +}