ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
token.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_STRINGS_UTIL_TOKEN)
12# endif
13
14# if ALIB_ENUMS
15# if !defined(HPP_ALIB_ENUMS_SERIALIZATION)
17# endif
18# if !defined(HPP_ALIB_LANG_COMMONENUMS)
20# endif
21
23
24# endif
25#endif // !defined(ALIB_DOX)
26
27
28// Windows.h might bring in max/min macros
29#if defined( max )
30 #undef max
31 #undef min
32#endif
33
34
35#if ALIB_BOXING
37#endif
38
39
40namespace alib { namespace strings { namespace util {
41
42Token::Token(const String& pName, lang::Case sensitivity, int8_t minLength)
43: name (pName)
44, format(Formats( int8_t(Formats::Normal )
45 + int8_t(sensitivity == lang::Case::Sensitive ? Formats(0) : ignoreCase) ) )
46, minLengths { minLength, 0,0,0,0,0,0 }
47{
48#if ALIB_DEBUG
49 if( minLength < 0 || minLength > name.Length() )
51
52 if( minLength == 0 )
54#endif
55
56}
57
58Token::Token( const String& pName, lang::Case sensitivity,
59 int8_t minLength1, int8_t minLength2, int8_t minLength3, int8_t minLength4, int8_t minLength5,
60 int8_t minLength6, int8_t minLength7 )
61: name (pName)
62, minLengths { minLength1, minLength2, minLength3, minLength4, minLength5, minLength6, minLength7 }
63{
65 if( int(format) >= 0 && sensitivity == lang::Case::Ignore )
66 format= Formats( int8_t(format) | int8_t(ignoreCase) );
67}
68
69#if ALIB_ENUMS
70void Token::Define( const String& definition, character separator )
71{
73 minLengths[0]= 0;
74 minLengths[1]= -1;
77
78 Substring parser(definition);
79
80 // name
81 name = Substring( parser.ConsumeToken( separator ) ).Trim();
82 if( name.IsEmpty() )
83 return;
84
86 size_t qtyMinLengths= 0;
87 if(parser.IsNotEmpty() )
88 {
89 // letter case sensitivity
90 if( !enums::Parse( parser, letterCase ) )
91 {
93 return;
94 }
95
96 // list of minimum length values
97 while(parser.ConsumeChar( separator ) )
98 {
99 if( qtyMinLengths >= 7 )
100 {
102 return;
103 }
104
105 if( !isdigit(parser.CharAtStart()) )
106 {
108 return;
109 }
110
112 parser.ConsumeDecDigits( minLengths[qtyMinLengths++] );
114 }
115 }
116
118 if( qtyMinLengths == 0 )
119 minLengths[0]= static_cast<int8_t>( name.Length() );
120
121 if( qtyMinLengths > 0 && qtyMinLengths < 7 )
122 minLengths[qtyMinLengths]= -1;
124
125 #if ALIB_DEBUG
126 if( parser.IsNotEmpty() )
127 {
129 return;
130 }
131 #endif
132
133 detectFormat();
134
135 #if ALIB_DEBUG
136 if( int(format) < 0 )
137 return;
138 #endif
139
140 if( letterCase == lang::Case::Ignore )
142}
143#endif
144
146{
147 // detect number of min length values
149 int qtyMinLength= 1;
150 while( qtyMinLength < 7 && minLengths[qtyMinLength] >= 0 )
151 ++qtyMinLength;
153
154 // just one length given? Keep format "normal"
156 if( qtyMinLength > 1 )
157 {
158 // count hyphens, underscores, camel humps...
159 bool hasLowerCases= isalpha(name[0]) && islower(name[0]);
160 int qtyUpperCases= 0;
161 int qtyUnderscores= 0;
162 int qtyHyphens= 0;
163 for( integer idx= 1; idx < name.Length() ; ++idx )
164 {
165 character c= name[idx];
166 if( c == '_' ) ++qtyUnderscores;
167 else if( c == '-' ) ++qtyHyphens;
168 else if( isalpha(c) )
169 {
170 if( islower(c) )
171 hasLowerCases= true;
172 else
173 ++qtyUpperCases;
174 }
175 else
176 hasLowerCases= true;
177 }
178
179 // Snake_Case?
180 if( qtyUnderscores > 0 )
181 {
183 #if ALIB_DEBUG
184 if( (qtyUnderscores >= 7 && qtyMinLength != 7 )
185 || (qtyUnderscores < 7 && qtyMinLength != qtyUnderscores + 1 ) )
187 #endif
188 }
189
190 // Kebab-Case?
191 else if( qtyHyphens > 0 )
192 {
194 #if ALIB_DEBUG
195 if( (qtyHyphens >= 7 && qtyMinLength != 7 )
196 || (qtyHyphens < 7 && qtyMinLength != qtyHyphens + 1 ) )
198 #endif
199 }
200
201 // CamelCase
202 else if( hasLowerCases && ( qtyUpperCases > 0 ) )
203 {
205 #if ALIB_DEBUG
206 if( (qtyUpperCases >= 7 && qtyMinLength != 7 )
207 || (qtyUpperCases < 7 && qtyMinLength != qtyUpperCases + 1 ) )
209 #endif
210 }
211
212 // normal
213 #if ALIB_DEBUG
214 else
216 #endif
217 }
218
219 // check segment sizes against minLengths
220 #if ALIB_DEBUG
221 if( int(format) < 0 )
222 return;
223
224 if( GetFormat() == Formats::Normal )
225 {
226 if( minLengths[0] > name.Length() )
227 {
229 return;
230 }
231 if( minLengths[0] <= 0 )
232 {
234 return;
235 }
236 }
237 else
238 {
239 int segmentNo = 0;
240 int segmentLength= 0;
241 integer charIdx = 1;
242 while( charIdx < name.Length() )
243 {
244 ++segmentLength;
245 character c= name.CharAt( charIdx++ );
246 bool segmentEnd= c == '\0'
247 || (format == Formats::SnakeCase && c == '_' )
248 || (format == Formats::KebabCase && c == '-' )
249 || (format == Formats::CamelCase && isalpha(c) && isupper(c) );
250
251 if( segmentEnd )
252 {
254 if( segmentNo < 7 && minLengths[segmentNo] > segmentLength )
255 {
257 return;
258 }
260
261 segmentLength= (format == Formats::CamelCase ? 1 : 0);
262 ++segmentNo;
263 }
264 }
265
267 for( int minLenIdx= 0 ; minLenIdx < 7 && minLengths[minLenIdx] >= 0 ; ++minLenIdx )
268 {
269 if( minLengths[minLenIdx] == 0
271 || !( minLenIdx == 6 || minLengths[minLenIdx + 1] == -1 ) ) )
272 {
274 return;
275 }
276 }
278 }
279 #endif
280
281
282}
283
284bool Token::Match( const String& needle )
285{
286 ALIB_ASSERT_ERROR( needle.Length() > 0,
287 "STRINGS/TOK", "Empty search string in when matching function name." )
288 lang::Case sensitivity= Sensitivity();
289
290 Formats caseType= GetFormat();
291 bool isNormal= (caseType == Formats::Normal );
292 bool isCamel = (caseType == Formats::CamelCase );
293 bool isSnake = (caseType == Formats::SnakeCase );
294 bool isKebab = (caseType == Formats::KebabCase );
295
296 int segNo = 0;
297 int segLen = 0;
298 bool same = false;
299 integer hIdx = 0;
300 integer nIdx = 0;
301 integer rollbackLen = 0;
302 bool isSegOK = false;
303 int segMinLen = minLengths[0];
304 while( hIdx < name.Length() )
305 {
306 // read current haystack and needle
307 ++segLen;
308 character h= name .CharAt( hIdx++ );
309 character n= needle.CharAt( nIdx++ );
310
311 same= sensitivity == lang::Case::Ignore
314 : h
315 == n;
316
317 // special CamelCase treatment
318 if( isCamel )
319 {
320 // end of needle and final, omitable segment?
321 if( n == '\0' && segMinLen == 0)
322 return true;
323
324 // rollback
325 if( !same )
326 {
327 if( segLen == 1 && rollbackLen > 0)
328 {
329 nIdx-= 2;
330 --rollbackLen;
331 --hIdx;
332 --segLen;
333 continue;
334 }
335
336 --nIdx;
337 }
338
339 if( segLen == 1)
340 rollbackLen= 0;
341
342 else if( same && isSegOK )
343 ++rollbackLen;
344 }
345
346 // end of haystack segment?
347 bool isSegEnd= hIdx == name.Length()
348 || (isSnake && h == '_' )
349 || (isKebab && h == '-' )
350 || (isCamel && isalpha(name.CharAt( hIdx ))
351 && isupper(name.CharAt( hIdx )) );
352
353 // update segOK flag
354 if( same )
355 {
356 isSegOK= ( ( segMinLen >= 0 && segLen >= segMinLen )
357 || ( segMinLen < 0 && isSegEnd ) );
358 }
359
360 // result false, if not same and first of actual segment
361 else if( segLen == 1 && segMinLen != 0 )
362 return false;
363
364
365 // end of segment and needle not empty?
366 if( isSegEnd && n != '\0')
367 {
368 if( !isSegOK )
369 return false;
370 }
371
372 // not same and either not end of segment or empty needle
373 else if( !same )
374 {
375 if( !isSegOK )
376 return false;
377
378 // skip rest of segment
379 while( h != '\0'
380 && ( ( isCamel && (!isalpha(h) || !isupper(h) ) )
381 || ( isSnake && h != '_' )
382 || ( isKebab && h != '-' ) ) )
383 h= name.CharAt( hIdx++ );
384
385 if( isCamel )
386 --hIdx;
387 }
388
389 // start new segment
390 if( !same || isSegEnd )
391 {
392 ++segNo;
393 segLen= 0;
395 segMinLen = segNo < 7 ? minLengths[segNo] : -2;
397
398 // oh,oh!
399 if( n == '\0' && (!isCamel || h == '\0' || rollbackLen == 0) )
400 return h == '\0' || isNormal || segMinLen == 0;
401 }
402 }
403
404 return same && isSegOK && (nIdx == needle.Length());
405}
406
407#if ALIB_CAMP && !defined(ALIB_DOX)
408
410void Token::LoadResourcedTokens( ResourcePool& resourcePool,
411 const NString& resourceCategory,
412 const NString& resourceName,
414 ALIB_DBG( int dbgSizeVerifier, )
415 character outerSeparator,
416 character innerSeparator )
417{
419 ALIB_DBG( int tableSize= 0; )
420 int resourceNo= -1; // disble number parsing
421
422 Substring parser= resourcePool.Get( resourceCategory, resourceName ALIB_DBG(, false ) );
423 if( parser.IsNull() )
424 resourceNo= 0; // enable number parsing
425
426 for( ;; )
427 {
428 if (resourceNo >= 0)
429 parser= resourcePool.Get( resourceCategory, NString256() << resourceName << resourceNo++
430 ALIB_DBG(, false ) );
431
432 ALIB_ASSERT_ERROR( resourceNo != 1 || parser.IsNotNull(), "STRINGS/TOK",
433 NString256() << "Resource string(s) \"" << resourceCategory
434 << "/" << resourceName
435 << "(nn)\" not found when parsing token." )
436
437 if( parser.IsEmpty() )
438 break;
439
440 while( parser.IsNotEmpty() )
441 {
442 String actValue= parser.ConsumeToken( outerSeparator );
443 token->Define( actValue, innerSeparator );
444
445 #if ALIB_DEBUG
446 NCString errorMessage;
447 switch( token->DbgGetError() )
448 {
450 break;
452 errorMessage= "No token name found.";
453 break;
455 errorMessage= "Sensitivity value not found.";
456 break;
458 errorMessage= "Error parsing the list of minimum lengths.";
459 break;
461 errorMessage= " A maximum of 7 minimum length values was exceeded.";
462 break;
464 errorMessage= "The number of given minimum length values is greater than 1 "
465 "but does not match the number of segments in the identifier.";
466 break;
468 errorMessage= "More than one minimum length value was given but no "
469 "segmentation scheme could be detected." ;
470 break;
472 errorMessage= "A minimum length is specified to be higher than the token "
473 "name, respectively the according segment name.";
474 break;
476 errorMessage= "The definition string was not completely consumed.";
477 break;
479 errorMessage= "Zero minimum length provided for segment which is not the last\n"
480 "of a camel case token.";
481 break;
482 }
483
484 if( errorMessage.IsNotEmpty() )
485 {
486 ALIB_ERROR( "STRINGS", errorMessage, NString512() <<
487 "\n(While reading token table.)\n"
488 " Resource category (module name): \"" << resourceCategory << "\"\n"
489 " Resource name: \"" << resourceName << "\"\n"
490 " Token value parsed: \"" << actValue << "\"" )
491 }
492
493 #endif
494
495
496 ++token;
497 ALIB_DBG( tableSize++; )
498 }
499 }
500
501 // check if there are more coming (a gap in numbered definition)
502 #if ALIB_DEBUG
503 if( resourceNo > 1 )
504 for( int i= 0 ; i < 35 ; ++i )
505 {
506 if( resourcePool.Get( resourceCategory, NString256() << resourceName << (resourceNo + i)
507 ALIB_DBG(, false ) ).IsNotNull() )
508 {
509 ALIB_ERROR( "STRINGS", NString128()
510 << "Detected a \"gap\" in numbering of resource strings while parsing "
511 "resource token table: "
512 "From index " << resourceNo - 1 << " to " << resourceNo + i - 1 << ".\n"
513 "Resource category/name: " << resourceCategory << '/' << resourceName << "." )
514 }
515 }
516 #endif
517
518
519 ALIB_ASSERT_ERROR( dbgSizeVerifier == tableSize, "STRINGS/TOK", NString512() <<
520 "Size mismatch in resourced token table:\n"
521 " Resource category (module name): \"" << resourceCategory << "\"\n"
522 " Resource name: \"" << resourceName << "\"\n"
523 " Resourced table size: [" << tableSize << "]\n"
524 " Expected table size: [" << dbgSizeVerifier << "]" )
525
526}
527#endif
528
529}}} // namespace [alib::strings::util]
530
531//! @cond NO_DOX
533::operator()( TAString<alib::character>& target, const alib::strings::util::Token& src)
534{
535 target << src.GetRawName();
536
537 // low the last character in if CamelCase and the last min length equals 0.
538 if( src.GetFormat() == Token::Formats::CamelCase && src.Sensitivity() == lang::Case::Ignore )
539 {
540 for( int i= 0 ; i < 7 ; ++i )
541 {
542 auto minLen= src.GetMinLength( i );
543 if( minLen == 0 )
544 {
545 target[target.Length()-1]= characters::CharArray<character>::ToLower(target[target.Length()-1]);
546 break;
547 }
548 if( minLen == -1 )
549 break;
550 }
551 }
552}
553//! @endcond
virtual const String & Get(const NString &category, const NString &name, bool dbgAssert)=0
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
TSubstring & Trim(const TCString< TChar > &whiteSpaces=TT_StringConstants< TChar >::DefaultWhitespaces())
TString< TChar > ConsumeToken(TChar separator=',')
bool ConsumeDecDigits(TIntegral &result)
ALIB_API void detectFormat()
Definition token.cpp:145
DbgDefinitionError DbgGetError()
Definition token.hpp:291
lang::Case Sensitivity() const
Definition token.hpp:350
static constexpr Formats ignoreCase
Definition token.hpp:205
const String & GetRawName() const
Definition token.hpp:311
Formats GetFormat() const
Definition token.hpp:326
ALIB_API void Define(const String &definition, character separator=';')
Definition token.cpp:70
@ CamelCase
UpperCamelCase or lowerCamelCase.
@ SnakeCase
snake_case using underscores.
@ Normal
Normal, optionally abbreviated words.
@ KebabCase
kebab-case using hyphens.
static ALIB_API void LoadResourcedTokens(lang::resources::ResourcePool &resourcePool, const NString &resourceCategory, const NString &resourceName, strings::util::Token *target, int dbgSizeVerifier, character outerSeparator=',', character innerSeparator=' ')
@ ErrorReadingSensitivity
Sensitivity value not found.
@ TooManyMinLengthsGiven
A maximum of 7 minimum length values was exceeded.
@ ErrorReadingMinLengths
Error parsing the list of minimum lengths.
@ DefinitionStringNotConsumed
The definition string was not completely consumed.
int8_t GetMinLength(int idx) const
Definition token.hpp:374
ALIB_API bool Match(const String &needle)
Definition token.cpp:284
#define ALIB_BOXING_VTABLE_DEFINE(TMapped, Identifier)
Definition vtable.inl:490
#define ALIB_ENUMS_MAKE_BITWISE(TEnum)
Definition bitwise.hpp:120
#define ALIB_WARNINGS_RESTORE
Definition alib.hpp:715
#define ALIB_ERROR(...)
Definition alib.hpp:980
#define ALIB_ASSERT_ERROR(cond,...)
Definition alib.hpp:984
#define ALIB_WARNINGS_ALLOW_UNSAFE_BUFFER_USAGE
Definition alib.hpp:644
#define ALIB_DBG(...)
Definition alib.hpp:457
#define ALIB_REL_DBG(releaseCode,...)
Definition alib.hpp:459
bool Parse(strings::TSubstring< TChar > &input, TEnum &result)
Definition alib.cpp:57
NLocalString< 128 > NString128
Type alias name for TLocalString<nchar,128> .
strings::TSubstring< character > Substring
Type alias in namespace alib.
NLocalString< 256 > NString256
Type alias name for TLocalString<nchar,256> .
characters::character character
Type alias in namespace alib.
NLocalString< 512 > NString512
Type alias name for TLocalString<nchar,512> .
lang::integer integer
Type alias in namespace alib.
Definition integers.hpp:286
static TChar ToUpper(TChar c)