view toys/pending/printf.c @ 1647:35c035b7e3e0 draft

More printf cleanup, and test suite entries. Fixes bug introduced last time where toys.optargs was both snapshotted and used directly and the two fell out of sync.
author Rob Landley <rob@landley.net>
date Tue, 06 Jan 2015 12:07:20 -0600
parents 2ffee259f519
children 66bb2847993d
line wrap: on
line source

/* printf.c - Format and Print the data.
 *
 * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com>
 * Copyright 2014 Kyungwan Han <asura321@gmail.com>
 *
 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html

USE_PRINTF(NEWTOY(printf, "<1", TOYFLAG_USR|TOYFLAG_BIN))

config PRINTF 
  bool "printf"
  default n
  help
    usage: printf FORMAT [ARGUMENT...]
    
    Format and print ARGUMENT(s) according to FORMAT, using C printf syntax
    (% escapes for cdeEfgGiosuxX, \ escapes for abefnrtv0 or \OCTAL or \xHEX).
*/

#define FOR_printf
#include "toys.h"

GLOBALS(
  char *hv_w;
  char *hv_p;
  int encountered;
)

// Detect matching character (return true/valse) and advance pointer if match.
static int eat(char **s, char c)
{
  int x = (**s == c);

  if (x) ++*s;

  return x;
}

// Add ll and L to Interger and floating point formats respectively.
static char *get_format(char *f)
{
  int len = strlen(f);
  char last = f[--len], *post = "";

  f[len] = 0;
  if (strchr("diouxX", last)) post = "ll";  // add ll to integer modifier.
  else if (strchr("feEgG", last)) post = "L"; // add L to float modifier.
  return xmprintf("%s%s%c", f, post, last);
}

// Print arguments with corresponding conversion and width and precision.
static void print(char *fmt, int w, int p, char *arg)
{
  char *ptr = fmt, *ep = 0, *format = 0;

  errno = 0;
  if (strchr("diouxX", *ptr)) {
    long long val = 0;

    if (arg) {
      if (*arg == '\'' || *arg == '"') val = arg[1];
      else {
        val = strtoll(arg, &ep, 0);
        if (errno || (ep && (*ep || ep == arg))) {
          perror_msg("Invalid num %s", arg);
          val = 0;
        }
      }
    }
    format = get_format(fmt);
    TT.hv_w ? (TT.hv_p ? printf(format, w, p, val) : printf(format, w, val))
      : (TT.hv_p ? printf(format, p, val) : printf(format, val));
  } else if (strchr("gGeEf", *ptr)) {
    long double dval = 0;

    if (arg) {
      dval = strtold(arg, &ep);
      if (errno || (ep && (*ep || ep == arg))) {
        perror_msg("Invalid num %s", arg);
        dval = 0;
      }
    }
    format = get_format(fmt);
    TT.hv_w ? (TT.hv_p ? printf(format, w, p, dval) : printf(format, w, dval))
      : (TT.hv_p ? printf(format, p, dval) :  printf(format, dval));
  } else if (*ptr == 's') {
    char *str = arg;

    if (!str) str = "";

    TT.hv_w ? (TT.hv_p ? printf(fmt,w,p,str): printf(fmt, w, str))
      : (TT.hv_p ? printf(fmt, p, str) : printf(fmt, str));
  } else if (*ptr == 'c') printf(fmt, arg ? *arg : 0);

  if (format) free(format);
}

// Parse escape sequences.
static int handle_slash(char **esc_val)
{
  char *ptr = *esc_val;
  int len = 1, base = 0;
  unsigned result = 0;

  if (*ptr == 'c') xexit();

  // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits.
  if (eat(&ptr, 'x')) base = 16;
  else if (*ptr >= '0' && *ptr <= '8') base = 8;
  len += (base-8)/8;

  // Not a hex or octal escape? (This catches trailing \)
  if (!len) {
    if (!(result = unescape(*ptr))) result = '\\';
    else ++*esc_val;

    return result;
  }

  while (len) {
    unsigned num = tolower(*ptr)-'0';

    if (num > 10) num += '0'-'a'+10;
    if (num >= base) {
      // Don't parse invalid hex value ala "\xvd", print it verbatim
      if (base == 16 && len == 2) {
        ptr--;
        result = '\\';
      }
      break;
    }
    result = (result*base)+num;
    ptr++;
    len--;
  }
  *esc_val = ptr;

  return (char)result;
}

void printf_main(void)
{
  char *format = *toys.optargs, **arg = toys.optargs+1, *f, *p;

  for (f = format; *f; f++) {
    if (eat(&f, '\\')) putchar(handle_slash(&f));
    else if (!eat(&f, '%') || *f == '%') putchar(*f);
    else if (*f == 'b')
      for (p = *arg ? *(arg++) : ""; *p; p++) 
        putchar(eat(&p, '\\') ? handle_slash(&p) : *p);
    else {
      char *start = f;
      int wp[2], i;

      // todo: we currently ignore these?
      if (strchr("-+# ", *f)) f++;
      memset(wp, 0, 8);
      for (i=0; i<2; i++) {
        if (eat(&f, '*')) {
          if (*arg) wp[i] = atolx(*(arg++));
        } else while (isdigit(*f)) f++;
        if (!eat(&f, '.')) break;
      }
      if (!(p = strchr("diouxXfeEgGcs", *f)))
        error_exit("bad format@%ld", f-format);
      else {
        int len = f-start;

        TT.hv_p = strstr(start, ".*");
        TT.hv_w = strchr(start, '*');
        //pitfall: handle diff b/w * and .*
        if ((TT.hv_w-1) == TT.hv_p) TT.hv_w = NULL;
        memcpy((p = xzalloc(len+1)), start, len);
        print(p+len-1, wp[0], wp[1], *arg);
        if (*arg) arg++;
        free(p);
        p = NULL;
      } 
      TT.encountered = 1;
    }
  }
}