ALib C++ Library
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
formatterpythonstyle.cpp
1//##################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2025 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6//##################################################################################################
7#include "alib_precompile.hpp"
8#if !defined(ALIB_C20_MODULES) || ((ALIB_C20_MODULES != 0) && (ALIB_C20_MODULES != 1))
9# error "Symbol ALIB_C20_MODULES has to be given to the compiler as either 0 or 1"
10#endif
11#if ALIB_C20_MODULES
12 module;
13#endif
14//========================================= Global Fragment ========================================
15#include "alib/alib.inl"
16//============================================== Module ============================================
17#if ALIB_C20_MODULES
18 module ALib.Format.FormatterPythonStyle;
19 import ALib.Lang;
20 import ALib.EnumOps;
21 import ALib.Strings;
22 import ALib.Exceptions;
23# if ALIB_CAMP
24 import ALib.Camp.Base;
25# endif
26#else
27# include "ALib.Lang.H"
28# include "ALib.Strings.H"
29# include "ALib.Exceptions.H"
31# include "ALib.Camp.Base.H"
32#endif
33//========================================== Implementation ========================================
34using namespace alib::strings;
35
36namespace alib::format {
37
38
40: FormatterStdImpl( A_CHAR( "FormatterPythonStyle" ) )
42 // set number format to python defaults
43 DefaultNumberFormat.Flags-= NumberFormatFlags::ForceDecimalPoint;
44 DefaultNumberFormat.Flags+= NumberFormatFlags::WriteExponentPlusSign;
45}
46
47
49 // create a clone
50 SPFormatter clone;
52
53 // create a clone of #Next, in the case that next is derived from std base class
54 if( Next )
55 clone->Next= Next->Clone();
56
57 // copy my settings, that's it
58 clone->CloneSettings( *this );
59 return clone;
60}
61
63 // first invoke parent's setting...
65
66 // ...then make some "python like" adjustments
67 placeholderPS.Conversion = nullptr;
68 placeholderPS.ConversionPos = -1;
69
70 placeholder.NF.Flags -= NumberFormatFlags::ForceDecimalPoint;
71 placeholder.NF.ExponentSeparator = AlternativeNumberFormat.ExponentSeparator; // choose lower case as default
72 placeholder.NF.INFLiteral = AlternativeNumberFormat.INFLiteral; // choose lower case as default
73 placeholder.NF.NANLiteral = AlternativeNumberFormat.NANLiteral; // choose lower case as default
74 placeholder.NF.Flags += NumberFormatFlags::OmitTrailingFractionalZeros;
75
76 placeholderPS.Precision = -1;
77 placeholderPS.PrecisionPos = -1;
78 placeholderPS.DefaultPrecision = 6;
79}
80
81
83 integer idx= 0;
84 while( (idx= parser.IndexOf('{', idx )) >= 0
85 && parser.CharAt( idx + 1 ) == '{' )
86 {
87 // double ESC found (escaped {{)
88 idx+= + 2;
89 }
90 return idx;
91}
92
93
95 // read position
96 if( isdigit( parser.CharAtStart() ) ) {
97 int argNo;
98 parser.ConsumeDecDigits( argNo );
99 setArgument( argNo );
100 }
101
102 // read conversion
103 if( parser.CharAtStart() == '!' ) {
104 placeholderPS.ConversionPos = int( formatString.Length()
105 - parser.Length() - 1 );
106 integer endConversion= parser.IndexOfAny<lang::Inclusion::Include>( A_CHAR( ":}" ) );
107 if( endConversion < 0 )
109 formatString, placeholderPS.ConversionPos );
110
111 parser.ConsumeChars( endConversion, &placeholderPS.Conversion );
112 }
113
114 // read colon and format spec
115 if( parser.CharAtStart() == ':' ) {
116 parser.ConsumeChars(1);
117
118 // find end of format spec: allow "\}", "\{" and nested "{..}" in format string
119 Substring subParser= parser;
120 int depth= 0;
121 while( subParser.IsNotEmpty() ) {
122 if( subParser.CharAtStart() == '}' ) {
123 if(depth == 0 )
124 break;
125
126 --depth;
127 subParser.ConsumeChars<NC>(1);
128 continue;
129 }
130
131 if( subParser.CharAtStart() == '\\' ) { subParser.ConsumeChars(2); continue; }
132 if( subParser.CharAtStart() == '{' ) ++depth;
133 subParser.ConsumeChars<NC>(1);
134 }
135
136 if ( subParser.IsEmpty() )
139 formatString.Length() );
140
141 // extract format spec to separate substring
142 parser.ConsumeChars( parser.Length() - subParser.Length(), &(placeholder.FormatSpec) ) ;
143 }
144
145 // check for closing brace
146 if( parser.CharAtStart() != '}' )
149 formatString.Length() - parser.Length() );
150
151 parser.ConsumeChars(1);
152 return true;
153}
154
155
156
158 // parse alignment and fill character. This is a bit tricky to shorten the code.
159 // The alignment token <,>,^ or = can be on first or second position. If second, then the
160 // first was a fill character. Therefore , we loop from 0 to 1....
161 integer charsToConsume= 1;
162 placeholder.AlignmentSpecified= true;
163 Substring& formatSpec= placeholder.FormatSpec;
164
165 int cNo= 0;
166 do switch ( formatSpec.CharAt( cNo++ ) ) {
167 case '<': placeholder.ValueAlignment= lang::Alignment::Left; break;
168 case '>': placeholder.ValueAlignment= lang::Alignment::Right; break;
169 case '^': placeholder.ValueAlignment= lang::Alignment::Center; break;
170 case '=': placeholder.ValueAlignment= lang::Alignment::Right;
171 placeholder.SignPaddingMode= true; break;
172
173 default:
174 if(cNo == 1 ) {
175 charsToConsume= 2;
176 placeholder.FillChar= formatSpec.CharAtStart();
177 } else {
178 charsToConsume= 0;
179 placeholder.FillChar= ' ';
180 placeholder.AlignmentSpecified= false;
181 }
182 break;
183 }
184 while( cNo < 2 && charsToConsume != 1 );
185 formatSpec.ConsumeChars( charsToConsume );
186
187 // other things to parse
188 character actChar;
189 while( (actChar= formatSpec.CharAtStart()) != '\0' ) {
190 // width
191 if( isdigit( actChar ) ) {
192 // Python doc says:
193 // When no explicit alignment is given, preceding the width field by a zero ('0') character
194 // enables sign-aware zero-padding for numeric types.
195 // This is equivalent to a fill character of '0' with an alignment type of '='.
196 if ( actChar == '0' )
197 placeholder.SignPaddingMode= true;
198 formatSpec.ConsumeDecDigits( placeholder.Width );
199
200 continue; // not consume below
201 }
202
203 // precision. Python doc says:
204 // "The precision is a decimal number indicating how many digits should be displayed
205 // after the decimal point for a floating point value formatted with 'f' and 'F', or
206 // before and after the decimal point for a floating point value formatted with 'g' or 'G'.
207 // For non-number types the field indicates the maximum field size - in other words,
208 // how many characters will be used from the field content. The precision is not
209 // allowed for integral values."
210 else if( actChar == '.' ) {
211 placeholderPS.PrecisionPos= int( formatString.Length()
212 - parser.Length()
213 - formatSpec.Length() -1 );
214 formatSpec.ConsumeChars( 1 );
215 if ( ! formatSpec.ConsumeDecDigits( placeholderPS.Precision ) )
218 formatString.Length()
219 - parser.Length()
220 - formatSpec.Length() - 1 );
221 continue;
222 }
223
224
225 //------------------------------------------- Types ------------------------------------------
226 else if (String(A_CHAR( "sdcboxXeEfFngGhHB%" )).IndexOf( actChar ) >= 0 ) {
227 if ( placeholder.TypeCode != '\0' )
229 actChar, placeholder.TypeCode,
230 placeholder.Arg->TypeID(),
232 formatString.Length() - parser.Length()
233 - formatSpec.Length() - 1 );
234
235 placeholder.TypeCode= actChar;
236 placeholder.TypeCodePosition= int( formatString.Length()
237 - parser.Length()
238 - formatSpec.Length() - 1 );
239
240 if( String(A_CHAR( "EGF" )).IndexOf( actChar ) >= 0 ) {
241 placeholder.NF.ExponentSeparator= DefaultNumberFormat.ExponentSeparator;
242 placeholder.NF.INFLiteral= DefaultNumberFormat.INFLiteral;
243 placeholder.NF.NANLiteral= DefaultNumberFormat.NANLiteral;
244 }
245
246 switch( actChar ) {
247 case 's': placeholder.Type= PHTypes::String; break;
248 case 'd': placeholder.Type= PHTypes::IntBase10; break;
249 case 'c': placeholder.Type= PHTypes::Character; break;
250 case 'b': placeholder.Type= PHTypes::IntBinary; break;
251 case 'o': placeholder.Type= PHTypes::IntOctal; break;
252
253 case 'X': placeholder.NF.Flags-= NumberFormatFlags::HexLowerCase; ALIB_FALLTHROUGH
254 case 'x': placeholder.Type= PHTypes::IntHex; break;
255
256 case 'H': placeholder.NF.Flags-= NumberFormatFlags::HexLowerCase; ALIB_FALLTHROUGH
257 case 'h': placeholder.Type= PHTypes::HashCode; break;
258 case 'B': placeholder.Type= PHTypes::Bool; break;
259
260 // float types
261 case 'E':
262 case 'e': placeholder.Type= PHTypes::Float;
263 placeholder.NF.Flags+= NumberFormatFlags::ForceScientific; break;
264
265 case '%': placeholder.IsPercentage= true; ALIB_FALLTHROUGH
266 case 'f':
267 case 'F': placeholder.NF.Flags-= NumberFormatFlags::OmitTrailingFractionalZeros;
268 placeholder.Type= PHTypes::Float; break;
269
270 case 'n': placeholder.NF.DecimalPointChar= AlternativeNumberFormat.DecimalPointChar;
271 placeholder.NF.ThousandsGroupChar= AlternativeNumberFormat.ThousandsGroupChar;
273 case 'G': ALIB_FALLTHROUGH
274 case 'g': placeholder.Type= PHTypes::Float;
275 placeholderPS.DefaultPrecision= -1;
276 break;
277
278 default: ALIB_ERROR( "FORMAT", "Unhandled character in choices string above" )
279 return false;
280
281 }//switch
282 }
283
284
285 // others
286 else switch( actChar ) {
287 // sign
288 case '+': placeholder.NF.PlusSign= '+' ; break;
289 case '-': placeholder.NF.PlusSign= '\0'; break;
290 case ' ': placeholder.NF.PlusSign= ' ' ; break;
291
292 // alternate version ('#')
293 case '#': placeholder.WriteBinOctHexPrefix= true;
294 placeholder.NF.Flags+= NumberFormatFlags::ForceDecimalPoint;
295 placeholder.NF.Flags-= NumberFormatFlags::OmitTrailingFractionalZeros;
296 break;
297
298 // Enable grouping
299 case ',': placeholder.NF.Flags+= NumberFormatFlags::WriteGroupChars;
300 break;
301
302
303 default:
305 actChar,
307 formatString.Length()
308 - parser.Length()
309 - formatSpec.Length() - 1,
310 placeholder.Arg->TypeID()
311 );
312 }
313
314 formatSpec.ConsumeChars(1);
315 }
316
317 return true;
318}
319
320
322 if( length == 0)
323 return;
324
325 targetString->EnsureRemainingCapacity( length );
326 auto* src = parser.Buffer();
327 auto* dest= targetString->VBuffer() + targetString->Length();
328 parser.ConsumeChars( length );
329
330 character c1;
331 character c2= *src;
332 while( length > 1 ) {
333 c1= c2;
334 c2= *++src;
335
336 if( c1 == '\n' ) // reset auto-sizes when act ual character is '\n'
337 Sizes->Restart();
338
339 else if( ( c1 == '{' && c2 =='{')
340 || ( c1 == '}' && c2 =='}')
341 || c1 == '\\' )
342 {
343 if( c1 == '\\' )
344 switch(c2) {
345 case 'r': c1= '\r' ; break;
346 case 'n': c1= '\n' ;
347 // reset auto-sizes with \n
348 targetStringStartLength= dest - targetString->VBuffer() + 1;
349 Sizes->Restart();
350 break;
351 case 't': c1= '\t' ; break;
352 case 'a': c1= '\a' ; break;
353 case 'b': c1= '\b' ; break;
354 case 'v': c1= '\v' ; break;
355 case 'f': c1= '\f' ; break;
356 case '"': c1= '"' ; break;
357 default: c1= '?' ; break;
358 }
359
360 c2= *++src;
361 --length;
362 }
363
364 *dest++= c1;
365 --length;
366 }
367
368 // copy last character and adjust target string length:
369 // Note: length usually is 1. Only if last character is an escape sequence, it is 0.
370 if( length == 1) {
371 *dest= *src;
372 // reset auto-sizes when last character is '\n'
373 if( *dest == '\n' )
374 Sizes->Restart();
375 }
376 targetString->SetLength( dest - targetString->VBuffer() + length);
377}
378
379
381 bool isPreProcess= startIdx < 0;
382 bool isPostProcess= startIdx>= 0 && target == nullptr;
383 Substring conversion= placeholderPS.Conversion;
384 ++placeholderPS.ConversionPos;
385
386 while( conversion.IsNotEmpty() ) {
387 if( !conversion.ConsumeChar('!') )
389 placeholder.Arg->TypeID(),
390 formatString, placeholderPS.ConversionPos
391 + placeholderPS.Conversion.Length()
392 - conversion.Length() );
393
394 if( conversion.ConsumePartOf( A_CHAR( "Xtinguish" ) ) > 0 ) { return false; }
395 if( conversion.ConsumePartOf( A_CHAR( "Upper" ) ) > 0 ) { if (isPostProcess) targetString->ToUpper( startIdx ); }
396 else if( conversion.ConsumePartOf( A_CHAR( "Lower" ) ) > 0 ) { if (isPostProcess) targetString->ToLower( startIdx ); }
397 else if( conversion.ConsumePartOf( A_CHAR( "str" ) ) > 0
398 ||conversion.ConsumePartOf( A_CHAR( "Quote" ) ) > 0 )
399 {
400 String8 open = A_CHAR( '"' );
401 String8 close= A_CHAR( '"' );
402 if (conversion.IsNotEmpty() && conversion.CharAtStart() != A_CHAR('!')) {
403 open .Reset( conversion.ConsumeChar() );
404 close.Reset( conversion.IsNotEmpty() && conversion.CharAtStart() != A_CHAR('!')
405 ? conversion.ConsumeChar()
406 : open.CharAtStart() );
407 }
408
409 if (isPostProcess)
410 targetString->InsertAt<NC>( open, startIdx ) .Append<NC>( close );
411 }
412
413 else if( conversion.ConsumePartOf( A_CHAR( "Fill" ) ) > 0 ) {
415 placeholder.FillChar= conversion.ConsumeChar<lang::Case::Ignore>('C' ) && conversion.Length() > 0
416 ? conversion.ConsumeChar<NC>()
417 : ' ';
418
419 }
420
421 else if( conversion.ConsumePartOf( A_CHAR( "Tab" ) ) > 0 ) {
422 character tabChar= conversion.ConsumeChar<lang::Case::Ignore>('C') && conversion.Length() > 0
423 ? conversion.ConsumeChar<NC>()
424 : ' ';
425 int tabSize;
426 if( !conversion.ConsumeDecDigits<int>( tabSize ) )
427 tabSize= 8;
428
429 if( isPreProcess )
430 targetString->_<NC>( strings::TTab<character>( tabSize, -1, 1, tabChar ) );
431
432 }
433
434 else if( conversion.ConsumePartOf( A_CHAR( "ATab" ), 2 ) > 0 ) {
435
436 if( conversion.ConsumePartOf( A_CHAR( "Reset") ) > 0 ) {
437 if( isPreProcess )
438 Sizes->Reset();
439 } else {
440 character tabChar= conversion.ConsumeChar<lang::Case::Ignore>('C' ) && conversion.Length() > 0
441 ? conversion.ConsumeChar<NC>()
442 : ' ';
443
444 int growth;
445 if( !conversion.ConsumeDecDigits<int>( growth ) )
446 growth= 3;
447
448 if( isPreProcess ) {
449 integer actPos= targetString->Length() - targetStringStartLength;
450 integer tabStop= Sizes->Next( AutoSizes::Types::Tabstop, actPos , growth );
451 targetString->InsertChars<NC>( tabChar, tabStop - actPos );
452 } } }
453
454 else if( conversion.ConsumePartOf( A_CHAR( "AWidth" ), 2 ) > 0 ) {
455 if( conversion.ConsumePartOf( A_CHAR( "Reset" ) ) > 0 ) {
456 if( isPreProcess )
457 Sizes->Reset();
458 } else {
459 int extraPadding;
460 conversion.ConsumeDecDigits<int>( extraPadding );
461
462 if( isPreProcess )
463 placeholder.Width= int( Sizes->Actual( AutoSizes::Types::Field, 0, extraPadding ) );
464 else if ( isPostProcess )
465 Sizes->Next( AutoSizes::Types::Field, targetString->Length() - startIdx, extraPadding );
466 } }
467
468 else if( conversion.ConsumePartOf( A_CHAR( "Esc" ) ) > 0
469 || conversion.ConsumePartOf( A_CHAR( "A" ) ) > 0 )
470 {
472 conversion.ConsumeChar('<');
473 if(conversion.ConsumeChar('>') )
474 toESC= lang::Switch::Off;
475
476 if( isPostProcess )
477 targetString->_<NC>( typename strings::TEscape<character>( toESC, startIdx ) );
478 }
479
480 else if( conversion.ConsumePartOf( A_CHAR( "Replace" ), 2 ) > 0 ) {
481 String search = conversion.ConsumeField( '<', '>' );
482 String replace= conversion.ConsumeField( '<', '>' );
483 if( search.IsNull() || replace.IsNull() )
485 placeholder.Arg->TypeID(),
486 formatString, placeholderPS.ConversionPos
487 + placeholderPS.Conversion.Length()
488 - conversion.Length() );
489
490 if( target != nullptr ) {
491 // special case: fill empty fields
492 if( search.IsEmpty() && target->Length() - startIdx == 0 ) {
493 *target << replace;
494 }
495 else
496 target->SearchAndReplace( search, replace, startIdx );
497 } }
498
499 // error (not recognized)
500 else
502 conversion, placeholder.Arg->TypeID(),
503 formatString, placeholderPS.ConversionPos
504 + placeholderPS.Conversion.Length()
505 - conversion.Length() );
506 }
507
508 return true;
509}
510
511
513 bool wasFloat= placeholder.Type == PHTypes::Float;
514 if( wasFloat ) {
515 if ( placeholderPS.Precision >= 0 )
516 placeholder.NF.FractionalPartWidth= int8_t( placeholderPS.Precision );
517 else if( placeholder.NF.FractionalPartWidth < 0 )
518 placeholder.NF.FractionalPartWidth= int8_t( placeholderPS.DefaultPrecision);
519 }
520
522
523 if( !wasFloat && placeholder.Type == PHTypes::Float ) {
524 if ( placeholderPS.Precision >= 0 )
525 placeholder.NF.FractionalPartWidth= int8_t( placeholderPS.Precision );
526 }
527
528 if( placeholder.Type == PHTypes::String
529 || placeholder.Type == PHTypes::Bool )
530 placeholder.CutContent= placeholderPS.Precision;
531 else if ( placeholderPS.Precision >= 0
532 && placeholder.Type != PHTypes::Float )
534 placeholder.Arg->TypeID(),
535 formatString, placeholderPS.PrecisionPos );
536 return result;
537
538}
539
540
541} // namespace [alib::format]
void InsertDerived(TArgs &&... args)
virtual ALIB_DLL integer findPlaceholder() override
virtual ALIB_DLL void writeStringPortion(integer length) override
virtual ALIB_DLL void resetPlaceholder() override
virtual ALIB_DLL bool parseStdFormatSpec() override
virtual ALIB_DLL bool preAndPostProcess(integer startIdx, AString *target) override
virtual ALIB_DLL bool checkStdFieldAgainstArgument() override
AutoSizes SizesDefaultInstance
The default instance of field Sizes. This might be replaced with an external object.
PlaceholderAttributesPS placeholderPS
The extended placeholder attributes.
virtual ALIB_DLL SPFormatter Clone() override
virtual ALIB_DLL bool parsePlaceholder() override
AString * targetString
The target string as provided with method Format.
FormatterStdImpl(const String &formatterClassName)
Substring parser
The current (remaining) format string.
integer targetStringStartLength
The length of the target string before adding the formatted contents.
@ Float
Outputs a number in floating point format.
@ IntBinary
Outputs a given number in base 2.
@ IntBase10
Outputs a given number in base 10. The default.
@ IntHex
Outputs a given number in base 16.
@ IntOctal
Outputs a given number in base 8.
String formatString
The format string as provided with method Format.
NumberFormat DefaultNumberFormat
Definition formatter.inl:96
virtual ALIB_DLL void CloneSettings(Formatter &reference)
NumberFormat AlternativeNumberFormat
SharedPtr< Formatter > Next
ALIB_DLL integer SearchAndReplace(TChar needle, TChar replacement, integer startIdx=0, integer endIdx=strings::MAX_LEN)
constexpr integer Length() const
Definition string.inl:316
constexpr bool IsEmpty() const
Definition string.inl:365
TChar CharAtStart() const
Definition string.inl:433
TChar CharAt(integer idx) const
Definition string.inl:415
constexpr bool IsNotEmpty() const
Definition string.inl:369
constexpr bool IsNull() const
Definition string.inl:350
TString< TChar > ConsumeField(TChar startChar, TChar endChar)
integer ConsumePartOf(const TString< TChar > &consumable, int minChars=1)
bool ConsumeDecDigits(std::integral auto &result)
integer ConsumeChars(integer regionLength, TSubstring *target=nullptr)
@ Field
denotes a field width entry.
Definition autosizes.inl:58
@ Tabstop
denotes a tab stop entry.
Definition autosizes.inl:57
#define ALIB_FALLTHROUGH
Definition alib.inl:722
#define ALIB_CALLER_NULLED
Definition alib.inl:1027
#define A_CHAR(STR)
#define ALIB_ERROR(domain,...)
Definition alib.inl:1062
@ Center
Chooses centered alignment.
@ Right
Chooses right alignment.
@ Left
Chooses left alignment.
Switch
Denotes if sth. is switched on or off.
@ On
Switch it on, switched on, etc.
@ Off
Switch it off, switched off, etc.
@ Include
Chooses inclusion.
strings::TAString< character, lang::HeapAllocator > AString
Type alias in namespace alib.
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
LocalString< 8 > String8
Type alias name for TLocalString<character,8>.
exceptions::Exception Exception
Type alias in namespace alib.
containers::SharedPtr< format::Formatter > SPFormatter
Definition formatter.inl:42
characters::character character
Type alias in namespace alib.
strings::TSubstring< character > Substring
Type alias in namespace alib.