• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 24035539233

06 Apr 2026 02:21PM UTC coverage: 60.76% (+0.06%) from 60.696%
24035539233

push

github

web-flow
using fmt instead of ostringstream (#873)

* using fmt instead of ostringstream

misc cleanup

* missing external libs for clang tidy check

* added test for cprops ignore while stacking
door descriptor

* use contains instead of count, removed disabled ancient code

* pack/packonto simplification/speedup

instead of using ostream and convert to string, use format and directly
a string

90 of 141 new or added lines in 17 files covered. (63.83%)

17 existing lines in 10 files now uncovered.

44503 of 73244 relevant lines covered (60.76%)

514255.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.54
/pol-core/bscript/str.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2007/12/09 Shinigami: removed ( is.peek() != EOF ) check from String::unpackWithLen()
5
 *                         will not work with Strings in Arrays, Dicts, etc.
6
 * - 2008/02/08 Turley:    String::unpackWithLen() will accept zero length Strings
7
 * - 2009/09/12 MuadDib:   Disabled 4244 in this file due to it being on a string iter. Makes no
8
 * sense.
9
 */
10

11
#include <cstdlib>
12
#include <ctype.h>
13
#include <cwctype>
14
#include <fmt/compile.h>
15
#include <iterator>
16
#include <string>
17
#include <utf8cpp/utf8.h>
18

19
#include "../clib/clib_endian.h"
20
#include "../clib/stlutil.h"
21
#include "../clib/strutil.h"
22
#include "berror.h"
23
#include "bobject.h"
24
#include "contiter.h"
25
#include "executor.h"
26
#include "impstr.h"
27
#include "objmethods.h"
28
#include "regexp.h"
29
#include "str.h"
30

31
#ifdef __GNUG__
32
#include <streambuf>
33
#endif
34

35
#ifdef _MSC_VER
36
#include "../clib/Header_Windows.h"
37
#include <codecvt>
38
#endif
39

40

41
namespace Pol::Bscript
42
{
43
using namespace fmt::literals;
44

UNCOV
45
String::String( BObjectImp& objimp ) : BObjectImp( OTString ), value_( objimp.getStringRep() ) {}
×
46

47
String::String( const char* s, size_t len, Tainted san ) : BObjectImp( OTString ), value_( s, len )
19,823✔
48
{
49
  if ( san == Tainted::YES )
19,823✔
50
    Clib::sanitizeUnicodeWithIso( &value_ );
×
51
}
19,823✔
52

53
String::String( const std::string& str, std::string::size_type pos, std::string::size_type n )
6,821✔
54
    : BObjectImp( OTString ), value_( str, pos, n )
6,821✔
55
{
56
}
6,821✔
57

58
String::String( const char* str, Tainted san ) : BObjectImp( OTString ), value_( str )
110,356✔
59
{
60
  if ( san == Tainted::YES )
110,356✔
61
    Clib::sanitizeUnicodeWithIso( &value_ );
71✔
62
}
110,356✔
63

64
String::String( const std::string& str, Tainted san ) : BObjectImp( OTString ), value_( str )
63,242✔
65
{
66
  if ( san == Tainted::YES )
63,242✔
67
    Clib::sanitizeUnicodeWithIso( &value_ );
27,572✔
68
}
63,242✔
69

70
String::String( const std::string_view& str, Tainted san ) : BObjectImp( OTString ), value_( str )
10✔
71
{
72
  if ( san == Tainted::YES )
10✔
73
    Clib::sanitizeUnicodeWithIso( &value_ );
10✔
74
}
10✔
75

76
String::String( const std::wstring& str, Tainted san ) : BObjectImp( OTString )
147✔
77
{
78
  for ( const auto& c : str )
348✔
79
  {
80
    utf8::unchecked::append( c, std::back_inserter( value_ ) );
201✔
81
  }
82

83
  if ( san == Tainted::YES )
147✔
84
    Clib::sanitizeUnicodeWithIso( &value_ );
×
85
}
147✔
86

87
String* String::StrStr( int begin, int len ) const
39✔
88
{
89
  auto itr = value_.cbegin();
39✔
90
  --begin;
39✔
91
  size_t startpos = getBytePosition( &itr, begin );
39✔
92
  size_t endpos = getBytePosition( &itr, len );
39✔
93
  if ( startpos != std::string::npos )
39✔
94
    return new String( value_.substr( startpos, endpos - startpos ) );
36✔
95
  return new String( value_ );
3✔
96
}
97

98
size_t String::length() const
7,675✔
99
{
100
  return utf8::unchecked::distance( value_.begin(), value_.end() );
7,675✔
101
}
102

103
String* String::ETrim( const char* CRSet, int type ) const
6✔
104
{
105
  std::string tmp = value_;
6✔
106

107
  if ( type == 1 )  // This is for Leading Only.
6✔
108
  {
109
    // Find the first character position after excluding leading blank spaces
110
    size_t startpos = tmp.find_first_not_of( CRSet );
×
111
    if ( std::string::npos != startpos )
×
112
      tmp = tmp.substr( startpos );
×
113
    else
114
      tmp = "";
×
115
    return new String( tmp );
×
116
  }
117
  if ( type == 2 )  // This is for Trailing Only.
6✔
118
  {
119
    // Find the first character position from reverse
120
    size_t endpos = tmp.find_last_not_of( CRSet );
×
121
    if ( std::string::npos != endpos )
×
122
      tmp = tmp.substr( 0, endpos + 1 );
×
123
    else
124
      tmp = "";
×
125
    return new String( tmp );
×
126
  }
127
  if ( type == 3 )
6✔
128
  {
129
    // Find the first character position after excluding leading blank spaces
130
    size_t startpos = tmp.find_first_not_of( CRSet );
6✔
131
    // Find the first character position from reverse af
132
    size_t endpos = tmp.find_last_not_of( CRSet );
6✔
133

134
    // if all spaces or empty return on empty string
135
    if ( ( std::string::npos == startpos ) || ( std::string::npos == endpos ) )
6✔
136
      tmp = "";
×
137
    else
138
      tmp = tmp.substr( startpos, endpos - startpos + 1 );
6✔
139
    return new String( tmp );
6✔
140
  }
141
  return new String( tmp );
×
142
}
6✔
143

144
void String::EStrReplace( String* str1, String* str2 )
9✔
145
{
146
  std::string::size_type valpos = 0;
9✔
147
  while ( std::string::npos != ( valpos = value_.find( str1->value_, valpos ) ) )
15✔
148
  {
149
    value_.replace( valpos, str1->value_.size(), str2->value_ );
6✔
150
    valpos += str2->value_.size();
6✔
151
  }
152
}
9✔
153

154
void String::ESubStrReplace( String* replace_with, unsigned int index, unsigned int len )
6✔
155
{
156
  auto itr = value_.cbegin();
6✔
157
  size_t begin = getBytePosition( &itr, index - 1 );
6✔
158
  size_t end = getBytePosition( &itr, len );
6✔
159
  if ( begin != std::string::npos )
6✔
160
    value_.replace( begin, end - begin, replace_with->value_ );
6✔
161
}
6✔
162

163
std::string String::pack() const
23✔
164
{
165
  return fmt::format( "s{}"_cf, value_ );
46✔
166
}
167
void String::packonto( std::string& str ) const
48✔
168
{
169
  fmt::format_to( std::back_inserter( str ), "S{}:{}"_cf, value_.size(), value_ );
48✔
170
}
48✔
171
void String::packonto( std::string& str, const std::string& value )
15✔
172
{
173
  fmt::format_to( std::back_inserter( str ), "S{}:{}"_cf, value.size(), value );
15✔
174
}
15✔
175

176
BObjectImp* String::unpack( std::istream& is )
25✔
177
{
178
  std::string tmp;
25✔
179
  getline( is, tmp );
25✔
180

181
  return new String( tmp );
50✔
182
}
25✔
183

184
BObjectImp* String::unpackWithLen( std::istream& is )
34✔
185
{
186
  unsigned len;
187
  char colon;
188
  if ( !( is >> len >> colon ) )
34✔
189
  {
190
    return new BError( "Unable to unpack string length." );
×
191
  }
192
  if ( (int)len < 0 )
34✔
193
  {
194
    return new BError( "Unable to unpack string length. Invalid length!" );
×
195
  }
196
  if ( colon != ':' )
34✔
197
  {
198
    return new BError( "Unable to unpack string length. Bad format. Colon not found!" );
×
199
  }
200

201
  is.unsetf( std::ios::skipws );
34✔
202
  std::string tmp;
34✔
203
  tmp.reserve( len );
34✔
204
  while ( len-- )
191✔
205
  {
206
    char ch = '\0';
157✔
207
    if ( !( is >> ch ) || ch == '\0' )
157✔
208
    {
209
      return new BError( "Unable to unpack string length. String length excessive." );
×
210
    }
211
    tmp += ch;
157✔
212
  }
213

214
  is.setf( std::ios::skipws );
34✔
215
  return new String( tmp );
34✔
216
}
34✔
217

218
size_t String::sizeEstimate() const
1,747✔
219
{
220
  return sizeof( String ) + value_.capacity();
1,747✔
221
}
222

223
/*
224
    0-based string find
225
    find( "str srch", 2, "srch"):
226
    01^-- start
227
    */
228
int String::find( int begin, const char* target ) const
19,854✔
229
{
230
  // returns -1 when begin is out of range for string
231
  auto itr = value_.cbegin();
19,854✔
232
  size_t pos = getBytePosition( &itr, begin );
19,854✔
233
  pos = value_.find( target, pos );
19,854✔
234
  if ( pos == std::string::npos )
19,854✔
235
    return -1;
16,297✔
236

237
  pos = utf8::unchecked::distance( value_.cbegin(), std::next( value_.cbegin(), pos ) );
3,557✔
238
  return static_cast<int>( pos );
3,557✔
239
}
240

241
unsigned int String::SafeCharAmt() const
24✔
242
{
243
  unsigned int strlen = static_cast<unsigned int>( length() );
24✔
244
  for ( unsigned int i = 0; i < strlen; ++i )
279✔
245
  {
246
    unsigned char tmp = value_[i];
255✔
247
    if ( tmp >= 0x80 )  // Ascii range
255✔
248
      return i;
×
249
    if ( isalnum( tmp ) )  // a-z A-Z 0-9
255✔
250
      continue;
237✔
251
    if ( ispunct( tmp ) )  // !"#$%&'()*+,-./:;<=>?@{|}~
18✔
252
    {
253
      if ( tmp == '{' || tmp == '}' )
18✔
254
        return i;
×
255
      continue;
18✔
256
    }
257

258
    return i;
×
259
  }
260
  return strlen;
24✔
261
}
262

263
BObjectImp* String::selfPlusObjImp( const BObjectImp& objimp ) const
7,424✔
264
{
265
  return objimp.selfPlusObj( *this );
7,424✔
266
}
267
BObjectImp* String::selfPlusObj( const BObjectImp& objimp ) const
1,165✔
268
{
269
  return new String( value_ + objimp.getStringRep() );
1,165✔
270
}
271
BObjectImp* String::selfPlusObj( const BLong& objimp ) const
2,030✔
272
{
273
  return new String( value_ + objimp.getStringRep() );
2,030✔
274
}
275
BObjectImp* String::selfPlusObj( const Double& objimp ) const
366✔
276
{
277
  return new String( value_ + objimp.getStringRep() );
366✔
278
}
279
BObjectImp* String::selfPlusObj( const String& objimp ) const
7,346✔
280
{
281
  return new String( value_ + objimp.getStringRep() );
7,346✔
282
}
283
BObjectImp* String::selfPlusObj( const ObjArray& objimp ) const
183✔
284
{
285
  return new String( value_ + objimp.getStringRep() );
183✔
286
}
287
void String::selfPlusObjImp( BObjectImp& objimp, BObject& obj )
136✔
288
{
289
  objimp.selfPlusObj( *this, obj );
136✔
290
}
136✔
291
void String::selfPlusObj( BObjectImp& objimp, BObject& /*obj*/ )
24✔
292
{
293
  value_ += objimp.getStringRep();
24✔
294
}
24✔
295
void String::selfPlusObj( BLong& objimp, BObject& /*obj*/ )
15✔
296
{
297
  value_ += objimp.getStringRep();
15✔
298
}
15✔
299
void String::selfPlusObj( Double& objimp, BObject& /*obj*/ )
12✔
300
{
301
  value_ += objimp.getStringRep();
12✔
302
}
12✔
303
void String::selfPlusObj( String& objimp, BObject& /*obj*/ )
118✔
304
{
305
  value_ += objimp.getStringRep();
118✔
306
}
118✔
307
void String::selfPlusObj( ObjArray& objimp, BObject& /*obj*/ )
9✔
308
{
309
  value_ += objimp.getStringRep();
9✔
310
}
9✔
311

312

313
void String::remove( const std::string& rm )
291✔
314
{
315
  auto pos = value_.find( rm );
291✔
316
  if ( pos != std::string::npos )
291✔
317
    value_.erase( pos, rm.size() );
60✔
318
}
291✔
319

320
BObjectImp* String::selfMinusObjImp( const BObjectImp& objimp ) const
165✔
321
{
322
  return objimp.selfMinusObj( *this );
165✔
323
}
324
BObjectImp* String::selfMinusObj( const BObjectImp& objimp ) const
33✔
325
{
326
  String* tmp = (String*)copy();
33✔
327
  tmp->remove( objimp.getStringRep() );
33✔
328
  return tmp;
33✔
329
}
330
BObjectImp* String::selfMinusObj( const BLong& objimp ) const
12✔
331
{
332
  String* tmp = (String*)copy();
12✔
333
  tmp->remove( objimp.getStringRep() );
12✔
334
  return tmp;
12✔
335
}
336
BObjectImp* String::selfMinusObj( const Double& objimp ) const
12✔
337
{
338
  String* tmp = (String*)copy();
12✔
339
  tmp->remove( objimp.getStringRep() );
12✔
340
  return tmp;
12✔
341
}
342
BObjectImp* String::selfMinusObj( const String& objimp ) const
138✔
343
{
344
  String* tmp = (String*)copy();
138✔
345
  tmp->remove( objimp.value_ );
138✔
346
  return tmp;
138✔
347
}
348
BObjectImp* String::selfMinusObj( const ObjArray& objimp ) const
×
349
{
350
  String* tmp = (String*)copy();
×
351
  tmp->remove( objimp.getStringRep() );
×
352
  return tmp;
×
353
}
354
void String::selfMinusObjImp( BObjectImp& objimp, BObject& obj )
57✔
355
{
356
  objimp.selfMinusObj( *this, obj );
57✔
357
}
57✔
358
void String::selfMinusObj( BObjectImp& objimp, BObject& /*obj*/ )
33✔
359
{
360
  remove( objimp.getStringRep() );
33✔
361
}
33✔
362
void String::selfMinusObj( BLong& objimp, BObject& /*obj*/ )
12✔
363
{
364
  remove( objimp.getStringRep() );
12✔
365
}
12✔
366
void String::selfMinusObj( Double& objimp, BObject& /*obj*/ )
12✔
367
{
368
  remove( objimp.getStringRep() );
12✔
369
}
12✔
370
void String::selfMinusObj( String& objimp, BObject& /*obj*/ )
39✔
371
{
372
  remove( objimp.value_ );
39✔
373
}
39✔
374
void String::selfMinusObj( ObjArray& objimp, BObject& /*obj*/ )
×
375
{
376
  remove( objimp.getStringRep() );
×
377
}
×
378

379
bool String::operator==( const BObjectImp& objimp ) const
26,918✔
380
{
381
  if ( objimp.isa( OTString ) )
26,918✔
382
    return ( value_ == static_cast<const String&>( objimp ).value_ );
26,748✔
383

384
  if ( objimp.isa( OTBoolean ) )
170✔
385
    return isTrue() == static_cast<const BBoolean&>( objimp ).isTrue();
3✔
386
  return base::operator==( objimp );
167✔
387
}
388

389
bool String::operator<( const BObjectImp& objimp ) const
2,458✔
390
{
391
  if ( objimp.isa( OTString ) )
2,458✔
392
    return ( value_ < static_cast<const String&>( objimp ).value_ );
2,241✔
393

394
  return base::operator<( objimp );
217✔
395
}
396

397
void String::toUpper()
42✔
398
{
399
  if ( !hasUTF8Characters() )
42✔
400
  {
401
    Clib::mkupperASCII( value_ );
33✔
402
    return;
33✔
403
  }
404
#ifndef WINDOWS
405
  auto wstr = Clib::to_wstring( value_ );
9✔
406
  value_.clear();
9✔
407
  for ( const auto& c : wstr )
237✔
408
  {
409
    utf8::unchecked::append( std::towupper( c ), std::back_inserter( value_ ) );
228✔
410
  }
411
#else
412
  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
413
  std::wstring str = converter.from_bytes( value_ );
414

415
  int len = LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
416
                          static_cast<int>( str.size() ), 0, 0 );
417
  if ( !len )
418
    return;
419
  else if ( len == static_cast<int>( str.size() ) )
420
  {
421
    LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
422
                  static_cast<int>( str.size() ), &str[0], static_cast<int>( str.size() ) );
423
    value_ = converter.to_bytes( str );
424
  }
425
  else
426
  {
427
    std::wstring buf;
428
    buf.reserve( len );
429
    LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
430
                  static_cast<int>( str.size() ), &buf[0], static_cast<int>( buf.size() ) );
431
    value_ = converter.to_bytes( buf );
432
  }
433
#endif
434
}
9✔
435

436
void String::toLower()
27✔
437
{
438
  if ( !hasUTF8Characters() )
27✔
439
  {
440
    Clib::mklowerASCII( value_ );
18✔
441
    return;
18✔
442
  }
443
#ifndef WINDOWS
444
  auto wstr = Clib::to_wstring( value_ );
9✔
445
  value_.clear();
9✔
446
  for ( const auto& c : wstr )
237✔
447
  {
448
    utf8::unchecked::append( std::towlower( c ), std::back_inserter( value_ ) );
228✔
449
  }
450
#else
451
  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
452
  std::wstring str = converter.from_bytes( value_ );
453

454
  int len = LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
455
                          static_cast<int>( str.size() ), 0, 0 );
456
  if ( !len )
457
    return;
458
  else if ( len == static_cast<int>( str.size() ) )
459
  {
460
    LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
461
                  static_cast<int>( str.size() ), &str[0], static_cast<int>( str.size() ) );
462
    value_ = converter.to_bytes( str );
463
  }
464
  else
465
  {
466
    std::wstring buf;
467
    buf.reserve( len );
468
    LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
469
                  static_cast<int>( str.size() ), &buf[0], static_cast<int>( buf.size() ) );
470
    value_ = converter.to_bytes( buf );
471
  }
472
#endif
473
}
9✔
474

475
size_t String::getBytePosition( std::string::const_iterator* itr, size_t codeindex ) const
52,970✔
476
{
477
  auto itr_end = value_.cend();
52,970✔
478
  for ( size_t i = 0; i < codeindex && *itr != itr_end; ++i )
112,215✔
479
    utf8::unchecked::next( *itr );
59,245✔
480

481
  if ( *itr != itr_end )
52,970✔
482
  {
483
    return std::distance( value_.cbegin(), *itr );
51,311✔
484
  }
485
  return std::string::npos;
1,659✔
486
}
487

488
BObjectImp* String::array_assign( BObjectImp* idx, BObjectImp* target, bool /*copy*/ )
15✔
489
{
490
  std::string::size_type pos, len;
491

492
  // first, determine position and length.
493
  if ( idx->isa( OTString ) )
15✔
494
  {
495
    String& rtstr = (String&)*idx;
12✔
496
    pos = value_.find( rtstr.value_ );
12✔
497
    len = rtstr.value_.size();
12✔
498
  }
499
  else if ( idx->isa( OTLong ) )
3✔
500
  {
501
    BLong& lng = (BLong&)*idx;
3✔
502
    len = 1;
3✔
503
    pos = lng.value() - 1;
3✔
504
    auto itr = value_.cbegin();
3✔
505
    pos = getBytePosition( &itr, pos );
3✔
506
    if ( pos != std::string::npos )
3✔
507
    {
508
      utf8::unchecked::next( itr );
3✔
509
      len = std::distance( value_.cbegin(), itr ) - pos;
3✔
510
    }
511
    else
512
      pos = std::string::npos;
×
513
  }
514
  else if ( idx->isa( OTDouble ) )
×
515
  {
516
    Double& dbl = (Double&)*idx;
×
517
    pos = static_cast<std::string::size_type>( dbl.value() ) - 1;
×
518
    len = 1;
×
519
    auto itr = value_.cbegin();
×
520
    pos = getBytePosition( &itr, pos );
×
521
    if ( pos != std::string::npos )
×
522
    {
523
      utf8::unchecked::next( itr );
×
524
      len = std::distance( value_.cbegin(), itr ) - pos;
×
525
    }
526
    else
527
      pos = std::string::npos;
×
528
  }
529
  else
530
  {
531
    return UninitObject::create();
×
532
  }
533

534
  if ( pos != std::string::npos )
15✔
535
  {
536
    if ( target->isa( OTString ) )
15✔
537
    {
538
      String* target_str = (String*)target;
15✔
539
      value_.replace( pos, len, target_str->value_ );
15✔
540
    }
541
    return this;
15✔
542
  }
543

544
  return UninitObject::create();
×
545
}
546

547
BObjectRef String::OperMultiSubscriptAssign( std::stack<BObjectRef>& indices, BObjectImp* target )
6✔
548
{
549
  BObjectRef start_ref = indices.top();
6✔
550
  indices.pop();
6✔
551
  BObjectRef length_ref = indices.top();
6✔
552
  indices.pop();
6✔
553

554
  BObject& length_obj = *length_ref;
6✔
555
  BObject& start_obj = *start_ref;
6✔
556

557
  BObjectImp& length = length_obj.impref();
6✔
558
  BObjectImp& start = start_obj.impref();
6✔
559

560
  // first deal with the start position.
561
  size_t index;
562
  if ( start.isa( OTLong ) )
6✔
563
  {
564
    BLong& lng = (BLong&)start;
6✔
565
    index = (size_t)lng.value();
6✔
566
    if ( index == 0 || index > value_.size() )
6✔
567
      return BObjectRef( new BError( "Subscript out of range" ) );
×
568
    --index;
6✔
569
    auto itr = value_.cbegin();
6✔
570
    index = getBytePosition( &itr, index );
6✔
571
    if ( index == std::string::npos )
6✔
572
      return BObjectRef( new BError( "Subscript out of range" ) );
×
573
  }
574
  else if ( start.isa( OTString ) )
×
575
  {
576
    String& rtstr = (String&)start;
×
577
    std::string::size_type pos = value_.find( rtstr.value_ );
×
578
    if ( pos != std::string::npos )
×
579
      index = static_cast<size_t>( pos );
×
580
    else
581
      return BObjectRef( new UninitObject );
×
582
  }
583
  else
584
  {
585
    return BObjectRef( copy() );
×
586
  }
587

588
  // now deal with the length.
589
  size_t len;
590
  if ( length.isa( OTLong ) )
6✔
591
  {
592
    BLong& lng = (BLong&)length;
6✔
593

594
    len = (size_t)lng.value();
6✔
595
  }
596
  else if ( length.isa( OTDouble ) )
×
597
  {
598
    Double& dbl = (Double&)length;
×
599

600
    len = (size_t)dbl.value();
×
601
  }
602
  else
603
  {
604
    return BObjectRef( copy() );
×
605
  }
606
  auto itr = value_.cbegin();
6✔
607
  std::advance( itr, index );
6✔
608
  size_t index_len = getBytePosition( &itr, len );
6✔
609

610
  if ( index_len != std::string::npos )
6✔
611
    len = index_len - index;
6✔
612
  else
613
    len = index_len;
×
614

615
  if ( target->isa( OTString ) )
6✔
616
  {
617
    String* target_str = (String*)target;
6✔
618
    value_.replace( index, len, target_str->value_ );
6✔
619
  }
620
  else
621
  {
622
    return BObjectRef( UninitObject::create() );
×
623
  }
624

625
  return BObjectRef( this );
6✔
626
}
6✔
627

628

629
BObjectRef String::OperMultiSubscript( std::stack<BObjectRef>& indices )
7,217✔
630
{
631
  BObjectRef start_ref = indices.top();
7,217✔
632
  indices.pop();
7,217✔
633
  BObjectRef length_ref = indices.top();
7,217✔
634
  indices.pop();
7,217✔
635

636
  BObject& length_obj = *length_ref;
7,217✔
637
  BObject& start_obj = *start_ref;
7,217✔
638

639
  BObjectImp& length = length_obj.impref();
7,217✔
640
  BObjectImp& start = start_obj.impref();
7,217✔
641

642
  // first deal with the start position.
643
  size_t index;
644
  if ( start.isa( OTLong ) )
7,217✔
645
  {
646
    BLong& lng = (BLong&)start;
7,211✔
647
    index = (size_t)lng.value();
7,211✔
648
    if ( index == 0 || index > value_.size() )
7,211✔
649
      return BObjectRef( new BError( "Subscript out of range" ) );
1,264✔
650
    --index;
6,579✔
651
    auto itr = value_.cbegin();
6,579✔
652
    index = getBytePosition( &itr, index );
6,579✔
653
    if ( index == std::string::npos )
6,579✔
654
      return BObjectRef( new BError( "Subscript out of range" ) );
×
655
  }
656
  else if ( start.isa( OTString ) )
6✔
657
  {
658
    String& rtstr = (String&)start;
6✔
659
    std::string::size_type pos = value_.find( rtstr.value_ );
6✔
660
    if ( pos != std::string::npos )
6✔
661
      index = static_cast<unsigned int>( pos );
6✔
662
    else
663
      return BObjectRef( new UninitObject );
×
664
  }
665
  else
666
  {
667
    return BObjectRef( copy() );
×
668
  }
669

670
  // now deal with the length.
671
  size_t len;
672
  if ( length.isa( OTLong ) )
6,585✔
673
  {
674
    BLong& lng = (BLong&)length;
6,585✔
675

676
    len = (size_t)lng.value();
6,585✔
677
  }
678
  else if ( length.isa( OTDouble ) )
×
679
  {
680
    Double& dbl = (Double&)length;
×
681

682
    len = (size_t)dbl.value();
×
683
  }
684
  else
685
  {
686
    return BObjectRef( copy() );
×
687
  }
688
  auto itr = value_.cbegin();
6,585✔
689
  std::advance( itr, index );
6,585✔
690
  size_t index_len = getBytePosition( &itr, len );
6,585✔
691

692
  if ( index_len != std::string::npos )
6,585✔
693
    len = index_len - index;
5,467✔
694
  else
695
    len = index_len;
1,118✔
696
  return BObjectRef( new String( value_, index, len ) );
6,585✔
697
}
7,217✔
698

699
BObjectRef String::OperSubscript( const BObject& rightobj )
22,408✔
700
{
701
  const BObjectImp& right = rightobj.impref();
22,408✔
702
  if ( right.isa( OTLong ) )
22,408✔
703
  {
704
    BLong& lng = (BLong&)right;
19,835✔
705

706
    if ( lng.value() < 0 )
19,835✔
707
      return BObjectRef( new BError( "Subscript out of range" ) );
3✔
708

709
    size_t index = (size_t)lng.value();
19,832✔
710

711
    if ( index == 0 || index > value_.size() )
19,832✔
712
      return BObjectRef( new BError( "Subscript out of range" ) );
9✔
713

714
    --index;
19,823✔
715
    auto itr = value_.cbegin();
19,823✔
716
    index = getBytePosition( &itr, index );
19,823✔
717
    if ( index != std::string::npos )
19,823✔
718
    {
719
      utf8::unchecked::next( itr );
19,823✔
720
      size_t len = std::distance( value_.cbegin(), itr ) - index;
19,823✔
721
      return BObjectRef( new BObject( new String( value_.c_str() + index, len ) ) );
19,823✔
722
    }
723
    return BObjectRef( new BError( "Subscript out of range" ) );
×
724
  }
725
  if ( right.isa( OTDouble ) )
2,573✔
726
  {
727
    Double& dbl = (Double&)right;
9✔
728

729
    if ( dbl.value() < 0 )
9✔
730
      return BObjectRef( new BError( "Subscript out of range" ) );
3✔
731
    size_t index = (size_t)dbl.value();
6✔
732

733
    if ( index == 0 || index > value_.size() )
6✔
734
      return BObjectRef( new BError( "Subscript out of range" ) );
6✔
735

736
    --index;
×
737
    auto itr = value_.cbegin();
×
738
    index = getBytePosition( &itr, index );
×
739
    if ( index != std::string::npos )
×
740
    {
741
      utf8::unchecked::next( itr );
×
742
      size_t len = std::distance( value_.cbegin(), itr ) - index;
×
743
      return BObjectRef( new BObject( new String( value_.c_str() + index, len ) ) );
×
744
    }
745
    return BObjectRef( new BError( "Subscript out of range" ) );
×
746
  }
747
  if ( right.isa( OTString ) )
2,564✔
748
  {
749
    String& rtstr = (String&)right;
2,543✔
750
    auto pos = value_.find( rtstr.value_ );
2,543✔
751
    if ( pos != std::string::npos )
2,543✔
752
    {
753
      auto itr = value_.cbegin();
236✔
754
      std::advance( itr, pos );
236✔
755
      utf8::unchecked::next( itr );
236✔
756
      size_t len = std::distance( value_.cbegin(), itr ) - pos;
236✔
757
      return BObjectRef( new BObject( new String( value_, pos, len ) ) );
236✔
758
    }
759
    return BObjectRef( new UninitObject );
2,307✔
760
  }
761

762
  return BObjectRef( new UninitObject );
21✔
763
}
764

765
// -- format related stuff --
766
bool s_parse_int( int& i, std::string const& s )
39✔
767
{
768
  if ( s.empty() )
39✔
769
    return false;
×
770

771
  char* end;
772
  i = strtol( s.c_str(), &end, 10 );
39✔
773

774
  if ( !*end )
39✔
775
  {
776
    return true;
27✔
777
  }
778

779
  return false;
12✔
780
}
781

782
void int_to_binstr( int& value, std::stringstream& s )
×
783
{
784
  int i;
785
  for ( i = 31; i > 0; i-- )
×
786
  {
787
    if ( value & ( 1 << i ) )
×
788
      break;
×
789
  }
790
  for ( ; i >= 0; i-- )
×
791
  {
792
    if ( value & ( 1 << i ) )
×
793
      s << "1";
×
794
    else
795
      s << "0";
×
796
  }
797
}
×
798

799
// suplementory function to format
800
bool try_to_format( std::stringstream& to_stream, BObjectImp* what, std::string& frmt )
1,281✔
801
{
802
  if ( frmt.empty() )
1,281✔
803
  {
804
    to_stream << what->getStringRep();
1,051✔
805
    return false;
1,051✔
806
  }
807

808
  if ( frmt.find( 'b' ) != std::string::npos )
230✔
809
  {
810
    if ( auto* plong = impptrIf<BLong>( what ) )
×
811
    {
812
      int n = plong->value();
×
813
      if ( frmt.find( '#' ) != std::string::npos )
×
814
        to_stream << ( ( n < 0 ) ? "-" : "" ) << "0b";
×
815
      int_to_binstr( n, to_stream );
×
816
    }
817
    else
818
    {
819
      to_stream << "<needs Int>";
×
820
      return false;
×
821
    }
822
  }
823
  else if ( frmt.find( 'x' ) != std::string::npos )
230✔
824
  {
825
    if ( auto* plong = impptrIf<BLong>( what ) )
230✔
826
    {
827
      int n = plong->value();
195✔
828
      if ( frmt.find( '#' ) != std::string::npos )
195✔
829
        to_stream << "0x";
9✔
830
      to_stream << std::hex << n << std::dec;
195✔
831
    }
832
    else
833
    {
834
      to_stream << "<needs Int>";
35✔
835
      return false;
35✔
836
    }
837
  }
838
  else if ( frmt.find( 'o' ) != std::string::npos )
×
839
  {
840
    if ( auto* plong = impptrIf<BLong>( what ) )
×
841
    {
842
      int n = plong->value();
×
843
      if ( frmt.find( '#' ) != std::string::npos )
×
844
        to_stream << "0o";
×
845
      to_stream << std::oct << n << std::dec;
×
846
    }
847
    else
848
    {
849
      to_stream << "<needs Int>";
×
850
      return false;
×
851
    }
852
  }
853
  else if ( frmt.find( 'd' ) != std::string::npos )
×
854
  {
855
    int n;
856
    if ( auto* plong = impptrIf<BLong>( what ) )
×
857
      n = plong->value();
×
858
    else if ( auto* pdbl = impptrIf<Double>( what ) )
×
859
      n = (int)pdbl->value();
×
860
    else
861
    {
862
      to_stream << "<needs Int, Double>";
×
863
      return false;
×
864
    }
865
    to_stream << std::dec << n;
×
866
  }
867
  else
868
  {
869
    to_stream << "<bad format: " << frmt << ">";
×
870
    return false;
×
871
  }
872
  return true;
195✔
873
}
874
// --
875

876
std::string get_formatted( BObjectImp* what, std::string& frmt )
218✔
877
{
878
  std::stringstream result;
218✔
879
  try_to_format( result, what, frmt );
218✔
880
  return result.str();
436✔
881
}
218✔
882

883
BObjectImp* String::call_method( const char* methodname, Executor& ex )
×
884
{
885
  ObjMethod* objmethod = getKnownObjMethod( methodname );
×
886
  if ( objmethod != nullptr )
×
887
    return this->call_method_id( objmethod->id, ex );
×
888
  return nullptr;
×
889
}
890
BObjectImp* String::call_method_id( const int id, Executor& ex, bool /*forcebuiltin*/ )
22,719✔
891
{
892
  switch ( id )
22,719✔
893
  {
894
  case MTH_LENGTH:
5,639✔
895
    if ( ex.numParams() == 0 )
5,639✔
896
      return new BLong( static_cast<int>( length() ) );
5,639✔
897
    else
898
      return new BError( "string.length() doesn't take parameters." );
×
899
    break;
900
  case MTH_FIND:
15,884✔
901
  {
902
    if ( ex.numParams() > 2 )
15,884✔
903
      return new BError( "string.find(Search, [Start]) takes only two parameters" );
×
904
    if ( ex.numParams() < 1 )
15,884✔
905
      return new BError( "string.find(Search, [Start]) takes at least one parameter" );
×
906

907
    int d = 0;
15,884✔
908
    if ( ex.numParams() == 2 )
15,884✔
909
      d = ex.paramAsLong( 1 ) - 1;
107✔
910

911
    if ( auto s = impptrIf<String>( ex.getParamImp( 0 ) ) )
15,884✔
912
    {
913
      int posn = find( d, s->data() ) + 1;
15,800✔
914
      return new BLong( posn );
15,800✔
915
    }
916
    if ( auto regex = impptrIf<BRegExp>( ex.getParamImp( 0 ) ) )
84✔
917
    {
918
      return regex->find( this, d );
84✔
919
    }
920
    return new BError( "string.find(Search, [Start]): Search must be a string or regex" );
×
921
  }
922
  case MTH_MATCH:
63✔
923
  {
924
    if ( ex.numParams() != 1 )
63✔
925
      return new BError( "string.match(Pattern) takes exactly one parameter" );
6✔
926

927
    auto regex = impptrIf<BRegExp>( ex.getParamImp( 0 ) );
57✔
928

929
    if ( !regex )
57✔
930
      return new BError( "string.match(Pattern): Pattern must be a RegExp" );
6✔
931

932
    return regex->match( this );
51✔
933
  }
934
  case MTH_REPLACE:
75✔
935
  {
936
    if ( ex.numParams() != 2 )
75✔
937
      return new BError( "string.replace(Search, Replace) takes exactly two parameters" );
6✔
938

939
    auto regex = impptrIf<BRegExp>( ex.getParamImp( 0 ) );
69✔
940
    if ( !regex )
69✔
941
      return new BError( "string.replace(Search, Replace): Search must be a RegExp" );
6✔
942

943
    if ( auto s = impptrIf<String>( ex.getParamImp( 1 ) ) )
63✔
944
    {
945
      return regex->replace( this, s );
36✔
946
    }
947
    if ( auto funcref = impptrIf<BFunctionRef>( ex.getParamImp( 1 ) ) )
27✔
948
    {
949
      return regex->replace( ex, this, funcref );
21✔
950
    }
951

952
    return new BError( "string.replace(Search, Replace): Replace must be a string or function" );
6✔
953
  }
954
  case MTH_SPLIT:
57✔
955
  {
956
    size_t limit = std::numeric_limits<size_t>::max();
57✔
957

958
    if ( ex.numParams() == 0 )
57✔
959
      return new BError( "string.split(Separator[, Max_Split]) takes at least one parameter" );
×
960
    if ( ex.numParams() > 2 )
57✔
961
      return new BError( "string.split(Separator[, Max_Split]) takes at most two parameters" );
×
962

963
    if ( ex.numParams() == 2 )
57✔
964
    {
965
      int max_splits = ex.paramAsLong( 1 );
21✔
966
      if ( max_splits > -1 )
21✔
967
        limit = Clib::clamp_convert<size_t>( max_splits + 1 );
21✔
968
    }
969

970
    if ( auto regex = impptrIf<BRegExp>( ex.getParamImp( 0 ) ) )
57✔
971
    {
972
      return regex->split( this, limit );
30✔
973
    }
974
    if ( auto string_sep = impptrIf<String>( ex.getParamImp( 0 ) ) )
27✔
975
    {
976
      std::unique_ptr<ObjArray> result( new ObjArray );
27✔
977
      const auto& sep = string_sep->value_;
27✔
978

979
      // Empty separator splits into characters
980
      if ( sep.empty() )
27✔
981
      {
982
        return getCharacters( value_, limit );
12✔
983
      }
984

985
      // Has a non-empty separator, splits by `sep`
986
      std::size_t start = 0;
15✔
987
      while ( start < value_.size() )
60✔
988
      {
989
        // Limit reached, push rest and break
990
        if ( result->ref_arr.size() == limit - 1 )
57✔
991
        {
992
          result->addElement( new String( value_.substr( start ) ) );
9✔
993
          break;
9✔
994
        }
995

996
        std::size_t pos = value_.find( sep, start );
48✔
997
        if ( pos == std::string::npos )
48✔
998
        {
999
          // No more separators, push rest
1000
          result->addElement( new String( value_.substr( start ) ) );
3✔
1001
          break;
3✔
1002
        }
1003

1004
        // Extract part
1005
        result->addElement( new String( value_.substr( start, pos - start ) ) );
45✔
1006
        start = pos + sep.size();
45✔
1007
      }
1008

1009
      return result.release();
15✔
1010
    }
27✔
1011

1012
    return new BError(
1013
        "string.split(Separator[, Max_Split]): Separator must be a RegExp or string" );
×
1014
  }
1015

1016
  case MTH_UPPER:
9✔
1017
  {
1018
    if ( ex.numParams() == 0 )
9✔
1019
    {
1020
      toUpper();
9✔
1021
      return this;
9✔
1022
    }
1023
    return new BError( "string.upper() doesn't take parameters." );
×
1024
  }
1025

1026
  case MTH_LOWER:
9✔
1027
  {
1028
    if ( ex.numParams() == 0 )
9✔
1029
    {
1030
      toLower();
9✔
1031
      return this;
9✔
1032
    }
1033
    return new BError( "string.lower() doesn't take parameters." );
×
1034
  }
1035
  case MTH_FORMAT:
951✔
1036
  {
1037
    if ( ex.numParams() > 0 )
951✔
1038
    {
1039
      // string s = this->getStringRep(); // string itself
1040
      std::stringstream result;
951✔
1041

1042
      size_t tag_start_pos;  // the position of tag's start "{"
1043
      size_t tag_stop_pos;   // the position of tag's end "}"
1044
      size_t tag_dot_pos;
1045

1046
      int tag_param_idx;
1047

1048
      size_t str_pos = 0;               // current string position
951✔
1049
      unsigned int next_param_idx = 0;  // next index of .format() parameter
951✔
1050

1051
      char w_spaces[] = "\t ";
951✔
1052

1053
      // Tells whether last found tag was an integer
1054
      bool last_tag_was_int = true;
951✔
1055

1056
      while ( ( tag_start_pos = value_.find( '{', str_pos ) ) != std::string::npos )
2,017✔
1057
      {
1058
        if ( ( tag_stop_pos = value_.find( '}', tag_start_pos ) ) != std::string::npos )
1,066✔
1059
        {
1060
          result << value_.substr( str_pos, tag_start_pos - str_pos );
1,066✔
1061
          str_pos = tag_stop_pos + 1;
1,066✔
1062

1063
          std::string tag_body =
1064
              value_.substr( tag_start_pos + 1, ( tag_stop_pos - tag_start_pos ) - 1 );
1,066✔
1065

1066
          tag_start_pos = tag_body.find_first_not_of( w_spaces );
1,066✔
1067
          tag_stop_pos = tag_body.find_last_not_of( w_spaces );
1,066✔
1068

1069
          // cout << "' tag_body1: '" << tag_body << "'";
1070

1071
          // trim the tag of whitespaces (slightly faster code ~25%)
1072
          if ( tag_start_pos != std::string::npos && tag_stop_pos != std::string::npos )
1,066✔
1073
            tag_body = tag_body.substr( tag_start_pos, ( tag_stop_pos - tag_start_pos ) + 1 );
51✔
1074
          else if ( tag_start_pos != std::string::npos )
1,015✔
1075
            tag_body = tag_body.substr( tag_start_pos );
×
1076
          else if ( tag_stop_pos != std::string::npos )
1,015✔
1077
            tag_body = tag_body.substr( 0, tag_stop_pos + 1 );
×
1078

1079
          // cout << "' tag_body2: '" << tag_body << "'";
1080

1081
          std::string frmt;
1,066✔
1082
          size_t formatter_pos = tag_body.find( ':' );
1,066✔
1083

1084
          if ( formatter_pos != std::string::npos )
1,066✔
1085
          {
1086
            frmt = tag_body.substr( formatter_pos + 1, std::string::npos );  //
12✔
1087
            tag_body = tag_body.substr( 0, formatter_pos );  // remove property from the tag
12✔
1088
          }
1089

1090
          std::string prop_name;
1,066✔
1091
          // parsing {1.this_part}
1092
          tag_dot_pos = tag_body.find( '.', 0 );
1,066✔
1093

1094
          // '.' is found within the tag, there is a property name
1095
          if ( tag_dot_pos != std::string::npos )
1,066✔
1096
          {
1097
            last_tag_was_int = true;
12✔
1098
            prop_name = tag_body.substr( tag_dot_pos + 1, std::string::npos );  //
12✔
1099
            tag_body = tag_body.substr( 0, tag_dot_pos );  // remove property from the tag
12✔
1100

1101
            // if s_tag_body is numeric then use it as an index
1102
            if ( s_parse_int( tag_param_idx, tag_body ) )
12✔
1103
            {
1104
              tag_param_idx -= 1;  // sinse POL indexes are 1-based
12✔
1105
            }
1106
            else
1107
            {
1108
              result << "<idx required before: '" << prop_name << "'>";
×
1109
              continue;
×
1110
            }
1111
          }
1112
          else
1113
          {
1114
            if ( tag_body.empty() )
1,054✔
1115
            {
1116
              // empty body just takes next integer idx
1117
              last_tag_was_int = true;
1,027✔
1118
              tag_param_idx = next_param_idx++;
1,027✔
1119
            }
1120
            else if ( s_parse_int( tag_param_idx, tag_body ) )
27✔
1121
            {
1122
              last_tag_was_int = true;
15✔
1123
              tag_param_idx -= 1;  // sinse POL indexes are 1-based
15✔
1124
            }
1125
            else
1126
            {
1127
              // string body takes next idx in line if this is
1128
              // the first string body occurrence,
1129
              // will reuse last idx if this is 2nd or more in a row
1130
              last_tag_was_int = false;
12✔
1131
              prop_name = tag_body;
12✔
1132
              tag_param_idx = last_tag_was_int ? next_param_idx++ : next_param_idx;
12✔
1133
            }
1134
          }
1135

1136
          // -- end of property parsing
1137

1138
          // cout << "prop_name: '" << prop_name << "' tag_body: '" << tag_body << "'";
1139
          if ( tag_param_idx < 0 || (int)ex.numParams() <= tag_param_idx )
1,066✔
1140
          {
1141
            result << "<invalid index: #" << ( tag_param_idx + 1 ) << ">";
3✔
1142
            continue;
3✔
1143
          }
1144

1145
          BObjectImp* imp = ex.getParamImp( tag_param_idx );
1,063✔
1146

1147
          if ( !prop_name.empty() )
1,063✔
1148
          {  // accesing object
1149
            BObjectRef obj_member = imp->get_member( prop_name.c_str() );
24✔
1150
            BObjectImp* member_imp = obj_member->impptr();
24✔
1151
            try_to_format( result, member_imp, frmt );
24✔
1152
          }
24✔
1153
          else
1154
          {
1155
            try_to_format( result, imp, frmt );
1,039✔
1156
          }
1157
        }
1,072✔
1158
        else
1159
        {
1160
          break;
×
1161
        }
1162
      }
1163

1164
      if ( str_pos < value_.length() )
951✔
1165
      {
1166
        result << value_.substr( str_pos, std::string::npos );
812✔
1167
      }
1168

1169
      return new String( result.str() );
951✔
1170
    }
951✔
1171

1172
    return new BError( "string.format() requires a parameter." );
×
1173
  }
1174
  case MTH_JOIN:
26✔
1175
  {
1176
    BObject* cont;
1177
    if ( ex.numParams() == 1 && ( cont = ex.getParamObj( 0 ) ) != nullptr )
26✔
1178
    {
1179
      if ( !( cont->isa( OTArray ) ) )
26✔
1180
        return new BError( "string.join expects an array" );
×
1181
      ObjArray* container = cont->impptr<ObjArray>();
26✔
1182
      // no empty check here on purpose
1183
      std::string joined;
26✔
1184
      bool first = true;
26✔
1185
      for ( const BObjectRef& ref : container->ref_arr )
191✔
1186
      {
1187
        if ( ref.get() )
165✔
1188
        {
1189
          BObject* bo = ref.get();
153✔
1190

1191
          if ( bo == nullptr )
153✔
1192
            continue;
×
1193
          if ( !first )
153✔
1194
            joined += value_;
130✔
1195
          else
1196
            first = false;
23✔
1197
          joined += bo->impptr()->getStringRep();
153✔
1198
        }
1199
      }
1200
      return new String( joined );
26✔
1201
    }
26✔
1202
    return new BError( "string.join(array) requires a parameter." );
×
1203
  }
1204
  default:
6✔
1205
    return nullptr;
6✔
1206
  }
1207
}
1208

1209
ObjArray* String::getCharacters( const std::string& source, size_t limit )
27✔
1210
{
1211
  std::unique_ptr<ObjArray> result( new ObjArray );
27✔
1212

1213
  for ( auto rit = source.cbegin(); rit != source.cend(); )
150✔
1214
  {
1215
    // Limit reached, push rest and break
1216
    if ( result->ref_arr.size() == limit - 1 )
138✔
1217
    {
1218
      result->addElement( new String( std::string( rit, source.cend() ) ) );
15✔
1219
      break;
15✔
1220
    }
1221

1222
    auto previous = rit;
123✔
1223
    utf8::unchecked::next( rit );
123✔
1224

1225
    result->addElement( new String( std::string( previous, rit ) ) );
123✔
1226
  }
1227

1228
  return result.release();
54✔
1229
}
27✔
1230

1231
bool String::hasUTF8Characters() const
70✔
1232
{
1233
  return hasUTF8Characters( value_ );
70✔
1234
}
1235

1236
bool String::hasUTF8Characters( const std::string& str )
71✔
1237
{
1238
  for ( const auto& c : str )
723✔
1239
  {
1240
    if ( c & 0x80 )
670✔
1241
      return true;
18✔
1242
  }
1243
  return false;
53✔
1244
}
1245

1246
std::vector<unsigned short> String::toUTF16() const
48✔
1247
{
1248
  std::vector<unsigned short> u16;
48✔
1249
  utf8::unchecked::utf8to16( value_.begin(), value_.end(), std::back_inserter( u16 ) );
48✔
1250
  return u16;
48✔
1251
}
×
1252

1253
std::string String::fromUTF16( unsigned short code )
436✔
1254
{
1255
  std::string s;
436✔
1256
  std::vector<unsigned short> utf16( 1, code );
436✔
1257
  utf8::unchecked::utf16to8( utf16.begin(), utf16.end(), std::back_inserter( s ) );
436✔
1258
  Clib::sanitizeUnicode( &s );
436✔
1259
  return s;
872✔
1260
}
436✔
1261

1262
std::string String::fromUTF16( const unsigned short* code, size_t len, bool big_endian )
1✔
1263
{
1264
  std::string s;
1✔
1265
  size_t short_len = 0;
1✔
1266
  // convert until the first null terminator
1267
  while ( code[short_len] != 0 && short_len < len )
11✔
1268
    ++short_len;
10✔
1269

1270
  // minimum incomplete iterator implementation, just for the internal usage with utf8lib to
1271
  // directly decode flipped bytes
1272
  struct BigEndianIterator
1273
  {
1274
    const u16* ptr;
1275
    BigEndianIterator( const u16* begin ) : ptr( begin ) {};
12✔
1276
    BigEndianIterator& operator++()
1277
    {
1278
      ++ptr;
1279
      return *this;
1280
    };
1281
    BigEndianIterator operator++( int )
10✔
1282
    {
1283
      BigEndianIterator itr( ptr );
10✔
1284
      ++ptr;
10✔
1285
      return itr;
10✔
1286
    };
1287
    u16 operator*() { return cfBEu16( *ptr ); };
10✔
1288
    bool operator!=( const BigEndianIterator& o ) { return ptr != o.ptr; };
11✔
1289
    bool operator==( const BigEndianIterator& o ) { return ptr == o.ptr; };
×
1290
  };
1291
  if ( big_endian )
1✔
1292
    utf8::unchecked::utf16to8( BigEndianIterator( code ), BigEndianIterator( code + short_len ),
1✔
1293
                               std::back_inserter( s ) );
1294
  else
1295
    utf8::unchecked::utf16to8( code, code + short_len, std::back_inserter( s ) );
×
1296
  Clib::sanitizeUnicode( &s );
1✔
1297
  return s;
1✔
1298
}
×
1299

1300
std::string String::fromUTF8( const char* code, size_t len )
2✔
1301
{
1302
  size_t short_len = 0;
2✔
1303
  // convert until the first null terminator
1304
  while ( code[short_len] != 0 && short_len < len )
22✔
1305
    ++short_len;
20✔
1306
  std::string s( code, short_len );
2✔
1307
  Clib::sanitizeUnicode( &s );
2✔
1308
  return s;
2✔
1309
}
×
1310

1311
std::vector<unsigned short> String::toUTF16( const std::string& text )
21✔
1312
{
1313
  std::vector<unsigned short> utf16;
21✔
1314
  if ( text.empty() )
21✔
1315
    return utf16;
1✔
1316
  utf8::unchecked::utf8to16( text.begin(), text.end(), std::back_inserter( utf16 ) );
20✔
1317
  return utf16;
20✔
1318
}
×
1319

1320
bool String::compare( const String& str ) const
3✔
1321
{
1322
  return value_.compare( str.value_ ) == 0;
3✔
1323
}
1324

1325
bool String::compare( size_t pos1, size_t len1, const String& str ) const
×
1326
{
1327
  auto itr1 = value_.cbegin();
×
1328
  pos1 = getBytePosition( &itr1, pos1 );
×
1329
  len1 = getBytePosition( &itr1, len1 ) - pos1;
×
1330
  return value_.compare( pos1, len1, str.value_ ) == 0;
×
1331
}
1332

1333
bool String::compare( size_t pos1, size_t len1, const String& str, size_t pos2, size_t len2 ) const
6✔
1334
{
1335
  auto itr1 = value_.cbegin();
6✔
1336
  pos1 = getBytePosition( &itr1, pos1 );
6✔
1337
  len1 = getBytePosition( &itr1, len1 ) - pos1;
6✔
1338
  auto itr2 = str.value_.cbegin();
6✔
1339
  pos2 = str.getBytePosition( &itr2, pos2 );
6✔
1340
  len2 = str.getBytePosition( &itr2, len2 ) - pos2;
6✔
1341
  return value_.compare( pos1, len1, str.value_, pos2, len2 ) == 0;
6✔
1342
}
1343

1344
String* String::fromUCArray( ObjArray* array, bool break_first_null )
6✔
1345
{
1346
  std::string res;
6✔
1347
  std::vector<u16> utf16;
6✔
1348
  for ( const auto& c : array->ref_arr )
30✔
1349
  {
1350
    if ( !c )
24✔
1351
      continue;
3✔
1352
    if ( auto* blong = c.get()->impptr_if<BLong>() )
21✔
1353
    {
1354
      if ( blong->value() == 0 && break_first_null )
21✔
1355
        break;
×
1356
      utf16.push_back( blong->value() & 0xFFFF );
21✔
1357
    }
1358
  }
1359
  if ( !utf16.empty() )
6✔
1360
    utf8::unchecked::utf16to8( utf16.begin(), utf16.end(), std::back_inserter( res ) );
6✔
1361

1362
  Clib::sanitizeUnicode( &res );
6✔
1363
  return new String( res );
12✔
1364
}
6✔
1365

1366
class StringIterator final : public ContIterator
1367
{
1368
public:
1369
  StringIterator( String* str, BObject* pIter );
1370
  BObject* step() override;
1371

1372
private:
1373
  // Keep String alive, to ensure iterators stay valid
1374
  BObject m_StringObj;
1375
  BObjectRef m_IterVal;
1376
  BLong* m_pIterVal;
1377
  std::string::const_iterator itr;
1378
  std::string::const_iterator end;
1379
};
1380

1381
StringIterator::StringIterator( String* pString, BObject* pIterVal )
54✔
1382
    : ContIterator(),
1383
      m_StringObj( pString ),
54✔
1384
      m_IterVal( pIterVal ),
54✔
1385
      m_pIterVal( new BLong( 0 ) ),
54✔
1386
      itr( pString->value().cbegin() ),
54✔
1387
      end( pString->value().cend() )
108✔
1388
{
1389
}
54✔
1390

1391
BObject* StringIterator::step()
324✔
1392
{
1393
  if ( itr == end )
324✔
1394
    return nullptr;
57✔
1395

1396
  // Iterate one utf8 character
1397
  auto previous = itr;
267✔
1398
  utf8::unchecked::next( itr );
267✔
1399
  m_pIterVal->increment();
267✔
1400
  m_IterVal->setimp( m_pIterVal );
267✔
1401

1402
  // Set current value to new substring
1403
  return new BObject( new String( std::string( previous, itr ) ) );
267✔
1404
}
1405

1406
ContIterator* String::createIterator( BObject* pIterVal )
54✔
1407
{
1408
  return new StringIterator( this, pIterVal );
54✔
1409
}
1410
}  // namespace Pol::Bscript
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc