Source file: /~heha/mb-iwp/Bergwerk/fba-rpi-230421.zip/heha_print.cpp

/**************************************************
 ** printf-Nachbau mit einigen nützlichen Extras **
 **************************************************/
/*
Die Features sind:
- Ausgabe Dezimaltrennzeichen Komma oder Punkt ("," statt "." angeben)
- Dezimaltrennzeichen auch bei Ganzzahlen (via Genauigkeitsangabe)
- Ausgabe ±Inf und NaN
- Unterdrückung ± vor Null und NaN
- "%#X" gibt 0x-Präfix aus, nicht 0X
- "%#,1d" unterdrückt ",0" am Ende
- "% #,1i" ersetzt ",0" am Ende durch Leerzeichen
- Zifferngruppierung mit "`", gibt '\'' aus, hex/binär: Vierergruppe, sonst Dreiergruppe
- %j wie %i aber mit Inf/NaN-Ausgabe
*/
#include "heha_print.h"
#include <unistd.h>	// write()
#include <math.h>	// isinf(), isnan()
#include <stdlib.h>	// abs()
#include <cstring>	// strlen()

#define nobreak

namespace heha{

// Anzahl Codepoints im (gültigen) UTF8-String
int utf8len(const char*p,int len) {
 if (len<0) len=strlen(p);
 int cnt=0;
 if (len) do{
  if (*p++>=-64) ++cnt;		// Trailbytes (<-64) nicht zählen
 }while (--len);
 return cnt;
}

// Codepoints im (gültigen) UTF8-String vor- oder zurückrücken
// s muss auf ASCII oder Leadbyte zeigen!
const char*utf8pos(const char*s,int pos) {
 while (pos>0) {
  if (*++s>=-64) --pos;
 }
 while (pos<0) {
  if (*--s>=-64) ++pos;
 }
 return s;
}

// Einfache Funktion, beachtet Zeichenmodifizierer nicht
// Steuerzeichen werden übergangen.
// Eigentlich geht es um die Darstellungsbreite im Terminal ...
int utf8width(const char*p,int len) {
 if (len<0) len=strlen(p);
 const char*e = p+len;
 int cnt=0;
 while (p!=e) {
  char c=*p++;
  if (c>=' ' || -64<=c && c<0) ++cnt;	// Druckbare Zeichen oder UTF8-Leadbytes
  else if (p!=e && c=='\e') {	// Escape-Sequenz?
   c=*++p;
   if (p==e) break;
   if (c=='[') for(;;) {	// CSI-Sequenz?
    c=*++p|0x20;		// Kleinbuchstaben
    if (p==e) break;
    if ('a'<=c && c<='z') break;// bis zum ersten Buchstaben verschlucken
   }else if (c=='#') ++p;	// 2 Zeichen verschlucken
  }			// sonst 1 Zeichen verschlucken
 }
 return cnt;
}

static int limit(int c,int u=-127,int o=127) {
 return c<u ? u : c>o ? o : c;
}

/****************
 * Ausgabestrom	*
 ****************/
struct OutF:public Out{
 virtual void operator()(char c) override{
  ++count;
  ::fputc(c,f);
  ::fflush(f);
 }
 OutF(FILE*F):f(F) {}
private:
 FILE*f;
};

struct OutM:public Out{
 virtual void operator()(char c) override{
  ++count;
  if (p!=e) *p++=c;
 }
 OutM(char*buf,int l):p(buf),e(buf+l) {}
private:
 char*p,*const e;
};

struct OutH:public Out{
 virtual void operator()(char c) override{
  ++count;
  ::write(h,&c,1);
 }
 OutH(int _h):h(_h) {}
private:
 int h;
};

struct OutS:public Out{
 virtual void operator()(char c) override{
  ++count;
  os<<c;
 }
 OutS(std::ostream&o):os(o) {}
private:
 std::ostream&os;
};

/********************************
 * Print-Objekt			*
 * (ersatzhalber für fehlende	*
 *  Lambdas beim C2000)		*
 ********************************/

class Print{
// Kopie der Funktionsargumente
 Out*out;	// Ausgabe-Objekt
 const char*fmt;// Formatstring
 va_list va;	// Eingabe-„Objekt“
// Weitere Veratbeitungsdaten
 union{
  signed char fmtargs[2];
  struct{signed char width,nk;};
 };
 unsigned short flags;
 enum{	// für flags
  fArg,		// Bit 0: Vor oder nach Punkt/Komma
  fKomma,	// Bit 1: Komma statt Punkt ausgeben (0x2C vs. 0x2E)
  fPlus,	// Bit 2: Positives Vorzeichen ('+') ausgeben, Vorrang vor Bit 3
  fSpace,	// Bit 3: Unterdrückte Stellen (Vorzeichen, Nachkommastellen*) mit Leerzeichen auffüllen
  fNull,	// Bit 4: Nullen statt Leerzeichen (0x30 vs. 0x20)
  fGross,	// Bit 5: Großbuchstaben für Hexziffern oder Exponent-Ausgabe (Differenz 0x20)
  fGroup,	// Bit 6: Zifferngruppierung mit '\'' (*)
  fMinus,	// Bit 7: Minus = linksbündige Ausgabe (TODO: Feldbreite!)
  fInf,		// Bit 8: "Inf" ausgeben, arg ignorieren
  fNaN,		// Bit 9: "NaN" ausgeben, arg ignorieren
  fUl,		// Bit 10: Unterstrich = signifikante statt Kommastellen (*)
  fExp,		// Bit 11: Gleitkommadarstellung mit Exponent (stets bei %e; bei %g wenn Argument außerhalb 1E-3 .. 1E7)
  fHash,	// Bit 12: Alternative Form:
  		//	%oxb: Präfix ausgeben ("0b", "0x" oder "0o")
                //	%c: '\0' unterdrücken (*)
                //	%aefg: Nachkommastellen unterdrücken
  fEm,		// Bit 13: %dijuoxb: Immer Dezimaltrennzeichen
  fQm,		// Bit 14: %dijuoxb: Nachkomma-Nullen unterdrücken
  fNegative,	// Bit 15: Negatives Vorzeichen ('-') ausgeben, Vorrang vor Bit 2 und 3
// Größenmodifizierer ab Bit 16
// Für %j sind trotz (oder wegen "falscher") Integer-Erweiterung
// alle Größenmodifizierer wichtig
// für die korrekte Auswertung von Inf und NaN.
 };
 unsigned char fsize;
 enum{	// für fsize
  fl,		// Bit 16: 32-Bit-Zahl (Linux: 64-Bit-Zahl)
  fll,		// Bit 17: 64-Bit-Zahl
  fh,		// Bit 18: 16-Bit-Zahl (Win32, Linux: Kommt nicht auf Stack)
  fhh,		// Bit 19: 8-Bit-Zahl (kommt nicht auf Stack)
  fL,		// Bit 20: long double (Win32: 80 Bit)
  fj,		// Bit 21: Hier nicht!
  ft,		// Bit 22: Hier nicht!
  fz		// Bit 23: Hier nicht!
 };
 unsigned char base;
 short exp;
 union{
  unsigned long long u;
  long long s;
  long double f;
  void* p;
 }arg;
 void detect_nan_inf();
 void float2int(char c);
 char upcase(char c) const {return c&0x40 && flags&1<<fGross ? c&~0x20 : c;}
 static char digit(unsigned c) {return c+(c>=10?'a'-10:'0');}
 void makepositive() {if (arg.s<0) {arg.s=-arg.s; flags^=1<<fNegative;}}
 char*emitnumber(char*stack);
 void fill(signed char l) const {
  l = width-l;
//printf("<width=%d,l=%d>",width,l);
  if (l>0) do (*out)(' '); while (--l);
 }
 virtual void collect_flags();
 virtual void convert();
public:
// Nicht im Konstruktor, damit verschiedene Print-Prozessoren
// statisch bereitliegen dürfen
 void operator() (Out&,const char*,va_list);
};

// Hier: Implementierung für int = 32 Bit
void Print::detect_nan_inf() {
 if (fsize&1<<fll) {
  if (arg.s==-0x8000000000000000LL) flags|=1<<fNaN;
  else if (arg.s==0x7FFFFFFFFFFFFFFFLL) flags|=1<<fInf;
 }else if (fsize&1<<fhh) {
  signed char z = arg.s; z^=1<<7;	// MSB toggeln: 0x80=>0, 0x7F=>-1
  if (!z) flags|=1<<fNaN;
  else if (!++z) flags|=1<<fInf;
 }else if (fsize&1<<fh) {
  short z = arg.s; z^=1<<15;
  if (!z) flags|=1<<fNaN;
  else if (!++z) flags|=1<<fInf;
 }else{
  int z = arg.s; z^=1UL<<31;
  if (!z) flags|=1<<fNaN;
  else if (!++z) flags|=1<<fInf;
 }
}

// Gleitkommazahl in Integerzahl, ggf. mit Exponent wandeln
void Print::float2int(char c) {
 if (isnan(arg.f)) {flags|=1<<fNaN; return;}
 if (arg.f<0) {flags|=1<<fNegative; arg.f=-arg.f;}	// arg.f positiv
 if (isinf(arg.f)) {flags|=1<<fInf; return;}
 if (nk<0) nk=6;	// Nachkommastellen bei fehlender Angabe
 c|=' ';		// Kleinbuchstabe
 if (c=='g') flags^=1<<fHash;	// Nachkommaunterdrückung umkehren
 if (arg.f) {
  if (c=='e' || c=='g' && (arg.f>1E7 || arg.f<1E-3)) {
   flags|=1<<fExp;	// Normalisieren zur Basis 10 (ohne exp/log/pow)
   while (arg.f<1) {arg.f*=10; --exp;}
   while (arg.f>=10) {arg.f*=0.1; ++exp;}	// keine Gleitkomma-Division
  }
  for (signed char e=0; e<nk; e++) arg.f*=10;
 }
 arg.s=arg.f+0.5;	// in Integer umwandeln und runden
}

// Ziffernfolge rückwärts generieren
// TODO: Es ist /doch/ vorteilhaft, bei fNull /hier/ die Nullen zu ergänzen!
char*Print::emitnumber(char*stack) {
 if (!arg.u) flags&=~(1<<fPlus);	// Niemals '+' für "0" (Erweiterung)
 if (nk<0) nk=0;
 unsigned G = base==2|base==16 ? 4 : 3;	// Gruppengröße
 if (flags&1<<fNaN) {
  *stack++='N';
  *stack++=upcase('a');
  *stack++='N';
  flags&=~(1<<fNegative|1<<fPlus);	// Kein Plus ausgeben
  return stack;
 }else if (flags&1<<fInf) {
  *stack++=upcase('f');
  *stack++=upcase('n');
  *stack++='I';
 }else{
  char*stacktop = stack;		// retten für Null-Auffüllung
  if (flags&1<<fExp) {
   unsigned e = exp<0 ? -exp : exp;
   int digits = 0;
   do{
    *stack++ = e%10 + '0';	// immer dezimal
    e/=10;			// löst der Compiler mit udiv() auf
   }while (++digits<2 ||e);
   if (exp<0) *stack++='-';
   else if (flags&1<<fPlus) *stack++='+';
   *stack++=upcase(base==16?'p':'e');
  }	// Exponent (einer Gleitkommazahl) erledigt
  do{
   if (nk) {
    if (flags&1<<fGroup		// Gruppierung?
      && stacktop!=stack		// Nicht ganz rechts
      && !(unsigned(abs(nk))%G))	// in Dreier- oder Vierergruppe?
      *stack++=flags&1<<fSpace ? ' ' : '\'';	// Spalten nicht zerhacken
// In der derzeitigen Implementierung darf die Zahlenkonvertierung
// keine Nicht-ASCII-Zeichen generieren
   }else if (flags&1<<fEm) *stack++=flags&1<<fKomma ? ',' : '.';
   unsigned n=arg.u%base; arg.u/=base;	// löst der Compiler mit lldiv() auf
   if (n || nk<=0) flags&=~(1<<fQm);	// Keine (weitere) Ziffer-Unterdrückung
   if (!(flags&1<<fQm)) {
    *stack++ = upcase(digit(n));
    flags |= 1<<fEm;	// (ab jetzt) Komma erzwingen
   }else if (flags&1<<fSpace) *stack++=' ';
  }while(--nk>=0 || arg.u	// mindestens eine Vorkommastelle
   	|| flags&1<<fNull && stack-stacktop<width);
 }
 if (flags&1<<fHash && base!=10) {
  *stack++ = base==16 ? 'x' : base==8 ? 'o' : 'b';
  *stack++ = '0';
 }
 if (flags&1<<fNegative) *stack++ = '-';
 else if (flags&1<<fPlus) *stack++ = '+';
 else if (flags&1<<fSpace) *stack++ = ' ';
 return stack;
}

void Print::collect_flags() {
 nk=width=-1; base=10; flags=fsize=exp=0;
 for(;;) {
  char c=*fmt;
  switch (c) {
   case '.': flags|=1<<fArg; break;		// Kommastellen *auch für Dezimalzahlen
   case ',': flags|=1<<fArg|1<<fKomma; break;	// *Dezimalkomma statt Punkt
   case '+': flags|=1<<fPlus; break;		// Zwangs-Plus
   case ' ': flags|=1<<fSpace; break;		// 1 Leerzeichen bei positiven Zahlen; *Leerzeichen statt Dezimalnullen
   case '\'':
   case '`': flags|=1<<fGroup; break;	// Zifferngruppierung: %bx: 4, sonst 3 (auch China/Japan)
   case 'h': if (!(fsize&1<<fhh)) fsize+=1<<fh; break;	// int16, int8
   case 'l': if (!(fsize&1<<fll)) fsize+=1<<fl; break;	// int32, int64
   case 'L': fsize|=1<<fL; break;
   case '_': flags|=1<<fUl|1<<fArg; break;	// *Signifikante Stellen statt Kommastellen (LabVIEW)
   case '-': flags^=1<<fMinus; break;	// linksbündig in Feldbreite
   case '#': flags^=1<<fHash; break;	// Diverse Alternativen
   case '!': flags^=1<<fEm; break;	// Dezimaltrennzeichen erzwingen
   case '?': flags^=1<<fQm; break;	// Null-Nachkommastellen unterdrücken
   case '*': {
    int z = va_arg(va,int);
    if (z<0) {flags^=1<<fMinus; z=-z;}
    fmtargs[flags&1] = z;
   }break;
   case '0': flags|=1<<fNull; break;	// Führende Nullen statt Leerzeichen zum Auffüllen der Feldbreite
   default: if ('1'<=c && c<='9') {
    signed char&z = fmtargs[flags&1];
    if (z<0) z=0;
    do{
     z = z*10 + c-'0';
     c = *++fmt;
    }while ('0'<=c && c<='9');
    continue;
   }else return;	// Erstes „unverdauliches“ Zeichen
  }
  ++fmt;	// „Verdauliches“ Zeichen verarbeitet
 }
}

// Normalerweise entnimmt convert() genau 1 Zeichen.
// Für den Fall der Plural-Stringmanipulation, Beispiele:
//	file%{s}
//	M%{aus|äuse}
//	dver%.5{a|y}
// entnimmt die Funktion Zeichen bis zum '}',
// wobei der Feldbreiten-Parameter die Auswertungsregel selektiert.
// Dasselbe für die Mehrfachauswahl, Beispiel:
//	%(false|true)
void Print::convert() {
 char buf[128], *sp=buf, c=*fmt;
 if (!c) return;
 fmt++;
 signed char len;
 bool reverse=false;
 switch (c) {
  case 'd':
  case 'i':
  case 'j': {					// *mit Inf/NaN
   arg.s = fsize&1<<fll ? va_arg(va,long long)
	:va_arg(va,int);
   makepositive();
   if (c=='j') detect_nan_inf();
  }goto makezahl;
  case 'A': flags|=1<<fGross; nobreak;	// Großbuchstaben
  case 'a': base=16; goto hexfloat;	// Hex-Float
	// Hex-Float geht eigentlich anders! 0x1.<Mantisse-hex>p±<Exponent-dez>
  case 'o': base=8; goto unsig;
  case 'b': base=2; goto unsig;
  case 'p': flags|=1<<fNull; width=8; nobreak;	// 32-Bit-Adresse
  case 'X': flags|=1<<fGross; nobreak;	// Großbuchstaben
  case 'x': base=16; nobreak;
  case 'u': unsig: {
   arg.u = fsize&1<<fll ? va_arg(va,unsigned long long)
	:va_arg(va,unsigned);	// Null-Erweiterung
  }goto makezahl;
  case 'g': flags|=1<<fQm; goto hexfloat;
  case 'G': flags|=1<<fQm; nobreak;	// %Gg: Nullen bei Kommastellen unterdrücken
  case 'E': flags|=1<<fGross; nobreak;	// Großbuchstaben
  case 'e':
  case 'f': hexfloat:
  arg.f = fsize&1<<fL ? va_arg(va,long double)
	: va_arg(va,double);
  float2int(c);
  if (flags&1<<fHash) flags|=1<<fEm;	// Dezimaltrenner erzwingen
  makezahl:
  sp = emitnumber(buf);
  len = limit(sp-buf);
  reverse = true;
  break;
  case 'S': flags|=1<<fGross; nobreak;
  case 's': sp = va_arg(va,char*);
  len = limit(strlen(sp)); if (nk>=0 && len>nk) len=nk;	// so muss s ein nullterminierter String sein
  break;
  case 'C': flags|=1<<fGross;	// 8-Bit-Zeichen
  case 'c': *sp = va_arg(va,int);
  len = 1;
  if (flags&1<<fHash && !*sp) len = 0;	//* alternative Form: NUL-Zeichen unterdrücken
  break;
  case '(': {	// Alternative Strings (wie enum)
   int sel = va_arg(va,int);
// TODO: Grammatikauswertung kann hier hineingepackt werden
   sp = const_cast<char*>(fmt);		// Erste mögliche Alternative
   auto q = strchr(fmt,')');		// Ende der Liste suchen
   auto e = q ? q : fmt + strlen(fmt);	// auf ')' oder '\0'
   fmt = q ? q+1 : e;			// Quellzeiger schon mal vorrücken
   for(;;--sel) {
    char*t = (char*)memchr(sp,'|',e-sp);// Alternativen-Trenner (TODO: Per Flags wählbar)
    len = limit(t ? t-sp : e-sp);
    if (sel<=0) break;			// Nimm diese Alternative
    if (!t) break;			// Nimm die letzte Alternative
    sp = t+1;				// Nächster Teilstring
   }
  }break;
  default: (*out)(c); return;
 }
 int cells = reverse ? len : utf8width(sp,len);
//printf("<len=%d,utf8width=%d,count=%d>",len,cells,out->count);
 if (!(flags&1<<fMinus) && !(flags&1<<fNull)) fill(cells);	// Leerzeichen vor Vorzeichen
 if (len>0) do
   if (reverse) (*out)(*--sp);
   else (*out)(*sp++);
 while(--len);
 if (flags&1<<fMinus) fill(cells);	// linksbündig: rechts Leerzeichen
}

void Print::operator()(Out&o,const char*f,va_list v) {
 out = &o;
 fmt = f;
 va_copy(va,v);
 for(;;) {
  char c=*fmt++;
  if (!c) return;
  if (c=='%') {
   collect_flags();
//printf("da %c\n",*fmt);
   convert();
  }else (*out)(c);
 }
}

static Print printobj;

void print(Out&out, const char*fmt,va_list va) {
 printobj(out,fmt,va);
}

int vprint(FILE*f,const char*fmt,va_list va) {
 OutF out(f);
 print(out,fmt,va);
 return out.count;
}

int vprint(const char*fmt,va_list va) {
 return vprint(stdout,fmt,va);
}

int print(const char*fmt,...) {
 va_list va;
 va_start(va,fmt);
 int ret=vprint(fmt,va);
 va_end(va);
 return ret;
}

int print(FILE*f,const char*fmt,...) {
 va_list va;
 va_start(va,fmt);
 int ret=vprint(f,fmt,va);
 va_end(va);
 return ret;
}

int vprint(int h,const char*fmt,va_list va) {
 OutH out(h);
 print(out,fmt,va);
 return out.count;
}

int print(int h,const char*fmt,...) {
 va_list va;
 va_start(va,fmt);
 int ret=vprint(h,fmt,va);
 va_end(va);
 return ret;
}

int vprint(std::ostream&os,const char*fmt,va_list va) {
 OutS out(os);
 print(out,fmt,va);
 return out.count;
}

int print(std::ostream&os,const char*fmt,...) {
 va_list va;
 va_start(va,fmt);
 int ret=vprint(os,fmt,va);
 va_end(va);
 return ret;
}

int snvprint(char*buf, int len, const char*fmt,va_list va) {
 OutM out(buf,len);
 print(out,fmt,va);
 int ret=out.count;
 out(0);	// Nullterminieren wenn Platz (verändert out.count)
 return ret;
}

int snprint(char*buf, int len, const char*fmt,...) {
 va_list va;
 va_start(va,fmt);
 int ret=snvprint(buf,len,fmt,va);
 va_end(va);
 return ret;
}

}//namespace
Detected encoding: UTF-80