/*
* Copyright (c) 2008, 2009, 2010, 2011
* Jonathan Schleifer <js@webkeks.org>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <limits.h>
#include <wchar.h>
#import "OFString.h"
#import "OFAutoreleasePool.h"
#import "asprintf.h"
#define MAX_SUBFMT_LEN 64
struct context {
const char *fmt;
size_t fmt_len;
char subfmt[MAX_SUBFMT_LEN + 1];
size_t subfmt_len;
va_list args;
char *buf;
size_t buf_len;
size_t i, last;
enum {
STATE_STRING,
STATE_FORMAT_FLAGS,
STATE_FORMAT_FIELD_WIDTH,
STATE_FORMAT_LENGTH_MODIFIER,
STATE_FORMAT_CONVERSION_SPECIFIER
} state;
enum {
LENGTH_MODIFIER_NONE,
LENGTH_MODIFIER_HH,
LENGTH_MODIFIER_H,
LENGTH_MODIFIER_L,
LENGTH_MODIFIER_LL,
LENGTH_MODIFIER_J,
LENGTH_MODIFIER_Z,
LENGTH_MODIFIER_T,
LENGTH_MODIFIER_CAPITAL_L
} len_mod;
};
static bool
append_str(struct context *ctx, const char *astr, size_t astr_len)
{
char *nbuf;
if (astr_len == 0)
return true;
if ((nbuf = realloc(ctx->buf, ctx->buf_len + astr_len + 1)) == NULL)
return false;
memcpy(nbuf + ctx->buf_len, astr, astr_len);
ctx->buf = nbuf;
ctx->buf_len += astr_len;
return true;
}
static bool
append_subfmt(struct context *ctx, const char *asubfmt, size_t asubfmt_len)
{
if (ctx->subfmt_len + asubfmt_len > MAX_SUBFMT_LEN)
return false;
memcpy(ctx->subfmt + ctx->subfmt_len, asubfmt, asubfmt_len);
ctx->subfmt_len += asubfmt_len;
ctx->subfmt[ctx->subfmt_len] = 0;
return true;
}
static bool
state_string(struct context *ctx)
{
if (ctx->fmt[ctx->i] == '%') {
if (ctx->i > 0)
if (!append_str(ctx, ctx->fmt + ctx->last,
ctx->i - ctx->last))
return false;
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
ctx->last = ctx->i + 1;
ctx->state = STATE_FORMAT_FLAGS;
}
return true;
}
static bool
state_format_flags(struct context *ctx)
{
switch (ctx->fmt[ctx->i]) {
case '-':
case '+':
case ' ':
case '#':
case '0':
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
break;
default:
ctx->state = STATE_FORMAT_FIELD_WIDTH;
ctx->i--;
break;
}
return true;
}
static bool
state_format_field_width(struct context *ctx)
{
if ((ctx->fmt[ctx->i] >= '0' && ctx->fmt[ctx->i] <= '9') ||
ctx->fmt[ctx->i] == '*' || ctx->fmt[ctx->i] == '.') {
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
} else {
ctx->state = STATE_FORMAT_LENGTH_MODIFIER;
ctx->i--;
}
return true;
}
static bool
state_format_length_modifier(struct context *ctx)
{
/* Only one allowed */
switch (ctx->fmt[ctx->i]) {
case 'h': /* and also hh */
if (ctx->fmt_len > ctx->i + 1 && ctx->fmt[ctx->i + 1] == 'h') {
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 2))
return false;
ctx->i++;
ctx->len_mod = LENGTH_MODIFIER_HH;
} else {
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
ctx->len_mod = LENGTH_MODIFIER_H;
}
break;
case 'l': /* and also ll */
if (ctx->fmt_len > ctx->i + 1 && ctx->fmt[ctx->i + 1] == 'l') {
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 2))
return false;
ctx->i++;
ctx->len_mod = LENGTH_MODIFIER_LL;
} else {
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
ctx->len_mod = LENGTH_MODIFIER_L;
}
break;
case 'j':
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
ctx->len_mod = LENGTH_MODIFIER_J;
break;
case 'z':
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
ctx->len_mod = LENGTH_MODIFIER_Z;
break;
case 't':
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
ctx->len_mod = LENGTH_MODIFIER_T;
break;
case 'L':
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
ctx->len_mod = LENGTH_MODIFIER_CAPITAL_L;
break;
default:
ctx->i--;
break;
}
ctx->state = STATE_FORMAT_CONVERSION_SPECIFIER;
return true;
}
static bool
state_format_conversion_specifier(struct context *ctx)
{
char *tmp = NULL;
int tmp_len = 0;
if (!append_subfmt(ctx, ctx->fmt + ctx->i, 1))
return false;
switch (ctx->fmt[ctx->i]) {
case '@':;
OFAutoreleasePool *pool;
ctx->subfmt[ctx->subfmt_len - 1] = 's';
@try {
pool = [[OFAutoreleasePool alloc] init];
} @catch (id e) {
[e release];
return false;
}
@try {
id obj;
if ((obj = va_arg(ctx->args, id)) != nil)
tmp_len = asprintf(&tmp, ctx->subfmt,
[[obj description] cString]);
else
tmp_len = asprintf(&tmp, ctx->subfmt, "(nil)");
} @catch (id e) {
free(ctx->buf);
@throw e;
} @finally {
[pool release];
}
break;
case 'd':
case 'i':
switch (ctx->len_mod) {
case LENGTH_MODIFIER_NONE:
case LENGTH_MODIFIER_HH:
case LENGTH_MODIFIER_H:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, int));
break;
case LENGTH_MODIFIER_L:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, long));
break;
case LENGTH_MODIFIER_LL:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, long long));
break;
case LENGTH_MODIFIER_J:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, intmax_t));
break;
case LENGTH_MODIFIER_Z:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, ssize_t));
break;
case LENGTH_MODIFIER_T:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, ptrdiff_t));
break;
default:
return false;
}
break;
case 'o':
case 'u':
case 'x':
case 'X':
switch (ctx->len_mod) {
case LENGTH_MODIFIER_NONE:
case LENGTH_MODIFIER_HH:
case LENGTH_MODIFIER_H:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, unsigned int));
break;
case LENGTH_MODIFIER_L:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, unsigned long));
break;
case LENGTH_MODIFIER_LL:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, unsigned long long));
break;
case LENGTH_MODIFIER_J:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, uintmax_t));
break;
case LENGTH_MODIFIER_Z:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, size_t));
break;
case LENGTH_MODIFIER_T:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, ptrdiff_t));
break;
default:
return false;
}
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'a':
case 'A':
switch (ctx->len_mod) {
case LENGTH_MODIFIER_NONE:
case LENGTH_MODIFIER_L:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, double));
break;
case LENGTH_MODIFIER_CAPITAL_L:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, long double));
break;
default:
return false;
}
break;
case 'c':
switch (ctx->len_mod) {
case LENGTH_MODIFIER_NONE:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, int));
break;
case LENGTH_MODIFIER_L:
#if WINT_MAX >= INT_MAX
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, wint_t));
#else
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, int));
#endif
break;
default:
return false;
}
break;
case 's':
switch (ctx->len_mod) {
case LENGTH_MODIFIER_NONE:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, const char*));
break;
case LENGTH_MODIFIER_L:
tmp_len = asprintf(&tmp, ctx->subfmt,
va_arg(ctx->args, const wchar_t*));
break;
default:
return false;
}
break;
case 'p':
if (ctx->len_mod != LENGTH_MODIFIER_NONE)
return false;
tmp_len = asprintf(&tmp, ctx->subfmt, va_arg(ctx->args, void*));
break;
case 'n':
switch (ctx->len_mod) {
case LENGTH_MODIFIER_NONE:
*va_arg(ctx->args, int*) = ctx->buf_len;
break;
case LENGTH_MODIFIER_HH:
*va_arg(ctx->args, signed char*) = ctx->buf_len;
break;
case LENGTH_MODIFIER_H:
*va_arg(ctx->args, short*) = ctx->buf_len;
break;
case LENGTH_MODIFIER_L:
*va_arg(ctx->args, long*) = ctx->buf_len;
break;
case LENGTH_MODIFIER_LL:
*va_arg(ctx->args, long long*) = ctx->buf_len;
break;
case LENGTH_MODIFIER_J:
*va_arg(ctx->args, intmax_t*) = ctx->buf_len;
break;
case LENGTH_MODIFIER_Z:
*va_arg(ctx->args, size_t*) = ctx->buf_len;
break;
case LENGTH_MODIFIER_T:
*va_arg(ctx->args, ptrdiff_t*) = ctx->buf_len;
break;
default:
return false;
}
break;
case '%':
if (ctx->len_mod != LENGTH_MODIFIER_NONE)
return false;
if (!append_str(ctx, "%", 1))
return false;
break;
default:
return false;
}
if (tmp_len == -1)
return false;
if (tmp != NULL) {
if (!append_str(ctx, tmp, tmp_len)) {
free(tmp);
return false;
}
free(tmp);
}
memset(ctx->subfmt, 0, MAX_SUBFMT_LEN);
ctx->subfmt_len = 0;
ctx->len_mod = LENGTH_MODIFIER_NONE;
ctx->last = ctx->i + 1;
ctx->state = STATE_STRING;
return true;
}
static bool (*states[])(struct context*) = {
state_string,
state_format_flags,
state_format_field_width,
state_format_length_modifier,
state_format_conversion_specifier
};
int
of_vasprintf(char **ret, const char *fmt, va_list args)
{
struct context ctx;
ctx.fmt = fmt;
ctx.fmt_len = strlen(fmt);
memset(ctx.subfmt, 0, MAX_SUBFMT_LEN + 1);
ctx.subfmt_len = 0;
va_copy(ctx.args, args);
ctx.buf_len = 0;
ctx.last = 0;
ctx.state = STATE_STRING;
ctx.len_mod = LENGTH_MODIFIER_NONE;
if ((ctx.buf = malloc(1)) == NULL)
return -1;
for (ctx.i = 0; ctx.i < ctx.fmt_len; ctx.i++) {
if (!states[ctx.state](&ctx)) {
free(ctx.buf);
return -1;
}
}
if (ctx.state != STATE_STRING) {
free(ctx.buf);
return -1;
}
if (!append_str(&ctx, ctx.fmt + ctx.last, ctx.fmt_len - ctx.last)) {
free(ctx.buf);
return -1;
}
ctx.buf[ctx.buf_len] = 0;
*ret = ctx.buf;
return ctx.buf_len;
}
int
of_asprintf(char **ret, const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
r = of_vasprintf(ret, fmt, args);
va_end(args);
return r;
}