ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
formatterpythonstyle.cpp
1// #################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2024 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6// #################################################################################################
8
9#if !defined(ALIB_DOX)
10# if !defined (HPP_ALIB_LANG_FORMAT_FORMATTER_PYTHONSTYLE)
12# endif
13# if !defined (HPP_ALIB_STRINGS_FORMAT)
15# endif
16# if !defined(HPP_ALIB_LANG_FORMAT_EXCEPTIONS)
18# endif
19#endif // !defined(ALIB_DOX)
20
21
22// For code compatibility with ALox Java/C++
23// We have to use underscore as the start of the name and for this have to disable a compiler
24// warning. But this is a local code (cpp file) anyhow.
25#if defined(__clang__)
26 #pragma clang diagnostic push
27 #pragma clang diagnostic ignored "-Wreserved-id-macro"
28#endif
29
30
31#if defined(__clang__)
32 #pragma clang diagnostic pop
33#endif
34
35using namespace alib::strings;
36
37namespace alib::lang::format {
38
39
41: FormatterStdImpl( A_CHAR( "FormatterPythonStyle" ) )
42{
43 // set number format to python defaults
44 DefaultNumberFormat.Flags-= NumberFormatFlags::ForceDecimalPoint;
45 DefaultNumberFormat.Flags+= NumberFormatFlags::WriteExponentPlusSign;
46}
47
48
50{
51 // create a clone
53
54 // create a clone of #Next, in the case that next is derived from std base class
55 if( Next )
56 clone->Next.reset( Next->Clone() );
57
58 // copy my settings, that's it
59 clone->CloneSettings( *this );
60 return clone;
61}
62
63
68
69
74
75
76
78{
79 // first invoke parent's setting...
81
82 // ...then make some "python like" adjustments
83 placeholderPS.Conversion = nullptr;
85
86 placeholder.NF.Flags -= NumberFormatFlags::ForceDecimalPoint;
88 placeholder.NF.INFLiteral = AlternativeNumberFormat.INFLiteral; // choose lower case as default
89 placeholder.NF.NANLiteral = AlternativeNumberFormat.NANLiteral; // choose lower case as default
90 placeholder.NF.Flags += NumberFormatFlags::OmitTrailingFractionalZeros;
91
95}
96
97
99{
100 integer idx= 0;
101 while( (idx= parser.IndexOf('{', idx )) >= 0
102 && parser.CharAt( idx + 1 ) == '{' )
103 {
104 // double ESC found (escaped {{)
105 idx+= + 2;
106 }
107 return idx;
108}
109
110
112{
113 enum states
114 {
115 POSITION = 1,
116 CONVERSION = 2,
117 COLON = 3,
118 FORMAT_SPEC = 4,
119 END = 10,
120 };
121
122 states state= POSITION;
123 #define NEXTSTATE(s) { state= s; continue; }
124
125 while( true )
126 {
127 // switch over state. With 'break' we consume on character (kind of success) while
128 // with 'continue' we keep the current character (and go to another state)
129 switch ( state )
130 {
131 case POSITION:
132
133 if( isdigit( parser.CharAtStart() ) )
134 {
135 int argNo;
136 parser.ConsumeDecDigits( argNo );
137 setArgument( argNo );
138 }
139 NEXTSTATE(CONVERSION)
140
141
142 case CONVERSION:
143 if( parser.CharAtStart() == '!' )
144 {
145 placeholderPS.ConversionPos = static_cast<int>( formatString.Length()
146 - parser.Length() - 1 );
147 integer endConversion= parser.IndexOfAny<lang::Inclusion::Include>( A_CHAR( ":}" ) );
148 if( endConversion < 0 )
151
152 parser.ConsumeChars( endConversion, &placeholderPS.Conversion );
153 }
154 NEXTSTATE(COLON)
155
156 case COLON:
157 if( parser.CharAtStart() != ':' )
158 {
159 state= END;
160 continue;
161 }
162
164 NEXTSTATE(FORMAT_SPEC)
165
166 case FORMAT_SPEC:
167 {
168 // find end of format spec (allow "\}" in format string)
169 integer eoFormatSpec= -1;
170 do
171 {
172 eoFormatSpec= parser.IndexOf( '}', eoFormatSpec + 1 );
173 }
174 while( eoFormatSpec > 0
175 && parser.CharAt( eoFormatSpec - 1) == '\\' );
176
177 if ( eoFormatSpec < 0 )
181
182 // extract format spec to separate sub-string
183 parser.ConsumeChars( eoFormatSpec, &(placeholder.FormatSpec) ) ;
184
185 NEXTSTATE(END)
186 }
187
188 case END:
189 if( parser.CharAtStart() != '}' )
193
195 return true;
196
197 } // state switch
198
199 } // read loop
200
201}
202
203
204
205
207{
208 // parse alignment and fill character. This is a bit tricky to shorten the code.
209 // The alignment token <,>,^ or = can be on first or second position. If second, then the
210 // first was a fill character. Therefore we loop from 0 to 1....
211 integer charsToConsume= 1;
213 Substring& formatSpec= placeholder.FormatSpec;
214
215
216 int cNo= 0;
217 do switch ( formatSpec.CharAt( cNo++ ) )
218 {
223 placeholder.SignPaddingMode= true; break;
224
225 default:
226 if(cNo == 1 )
227 {
228 charsToConsume= 2;
229 placeholder.FillChar= formatSpec.CharAtStart();
230 }
231 else
232 {
233 charsToConsume= 0;
236 }
237 break;
238 }
239 while( cNo < 2 && charsToConsume != 1 );
240 formatSpec.ConsumeChars( charsToConsume );
241
242 // other things to parse
243 character actChar;
244 while( (actChar= formatSpec.CharAtStart()) != '\0' )
245 {
246 // width
247 if( isdigit( actChar ) )
248 {
249 // Python doc says:
250 // When no explicit alignment is given, preceding the width field by a zero ('0') character
251 // enables sign-aware zero-padding for numeric types.
252 // This is equivalent to a fill character of '0' with an alignment type of '='.
253 if ( actChar == '0' )
255 formatSpec.ConsumeDecDigits( placeholder.Width );
256
257 continue; // not consume below
258 }
259
260 // precision. Python doc says:
261 // "The precision is a decimal number indicating how many digits should be displayed
262 // after the decimal point for a floating point value formatted with 'f' and 'F', or
263 // before and after the decimal point for a floating point value formatted with 'g' or 'G'.
264 // For non-number types the field indicates the maximum field size - in other words,
265 // how many characters will be used from the field content. The precision is not
266 // allowed for integral values."
267 else if( actChar == '.' )
268 {
269 placeholderPS.PrecisionPos= static_cast<int>( formatString.Length()
270 - parser.Length()
271 - formatSpec.Length() -1 );
272 formatSpec.ConsumeChars( 1 );
273 if ( ! formatSpec.ConsumeDecDigits( placeholderPS.Precision ) )
277 - parser.Length()
278 - formatSpec.Length() - 1 );
279 continue;
280 }
281
282
283 // ----------------------------- Types -------------------------------------
284 else if (String(A_CHAR( "sdcboxXeEfFngGhHB%" )).IndexOf( actChar ) >= 0 )
285 {
286 if ( placeholder.TypeCode != '\0' )
288 actChar, placeholder.TypeCode,
291 - parser.Length()
292 - formatSpec.Length() - 1 );
293
294 placeholder.TypeCode= actChar;
296 - parser.Length()
297 - formatSpec.Length() - 1 );
298
299 if( String(A_CHAR( "EGF" )).IndexOf( actChar ) >= 0 )
300 {
304 }
305
306 switch( actChar )
307 {
308 case 's': placeholder.Type= PHTypes::String; break;
309 case 'd': placeholder.Type= PHTypes::IntBase10; break;
310 case 'c': placeholder.Type= PHTypes::Character; break;
311 case 'b': placeholder.Type= PHTypes::IntBinary; break;
312 case 'o': placeholder.Type= PHTypes::IntOctal; break;
313
314 case 'X': placeholder.NF.Flags-= NumberFormatFlags::HexLowerCase; ALIB_FALLTHROUGH
315 case 'x': placeholder.Type= PHTypes::IntHex; break;
316
317 case 'H': placeholder.NF.Flags-= NumberFormatFlags::HexLowerCase; ALIB_FALLTHROUGH
318 case 'h': placeholder.Type= PHTypes::HashCode; break;
319 case 'B': placeholder.Type= PHTypes::Bool; break;
320
321 // float types
322 case 'E':
324 placeholder.NF.Flags+= NumberFormatFlags::ForceScientific; break;
325
327 case 'f':
328 case 'F': placeholder.NF.Flags-= NumberFormatFlags::OmitTrailingFractionalZeros;
330
334 case 'G': ALIB_FALLTHROUGH
337 break;
338
339 default: ALIB_ERROR( "FMT", "Unhandled character in choices string above" )
340 return false;
341
342 }//switch
343 }
344
345
346 // others
347 else switch( actChar )
348 {
349 // sign
350 case '+': placeholder.NF.PlusSign= '+' ; break;
351 case '-': placeholder.NF.PlusSign= '\0'; break;
352 case ' ': placeholder.NF.PlusSign= ' ' ; break;
353
354 // alternate version ('#')
355 case '#': placeholder.WriteBinOctHexPrefix= true;
356 placeholder.NF.Flags+= NumberFormatFlags::ForceDecimalPoint;
357 placeholder.NF.Flags-= NumberFormatFlags::OmitTrailingFractionalZeros;
358 break;
359
360 // Enable grouping
361 case ',': placeholder.NF.Flags+= NumberFormatFlags::WriteGroupChars;
362 break;
363
364
365 default:
367 actChar,
370 - parser.Length()
371 - formatSpec.Length() - 1 );
372 }
373
374 formatSpec.ConsumeChars(1);
375 }
376
377 return true;
378}
379
380
382{
383 if( length == 0)
384 return;
385
388 auto* src = parser.Buffer();
389 auto* dest= targetString->VBuffer() + targetString->Length();
390 parser.ConsumeChars( length );
391
392 character c1;
393 character c2= *src;
394 while( length > 1 )
395 {
396 c1= c2;
397 c2= *++src;
398
399 if( ( c1 == '{' && c2 =='{')
400 || ( c1 == '}' && c2 =='}')
401 || c1 == '\\' )
402 {
403 if( c1 == '\\' )
404 switch(c2)
405 {
406 case 'r': c1= '\r' ; break;
407 case 'n': c1= '\n' ;
408 // reset auto-sizes with \n
410 Sizes.Start();
411 break;
412 case 't': c1= '\t' ; break;
413 case 'a': c1= '\a' ; break;
414 case 'b': c1= '\b' ; break;
415 case 'v': c1= '\v' ; break;
416 case 'f': c1= '\f' ; break;
417 case '"': c1= '"' ; break;
418 default: c1= '?' ; break;
419 }
420
421 c2= *++src;
422 --length;
423 }
424
425 *dest++= c1;
426 --length;
427 }
429
430 // copy last character and adjust target string length:
431 // Note: length usually is 1. Only if last character is an escape sequence, it is 0.
432 if( length == 1)
433 *dest= *src;
434 targetString->SetLength( dest - targetString->VBuffer() + length);
435}
436
437
439{
440 bool isPreProcess= startIdx < 0;
441 bool isPostProcess= startIdx>= 0 && target == nullptr;
444
445 while( conversion.IsNotEmpty() )
446 {
447 if( !conversion.ConsumeChar('!') )
452 - conversion.Length() );
453
454 if( conversion.ConsumePartOf( A_CHAR( "Xtinguish" ) ) > 0 ) { return false; }
455 if( conversion.ConsumePartOf( A_CHAR( "Upper" ) ) > 0 ) { if (isPostProcess) targetString->ToUpper( startIdx ); }
456 else if( conversion.ConsumePartOf( A_CHAR( "Lower" ) ) > 0 ) { if (isPostProcess) targetString->ToLower( startIdx ); }
457 else if( conversion.ConsumePartOf( A_CHAR( "str" ) ) > 0
458 ||conversion.ConsumePartOf( A_CHAR( "Quote" ) ) > 0 )
459 {
460 String8 open = A_CHAR( '"' );
461 String8 close= A_CHAR( '"' );
462 if (conversion.IsNotEmpty() && conversion.CharAtStart() != A_CHAR('!'))
463 {
464 open .Reset( conversion.ConsumeChar() );
465 close.Reset( conversion.IsNotEmpty() && conversion.CharAtStart() != A_CHAR('!')
466 ? conversion.ConsumeChar()
467 : open.CharAtStart() );
468 }
469
470 if (isPostProcess)
471 targetString->InsertAt<false>( open, startIdx ) .Append <false>( close );
472 }
473
474 else if( conversion.ConsumePartOf( A_CHAR( "Fill" ) ) > 0 )
475 {
477 placeholder.FillChar= conversion.ConsumeChar<lang::Case::Ignore>('C' ) && conversion.Length() > 0
478 ? conversion.ConsumeChar<false>()
479 : ' ';
480
481 }
482
483 else if( conversion.ConsumePartOf( A_CHAR( "Tab" ) ) > 0 )
484 {
485 character tabChar= conversion.ConsumeChar<lang::Case::Ignore>('C') && conversion.Length() > 0
486 ? conversion.ConsumeChar<false>()
487 : ' ';
488 int tabSize;
489 if( !conversion.ConsumeDecDigits<int>( tabSize ) )
490 tabSize= 8;
491
492 if( isPreProcess )
493 targetString->_<false>( strings::TFormat<character>::Tab( tabSize, -1, 1, tabChar ) );
494
495 }
496
497 else if( conversion.ConsumePartOf( A_CHAR( "ATab" ), 2 ) > 0 )
498 {
499
500 if( conversion.ConsumePartOf( A_CHAR( "Reset") ) > 0 )
501 {
502 if( isPreProcess )
503 Sizes.Reset();
504 }
505 else
506 {
507 character tabChar= conversion.ConsumeChar<lang::Case::Ignore>('C' ) && conversion.Length() > 0
508 ? conversion.ConsumeChar<false>()
509 : ' ';
510
511 int growth;
512 if( !conversion.ConsumeDecDigits<int>( growth ) )
513 growth= 3;
514
515 if( isPreProcess )
516 {
518 integer tabStop= Sizes.Next( AutoSizes::Types::Tabstop, actPos , growth );
519 targetString->InsertChars<false>( tabChar, tabStop - actPos );
520 }
521 }
522 }
523
524 else if( conversion.ConsumePartOf( A_CHAR( "AWidth" ), 2 ) > 0 )
525 {
526 if( conversion.ConsumePartOf( A_CHAR( "Reset" ) ) > 0 )
527 {
528 if( isPreProcess )
529 Sizes.Reset();
530 }
531 else
532 {
533 int extraPadding;
534 conversion.ConsumeDecDigits<int>( extraPadding );
535
536 if( isPreProcess )
537 placeholder.Width= static_cast<int>( Sizes.Actual( AutoSizes::Types::Field, 0, extraPadding ) );
538 else if ( isPostProcess )
539 Sizes.Next( AutoSizes::Types::Field, targetString->Length() - startIdx, extraPadding );
540 }
541 }
542
543 else if( conversion.ConsumePartOf( A_CHAR( "Esc" ) ) > 0
544 || conversion.ConsumePartOf( A_CHAR( "A" ) ) > 0 )
545 {
547 conversion.ConsumeChar('<');
548 if(conversion.ConsumeChar('>') )
549 toESC= lang::Switch::Off;
550
551 if( isPostProcess )
552 targetString->_<false>( typename strings::TFormat<character>::Escape( toESC, startIdx ) );
553 }
554
555 else if( conversion.ConsumePartOf( A_CHAR( "Replace" ), 2 ) > 0 )
556 {
557 String search = conversion.ConsumeField( '<', '>' );
558 String replace= conversion.ConsumeField( '<', '>' );
559 if( search.IsNull() || replace.IsNull() )
564 - conversion.Length() );
565
566 if( target != nullptr )
567 {
568 // special case: fill empty fields
569 if( search.IsEmpty() && target->Length() - startIdx == 0 )
570 {
571 *target << replace;
572 }
573 else
574 target->SearchAndReplace( search, replace, startIdx );
575 }
576 }
577
578 // error (not recognized)
579 else
581 conversion,
585 - conversion.Length() );
586 }
587
588 return true;
589}
590
591
621
622
623} // namespace [alib::lang::format]
virtual ALIB_API void initializeFormat() override
virtual ALIB_API integer findPlaceholder() override
virtual ALIB_API bool preAndPostProcess(integer startIdx, AString *target) override
virtual ALIB_API bool checkStdFieldAgainstArgument() override
virtual ALIB_API FormatterStdImpl * Clone() override
virtual ALIB_API bool parsePlaceholder() override
virtual ALIB_API void writeStringPortion(integer length) override
virtual ALIB_API bool parseStdFormatSpec() override
virtual ALIB_API void resetPlaceholder() override
@ Float
Outputs a number in floating point format.
@ HashCode
Writes raw box data as hex.
@ 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.
virtual ALIB_API void CloneSettings(Formatter &reference)
std::shared_ptr< Formatter > Next
TAString & ToLower(integer regionStart=0, integer regionLength=MAX_LEN)
Definition astring.hpp:1866
TAString & ToUpper(integer regionStart=0, integer regionLength=MAX_LEN)
Definition astring.hpp:1832
TAString & InsertChars(TChar c, integer qty)
Definition astring.hpp:1405
TAString & _(const TString< TChar > &src, integer regionStart, integer regionLength=MAX_LEN)
Definition astring.hpp:1056
void EnsureRemainingCapacity(integer spaceNeeded)
Definition astring.hpp:680
TChar * VBuffer() const
Definition astring.hpp:780
void SetLength(integer newLength)
Definition astring.hpp:861
ALIB_API integer SearchAndReplace(TChar needle, TChar replacement, integer startIdx=0)
Definition astring.cpp:330
TAString & InsertAt(const TString< TChar > &src, integer pos)
Definition astring.hpp:1366
constexpr bool IsNull() const
Definition string.hpp:395
integer IndexOf(TChar needle, integer startIdx=0) const
Definition string.hpp:889
constexpr bool IsEmpty() const
Definition string.hpp:414
TChar CharAt(integer idx) const
Definition string.hpp:437
constexpr bool IsNotEmpty() const
Definition string.hpp:420
constexpr integer Length() const
Definition string.hpp:357
TChar CharAtStart() const
Definition string.hpp:459
integer IndexOfAny(const TString &needles, integer startIdx=0) const
Definition string.hpp:1083
constexpr const TChar * Buffer() const
Definition string.hpp:350
TString< TChar > ConsumeField(TChar startChar, TChar endChar)
integer ConsumePartOf(const TString< TChar > &consumable, int minChars=1)
integer ConsumeChars(integer regionLength, TSubstring *target=nullptr)
bool ConsumeDecDigits(TIntegral &result)
@ Field
denotes a field width entry.
@ Tabstop
denotes a tab stop entry.
ALIB_API integer Actual(Types type, integer requestedSize, integer growthPadding)
Definition autosizes.cpp:29
integer Next(Types type, integer requestedSize, integer growthPadding)
#define ALIB_FALLTHROUGH
Definition alib.hpp:718
#define ALIB_CALLER_NULLED
Definition alib.hpp:846
#define A_CHAR(STR)
#define ALIB_WARNINGS_RESTORE
Definition alib.hpp:715
#define ALIB_ERROR(...)
Definition alib.hpp:980
#define ALIB_WARNINGS_ALLOW_UNSAFE_BUFFER_USAGE
Definition alib.hpp:644
@ Include
Chooses inclusion.
platform_specific integer
Definition integers.hpp:50
@ On
Switch it on, switched on, etc.
@ Off
Switch it off, switched off, etc.
@ Center
Chooses centered alignment.
@ Right
Chooses right alignment.
@ Left
Chooses left alignment.
lang::Exception Exception
Type alias in namespace alib.
characters::character character
Type alias in namespace alib.
TCString< TChar > ExponentSeparator