/**************************************************
** 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-8 | 0
|