1/* -----------------------------------------------------------------------------
2 * fio.c
3 *
4 *     This file implements a number of standard I/O operations included
5 *     formatted output, readline, and splitting.
6 *
7 * Author(s) : David Beazley (beazley@cs.uchicago.edu)
8 *
9 * Copyright (C) 1999-2000.  The University of Chicago
10 * See the file LICENSE for information on usage and redistribution.
11 * ----------------------------------------------------------------------------- */
12
13char cvsroot_fio_c[] = "$Id: fio.c 9607 2006-12-05 22:11:40Z beazley $";
14
15#include "dohint.h"
16
17#define OBUFLEN  512
18
19static DOH *encodings = 0;	/* Encoding hash */
20
21/* -----------------------------------------------------------------------------
22 * Writen()
23 *
24 * Write's N characters of output and retries until all characters are
25 * written.  This is useful should a write operation encounter a spurious signal.
26 * ----------------------------------------------------------------------------- */
27
28static int Writen(DOH *out, void *buffer, int len) {
29  int nw = len, ret;
30  char *cb = (char *) buffer;
31  while (nw) {
32    ret = Write(out, cb, nw);
33    if (ret < 0)
34      return -1;
35    nw = nw - ret;
36    cb += ret;
37  }
38  return len;
39}
40
41/* -----------------------------------------------------------------------------
42 * DohEncoding()
43 *
44 * Registers a new printf encoding method.  An encoder function should accept
45 * two file-like objects and operate as a filter.
46 * ----------------------------------------------------------------------------- */
47
48void DohEncoding(char *name, DOH *(*fn) (DOH *s)) {
49  if (!encodings)
50    encodings = NewHash();
51  Setattr(encodings, (void *) name, NewVoid((void *) fn, 0));
52}
53
54/* internal function for processing an encoding */
55static DOH *encode(char *name, DOH *s) {
56  DOH *handle, *ns;
57  DOH *(*fn) (DOH *);
58  long pos;
59  char *cfmt = strchr(name, ':');
60  DOH *tmp = 0;
61  if (cfmt) {
62    tmp = NewString(cfmt + 1);
63    Append(tmp, s);
64    Setfile(tmp, Getfile((DOH *) s));
65    Setline(tmp, Getline((DOH *) s));
66    *cfmt = '\0';
67  }
68  if (!encodings || !(handle = Getattr(encodings, name))) {
69    return Copy(s);
70  }
71  if (tmp)
72    s = tmp;
73  pos = Tell(s);
74  Seek(s, 0, SEEK_SET);
75  fn = (DOH *(*)(DOH *)) Data(handle);
76  ns = (*fn) (s);
77  Seek(s, pos, SEEK_SET);
78  if (tmp)
79    Delete(tmp);
80  return ns;
81}
82
83/* -----------------------------------------------------------------------------
84 * DohvPrintf()
85 *
86 * DOH implementation of printf.  Output can be directed to any file-like object
87 * including bare FILE * objects.  The same formatting codes as printf are
88 * recognized with two extensions:
89 *
90 *       %s          - Prints a "char *" or the string representation of any
91 *                     DOH object.  This will implicitly result in a call to
92 *                     Str(obj).
93 *
94 *       %(encoder)* - Filters the output through an encoding function registered
95 *                     with DohEncoder().
96 *
97 * Note: This function is not particularly memory efficient with large strings.
98 * It's better to use Dump() or some other method instead.
99 * ----------------------------------------------------------------------------- */
100
101int DohvPrintf(DOH *so, const char *format, va_list ap) {
102  static char *fmt_codes = "dioxXucsSfeEgGpn";
103  int state = 0;
104  const char *p = format;
105  char newformat[256];
106  char obuffer[OBUFLEN];
107  char *fmt = 0;
108  char temp[64];
109  int widthval = 0;
110  int precval = 0;
111  int maxwidth;
112  char *w = 0;
113  int ivalue;
114  double dvalue;
115  void *pvalue;
116  char *stemp;
117  int nbytes = 0;
118  char encoder[128], *ec = 0;
119  int plevel = 0;
120
121  memset(newformat, 0, sizeof(newformat));
122
123  while (*p) {
124    switch (state) {
125    case 0:			/* Ordinary text */
126      if (*p != '%') {
127	Putc(*p, so);
128	nbytes++;
129      } else {
130	fmt = newformat;
131	widthval = 0;
132	precval = 0;
133	*(fmt++) = *p;
134	encoder[0] = 0;
135	state = 10;
136      }
137      break;
138    case 10:			/* Look for a width and precision */
139      if (isdigit((int) *p) && (*p != '0')) {
140	w = temp;
141	*(w++) = *p;
142	*(fmt++) = *p;
143	state = 20;
144      } else if (strchr(fmt_codes, *p)) {
145	/* Got one of the formatting codes */
146	p--;
147	state = 100;
148      } else if (*p == '*') {
149	/* Width field is specified in the format list */
150	widthval = va_arg(ap, int);
151	sprintf(temp, "%d", widthval);
152	for (w = temp; *w; w++) {
153	  *(fmt++) = *w;
154	}
155	state = 30;
156      } else if (*p == '%') {
157	Putc(*p, so);
158	fmt = newformat;
159	nbytes++;
160	state = 0;
161      } else if (*p == '(') {
162	++plevel;
163	ec = encoder;
164	state = 60;
165      } else {
166	*(fmt++) = *p;
167      }
168      break;
169
170    case 20:			/* Hmmm. At the start of a width field */
171      if (isdigit((int) *p)) {
172	*(w++) = *p;
173	*(fmt++) = *p;
174      } else if (strchr(fmt_codes, *p)) {
175	/* Got one of the formatting codes */
176	/* Figure out width */
177	*w = 0;
178	widthval = atoi(temp);
179	p--;
180	state = 100;
181      } else if (*p == '.') {
182	*w = 0;
183	widthval = atoi(temp);
184	w = temp;
185	*(fmt++) = *p;
186	state = 40;
187      } else {
188	/* ??? */
189	*w = 0;
190	widthval = atoi(temp);
191	state = 50;
192      }
193      break;
194
195    case 30:			/* Parsed a width from an argument.  Look for a . */
196      if (*p == '.') {
197	w = temp;
198	*(fmt++) = *p;
199	state = 40;
200      } else if (strchr(fmt_codes, *p)) {
201	/* Got one of the formatting codes */
202	/* Figure out width */
203	p--;
204	state = 100;
205      } else {
206	/* hmmm. Something else. */
207	state = 50;
208      }
209      break;
210
211    case 40:
212      /* Start of precision expected */
213      if (isdigit((int) *p) && (*p != '0')) {
214	*(fmt++) = *p;
215	*(w++) = *p;
216	state = 41;
217      } else if (*p == '*') {
218	/* Precision field is specified in the format list */
219	precval = va_arg(ap, int);
220	sprintf(temp, "%d", precval);
221	for (w = temp; *w; w++) {
222	  *(fmt++) = *w;
223	}
224	state = 50;
225      } else if (strchr(fmt_codes, *p)) {
226	p--;
227	state = 100;
228      } else {
229	*(fmt++) = *p;
230	state = 50;
231      }
232      break;
233    case 41:
234      if (isdigit((int) *p)) {
235	*(fmt++) = *p;
236	*(w++) = *p;
237      } else if (strchr(fmt_codes, *p)) {
238	/* Got one of the formatting codes */
239	/* Figure out width */
240	*w = 0;
241	precval = atoi(temp);
242	p--;
243	state = 100;
244      } else {
245	*w = 0;
246	precval = atoi(temp);
247	*(fmt++) = *p;
248	state = 50;
249      }
250      break;
251      /* Hang out, wait for format specifier */
252    case 50:
253      if (strchr(fmt_codes, *p)) {
254	p--;
255	state = 100;
256      } else {
257	*(fmt++) = *p;
258      }
259      break;
260
261      /* Got an encoding header */
262    case 60:
263      if (*p == '(') {
264	++plevel;
265	*ec = *p;
266	ec++;
267      } else if (*p == ')') {
268	--plevel;
269	if (plevel <= 0) {
270	  *ec = 0;
271	  state = 10;
272	} else {
273	  *ec = *p;
274	  ec++;
275	}
276      } else {
277	*ec = *p;
278	ec++;
279      }
280      break;
281    case 100:
282      /* Got a formatting code */
283      if (widthval < precval)
284	maxwidth = precval;
285      else
286	maxwidth = widthval;
287      if ((*p == 's') || (*p == 'S')) {	/* Null-Terminated string */
288	DOH *doh;
289	DOH *Sval;
290	DOH *enc = 0;
291	doh = va_arg(ap, DOH *);
292	if (DohCheck(doh)) {
293	  /* Is a DOH object. */
294	  if (DohIsString(doh)) {
295	    Sval = doh;
296	  } else {
297	    Sval = Str(doh);
298	  }
299	  if (strlen(encoder)) {
300	    enc = encode(encoder, Sval);
301	    maxwidth = maxwidth + strlen(newformat) + Len(enc);
302	  } else {
303	    maxwidth = maxwidth + strlen(newformat) + Len(Sval);
304	  }
305	  *(fmt++) = 's';
306	  *fmt = 0;
307	  if ((maxwidth + 1) < OBUFLEN) {
308	    stemp = obuffer;
309	  } else {
310	    stemp = (char *) DohMalloc(maxwidth + 1);
311	  }
312	  if (enc) {
313	    nbytes += sprintf(stemp, newformat, Data(enc));
314	  } else {
315	    nbytes += sprintf(stemp, newformat, Data(Sval));
316	  }
317	  if (Writen(so, stemp, strlen(stemp)) < 0)
318	    return -1;
319	  if ((DOH *) Sval != doh) {
320	    Delete(Sval);
321	  }
322	  if (enc)
323	    Delete(enc);
324	  if (*p == 'S') {
325	    Delete(doh);
326	  }
327	  if (stemp != obuffer) {
328	    DohFree(stemp);
329	  }
330	} else {
331	  if (!doh)
332	    doh = (char *) "";
333
334	  if (strlen(encoder)) {
335	    DOH *s = NewString(doh);
336	    Seek(s, 0, SEEK_SET);
337	    enc = encode(encoder, s);
338	    Delete(s);
339	    doh = Char(enc);
340	  } else {
341	    enc = 0;
342	  }
343	  maxwidth = maxwidth + strlen(newformat) + strlen((char *) doh);
344	  *(fmt++) = 's';
345	  *fmt = 0;
346	  if ((maxwidth + 1) < OBUFLEN) {
347	    stemp = obuffer;
348	  } else {
349	    stemp = (char *) DohMalloc(maxwidth + 1);
350	  }
351	  nbytes += sprintf(stemp, newformat, doh);
352	  if (Writen(so, stemp, strlen(stemp)) < 0)
353	    return -1;
354	  if (stemp != obuffer) {
355	    DohFree(stemp);
356	  }
357	  if (enc)
358	    Delete(enc);
359	}
360      } else {
361	*(fmt++) = *p;
362	*fmt = 0;
363	maxwidth = maxwidth + strlen(newformat) + 64;
364
365	/* Only allocate a buffer if it is too big to fit.  Shouldn't have to do
366	   this very often */
367
368	if (maxwidth < OBUFLEN)
369	  stemp = obuffer;
370	else
371	  stemp = (char *) DohMalloc(maxwidth + 1);
372	switch (*p) {
373	case 'd':
374	case 'i':
375	case 'o':
376	case 'u':
377	case 'x':
378	case 'X':
379	case 'c':
380	  ivalue = va_arg(ap, int);
381	  nbytes += sprintf(stemp, newformat, ivalue);
382	  break;
383	case 'f':
384	case 'g':
385	case 'e':
386	case 'E':
387	case 'G':
388	  dvalue = va_arg(ap, double);
389	  nbytes += sprintf(stemp, newformat, dvalue);
390	  break;
391	case 'p':
392	  pvalue = va_arg(ap, void *);
393	  nbytes += sprintf(stemp, newformat, pvalue);
394	  break;
395	default:
396	  break;
397	}
398	if (Writen(so, stemp, strlen(stemp)) < 0)
399	  return -1;
400	if (stemp != obuffer)
401	  DohFree(stemp);
402      }
403      state = 0;
404      break;
405    }
406    p++;
407  }
408  if (state) {
409    int r;
410    *fmt = 0;
411    r = Writen(so, fmt, strlen(fmt));
412    if (r < 0)
413      return -1;
414    nbytes += r;
415  }
416  return nbytes;
417}
418
419/* -----------------------------------------------------------------------------
420 * DohPrintf()
421 *
422 * Variable length argument entry point to Printf
423 * ----------------------------------------------------------------------------- */
424
425int DohPrintf(DOH *obj, const char *format, ...) {
426  va_list ap;
427  int ret;
428  va_start(ap, format);
429  ret = DohvPrintf(obj, format, ap);
430  va_end(ap);
431  return ret;
432}
433
434/* -----------------------------------------------------------------------------
435 * DohPrintv()
436 *
437 * Print a null-terminated variable length list of DOH objects
438 * ----------------------------------------------------------------------------- */
439
440int DohPrintv(DOHFile * f, ...) {
441  va_list ap;
442  int ret = 0;
443  DOH *obj;
444  va_start(ap, f);
445  while (1) {
446    obj = va_arg(ap, void *);
447    if ((!obj) || (obj == DohNone))
448      break;
449    if (DohCheck(obj)) {
450      ret += DohDump(obj, f);
451    } else {
452      ret += DohWrite(f, obj, strlen((char *) obj));
453    }
454  }
455  va_end(ap);
456  return ret;
457}
458
459/* -----------------------------------------------------------------------------
460 * DohCopyto()
461 *
462 * Copies all of the input from an input stream to an output stream. Returns the
463 * number of bytes copied.
464 * ----------------------------------------------------------------------------- */
465
466int DohCopyto(DOH *in, DOH *out) {
467  int nbytes = 0, ret;
468  int nwrite = 0, wret;
469  char *cw;
470  char buffer[16384];
471
472  if ((!in) || (!out))
473    return 0;
474  while (1) {
475    ret = Read(in, buffer, 16384);
476    if (ret > 0) {
477      nwrite = ret;
478      cw = buffer;
479      while (nwrite) {
480	wret = Write(out, cw, nwrite);
481	if (wret < 0)
482	  return -1;
483	nwrite = nwrite - wret;
484	cw += wret;
485      }
486      nbytes += ret;
487    } else {
488      return nbytes;
489    }
490  }
491}
492
493
494/* -----------------------------------------------------------------------------
495 * DohSplit()
496 *
497 * Split an input stream into a list of strings delimited by the specified
498 * character.  Optionally accepts a maximum number of splits to perform.
499 * ----------------------------------------------------------------------------- */
500
501DOH *DohSplit(DOH *in, char ch, int nsplits) {
502  DOH *list;
503  DOH *str;
504  int c;
505
506  list = NewList();
507
508  if (DohIsString(in)) {
509    Seek(in, 0, SEEK_SET);
510  }
511
512  while (1) {
513    str = NewStringEmpty();
514    do {
515      c = Getc(in);
516    } while ((c != EOF) && (c == ch));
517    if (c != EOF) {
518      Putc(c, str);
519      while (1) {
520	c = Getc(in);
521	if ((c == EOF) || ((c == ch) && (nsplits != 0)))
522	  break;
523	Putc(c, str);
524      }
525      nsplits--;
526    }
527    Append(list, str);
528    Delete(str);
529    if (c == EOF)
530      break;
531  }
532  return list;
533}
534
535/* -----------------------------------------------------------------------------
536 * DohSplitLines()
537 *
538 * Split an input stream into a list of strings delimited by newline characters.
539 * ----------------------------------------------------------------------------- */
540
541DOH *DohSplitLines(DOH *in) {
542  DOH *list;
543  DOH *str;
544  int c = 0;
545
546  list = NewList();
547
548  if (DohIsString(in)) {
549    Seek(in, 0, SEEK_SET);
550  }
551
552  while (c != EOF) {
553    str = NewStringEmpty();
554    while ((c = Getc(in)) != '\n' && c != EOF) {
555      Putc(c, str);
556    }
557    Append(list, str);
558    Delete(str);
559  }
560  return list;
561}
562
563
564/* -----------------------------------------------------------------------------
565 * DohReadline()
566 *
567 * Read a single input line and return it as a string.
568 * ----------------------------------------------------------------------------- */
569
570DOH *DohReadline(DOH *in) {
571  char c;
572  int n = 0;
573  DOH *s = NewStringEmpty();
574  while (1) {
575    if (Read(in, &c, 1) < 0) {
576      if (n == 0) {
577	Delete(s);
578	return 0;
579      }
580      return s;
581    }
582    if (c == '\n')
583      return s;
584    if (c == '\r')
585      continue;
586    Putc(c, s);
587    n++;
588  }
589}
590