Clever vsprintf

J

January Weiner

Hello,

I have the following problem.

I have a custom fprintf-like function which is supposed to prepend each
comment output line with a `%' if the output type is a Postscript
document (pseudo-code below). (Postscript does not matter here)

/* ------------------------------------------------------------- */

/* structure for holding options */
typedef struct {
int ps_generate_postscript ;
FILE *out ;
/* other options */
} opt_s ;

/* this is my custom fprintf-like function */
void print_output_line(opt_s *o, char *message, ...) {
va_list vl ;
va_start(vl,message) ;

/* check whether we are generating Postscript */
if(o->ps_generate_postscript) {
print_ps_line(o, message, vl) ;
} else {
vfprintf(o->out, message, vl) ;
fprintf(o->out, "\n") ;
}
}

/* print a Postscript comment */
/* the problem sits here */
void print_ps_line(opt_s *o, char *message, va_list vl) {

/* some PostScript specific operations go in here... */

/* print a `%', which is a comment in Postscript */
fprintf(o->out, "%% ") ;
vfprintf(o->out, message, vl) ;

}

/* ------------------------------------------------------------- */

OK, here is the problem. Given that o->ps_generate_postscript == 1, the
output of

print_output_line(o, "just a test") ;

is

% just a test

Fine. But

print_output_line(o, "just a %s test", "\n") ;

produces

% just a
test

Obviously, I would like to have the second line commented as well, or at
least to replace the newline characters with a space / whatever
(otherwise the Postscript interpreter sees "test" and does not know what
to do with it). A way to do it would be first to call

/* ------------------------------------------------------------- */
char *some_string ;
some_string = malloc(I_HAVE_NO_IDEA_HOW_MUCH * sizeof(some_string)) ;

vsprintf(some_string, message, vl) ;
/* process some_string even one character by one, if necessary */
/* ------------------------------------------------------------- */

However, how can I check how much space should I reserve for
some_string? Or, alternatively: how can I go through va_list vl and
examine it's contents?

Best regards,

January


P.S. Some time ago I have posted a problem in a thread called "Weird
runtime problem" ([email protected]). Obviously, some of
my follow-ups didn't ever got propagated to other news server than
news.uni-muenster.de. Thank you all very much for help; and BTW -- it was
a problem with my hashing function.
 
J

Jeremy Yallop

January said:
/* ------------------------------------------------------------- */
char *some_string ;
some_string = malloc(I_HAVE_NO_IDEA_HOW_MUCH * sizeof(some_string)) ;

vsprintf(some_string, message, vl) ;
/* process some_string even one character by one, if necessary */
/* ------------------------------------------------------------- */

A few ideas:

* Iterate twice over the format string: once to parse the
arguments, and estimate (an upper bound on) the length for each,
then again (via vsprintf) to actually print them. This is fairly
hard, in that you have to understand the same specifiers as
fprintf(), although you can probably ignore modifiers, etc.

* Print to a temporary file, and count the number of bytes printed
via the return value of vfprintf().

* Use snprintf() (if available) either to handle the input a bit at
a time, or to compute the total input length (by passing NULL as
the second argument).

Jeremy.
 
T

those who know me have no need of my name

in comp.lang.c i read:
va_start(vl,message) ;

as an aside: i hope you have a va_end somewhere -- you didn't show one.
However, how can I check how much space should I reserve for
some_string? Or, alternatively: how can I go through va_list vl and
examine it's contents?

there is no generic way to step through the contents of a va_list -- you
examine a vfprintf-like argument list the same way that vfprintf does, by
parsing the format string, but you might find that a tad tedious. the most
portable alternatives use tmpfile and vfprintf, then either read from the
file or use vfprintf's return value (+1 then *2) to allocate a buffer for a
subsequent call to vsprintf. horribly non-portable hacks are almost
certain to be available, but should be avoided if at all possible.

it may be that you merely need something more cleaver than a bare \n in
your messages, or some processing before passing it to your print function,
e.g.,

instead of:

print_output_line(o, "just a %s test", "foolish\nmulti-line");

you would do something like:

print_output_line(o, "just a %s test", muline(o, "foolish\nmulti-line"));

where muline has the requisite magic to transform '\n' into "\n% " when
o->ps_generate_postscript == 1, e.g., a trivial implementation might be:

[warning: untested, off the top of my head]

/* cheesy static buffer version */

#define STEP 1024

const char * muline (const opt_s * o, const char * in)
{
static size_t len;
static char * out;

assert(NULL != o);
assert(NULL != in);

if (1 != o->ps_generate_postscript) /* !1 means non-ps output */
{
return in;
}

size_t used; /* needed after loop terminates */
for (used = 0; *in; in++)
{
if (used+4 >= len) /* room for the "% " too */
{
assert(SIZE_MAX > len + STEP);
void * q = realloc(out, len + STEP);
if (! q)
{
/* memory exhausted */
abort();
}
out = q;
len += STEP;
}
out[used++] = *in;
if ('\n' == *in)
{
out[used++] = '%';
out[used++] = ' ';
}
}
out[used] = 0;
return out;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,045
Latest member
DRCM

Latest Threads

Top