* support/regression/ports/mcs51-xstack-auto/spec.mk: forgot -I(...)/mcs51
[fw/sdcc] / device / lib / printf_large.c
index ce799a3397e8b9218dce2e9537777846c32cbf68..ba43fd546cb52ccbf93a8765cf46e7d3c0b022e5 100644 (file)
 /*-------------------------------------------------------------------------
-  printf.c - formatted output conversion
+  printf_large.c - formatted output conversion
+
              Written By - Martijn van Balen aed@iae.nl (1999)
+             Added %f By - johan.knol@iduna.nl (2000)
+             Refactored by - Maarten Brock (2004)
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
 
-   This program is free software; you can redistribute it and/or modify it
-   under the terms of the GNU General Public License as published by the
-   Free Software Foundation; either version 2, or (at your option) any
-   later version.
-   
-   This program is distributed in the hope that it will be useful,
+   This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-   
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-   
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+
    In other words, you are welcome to use, share and improve this program.
    You are forbidden to forbid anyone else to use, share and improve
-   what you give them.   Help stamp out software-hoarding!  
+   what you give them.   Help stamp out software-hoarding!
 -------------------------------------------------------------------------*/
 
+#if defined(__ds390)
+#define USE_FLOATS 1
+#endif
+
 #include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
 #include <stdio.h>
+#include <stdbool.h>
+#include <sdcc-lib.h>
+
+#define PTR value.ptr
+
+#ifdef SDCC_ds390
+#define NULL_STRING "<NULL>"
+#define NULL_STRING_LENGTH 6
+#endif
+
+/****************************************************************************/
+
+//typedef char * ptr_t;
+#define ptr_t char *
+
+#ifdef toupper
+#undef toupper
+#endif
+
+//#define toupper(c) ((c)&=~0x20)
+#define toupper(c) ((c)&=0xDF)
+
+typedef union
+{
+  unsigned char  byte[5];
+  long           l;
+  unsigned long  ul;
+  float          f;
+  char           *ptr;
+} value_t;
+
+static const char memory_id[] = "IXCP-";
+
+#ifndef SDCC_STACK_AUTO
+  static BOOL lower_case;
+  static pfn_outputchar output_char;
+  static void* p;
+  static value_t value;
+#endif
+
+/****************************************************************************/
+
+#ifdef SDCC_STACK_AUTO
+  static void output_digit( unsigned char n, BOOL lower_case, pfn_outputchar output_char, void* p )
+#else
+  static void output_digit( unsigned char n )
+#endif
+  {
+    output_char( n <= 9 ? '0'+n :
+               (lower_case ? n+(char)('a'-10) : n+(char)('A'-10)), p );
+  }
+
+/*--------------------------------------------------------------------------*/
+
+#ifdef SDCC_STACK_AUTO
+  #define OUTPUT_2DIGITS( B )  output_2digits( B, lower_case, output_char, p )
+  static void output_2digits( unsigned char b, BOOL lower_case, pfn_outputchar output_char, void* p )
+  {
+    output_digit( b>>4,   lower_case, output_char, p );
+    output_digit( b&0x0F, lower_case, output_char, p );
+  }
+#else
+  #define OUTPUT_2DIGITS( B )  output_2digits( B )
+  static void output_2digits( unsigned char b )
+  {
+    output_digit( b>>4   );
+    output_digit( b&0x0F );
+  }
+#endif
+
+/*--------------------------------------------------------------------------*/
 
-int printf (const char *format, ...) reentrant
+#if defined SDCC_STACK_AUTO
+static void calculate_digit( value_t* value, unsigned char radix )
 {
-  va_list arg;
-  int done;
+  unsigned char i;
 
-  va_start (arg, format);
-  done = vprintf (format, arg);
-  va_end (arg);
+  for( i = 32; i != 0; i-- )
+  {
+    value->byte[4] = (value->byte[4] << 1) | ((value->ul >> 31) & 0x01);
+    value->ul <<= 1;
 
-  return done;
+    if (radix <= value->byte[4] )
+    {
+      value->byte[4] -= radix;
+      value->ul |= 1;
+    }
+  }
 }
+#else
+static void calculate_digit( unsigned char radix )
+{
+  unsigned char i;
+
+  for( i = 32; i != 0; i-- )
+  {
+    value.byte[4] = (value.byte[4] << 1) | ((value.ul >> 31) & 0x01);
+    value.ul <<= 1;
+
+    if (radix <= value.byte[4] )
+    {
+      value.byte[4] -= radix;
+      value.ul |= 1;
+    }
+  }
+}
+#endif
+
+#if USE_FLOATS
 
-int sprintf (const char *buf, const char *format, ...) reentrant
+/* This is a very inefficient but direct approach, since we have no math
+   library yet (e.g. log()).
+   It does most of the modifiers, but has some restrictions. E.g. the
+   abs(float) shouldn't be bigger than an unsigned long (that's
+   about 4294967295), but still makes it usefull for most real-life
+   applications.
+*/
+
+#define DEFAULT_FLOAT_PRECISION 6
+
+#ifdef SDCC_STACK_AUTO
+#define OUTPUT_FLOAT(F, W, D, L, Z, S, P)      output_float(F, W, D, L, Z, S, P, output_char, p)
+static int output_float (float f, unsigned char reqWidth,
+                         signed char reqDecimals,
+                         BOOL left, BOOL zero, BOOL sign, BOOL space,
+                         pfn_outputchar output_char, void* p)
+#else
+#define OUTPUT_FLOAT(F, W, D, L, Z, S, P)      output_float(F, W, D, L, Z, S, P)
+static int output_float (float f, unsigned char reqWidth,
+                         signed char reqDecimals,
+                         BOOL left, BOOL zero, BOOL sign, BOOL space)
+#endif
 {
-  va_list arg;
-  int done;
+  BOOL negative=0;
+  unsigned long integerPart;
+  float decimalPart;
+  char fpBuffer[128];
+  char fpBI=0, fpBD;
+  unsigned char minWidth, i;
+  int charsOutputted=0;
+
+  // save the sign
+  if (f<0) {
+    negative=1;
+    f=-f;
+  }
+
+  if (f>0x00ffffff) {
+    // this part is from Frank van der Hulst
+    signed char exp;
+
+    for (exp = 0; f >= 10.0; exp++) f /=10.0;
+    for (       ; f < 1.0;   exp--) f *=10.0;
 
-  va_start (arg, format);
-  done = vsprintf (buf, format, arg);
-  va_end (arg);
+    if (negative) {
+      output_char ('-', p);
+      charsOutputted++;
+    } else {
+      if (sign) {
+        output_char ('+', p);
+        charsOutputted++;
+      }
+    }
+    charsOutputted += OUTPUT_FLOAT(f, 0, reqDecimals, 0, 0, 0, 0);
+    output_char ('e', p);
+    charsOutputted++;
+    if (exp<0) {
+      output_char ('-', p);
+      charsOutputted++;
+      exp = -exp;
+    }
+    output_char ('0'+exp/10, p);
+    output_char ('0'+exp%10, p);
+    charsOutputted += 2;
+    return charsOutputted;
+  }
 
-  return done;
+  // split the float
+  integerPart=f;
+  decimalPart=f-integerPart;
+
+  // fill the buffer with the integerPart (in reversed order!)
+  while (integerPart) {
+    fpBuffer[fpBI++]='0' + integerPart%10;
+    integerPart /= 10;
+  }
+  if (!fpBI) {
+    // we need at least a 0
+    fpBuffer[fpBI++]='0';
+  }
+
+  // display some decimals as default
+  if (reqDecimals==-1)
+    reqDecimals=DEFAULT_FLOAT_PRECISION;
+
+  // fill buffer with the decimalPart (in normal order)
+  fpBD=fpBI;
+
+  for (i=reqDecimals; i>1; i--) {
+      decimalPart *= 10.0;
+      // truncate the float
+      integerPart=decimalPart;
+      fpBuffer[fpBD++]='0' + integerPart;
+      decimalPart-=integerPart;
+  }
+  if (i) {
+    decimalPart *= 10.0;
+    // truncate the float
+    integerPart = decimalPart + 0.5;
+    fpBuffer[fpBD++] = '0' + integerPart;
+  }
+
+  minWidth=fpBI; // we need at least these
+  minWidth+=reqDecimals?reqDecimals+1:0; // maybe these
+  if (negative || sign || space)
+    minWidth++; // and maybe even this :)
+
+  if (!left && reqWidth>i) {
+    if (zero) {
+      if (negative)
+      {
+        output_char('-', p);
+        charsOutputted++;
+      }
+      else if (sign)
+      {
+        output_char('+', p);
+        charsOutputted++;
+      }
+      else if (space)
+      {
+        output_char(' ', p);
+        charsOutputted++;
+      }
+      while (reqWidth-->minWidth)
+      {
+        output_char('0', p);
+        charsOutputted++;
+      }
+    } else {
+      while (reqWidth-->minWidth)
+      {
+        output_char(' ', p);
+        charsOutputted++;
+      }
+      if (negative)
+      {
+        output_char('-', p);
+        charsOutputted++;
+      }
+      else if (sign)
+      {
+        output_char('+', p);
+        charsOutputted++;
+      }
+      else if (space)
+      {
+        output_char(' ', p);
+        charsOutputted++;
+      }
+    }
+  } else {
+    if (negative)
+    {
+      output_char('-', p);
+      charsOutputted++;
+    }
+    else if (sign)
+    {
+      output_char('+', p);
+      charsOutputted++;
+    }
+    else if (space)
+    {
+      output_char(' ', p);
+      charsOutputted++;
+    }
+  }
+
+  // output the integer part
+  i=fpBI-1;
+  do {
+    output_char (fpBuffer[i], p);
+    charsOutputted++;
+  } while (i--);
+
+  // ouput the decimal part
+  if (reqDecimals) {
+    output_char ('.', p);
+    charsOutputted++;
+    i=fpBI;
+    while (reqDecimals--)
+    {
+      output_char (fpBuffer[i++], p);
+      charsOutputted++;
+    }
+  }
+
+  if (left && reqWidth>minWidth) {
+    while (reqWidth-->minWidth)
+    {
+      output_char(' ', p);
+      charsOutputted++;
+    }
+  }
+  return charsOutputted;
 }
+#endif
+
+int _print_format (pfn_outputchar pfn, void* pvoid, const char *format, va_list ap)
+{
+  BOOL   left_justify;
+  BOOL   zero_padding;
+  BOOL   prefix_sign;
+  BOOL   prefix_space;
+  BOOL   signed_argument;
+  BOOL   char_argument;
+  BOOL   long_argument;
+  BOOL   float_argument;
+#ifdef SDCC_STACK_AUTO
+  BOOL   lower_case;
+  value_t value;
+#endif
+  BOOL   lsd;
+
+  unsigned char radix;
+  int charsOutputted;
+  unsigned char  width;
+  signed char decimals;
+  unsigned char  length;
+  char           c;
+
+#ifdef SDCC_STACK_AUTO
+  #define output_char   pfn
+  #define p             pvoid
+#else
+  output_char = pfn;
+  p = pvoid;
+#endif
+
+  // reset output chars
+  charsOutputted=0;
+
+#ifdef SDCC_ds390
+  if (format==0) {
+    format=NULL_STRING;
+  }
+#endif
+
+  while( c=*format++ )
+  {
+    if ( c=='%' )
+    {
+      left_justify    = 0;
+      zero_padding    = 0;
+      prefix_sign     = 0;
+      prefix_space    = 0;
+      signed_argument = 0;
+      radix           = 0;
+      char_argument   = 0;
+      long_argument   = 0;
+      float_argument  = 0;
+      width           = 0;
+      decimals        = -1;
+
+get_conversion_spec:
+
+      c = *format++;
+
+      if (c=='%') {
+        output_char(c, p);
+        charsOutputted++;
+        continue;
+      }
+
+      if (isdigit(c)) {
+        if (decimals==-1) {
+          width = 10*width + (c - '0');
+          if (width == 0) {
+            /* first character of width is a zero */
+            zero_padding = 1;
+          }
+        } else {
+          decimals = 10*decimals + (c-'0');
+        }
+        goto get_conversion_spec;
+      }
+
+      if (c=='.') {
+        if (decimals=-1) decimals=0;
+        else
+          ; // duplicate, ignore
+        goto get_conversion_spec;
+      }
+
+      lower_case = islower(c);
+      if (lower_case)
+      {
+        c = toupper(c);
+      }
+
+      switch( c )
+      {
+      case '-':
+        left_justify = 1;
+        goto get_conversion_spec;
+      case '+':
+        prefix_sign = 1;
+        goto get_conversion_spec;
+      case ' ':
+        prefix_space = 1;
+        goto get_conversion_spec;
+      case 'B':
+        char_argument = 1;
+        goto get_conversion_spec;
+      case 'L':
+        long_argument = 1;
+        goto get_conversion_spec;
+
+      case 'C':
+        output_char( va_arg(ap,int), p );
+        charsOutputted++;
+        break;
+
+      case 'S':
+        PTR = va_arg(ap,ptr_t);
+
+#ifdef SDCC_ds390
+        if (PTR==0) {
+          PTR=NULL_STRING;
+          length=NULL_STRING_LENGTH;
+        } else {
+          length = strlen(PTR);
+        }
+#else
+        length = strlen(PTR);
+#endif
+        if ( decimals == -1 )
+        {
+          decimals = length;
+        }
+        if ( ( !left_justify ) && (length < width) )
+        {
+          width -= length;
+          while( width-- != 0 )
+          {
+            output_char( ' ', p );
+            charsOutputted++;
+          }
+        }
+
+        while ( *PTR  && (decimals-- > 0))
+        {
+          output_char( *PTR++, p );
+          charsOutputted++;
+        }
+
+        if ( left_justify && (length < width))
+        {
+          width -= length;
+          while( width-- != 0 )
+          {
+            output_char( ' ', p );
+            charsOutputted++;
+          }
+        }
+        break;
+
+      case 'P':
+        PTR = va_arg(ap,ptr_t);
+
+#ifdef SDCC_ds390
+        output_char(memory_id[(value.byte[3] > 3) ? 4 : value.byte[3]], p );
+        output_char(':', p);
+        output_char('0', p);
+        output_char('x', p);
+        OUTPUT_2DIGITS( value.byte[2] );
+        OUTPUT_2DIGITS( value.byte[1] );
+        OUTPUT_2DIGITS( value.byte[0] );
+        charsOutputted += 10;
+#else
+        output_char( memory_id[(value.byte[2] > 3) ? 4 : value.byte[2]], p );
+        output_char(':', p);
+        output_char('0', p);
+        output_char('x', p);
+        if ((value.byte[2] != 0x00 /* DSEG */) &&
+            (value.byte[2] != 0x03 /* SSEG */))
+        {
+          OUTPUT_2DIGITS( value.byte[1] );
+          charsOutputted += 2;
+        }
+        OUTPUT_2DIGITS( value.byte[0] );
+        charsOutputted += 6;
+#endif
+        break;
+
+      case 'D':
+      case 'I':
+        signed_argument = 1;
+        radix = 10;
+        break;
+
+      case 'O':
+        radix = 8;
+        break;
+
+      case 'U':
+        radix = 10;
+        break;
+
+      case 'X':
+        radix = 16;
+        break;
+
+      case 'F':
+        float_argument=1;
+        break;
+
+      default:
+        // nothing special, just output the character
+        output_char( c, p );
+        charsOutputted++;
+        break;
+      }
+
+      if (float_argument) {
+        value.f=va_arg(ap,float);
+#if !USE_FLOATS
+        PTR="<NO FLOAT>";
+        while (c=*PTR++)
+        {
+          output_char (c, p);
+          charsOutputted++;
+        }
+        // treat as long hex
+        //radix=16;
+        //long_argument=1;
+        //zero_padding=1;
+        //width=8;
+#else
+        // ignore b and l conversion spec for now
+        charsOutputted += OUTPUT_FLOAT(value.f, width, decimals, left_justify,
+                                     zero_padding, prefix_sign, prefix_space);
+#endif
+      } else if (radix != 0)
+      {
+        // Apperently we have to output an integral type
+        // with radix "radix"
+        unsigned char store[6];
+        unsigned char _AUTOMEM *pstore = &store[5];
+
+        // store value in byte[0] (LSB) ... byte[3] (MSB)
+        if (char_argument)
+        {
+          value.l = va_arg(ap,char);
+          if (!signed_argument)
+          {
+            value.l &= 0xFF;
+          }
+        }
+        else if (long_argument)
+        {
+          value.l = va_arg(ap,long);
+        }
+        else // must be int
+        {
+          value.l = va_arg(ap,int);
+          if (!signed_argument)
+          {
+            value.l &= 0xFFFF;
+          }
+        }
+
+        if ( signed_argument )
+        {
+          if (value.l < 0)
+            value.l = -value.l;
+          else
+            signed_argument = 0;
+        }
+
+        length=0;
+        lsd = 1;
+
+        do {
+          value.byte[4] = 0;
+#if defined SDCC_STACK_AUTO
+          calculate_digit(&value, radix);
+#else
+          calculate_digit(radix);
+#endif
+          if (!lsd)
+          {
+            *pstore = (value.byte[4] << 4) | (value.byte[4] >> 4) | *pstore;
+            pstore--;
+          }
+          else
+          {
+            *pstore = value.byte[4];
+          }
+          length++;
+          lsd = !lsd;
+        } while( value.ul );
+
+        if (width == 0)
+        {
+          // default width. We set it to 1 to output
+          // at least one character in case the value itself
+          // is zero (i.e. length==0)
+          width=1;
+        }
+
+        /* prepend spaces if needed */
+        if (!zero_padding && !left_justify)
+        {
+          while ( width > (unsigned char) (length+1) )
+          {
+            output_char( ' ', p );
+            charsOutputted++;
+            width--;
+          }
+        }
+
+        if (signed_argument) // this now means the original value was negative
+        {
+          output_char( '-', p );
+          charsOutputted++;
+          // adjust width to compensate for this character
+          width--;
+        }
+        else if (length != 0)
+        {
+          // value > 0
+          if (prefix_sign)
+          {
+            output_char( '+', p );
+            charsOutputted++;
+            // adjust width to compensate for this character
+            width--;
+          }
+          else if (prefix_space)
+          {
+            output_char( ' ', p );
+            charsOutputted++;
+            // adjust width to compensate for this character
+            width--;
+          }
+        }
+
+        /* prepend zeroes/spaces if needed */
+        if (!left_justify)
+          while ( width-- > length )
+          {
+            output_char( zero_padding ? '0' : ' ', p );
+            charsOutputted++;
+          }
+        else
+        {
+          /* spaces are appended after the digits */
+          if (width > length)
+            width -= length;
+          else
+            width = 0;
+        }
+
+        /* output the digits */
+        while( length-- )
+        {
+          lsd = !lsd;
+          if (!lsd)
+          {
+            pstore++;
+            value.byte[4] = *pstore >> 4;
+          }
+          else
+          {
+            value.byte[4] = *pstore & 0x0F;
+          }
+#ifdef SDCC_STACK_AUTO
+          output_digit( value.byte[4], lower_case, output_char, p );
+#else
+          output_digit( value.byte[4] );
+#endif
+          charsOutputted++;
+        }
+        if (left_justify)
+          while (width-- > 0)
+          {
+            output_char(' ', p);
+            charsOutputted++;
+          }
+      }
+    }
+    else
+    {
+      // nothing special, just output the character
+      output_char( c, p );
+      charsOutputted++;
+    }
+  }
+
+  return charsOutputted;
+}
+
+/****************************************************************************/