/*******************************************************************************
Authors: Shahid
Date: 20, December 2011
Version: 0.3
Bugs: Segfaults on invalid utf8
Notes: I've now realised that each Type will need it's own truncation code
History: 0.3 [24.03.11] - refactored in big Itoa changes
0.21[31.12.11] - Support the Varient type
0.21[20.12.11] - better itoa (non-asm)
0.2 [25.07.11] - better itoa
0.1 [14.07.11] - initial working code
0.0 [25.04.11] - initial printf idea
TODO:
Support 0 padding for Decimal printing ( sign after padding )
Printing arrays
Compile time state
repeat( string, times )
Examples:
Text I/O ( but only Output at the moment )
*******************************************************************************/
module shd.io.text;
import shd.exception,
shd.types,
shd.unicode;
import shd.aSm.x86.array;
import GlibC = shd.c.glibc: snprintf;
import shd.text.convert;
public enum Colour
{
Default=0,
// The Standard Colour set values
Red=1, Yellow, Green, Blue, Orange=5, Purple, Black, White, Grey, Brown=10,
/* ... more to come! */
Max // The last colour +1
}
/// Backend of TextFormater
interface TextPrinter
{
void colour( int x );
void flush();
void print( char_c[] s );
int width( dchar d ); // what is this?
}
/**
* String Formatter inspired by C# and python
*
* Start Date: 14, March 2011
*/
class TextFormatter
{
private {
TextPrinter printer;
}
this( TextPrinter printer )
{
this.printer = printer;
}
/**
* Print text
*/
final void print(A...)( A args )
{
// REFACTOR
foreach( arg; args )
format( "{}", arg );
}
final void println(A...)( A args ) /// ditto
{
print( args );
printer.print(['\n']);
printer.flush();
}
/**
* Print formatted text
*/
final void format(A...)( string formatString, A args )
{
Variant[A.length] a;
foreach( i, arg; args )
{
a[i].set( arg );
}
parse( formatString, a[] );
}
final void formatln(A...)( string formatString, A args ) //ditto
{
format( formatString, args );
printer.print(['\n']);
printer.flush();
}
/**
* Select the colour by index code
*
* Params: x - an "enum Colour". Though implementions may define more ( hence type int ).
*/
void colour( int c ) { printer.colour( c ); }
/// Shortcuts for colour()
void normal() { printer.colour( Colour.Default ); }
void red() { printer.colour( Colour.Red ); }
void yellow() { printer.colour( Colour.Yellow ); }
void green() { printer.colour( Colour.Green ); }
void blue() { printer.colour( Colour.Blue ); }
void orange() { printer.colour( Colour.Orange ); }
void purple() { printer.colour( Colour.Purple ); }
void black() { printer.colour( Colour.Black ); }
void white() { printer.colour( Colour.White ); }
void grey() { printer.colour( Colour.Grey ); }
void brown() { printer.colour( Colour.Brown ); }
void flush() { printer.flush(); }
/* *************************************
Internals
***************************************/
/*
* Implementation of format()
*
* TODO explain { Error msgs }
*
*/
private void parse( char_c[] format, ref Variant[] args )
{
char_c* ptr = format.ptr;
char_c* end = format.ptr + format.length;
int indexAuto; // Remember the last index used + 1
char[64] buffer; // must be 16 byte aligned ( not anymore )
//
// Begin
//
while( true )
{
AlignState s; // Formatting state
uint index = indexAuto;
char_c[] formatMod; // eg {:derp}
char_c* cp = find!char( '{', ptr, end );
// write out
printer.print( ptr[0 .. cp-ptr] );
if( cp is end )
break;
cp++;
//
// Second '{'. ie "{{"
//
if( *cp == '{' )
{
ptr = cp+1;
printer.print( "{" ); // REFACTOR
continue;
}
//
// Colour formatter
//
if( *cp == 'c' )
{
try
{
uint colourF;
uint colourB = uint.max;
// Foreground Colour
parseUInt( colourF, ++cp, end, Parse.Empty );
// Background Colour ( Currently not supported )
if( *cp == ',' )
{
parseUInt( colourB, ++cp, end, Parse.Empty );
}
//debug D.writefln("DEBUG colour](%d,%d)",colourF,colourB);
// Check everything parsed ok
if( *cp != '}' )
throw new ParseException("Open");
printer.colour( colourF );
// Optional background colour
//if( colourB != uint.max ) printer.colour2( colourB );
ptr = cp+1;
continue;
}
catch( ParseException e )
{
printer.print("{Bad Colour: " ~ e.msg ~ " }");
ptr = find!char( '}', cp, end ) + 1;
if( ptr > end )
break;
// Continue the loop
continue;
}
} // end if 'c'
//
// { index , alignment ! truncation : format }
//
try
{
// Shortcut
if( *cp == '}' )
goto ParsingDone;
// index
if( true )
{
eatSpace( cp, end ); // Whitespace
parseUInt( index, cp, end, Parse.Empty );
eatSpace( cp, end ); // Whitespace
}
// alignment
if( *cp == ',' )
{
eatSpace( ++cp, end ); // Whitespace
parseAlign( s.alignment, cp, end, &s );
eatSpace( cp, end ); // Whitespace
}
// truncation
if( *cp == '!' )
{
eatSpace( ++cp, end ); // Whitespace
parseAlign( s.truncation, cp, end );
eatSpace( cp, end ); // Whitespace
}
// format
if( *cp == ':' )
{
char_c* oldcp = cp+1;
do {
if( ++cp is end )
throw new ParseException("Open");
}
while( *cp != '}' );
formatMod = oldcp[0.. cp-oldcp];
}
if( *cp != '}' )
throw new ParseException("Open");
/++debug {
import std.stdio;
auto atc = ["","<","^",">", "" ];
// { index, alignment !truncation :formatMod }
writef("DEBUG] State: \"{%d", index );
if( s.alignment.type != AlignState.Type.Unset )
writef(",%s%d", atc[s.alignment.type ], s.alignment.disp );
if( s.truncation.type != AlignState.Type.Unset )
writef("!%s%d", atc[s.truncation.type], s.truncation.disp );
if( formatMod )
writef(":%s",formatMod);
writefln("}\"");
} // +/
/*
* format Parsing Done, now work on the argument(s)
*/
ParsingDone: // setup for next Iteration
ptr = cp+1;
indexAuto = index + 1;
if( index >= args.length ) {
printer.print("{Not enough args}");
continue;
}
Variant a = args[index];
itoaControl ifmt;
with( Variant.Type )
with( itoaControl )
final switch( a.type )
{
case Pointer:
ifmt.base = Base.Hex;
goto case uLong;
case Byte:
case Short:
case Int:
case Long: ifmt.isSigned = true;
// fall through
case uByte:
case uShort:
case uInt:
case uLong:
parseFormatMod( s, ifmt, formatMod );
s.str = itoa( a.uLong, buffer, ifmt );
// Derp
if( ! s.hasAlignment )
{
printer.print( s.str );
} else {
// Alignment + Truncation
// cast away const because s.str is a slice of buffer \\
itoaAlign( cast(char[]) s.str, buffer, ifmt, s );
printAlign( s );
}
continue; // while(true)
case Float:
a.Double = a.Float;
// fall through
case Double:
/* Very basic floating point support */
auto len = GlibC.snprintf( buffer.ptr, buffer.length, "%f", a.Double );
s.str = buffer[0..len];
break;
//case Real:
// a.Double = cast(double) a.Real; // FIXME
// goto case Double;
case Bool:
s.str = a.Bool ? "true" : "false";
break;
// Already a String
case Char:
s.str = (&a.Char)[0..1];
break;
case String:
s.str = a.String;
break;
case Data:
ifmt.padL = 4;
ifmt.padR = 10;
auto str = itoa( a.Data.length, buffer, ifmt );
str[0 .. 4] = "{…";
str[$-10..$] = " bytes…}";
s.str = str;
break;
// ---
case ByteA:
case uByteA:
case ShortA:
case uShortA:
case IntA:
case uIntA:
case LongA:
case uLongA: formatArray( a, s, formatMod );
continue; // while(true)
// --:
case FloatA:
case DoubleA:
// --:
case BoolA:
case PointerA:
goto case Data;
}
// If no Additional processing is needed
if( ! s.hasAlignment )
{
printer.print( s.str );
continue;
}
// First Truncate
with( AlignState.Type ) final switch( s.truncation.type )
{
case Unset:
// Calc width upto alignMax because
// This can break early if width >= alignment
with ( s.truncation )
{
disp = s.alignment.disp;
truncTail( s ); // calcs s.width
disp = 0;
// ---
s.strLeft = s.str;
s.truncCharCount = 0;
}
break;
case Left:
truncHead( s );
break;
case Centre:
truncBody( s );
break;
case Default:
case Right:
truncTail( s );
break;
}
// Alignment
s.calcA( s.width );
// Print Aligned
printAlign( s );
}
catch( ParseException e )
{
printer.print("{Bad Format: " ~ e.msg ~ " }");
ptr = find!char( '}', cp, end ) + 1;
if( ptr > end )
break;
}
//catch( InvalidArgException e ) {}
catch( UTFException e )
{
printer.print("{Malformed UTF8}");
}
} // End while {}
return;
}
void printAlign( ref AlignState s )
{
//REFACTOR ----
writeSpace( s.padL );
printer.print( s.strLeft );
foreach( x; 0..s.truncCharCount)
printer.print( s.truncChar );
printer.print( s.strRight );
writeSpace( s.padR );
//REFACTOR ----
}
/* *************************************
Parse Helpers
***************************************/
// Padding Assistance function
//
// Question: what if count is negative
// Should i trim the string?
// and if so from front/mid/end???
private void writeSpace( int count )
{
// 16 space chars
immutable char[16] pad = " ";
//mutable char[16] pad = "123456789abcdefX";
if( count <= 0 )
return;
while( count > 16 )
{
printer.print( pad );
count -= 16;
}
printer.print( pad[0..count] );
}
private void eatSpace( ref char_c* start, char_c* end )
{
while( start !is end )
{
if( *start != ' ' )
return;
start++;
}
throw new ParseException("Open");
}
// Read Align/Trunc spec
private void parseAlign( out AlignState.Input a, ref char_c* cp, char_c* end, AlignState* s=null )
{
with( AlignState.Type ) switch( *cp )
{
case '<': a.type = Left; cp++; break;
case '^': a.type = Centre; cp++; break;
case '>': a.type = Right; cp++; break;
default : a.type = Default;
}
if( s !is null && cp !is end && *cp == '0' )
s.padCharL = '0';
uint saget;
if( parseUInt( saget, cp, end, Parse.Empty ) )
a.type = AlignState.Type.Unset;
a.disp = cast(int) saget;
}
/*
* Format Array and print
*
* TODO: if itoaAlign() Overflows
* TODO: cleanup terrible mess
*/
private void formatArray( ref Variant v, ref AlignState s, char_c[] formatMod )
{
// I dunno why I called it onion
union Onion {
long i;
ubyte[8] a;
} static assert( Onion.sizeof == 8 );
int size; // size of the Element Type
byte signExtend; // 0x80 for Signed, else 0
char[64] buffer; // itoa
itoaControl ctrl; // 〃
Onion onion;
immutable bool compact = formatMod.length &&
formatMod[0] == 'c';
with( Variant.Type )
switch( v.type )
{
case ByteA: case uByteA: size = 1; break;
case ShortA: case uShortA: size = 2; break;
case IntA: case uIntA: size = 4; break;
case LongA: case uLongA: size = 8; break;
default: assert( false, "Not Numerical Array Type" );
}
// Setup
if( compact )
{
if( v.ByteA.length == 0 )
return;
parseFormatMod( s, ctrl, formatMod[1..$] );
if( s.alignment.type == AlignState.Type.Unset )
{
s.alignment.type = AlignState.Type.Default;
s.alignment.disp = size*2;
s.padCharL = '0';
}
ctrl.prefixLeft= true;
ctrl.base = ctrl.Base.Hex;
ctrl.isSigned = false;
signExtend = 0;
} else {
if( v.ByteA.length == 0 )
{
printer.print("[]");
return;
}
parseFormatMod( s, ctrl, formatMod );
buffer[62..64]= ", ";
ctrl.padR = 2;
s.alignment.disp += 2;
ctrl.isSigned = (v.type & 1);
signExtend = (v.type & 1) << 7; // MSB
debug { red(); printer.print("["); normal(); }
else {
printer.print("[");
}
}
// Horrible Hack
ubyte_c* cp = v.uByteA.ptr;
ubyte_c* end = cp + v.uByteA.length * size - size;
// Derp
if( s.hasAlignment )
{
for( ; cp < end; cp += size )
{
auto s2 = s;
// Sign Extend
onion.i = signExtend & cp[size-1];
onion.a[0..size] = cp[0..size];
s2.str = itoa( onion.i, buffer, ctrl );
// cast away const because s.str is a slice of buffer \\
itoaAlign( cast(char[]) s2.str, buffer, ctrl, s2 );
printAlign( s2 );
}
if( !compact )
{
buffer[63] = ']';
ctrl.padR = 1;
s.alignment.disp -= 1;
}
onion.i = signExtend & cp[size-1];
onion.a[0..size] = cp[0..size];
s.str = itoa( onion.i, buffer, ctrl );
// cast away const because s.str is a slice of buffer \\
itoaAlign( cast(char[]) s.str, buffer, ctrl, s );
printAlign( s );
}
else
{
for( ; cp < end; cp += size )
{
// Sign Extend
onion.i = cast(byte) (signExtend & cp[size-1]);
onion.a[0..size] = cp[0..size];
printer.print( itoa( onion.i, buffer, ctrl ) );
}
buffer[63] = ']';
ctrl.padR = 1;
onion.i = cast(byte) (signExtend & cp[size-1]);
onion.a[0..size] = cp[0..size];
printer.print( itoa( onion.i, buffer, ctrl ) );
}
}
/+ // Templated version
void formatArrayT( A : E[], E )( A a, State s, char_c[] formatMod )
{
char[64] buffer;
itoaControl ctrl;
buffer[62..64] = ", ";
ctrl.padR = 2;
ctrl.isSigned = isSigned!E;
parseFormatMod( s, ctrl, formatMod );
printer.print("[");
foreach( e; a[0..$-1] )
{
printer.print( itoa( e, buffer, ctrl ) );
}
buffer[63] = ']';
ctrl.padR = 1;
printer.print( itoa( a[$-1], buffer, ctrl ) );
}
+/
/* *************************************
Trunc code
***************************************/
// TODO list
// 1) optimise for ascii
// 2) deal with control/non-printable Unicode chars
// This is a pain because there is no easy way to do this
private void truncHead( ref AlignState s )
{
char_c* beg = s.str.ptr;
char_c* end = s.str.ptr + s.str.length;
char_c* cp = s.str.ptr + s.str.length-1;
auto columns = s.truncation.disp;
dchar d;
for( ; beg <= cp; cp-- )
{
// If we are in a multibyte
if( (*cp & 0xC0) == 0x80 )
continue;
// Fast lane
if( columns > 2 )
{
UTF8.toUTF32( d, cp, end );
columns -= printer.width( d );
continue;
}
auto temp1 = UTF8.toUTF32( d, cp, end );
auto temp2 = printer.width( d );
columns -= temp2;
if( columns < 1 )
{
if( cp is beg && columns == 0 )
break;
// Undo the last char
cp += temp1;
columns += temp2;
s.strRight = s.str[ cp - beg .. $ ];
s.width = s.truncation.disp;
s.truncCharCount= columns;
return;
}
}
s.strRight = s.str;
s.width = s.truncation.disp - columns;
s.truncCharCount=0;
return;
}
private void truncBody( ref AlignState s )
{
//REFACTOR
switch( s.truncation.disp )
{
case 1:
s.strLeft = "…";
s.strRight = null;
s.width = 1;
return;
case 2:
// s.strRight = null;
return truncTail( s );
default:
auto save = s.truncation.disp;
s.truncation.disp = save - save/2;
truncHead( s );
s.truncation.disp = save/2; // real
}
char_c* beg = s.str.ptr;
char_c* end = s.strRight.ptr; // s.str.ptr + s.str.length;
char_c* cp = s.str.ptr;
auto columns = s.truncation.disp;
dchar d;
// Similar version of TruncTail
while( cp < end )
{
// Fast lane
if( columns > 2 )
{
cp += UTF8.toUTF32( d, cp, end );
columns -= printer.width( d );
continue;
}
// attempt one more char
auto temp1 = UTF8.toUTF32( d, cp, end );
auto temp2 = printer.width( d );
if( columns-temp2 >= 0 )
{
cp += temp1;
columns -= temp2;
continue;
}
// t.disp >= s.width ( don't use truncChar )
if( cp+temp1 is end && temp2 <= s.truncCharCount + columns )
{
cp += temp1;
columns = 0;
s.truncCharCount = 0;
}
else if ( columns-temp2 == -1 && s.truncCharCount == 2 )
{
// rare case when we end up with 3 '…' in the middle
cp += temp1;
s.truncCharCount -= temp2;
}
s.strLeft = s.str[ 0 .. cp - beg ];
s.width += s.truncation.disp;
s.truncCharCount+= columns;
return;
}
// We c̲a̲n̲ actually get here
s.strLeft = s.str;
s.strRight = null;
s.width += s.truncation.disp - columns - s.truncCharCount;
s.truncCharCount =0;
return;
}
private void truncTail( ref AlignState s )
{
char_c* beg = s.str.ptr;
char_c* end = s.str.ptr + s.str.length;
char_c* cp = s.str.ptr;
auto columns = s.truncation.disp;
dchar d;
while( cp < end )
{
// Fast lane
if( columns > 2 )
{
cp += UTF8.toUTF32( d, cp, end );
columns -= printer.width( d );
continue;
}
// attempt one more char
auto temp1 = UTF8.toUTF32( d, cp, end );
auto temp2 = printer.width( d );
columns -= temp2;
if( columns > 0 )
{
cp += temp1;
continue;
}
if( cp+1 is end && columns == 0 )
break;
s.strLeft = s.str[ 0 .. cp - beg ];
s.width = s.truncation.disp;
s.truncCharCount= columns + temp2;
return;
}
s.strLeft = s.str;
s.width = s.truncation.disp - columns;
s.truncCharCount=0;
return;
}
}
/*
* Alignment:
*
* + : prefix with '+' or '-'
* - : prefix with '␠' or '-'
* : none = only prefix '-' ( default )
*
* Format:
*
* b : Binary [ No ]
* c : UTF8 character [ TODO ]
* d : Decimal ( default )
* o : Octal
* x : Hexadecimal Lowercase
* X : Hexadecimal Uppercase
* n : Separate Thousands comma
* _ : Separate Ten Thousands underscore
*/
void parseFormatMod( ref AlignState s, ref itoaControl ctrl, char_c[] formatMod ) pure
{
// TODO: Make this more general
// instead of '0' being special.
if ( s.padCharL == '0' )
ctrl.prefixLeft = true;
with( itoaControl )
foreach( c; formatMod )
switch( c )
{
case '+': ctrl.sign = Sign.Plus; break;
case '-': ctrl.sign = Sign.Minus; break;
//
case 'x': ctrl.lower = true; // fall through
case 'X': ctrl.base = Base.Hex; break;
case 'd': ctrl.base = Base.Dec; break;
case 'o': ctrl.base = Base.Oct; break;
//se 'b': ctrl.base = 2 ; break;
//
case 'n': ctrl.group = Group.Thou;
ctrl.grpChar= ','; break;
case '_': ctrl.group = Group.TThou;
ctrl.grpChar= '_'; break;
default: {
throw new ParseException("Malformed formatMod");
}
}
}
/* ******************************************************************************
Unit Tests!
*******************************************************************************/
version(unittest)
{
unittest
{
//import shd.c.glibc: LC, setlocale;
import shd.c.glibc;
import shd.output;
setlocale( LC.ALL, "en_GB.UTF-8" );
alias Shdout T;
string[] trunc = [
"abcdefghijklmnopqrstuvwxyz",
"A一一一一一一一一一一一一一一一一一一一A",
"一一一一一一一一一一一一一一一一一一一一",
];
// extended
string[] trunc2 = [
"一一一一一一一一一一一一一一一一一一一A",
"A一一一一一一一一一一一一一一一一一一一",
"abcdefghijklmnopqrs一一一一一一一一一一一一一一一一",
"abcdefghijklmnopqrs一一一一一一一一一一一一一一一A",
"A一一一一一一一一一一一一一一一一一jklmnopqrstuvwxyz",
"一一一一一一一一一一一一一一一一一jklmnopqrstuvwxyz",
];
char_c[] truncHeadGlue( char_c[] str, int columns )
{
AlignState s;
s.str = str;
s.truncation.disp = columns;
T.truncHead( s );
auto n = s.truncCharCount;
return "………"[0..(n*3)] ~ s.strRight;
}
char_c[] truncBodyGlue( char_c[] str, int columns )
{
AlignState s;
s.str = str;
s.truncation.disp = columns;
T.truncBody( s );
auto n = s.truncCharCount;
return s.strLeft ~ "………"[0..(n*3)] ~ s.strRight;
}
char_c[] truncTailGlue( char_c[] str, int columns )
{
AlignState s;
s.str = str;
s.truncation.disp = columns;
T.truncTail( s );
auto n = s.truncCharCount;
return s.strLeft ~ "………"[0..(n*3)];
// TODO check i
}
// Trunc Head
{
alias truncHeadGlue func;
string[] ans = [
"…","…","…",
//2
"…z","…A","……",
//3
"…yz","……A","…一",
//4
"…xyz","…一A","……一",
//5
"…wxyz",
"……一A",
"…一一",
//6
"…vwxyz",
"…一一A",
"……一一",
//20,21,22
"…hijklmnopqrstuvwxyz",
"…一一一一一一一一一A",
"……一一一一一一一一一",
"…ghijklmnopqrstuvwxyz",
"……一一一一一一一一一A",
"…一一一一一一一一一一",
"…fghijklmnopqrstuvwxyz",
"…一一一一一一一一一一A",
"……一一一一一一一一一一",
];
int i;
foreach( t; trunc ) { assert( func( t, 1) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 2) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 3) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 4) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 5) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 6) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 20) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 21) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 22) == ans[i++] ); }
}
// Trunc Tail
{
alias truncTailGlue func;
string[] ans = [
"…","…","…",
//2
"a…","A…","……",
//3
"ab…","A……","一…",
//4
"abc…","A一…","一……",
//5
"abcd…",
"A一……",
"一一…",
//6
"abcde…",
"A一一…",
"一一……",
//20,21,22
"abcdefghijklmnopqrs…",
"A一一一一一一一一一…",
"一一一一一一一一一……",
"abcdefghijklmnopqrst…",
"A一一一一一一一一一……",
"一一一一一一一一一一…",
"abcdefghijklmnopqrstu…",
"A一一一一一一一一一一…",
"一一一一一一一一一一……",
];
int i;
foreach( t; trunc ) { assert( func( t, 1) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 2) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 3) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 4) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 5) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 6) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 20) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 21) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 22) == ans[i++] ); }
}
// Trunc Body
{
alias truncBodyGlue func;
string[] ans = [
"…","…","…",
//2
"a…","A…","……",
//3
"a…z","A…A","一…",
//4
"ab…z","A……A","一……",
//5↓
"ab…yz",
"A一…A",
"一…一",
//6 ↓
"abc…yz",
"A一……A",
"一……一",
//20 ↓
"abcdefghij…rstuvwxyz",
"A一一一一……一一一一A",
"一一一一一……一一一一",
//21 ↓
"abcdefghij…qrstuvwxyz",
"A一一一一一…一一一一A",
"一一一一一…一一一一一",
//22 ↓
"abcdefghijk…qrstuvwxyz",
"A一一一一一……一一一一A",
"一一一一一……一一一一一",
];
int i;
foreach( t; trunc ) { assert( func( t, 1) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 2) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 3) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 4) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 5) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 6) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 20) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 21) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 22) == ans[i++] ); }
// extended
string[] ans2 = [
//1
"…","…","…","…","…","…",
//2
"……","A…","a…","a…","A…","……",
//3
"……A","A……","a……","a…A","A…z","……z",
//4
"A一…一",
"abc…一",
"abc……A",
"A一…yz",
"一……yz",
//5↓
"一……A",
"A……一",
"ab…一",
"ab……A",
"A……yz",
"一…yz",
//6 ↓
"一一…A", // Good no triple Ellipsis
"A一…一",
"abc…一",
"abc……A",
"A一…yz",
"一……yz",
//20 ↓
"一一一一一…一一一一A",
"A一一一一一…一一一一",
"abcdefghij……一一一一",
"abcdefghij…一一一一A",
"A一一一一……rstuvwxyz",
"一一一一一…rstuvwxyz",
//21 ↓
"一一一一一……一一一一A",
"A一一一一……一一一一一",
"abcdefghij…一一一一一",
"abcdefghij……一一一一A",
"A一一一一……qrstuvwxyz",
"一一一一一…qrstuvwxyz",
//22 ↓
"一一一一一一…一一一一A",
"A一一一一一…一一一一一",
"abcdefghijk…一一一一一",
"abcdefghijk……一一一一A",
"A一一一一一…qrstuvwxyz",
"一一一一一……qrstuvwxyz",
];
i=0;
foreach( t; trunc ) { assert( func( t, 1) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 2) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 3) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 4) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 5) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 6) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 20) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 21) == ans[i++] ); }
foreach( t; trunc ) { assert( func( t, 22) == ans[i++] ); }
/+ // Generate Answers
{
alias truncBodyGlue func;
foreach( i; [1,2,3,4] )
{
Shdout.format("\n//{}\n",i);
foreach( t; trunc2 ) { Shdout.format( "\"{}\",", func( t, i)); }
}
Shdout.print("\n");
foreach( i; [5,6,20,21,22] )
{
auto o = ( i > 9 ? 3 : 2 );
Shdout.format("//{}",i);
foreach( x; 0..(i/2)-o) Shdout.print(" "); Shdout.print("↓\n");
foreach( t; trunc2 ) { Shdout.format( "\"{}\",\n", func( t, i)); }
}
}// +/
}
}
unittest
{
import shd.output;
Shdout.println(">---Start format / itoa --<");
immutable int n = 123;
string[] Question = [
"|{ ,0:+}|", "|{ ,0:-}|", "|{ ,0}|",
"|{ ,1:+}|", "|{ ,1:-}|", "|{ ,1}|",
"|{ ,2:+}|", "|{ ,2:-}|", "|{ ,2}|",
"|{ ,3:+}|", "|{ ,3:-}|", "|{ ,3}|",
"|{ ,4:+}|", "|{ ,4:-}|", "|{ ,4}|",
"|{ ,5:+}|", "|{ ,5:-}|", "|{ ,5}|",
"|{ ,6:+}|", "|{ ,6:-}|", "|{ ,6}|",
"|{ ,7:+}|", "|{ ,7:-}|", "|{ ,7}|",
"|{ ,8:+}|", "|{ ,8:-}|", "|{ ,8}|",
"|{ ,9:+}|", "|{ ,9:-}|", "|{ ,9}|",
"|{,10:+}|", "|{,10:-}|", "|{,10}|",
"|{,11:+}|", "|{,11:-}|", "|{,11}|",
"|{,12:+}|", "|{,12:-}|", "|{,12}|",
"|{,13:+}|", "|{,13:-}|", "|{,13}|",
"|{,14:+}|", "|{,14:-}|", "|{,14}|",
"|{,15:+}|", "|{,15:-}|", "|{,15}|",
"|{,16:+}|", "|{,16:-}|", "|{,16}|",
"|{ ,<0:+}|", "|{ ,<0:-}|", "|{ ,<0}|",
"|{ ,<1:+}|", "|{ ,<1:-}|", "|{ ,<1}|",
"|{ ,<2:+}|", "|{ ,<2:-}|", "|{ ,<2}|",
"|{ ,<3:+}|", "|{ ,<3:-}|", "|{ ,<3}|",
"|{ ,<4:+}|", "|{ ,<4:-}|", "|{ ,<4}|",
"|{ ,<5:+}|", "|{ ,<5:-}|", "|{ ,<5}|",
"|{ ,<6:+}|", "|{ ,<6:-}|", "|{ ,<6}|",
"|{ ,<7:+}|", "|{ ,<7:-}|", "|{ ,<7}|",
"|{ ,<8:+}|", "|{ ,<8:-}|", "|{ ,<8}|",
"|{ ,<9:+}|", "|{ ,<9:-}|", "|{ ,<9}|",
"|{,<10:+}|", "|{,<10:-}|", "|{,<10}|",
"|{,<11:+}|", "|{,<11:-}|", "|{,<11}|",
"|{,<12:+}|", "|{,<12:-}|", "|{,<12}|",
"|{,<13:+}|", "|{,<13:-}|", "|{,<13}|",
"|{,<14:+}|", "|{,<14:-}|", "|{,<14}|",
"|{,<15:+}|", "|{,<15:-}|", "|{,<15}|",
"|{,<16:+}|", "|{,<16:-}|", "|{,<16}|",
"|{ ,^0:+}|", "|{ ,^0:-}|", "|{ ,^0}|",
"|{ ,^1:+}|", "|{ ,^1:-}|", "|{ ,^1}|",
"|{ ,^2:+}|", "|{ ,^2:-}|", "|{ ,^2}|",
"|{ ,^3:+}|", "|{ ,^3:-}|", "|{ ,^3}|",
"|{ ,^4:+}|", "|{ ,^4:-}|", "|{ ,^4}|",
"|{ ,^5:+}|", "|{ ,^5:-}|", "|{ ,^5}|",
"|{ ,^6:+}|", "|{ ,^6:-}|", "|{ ,^6}|",
"|{ ,^7:+}|", "|{ ,^7:-}|", "|{ ,^7}|",
"|{ ,^8:+}|", "|{ ,^8:-}|", "|{ ,^8}|",
"|{ ,^9:+}|", "|{ ,^9:-}|", "|{ ,^9}|",
"|{,^10:+}|", "|{,^10:-}|", "|{,^10}|",
"|{,^11:+}|", "|{,^11:-}|", "|{,^11}|",
"|{,^12:+}|", "|{,^12:-}|", "|{,^12}|",
"|{,^13:+}|", "|{,^13:-}|", "|{,^13}|",
"|{,^14:+}|", "|{,^14:-}|", "|{,^14}|",
"|{,^15:+}|", "|{,^15:-}|", "|{,^15}|",
"|{,^16:+}|", "|{,^16:-}|", "|{,^16}|",
];
string[] Question0 = [
"|{ ,01:+}|", "|{ ,01:-}|", "|{ ,01}|",
"|{ ,02:+}|", "|{ ,02:-}|", "|{ ,02}|",
"|{ ,03:+}|", "|{ ,03:-}|", "|{ ,03}|",
"|{ ,04:+}|", "|{ ,04:-}|", "|{ ,04}|",
"|{ ,05:+}|", "|{ ,05:-}|", "|{ ,05}|",
"|{ ,06:+}|", "|{ ,06:-}|", "|{ ,06}|",
"|{ ,07:+}|", "|{ ,07:-}|", "|{ ,07}|",
"|{ ,08:+}|", "|{ ,08:-}|", "|{ ,08}|",
"|{ ,09:+}|", "|{ ,09:-}|", "|{ ,09}|",
"|{,010:+}|", "|{,010:-}|", "|{,010}|",
"|{,011:+}|", "|{,011:-}|", "|{,011}|",
"|{,012:+}|", "|{,012:-}|", "|{,012}|",
"|{,013:+}|", "|{,013:-}|", "|{,013}|",
"|{,014:+}|", "|{,014:-}|", "|{,014}|",
"|{,015:+}|", "|{,015:-}|", "|{,015}|",
"|{,016:+}|", "|{,016:-}|", "|{,016}|",
"|{ ,<01:+}|", "|{ ,<01:-}|", "|{ ,<01}|",
"|{ ,<02:+}|", "|{ ,<02:-}|", "|{ ,<02}|",
"|{ ,<03:+}|", "|{ ,<03:-}|", "|{ ,<03}|",
"|{ ,<04:+}|", "|{ ,<04:-}|", "|{ ,<04}|",
"|{ ,<05:+}|", "|{ ,<05:-}|", "|{ ,<05}|",
"|{ ,<06:+}|", "|{ ,<06:-}|", "|{ ,<06}|",
"|{ ,<07:+}|", "|{ ,<07:-}|", "|{ ,<07}|",
"|{ ,<08:+}|", "|{ ,<08:-}|", "|{ ,<08}|",
"|{ ,<09:+}|", "|{ ,<09:-}|", "|{ ,<09}|",
"|{,<010:+}|", "|{,<010:-}|", "|{,<010}|",
"|{,<011:+}|", "|{,<011:-}|", "|{,<011}|",
"|{,<012:+}|", "|{,<012:-}|", "|{,<012}|",
"|{,<013:+}|", "|{,<013:-}|", "|{,<013}|",
"|{,<014:+}|", "|{,<014:-}|", "|{,<014}|",
"|{,<015:+}|", "|{,<015:-}|", "|{,<015}|",
"|{,<016:+}|", "|{,<016:-}|", "|{,<016}|",
"|{ ,^01:+}|", "|{ ,^01:-}|", "|{ ,^01}|",
"|{ ,^02:+}|", "|{ ,^02:-}|", "|{ ,^02}|",
"|{ ,^03:+}|", "|{ ,^03:-}|", "|{ ,^03}|",
"|{ ,^04:+}|", "|{ ,^04:-}|", "|{ ,^04}|",
"|{ ,^05:+}|", "|{ ,^05:-}|", "|{ ,^05}|",
"|{ ,^06:+}|", "|{ ,^06:-}|", "|{ ,^06}|",
"|{ ,^07:+}|", "|{ ,^07:-}|", "|{ ,^07}|",
"|{ ,^08:+}|", "|{ ,^08:-}|", "|{ ,^08}|",
"|{ ,^09:+}|", "|{ ,^09:-}|", "|{ ,^09}|",
"|{,^010:+}|", "|{,^010:-}|", "|{,^010}|",
"|{,^011:+}|", "|{,^011:-}|", "|{,^011}|",
"|{,^012:+}|", "|{,^012:-}|", "|{,^012}|",
"|{,^013:+}|", "|{,^013:-}|", "|{,^013}|",
"|{,^014:+}|", "|{,^014:-}|", "|{,^014}|",
"|{,^015:+}|", "|{,^015:-}|", "|{,^015}|",
"|{,^016:+}|", "|{,^016:-}|", "|{,^016}|",
];
string[] Answer = [
// Default
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
"| +123|", "| 123|", "| 123|",
// Left
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
// Centre
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123 |",
"|+123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
"| +123 |", "| 123 |", "| 123 |",
];
string[] Answer0 = [
// Default
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|0123|",
"|+0123|", "| 0123|", "|00123|",
"|+00123|", "| 00123|", "|000123|",
"|+000123|", "| 000123|", "|0000123|",
"|+0000123|", "| 0000123|", "|00000123|",
"|+00000123|", "| 00000123|", "|000000123|",
"|+000000123|", "| 000000123|", "|0000000123|",
"|+0000000123|", "| 0000000123|", "|00000000123|",
"|+00000000123|", "| 00000000123|", "|000000000123|",
"|+000000000123|", "| 000000000123|", "|0000000000123|",
"|+0000000000123|", "| 0000000000123|", "|00000000000123|",
"|+00000000000123|", "| 00000000000123|", "|000000000000123|",
"|+000000000000123|", "| 000000000000123|", "|0000000000000123|",
// Left
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
"|+123 |", "| 123 |", "|123 |",
// Centre
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123|",
"|+123|", "| 123|", "|123 |",
"|+123 |", "| 123 |", "|0123 |",
"|+0123 |", "| 0123 |", "|0123 |",
"|+0123 |", "| 0123 |", "|00123 |",
"|+00123 |", "| 00123 |", "|00123 |",
"|+00123 |", "| 00123 |", "|000123 |",
"|+000123 |", "| 000123 |", "|000123 |",
"|+000123 |", "| 000123 |", "|0000123 |",
"|+0000123 |", "| 0000123 |", "|0000123 |",
"|+0000123 |", "| 0000123 |", "|00000123 |",
"|+00000123 |", "| 00000123 |", "|00000123 |",
"|+00000123 |", "| 00000123 |", "|000000123 |",
"|+000000123 |", "| 000000123 |", "|000000123 |",
];
import std.array;
auto array = appender!(char[])();
class ArrayPrinter : TextPrinter
{
void colour( int x ) {};
void flush() {};
int width( dchar d ) { return 1; }
void print( char_c[] s ) { array.put( s ); }
}
auto A = new ArrayPrinter();
auto F = new TextFormatter( A );
foreach( i, q; Question )
{
array.clear;
F.format( q, n );
assert( Answer[i] == array.data );
}
foreach( i, q; Question0 )
{
array.clear;
F.format( q, n );
assert( Answer0[i] == array.data );
}
// TODO octal/dec tests
/+ // Generate Answers
{
import shd.output;
Shdout.print("\n\"");
foreach( i, q; Question )
{
Shdout.format( q, n );
if( (i+1)%3 )
Shdout.print("\",\t\"");
else
Shdout.print("\",\n\"");
}
}// +/
Shdout.println(">--- End format / itoa --<");
}
}// end version(unittest)