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

polserver / polserver / 13378462461

17 Feb 2025 08:59PM UTC coverage: 58.754% (+0.3%) from 58.475%
13378462461

push

github

web-flow
Add support for sequence and index bindings (#760)

* update grammar

* add ast nodes; ast building

* Rename to unpacking

* semantic analysis

* executor work part 1, unpacking indices

* renamings; implement index binding

* small cleanup

* use multi_index; more tests

* use multi_index only for rest, otherwise list

* initial formatting

* add missing token decoding

* address self-review comments

* formatting tweaks

* add test for var binding in classes

* add StringIterator

* fix spread tests

* add cfgfile iterator; add cfgelem opersubscript; tests

* add iterator for SQLResultSet and SQLRow; tests

* Copy value in take global/local

* Allow any iterable can index rest unpacking
Always use dictionary as rest object in index unpacking

* add docs, core-changes, doc tests

* formatting changes...

* address self-review comments

* update formatter, format all binding test srcs

* address review comments
- unset var scope

* add cfgfile/cfgelem docs

* reformat objref svg

501 of 550 new or added lines in 19 files covered. (91.09%)

14 existing lines in 3 files now uncovered.

42235 of 71885 relevant lines covered (58.75%)

377989.47 hits per line

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

82.75
/pol-core/bscript/compiler/file/PrettifyLineBuilder.cpp
1
#include "PrettifyLineBuilder.h"
2

3
#include "bscript/compilercfg.h"
4
#include "clib/filecont.h"
5
#include "clib/logfacility.h"
6
#include <EscriptGrammar/EscriptLexer.h>
7

8
#include <algorithm>
9
#include <iostream>
10
#include <utility>
11

12
// #define DEBUG_FORMAT_BREAK
13

14
namespace Pol::Bscript::Compiler
15
{
16
using namespace EscriptGrammar;
17

18
const std::vector<std::string>& PrettifyLineBuilder::formattedLines() const
621✔
19
{
20
  return _lines;
621✔
21
}
22

23
void PrettifyLineBuilder::setRawLines( std::vector<std::string> rawlines )
594✔
24
{
25
  _rawlines = std::move( rawlines );
594✔
26
}
594✔
27

28
void PrettifyLineBuilder::setComments( std::vector<FmtToken> comments )
594✔
29
{
30
  _comments = std::move( comments );
594✔
31
}
594✔
32

33
void PrettifyLineBuilder::setSkipLines( std::vector<Range> skiplines )
594✔
34
{
35
  _skiplines = std::move( skiplines );
594✔
36
}
594✔
37

38
void PrettifyLineBuilder::addPart( FmtToken part )
44,802✔
39
{
40
  for ( const auto& skip : _skiplines )
45,022✔
41
    if ( skip.contains( part.pos ) )
1,674✔
42
      return;
1,454✔
43
  _line_parts.emplace_back( std::move( part ) );
43,348✔
44
}
45

46
bool PrettifyLineBuilder::finalize()
594✔
47
{
48
  mergeEOFNonTokens();
594✔
49
  return _line_parts.empty() && _comments.empty() && _skiplines.empty();
594✔
50
}
51

52
const std::vector<FmtToken>& PrettifyLineBuilder::currentTokens() const
635✔
53
{
54
  return _line_parts;
635✔
55
}
56

57
void PrettifyLineBuilder::mergeRawContent( size_t nextlineno )
8,350✔
58
{
59
  for ( auto itr = _skiplines.begin(); itr != _skiplines.end(); )
8,400✔
60
  {
61
    if ( itr->start.line_number < nextlineno )
50✔
62
    {
63
      mergeCommentsBefore( itr->start.line_number );
6✔
64
      addEmptyLines( itr->start.line_number );
6✔
65
      for ( size_t i = itr->start.line_number - 1; i < itr->end.line_number && i < _rawlines.size();
104✔
66
            ++i )
67
      {
68
        _packableline_allowed = false;
98✔
69
        _lines.emplace_back( _rawlines[i] );
98✔
70
        _last_line = i + 1;
98✔
71
      }
72
      itr = _skiplines.erase( itr );
6✔
73
      continue;
6✔
74
    }
6✔
75
    ++itr;
44✔
76
  }
77
}
8,350✔
78

79
void PrettifyLineBuilder::mergeCommentsBefore( size_t nextlineno )
7,118✔
80
{
81
  // add comments before current line
82
  while ( !_comments.empty() )
7,622✔
83
  {
84
    if ( _comments.front().pos.line_number < nextlineno )
3,147✔
85
    {
86
      addEmptyLines( _comments.front().pos.line_number );
504✔
87
      _lines.push_back( indentSpacing() + _comments.front().text );
504✔
88
      if ( _packableline_allowed &&
524✔
89
           _packablelinestart ==
20✔
90
               _lines.size() - 1 )  // potential packed line not yet build, so safe to add a comment
20✔
91
        ++_packablelinestart;
10✔
92
      else if ( _comments.front().style & FmtToken::FORCED_BREAK )
494✔
93
        _packableline_allowed = false;
486✔
94

95
      _last_line = _comments.front().pos_end.line_number;
504✔
96
      _comments.erase( _comments.begin() );
504✔
97
    }
98
    else
99
      break;
2,643✔
100
  }
101
}
7,118✔
102

103
void PrettifyLineBuilder::mergeComments()
7,112✔
104
{
105
  if ( _line_parts.empty() )
7,112✔
106
    return;
×
107
  mergeRawContent( _line_parts.front().pos.line_number );
7,112✔
108
  mergeCommentsBefore( _line_parts.front().pos.line_number );
7,112✔
109
  // add comments inbetween
110
  for ( size_t i = 0; i < _line_parts.size(); )
24,867✔
111
  {
112
    if ( _comments.empty() )
22,224✔
113
      break;
4,469✔
114
    if ( _line_parts[i].pos.token_index > _comments.front().pos.token_index )
17,755✔
115
    {
116
      _packableline_allowed = false;
3✔
117
      auto info = _comments.front();
3✔
118
      info.group = i ? _line_parts[i - 1].group : _currentgroup;
3✔
119
      _line_parts.insert( _line_parts.begin() + i, std::move( info ) );
3✔
120
      _comments.erase( _comments.begin() );
3✔
121
    }
3✔
122
    else
123
      ++i;
17,752✔
124
  }
125
  // add comments at the end of the current line
126
  if ( !_comments.empty() )
7,112✔
127
  {
128
    if ( _comments.front().pos.line_number == _line_parts.back().pos.line_number &&
2,868✔
129
         _comments.front().pos.token_index <= _line_parts.back().pos.token_index + 1 )
225✔
130
    {
131
      // no disable of packableline since its at the end
132
      addPart( _comments.front() );
211✔
133
      _comments.erase( _comments.begin() );
211✔
134
    }
135
  }
136
}
137

138
std::vector<FmtToken> PrettifyLineBuilder::buildLineSplits()
7,112✔
139
{
140
  std::vector<FmtToken> lines;
7,112✔
141
  bool has_varcomma = false;  // only if a "var," exists split based on these, otherwise var
7,112✔
142
                              // statement would be splitted
143
  for ( size_t i = 0; i < _line_parts.size(); ++i )
50,206✔
144
  {
145
    if ( _line_parts[i].context == FmtContext::VAR_COMMA )
43,131✔
146
    {
147
      has_varcomma = true;
37✔
148
      break;
37✔
149
    }
150
  }
151
  FmtToken part;
7,112✔
152
  for ( size_t i = 0; i < _line_parts.size(); ++i )
50,463✔
153
  {
154
#ifdef DEBUG_FORMAT_BREAK
155
    INFO_PRINTLN( "\"{}\" {} {}", _line_parts[i].text, _line_parts[i].group, _line_parts[i].style );
156
#endif
157
    if ( part.text.empty() )
43,351✔
158
    {
159
      part.firstgroup = _line_parts[i].group;
14,699✔
160
      part.pos = _line_parts[i].pos;
14,699✔
161
      part.scope = _line_parts[i].scope;
14,699✔
162
      part.context = _line_parts[i].context;
14,699✔
163
    }
164
    part.text += _line_parts[i].text;
43,351✔
165
    if ( part.context == FmtContext::NONE )
43,351✔
166
      part.context = _line_parts[i].context;
35,214✔
167
    // add space if set, but not if the following part is attached
168
    if ( _line_parts[i].style & FmtToken::SPACE )
43,351✔
169
    {
170
      if ( i + 1 < _line_parts.size() )
40,822✔
171
      {
172
        if ( !( _line_parts[i + 1].style & FmtToken::ATTACHED ) )
33,710✔
173
          part.text += ' ';
19,952✔
174
      }
175
      else
176
        part.text += ' ';
7,112✔
177
      // if something is left and not attached
178
      if ( i + 1 < _line_parts.size() )
40,822✔
179
      {
180
        if ( !( _line_parts[i + 1].style & FmtToken::ATTACHED ) )
33,710✔
181
        {
182
          // if its already quite long split it
183
          if ( part.text.size() > compilercfg.FormatterLineWidth * 0.5 )
19,952✔
184
          {
185
            part.style = _line_parts[i].style;
26✔
186
            part.group = _line_parts[i].group;
26✔
187
            part.pos_end = _line_parts[i].pos_end;
26✔
188
            lines.emplace_back( std::move( part ) );
26✔
189
            part = FmtToken{};
26✔
190
            continue;
26✔
191
          }
192
        }
193
      }
194
    }
195
    // if a forced break does not appear at the end disallow merging the lines
196
    if ( _line_parts[i].style & FmtToken::FORCED_BREAK && i < _line_parts.size() - 1 )
43,325✔
197
      _packableline_allowed = false;
1✔
198
    if ( _line_parts[i].style & FmtToken::FORCED_BREAK ||
43,702✔
199
         ( has_varcomma && ( _line_parts[i].context == FmtContext::VAR_STATEMENT ||
377✔
200
                             _line_parts[i].context == FmtContext::VAR_COMMA ) ) )
340✔
201
    {
202
      // need to break directly after "var" to align multiple variables
203
      if ( _line_parts[i].context == FmtContext::VAR_STATEMENT ||
607✔
204
           _line_parts[i].context == FmtContext::VAR_COMMA )
285✔
205
        _line_parts[i].style |= FmtToken::PREFERRED_BREAK_VAR;
110✔
206
      part.style = _line_parts[i].style;
322✔
207
      part.group = _line_parts[i].group;
322✔
208
      part.pos_end = _line_parts[i].pos_end;
322✔
209
      lines.emplace_back( std::move( part ) );
322✔
210
      part = FmtToken{};
322✔
211
    }
212
    // start a new line if breakpoint
213
    else if ( _line_parts[i].style & FmtToken::BREAKPOINT )
43,003✔
214
    {
215
      // if the next part is attached, dont break now and instead later
216
      // eg dont split blubb[1]()
217
      bool skip = false;
17,439✔
218
      if ( i + 1 < _line_parts.size() )
17,439✔
219
      {
220
        // next one is attached
221
        if ( _line_parts[i + 1].style & FmtToken::ATTACHED )
11,645✔
222
        {
223
          // search for space or breakpoint
224
          for ( size_t j = i + 1; j < _line_parts.size(); ++j )
4,347✔
225
          {
226
            if ( _line_parts[j].style & FmtToken::ATTACHED )
4,347✔
227
              skip = true;
4,223✔
228
            if ( _line_parts[j].style & FmtToken::SPACE ||
4,499✔
229
                 _line_parts[j].style & FmtToken::BREAKPOINT )
152✔
230
            {
231
              _line_parts[j].style |= FmtToken::BREAKPOINT;
4,195✔
232
              skip = true;
4,195✔
233
              break;
4,195✔
234
            }
235
          }
236
        }
237
      }
238
      if ( skip )
17,439✔
239
        continue;
4,195✔
240
      part.style = _line_parts[i].style;
13,244✔
241
      part.group = _line_parts[i].group;
13,244✔
242
      part.pos_end = _line_parts[i].pos_end;
13,244✔
243
      lines.emplace_back( std::move( part ) );
13,244✔
244
      part = FmtToken{};
13,244✔
245
    }
246
  }
247
  // add remainings
248
  if ( !part.text.empty() )
7,112✔
249
  {
250
    part.style = _line_parts.back().style;
1,107✔
251
    part.group = _line_parts.back().group;
1,107✔
252
    part.pos_end = _line_parts.back().pos_end;
1,107✔
253
    lines.emplace_back( std::move( part ) );
1,107✔
254
  }
255

256
  return lines;
14,224✔
257
}
7,112✔
258

259
bool PrettifyLineBuilder::binPack( const FmtToken& part, std::string line, size_t index,
26✔
260
                                   size_t upto, const std::vector<FmtToken>& lines,
261
                                   bool only_single_line, std::vector<std::string>* finallines,
262
                                   std::map<size_t, size_t>* alignmentspace, size_t* skipindex,
263
                                   const std::map<size_t, size_t>& initial_alignmentspace,
264
                                   std::string& newpart ) const
265
{
266
  auto packTaktic =
267
      []( size_t index, size_t end_group, size_t max_len, const std::vector<FmtToken>& lines )
6✔
268
  {
269
    // fill up lines with given max_len
270
    std::string currline;
6✔
271
    std::vector<std::string> lineparts;
6✔
272
    for ( size_t j = index; j < end_group; ++j )
35✔
273
    {
274
      const auto& lpart = lines[j].text;
29✔
275
      if ( lines[j].style & FmtToken::FORCED_BREAK )
29✔
276
      {
277
        currline += lpart;
×
278
        lineparts.push_back( std::move( currline ) );
×
279
        currline.clear();
×
280
        continue;
×
281
      }
282
      // new part still fits, add to line
283
      if ( currline.size() + lpart.size() <= max_len )
29✔
284
      {
285
        currline += lpart;
19✔
286
        continue;
19✔
287
      }
288
      // we are already at end, start a new line
289
      if ( currline.size() >= max_len )
10✔
290
      {
291
        lineparts.push_back( std::move( currline ) );
2✔
292
        currline = lpart;
2✔
293
        continue;
2✔
294
      }
295
      // we were smaller, add it anyway and start a new line
296
      // the line will be bigger then the available room
297
      // but I think looks better
298
      currline += lpart;
8✔
299
      lineparts.push_back( std::move( currline ) );
8✔
300
      currline.clear();
8✔
301
    }
302
    if ( !currline.empty() )
6✔
303
      lineparts.push_back( currline );
3✔
304
    return lineparts;
12✔
305
  };
6✔
306
  auto indent = indentSpacing();
26✔
307
  size_t room = compilercfg.FormatterLineWidth - line.size();
26✔
308
  size_t end_group = 0;
26✔
309
  bool prefferesbreak = false;
26✔
310
  // where does the group end?
311
  for ( size_t j = index + 1; j <= upto; ++j )
82✔
312
  {
313
    if ( lines[j].style & FmtToken::PREFERRED_BREAK_VAR )
74✔
314
    {
315
      prefferesbreak = true;
×
316
    }
317
    if ( lines[j].group == part.group || lines[j].firstgroup == part.group )
74✔
318
    {
319
      continue;
56✔
320
    }
321
    end_group = j;
18✔
322
    break;
18✔
323
  }
324
  if ( end_group == 0 )
26✔
325
    end_group = upto + 1;
8✔
326
  *skipindex = end_group;  // independent of the result we will skip till end of group
26✔
327
  std::string total;
26✔
328
  bool total_valid{ true };
26✔
329
  for ( size_t j = index; j < end_group; ++j )
108✔
330
  {
331
    total += lines[j].text;
82✔
332
    if ( lines[j].style & FmtToken::FORCED_BREAK )
82✔
333
    {
334
      total_valid = false;
×
335
      break;
×
336
    }
337
  }
338
  // it fits directly into the line
339
  if ( total_valid && total.size() + line.size() < compilercfg.FormatterLineWidth )
26✔
340
  {
341
    if ( !alignmentspace->count( part.group ) )
20✔
342
      ( *alignmentspace )[part.group] = line.size() - indent.size();
9✔
343
    line += total;
20✔
344
    newpart = total;
20✔
345
    stripline( line );
20✔
346
    finallines->emplace_back( std::move( line ) );
20✔
347
    line.clear();
20✔
348
    return true;
20✔
349
  }
350
  // if its part of an active groupformatting only allow a single line
351
  if ( only_single_line )
6✔
352
    return false;
×
353
  // TODO: if only a few chars room are left start a newline before
354
  //  option 1: try to equally split the line into two, for structs and dicts half the available
355
  //  room
356
  size_t lenOption2 = std::min( room, total.size() / 2 );
6✔
357
  if ( ( part.scope & FmtToken::Scope::STRUCT ) == FmtToken::Scope::STRUCT ||
8✔
358
       ( part.scope & FmtToken::Scope::DICT ) == FmtToken::Scope::DICT )
2✔
359
  {
360
    lenOption2 = room / 2;
4✔
361
  }
362

363
  auto lineparts = packTaktic( index, end_group, lenOption2, lines );
6✔
364

365
  // option1 failed, for arrays its allowed to use 3 lines
366
  // no array/dict/struct scope means any length is allowed
367
  if ( lineparts.size() != 2 && part.scope != FmtToken::Scope::NONE &&
7✔
368
       part.scope != FmtToken::Scope::FUNCTION && part.scope != FmtToken::Scope::VAR )
7✔
369
  {
370
    if ( ( part.scope & FmtToken::Scope::STRUCT ) == FmtToken::Scope::STRUCT ||
1✔
UNCOV
371
         ( part.scope & FmtToken::Scope::DICT ) == FmtToken::Scope::DICT )
×
372
    {
373
      return false;
1✔
374
    }
UNCOV
375
    size_t lenOption3 = std::min( room, total.size() / 3 );
×
UNCOV
376
    lineparts = packTaktic( index, end_group, lenOption3, lines );
×
UNCOV
377
    if ( lineparts.size() != 3 )
×
378
    {
UNCOV
379
      return false;
×
380
    }
381
  }
382
  // dont keep spacing over var,
383
  if ( prefferesbreak )
5✔
384
    *alignmentspace = initial_alignmentspace;
×
385
  // we found a solution
386
  if ( !alignmentspace->count( part.group ) )
5✔
387
    ( *alignmentspace )[part.group] = line.size() - indent.size();
3✔
388
  newpart = "";
5✔
389
  for ( const auto& lpart : lineparts )
15✔
390
  {
391
    if ( line.empty() )
10✔
392
    {
393
      line = alignmentSpacing( ( *alignmentspace )[part.firstgroup] );
5✔
394
      line += indent;
5✔
395
    }
396
    line += lpart;
10✔
397
    newpart += lpart;
10✔
398
    stripline( line );
10✔
399
    finallines->emplace_back( std::move( line ) );
10✔
400
    line.clear();
10✔
401
  }
402
  return true;
5✔
403
}
26✔
404

405
std::vector<std::string> PrettifyLineBuilder::createBasedOnGroups(
13✔
406
    std::vector<FmtToken>& lines ) const
407
{
408
  // TODO align closing brackets better
409
  std::vector<std::string> finallines;
13✔
410
  std::string line;
13✔
411
  // store for each group the alignment
412
  std::map<size_t, size_t> alignmentspace = {};
13✔
413
  std::map<size_t, size_t> initial_alignmentspace = {};
13✔
414
  size_t lastgroup = 0xffffFFFF;
13✔
415
  bool newline = false;
13✔
416
  auto indent = indentSpacing();
13✔
417
  line = indent;
13✔
418
  bool groupdiffered = false;
13✔
419
  size_t i = 0, skipuntil = 0, tried_binpack_until = 0;
13✔
420
  for ( auto& part : lines )
212✔
421
  {
422
    if ( skipuntil > i )  // already handled during binpack
199✔
423
    {
424
      ++i;
52✔
425
      continue;
52✔
426
    }
427
    // it was a binpack before
428
    if ( line.empty() && !finallines.empty() )
147✔
429
    {
430
      // add a following comment to the line if it fits
431
      if ( part.context == FmtContext::LINE_COMMENT || part.context == FmtContext::COMMENT )
18✔
432
      {
433
        if ( part.text.size() + finallines.back().size() < compilercfg.FormatterLineWidth )
×
434
        {
435
          // if the last one was forced (comment) let the default behaviour later do it
436
          if ( !( lines[i - 1].style & FmtToken::FORCED_BREAK ) )
×
437
          {
438
            if ( lines[i - 1].style & FmtToken::SPACE )
×
439
              finallines.back() += ' ';  // needed since already stripped
×
440
            finallines.back() += part.text;
×
441
            stripline( finallines.back() );
×
442
            line = indent;
×
443
            ++i;
×
444
            continue;
×
445
          }
446
        }
447
      }
448
      // last element still fits
449
      if ( i + 1 == lines.size() &&
24✔
450
           ( part.text.size() + finallines.back().size() <= compilercfg.FormatterLineWidth ) )
6✔
451
      {
452
        if ( !( lines[i - 1].style & FmtToken::FORCED_BREAK ) )
6✔
453
        {
454
          if ( lines[i - 1].style & FmtToken::SPACE )
6✔
455
            finallines.back() += ' ';  // needed since already stripped
6✔
456
          finallines.back() += part.text;
6✔
457
          stripline( finallines.back() );
6✔
458
          ++i;
6✔
459
          continue;
6✔
460
        }
461
      }
462
    }
463
    if ( line.empty() && !alignmentspace.empty() )
141✔
464
    {
465
      if ( alignmentspace.find( part.firstgroup ) == alignmentspace.end() )
12✔
466
        alignmentspace[part.firstgroup] = alignmentspace[part.firstgroup - 1];
×
467
      line = alignmentSpacing( alignmentspace[part.firstgroup] );
12✔
468
      line += indent;
12✔
469
    }
470
    if ( part.style & FmtToken::FORCED_BREAK )
141✔
471
    {
472
      auto nonspace = line.find_first_not_of( " \t" );
×
473
      line += part.text;
×
474
      // do not indent comments if they start at the beginning
475
      if ( part.context == FmtContext::LINE_COMMENT || part.context == FmtContext::COMMENT )
×
476
      {
477
        if ( part.pos.character_column < 4 &&
×
478
             nonspace ==
479
                 std::string::npos )  // TODO startpos does not include original space indent
480
        {
481
          auto nonspace_part = part.text.find_first_not_of( " \t" );
×
482
          // is the comment really at the beginning
483
          if ( nonspace_part != std::string::npos && part.text[nonspace_part] == '/' )
×
484
            line = part.text;
×
485
        }
486
      }
487
      stripline( line );
×
488
      finallines.emplace_back( std::move( line ) );
×
489
      line.clear();
×
490
      line = alignmentSpacing( alignmentspace[part.firstgroup] );
×
491
      line += indent;
×
492
      ++i;
×
493
      // dont keep spacing over var,
494
      if ( part.style & FmtToken::PREFERRED_BREAK_VAR )
×
495
        alignmentspace = initial_alignmentspace;
×
496
      continue;
×
497
    }
×
498
    if ( lastgroup == 0xffffFFFF )
141✔
499
    {
500
      line += part.text;  // first part
13✔
501
      alignmentspace[part.firstgroup] = line.size() - indent.size();
13✔
502
      alignmentspace[part.group] = line.size() - indent.size();
13✔
503
      initial_alignmentspace = alignmentspace;
13✔
504
      newline = true;
13✔
505
#ifdef DEBUG_FORMAT_BREAK
506
      INFO_PRINTLN( "first {} {} a{}", line, part.firstgroup, alignmentspace );
507
#endif
508
      ++i;
13✔
509
      lastgroup = part.firstgroup;
13✔
510
      // dont keep spacing over var,
511
      if ( part.style & FmtToken::PREFERRED_BREAK_VAR )
13✔
512
        alignmentspace = initial_alignmentspace;
×
513
      continue;
13✔
514
    }
515
    if ( tried_binpack_until <= i )
128✔
516
    {
517
      size_t upto = lines.size() - 1;
124✔
518
      // check for nested groups, binpack is not allowed for these eg array of struct
519
      for ( size_t j = i + 1; j < lines.size(); ++j )
464✔
520
      {
521
        if ( lines[j].group > part.group )
439✔
522
        {
523
          upto = 0;
99✔
524
          break;
99✔
525
        }
526
      }
527
      bool invarpack = false;
124✔
528
      if ( upto == 0 && lines[i ? i - 1 : 0].style & FmtToken::PREFERRED_BREAK_VAR )
124✔
529
      {
530
        // multiple variables in a single statement, find matching groups and pack these
531
        for ( size_t j = i + 1; j < lines.size(); ++j )
×
532
        {
533
          // we are not inside of some group and eg array starts
534
          if ( lastgroup == 0 && part.group == 1 )
×
535
          {
536
            if ( lines[j].group > 2 )  // entries are 2
×
537
              break;
×
538
          }
539
          else if ( lines[j].group > part.group )
×
540
          {
541
            break;
×
542
          }
543
          if ( lines[j].group != part.group )
×
544
            if ( lines[j].style & FmtToken::PREFERRED_BREAK_VAR )
×
545
              break;  // stop at var, when another group starts
×
546
          if ( lines[j].style & FmtToken::PREFERRED_BREAK_VAR )
×
547
            upto = j;
×
548
        }
549
        if ( upto > 0 )
×
550
        {
551
          invarpack = true;  // retry with newline
×
552
        }
553
      }
554
      if ( upto > 0 && i != upto )
124✔
555
      {
556
#ifdef DEBUG_FORMAT_BREAK
557
        INFO_PRINTLN( "trybinpack {}->{} {}->{}", i, upto, lines[i].text, lines[upto].text );
558
#endif
559
        size_t skip;
560
        std::string newpart;
25✔
561
        if ( binPack( part, line, i, upto, lines, tried_binpack_until > 0, &finallines,
25✔
562
                      &alignmentspace, &skip, initial_alignmentspace, newpart ) )
563
        {
564
#ifdef DEBUG_FORMAT_BREAK
565
          INFO_PRINTLN( "binpack {}->{} {}->{}", i, upto, lines[i].text, lines[skip - 1].text );
566
#endif
567
          skipuntil = skip;
24✔
568
          line.clear();
24✔
569
          ++i;
24✔
570
          // dont keep spacing over var,
571
          if ( part.style & FmtToken::PREFERRED_BREAK_VAR )
24✔
572
            alignmentspace = initial_alignmentspace;
×
573
          continue;
24✔
574
        }
575
        else if ( invarpack && !line.empty() )
1✔
576
        {
577
          // try it again when starting a new line
578
          std::string oldline = line;
×
579
          stripline( line );
×
580
          if ( !line.empty() )
×
581
            finallines.emplace_back( std::move( line ) );
×
582
          line.clear();
×
583
          line = alignmentSpacing( alignmentspace[part.firstgroup] );
×
584
          line += indent;
×
585
          std::string tmp;
×
586
          if ( binPack( part, line, i, upto, lines, tried_binpack_until > 0, &finallines,
×
587
                        &alignmentspace, &skip, initial_alignmentspace, tmp ) )
588
          {
589
#ifdef DEBUG_FORMAT_BREAK
590
            INFO_PRINTLN( "binpack2 {}->{} {}->{}", i, upto, lines[i].text, lines[skip - 1].text );
591
#endif
592
            skipuntil = skip;
×
593
            line.clear();
×
594
            ++i;
×
595
            // dont keep spacing over var,
596
            if ( part.style & FmtToken::PREFERRED_BREAK_VAR )
×
597
              alignmentspace = initial_alignmentspace;
×
598
            continue;
×
599
          }
600
          else
601
          {
602
            line = oldline;
×
603
            finallines.pop_back();
×
604
          }
605
        }
×
606
        tried_binpack_until = skip;
1✔
607
      }
25✔
608
    }
609
    if ( lines[i].context == FmtContext::PREFERRED_BREAK_START )
104✔
610
    {
611
      // try to pack function calls, by modifying the current part
612
      size_t upto = lines.size() - 1;
1✔
613
      for ( size_t j = i + 1; j < lines.size(); ++j )
1✔
614
      {
615
        if ( lines[j].context == FmtContext::PREFERRED_BREAK_END )
1✔
616
        {
617
          upto = j;
1✔
618
          break;
1✔
619
        }
620
      }
621
#ifdef DEBUG_FORMAT_BREAK
622
      INFO_PRINTLN( "trybinpackfunc {}->{} {}->{}", i, upto, lines[i].text, lines[upto].text );
623
#endif
624
      size_t skip;
625
      std::string oldline = line;
1✔
626
      std::string newpart;
1✔
627
      if ( binPack( part, line, i, upto, lines, true, &finallines, &alignmentspace, &skip,
1✔
628
                    initial_alignmentspace, newpart ) )
629
      {
630
#ifdef DEBUG_FORMAT_BREAK
631
        INFO_PRINTLN( "binpackfunc {}->{} {}->{}", i, upto, lines[i].text, lines[skip - 1].text );
632
#endif
633
        skipuntil = skip;
1✔
634
        // dont keep spacing over var,
635
        if ( part.style & FmtToken::PREFERRED_BREAK_VAR )
1✔
636
          alignmentspace = initial_alignmentspace;
×
637
        // use the packed text as current part
638
        part.text = newpart;
1✔
639
        finallines.pop_back();
1✔
640
        line = oldline;
1✔
641
        part.group = lines[skip].group;
1✔
642
      }
643
    }
1✔
644
    if ( lastgroup < part.firstgroup )  // new group
104✔
645
    {
646
      groupdiffered = true;
28✔
647
      bool newgroup = false;
28✔
648
      if ( !alignmentspace.count( part.firstgroup ) )
28✔
649
      {
650
        newgroup = true;
21✔
651
        alignmentspace[part.firstgroup] = line.size() - indent.size();
21✔
652
      }
653
      // if its not a new group and not a fresh line start a new one
654
      // nested structs eg should start at the same line
655
      if ( !newgroup && !newline )
28✔
656
      {
657
        stripline( line );
3✔
658
        if ( !line.empty() )
3✔
659
          finallines.emplace_back( std::move( line ) );
3✔
660
        line = alignmentSpacing( alignmentspace[part.firstgroup] );
3✔
661
        line += indent;
3✔
662
        line += part.text;
3✔
663
        newline = true;
3✔
664
#ifdef DEBUG_FORMAT_BREAK
665
        INFO_PRINTLN( "!new {} - {}", line, alignmentspace );
666
#endif
667
      }
668
      else
669
      {
670
        line += part.text;
25✔
671
#ifdef DEBUG_FORMAT_BREAK
672
        INFO_PRINTLN( "new {} - {}", line, alignmentspace );
673
#endif
674
      }
675
      if ( !alignmentspace.count( part.group ) )
28✔
676
        alignmentspace[part.group] = line.size() - indent.size();
×
677
    }
678
    else if ( lastgroup > part.firstgroup )  // descending
76✔
679
    {
680
      newline = true;
9✔
681
      stripline( line );
9✔
682
      if ( !line.empty() )
9✔
683
        finallines.emplace_back( std::move( line ) );
9✔
684
      line = alignmentSpacing( alignmentspace[part.firstgroup] );
9✔
685
      line += indent;
9✔
686
      line += part.text;
9✔
687
#ifdef DEBUG_FORMAT_BREAK
688
      INFO_PRINTLN( "desc {} - {}", line, alignmentspace );
689
#endif
690
    }
691
    else  // same group
692
    {
693
      // only if we already had a different group split the line
694
      bool closing = false;
67✔
695
      if ( !part.text.empty() )
67✔
696
        closing = part.text[0] == ')';  // shouldnt force a new line
67✔
697
      if ( groupdiffered && !closing )
67✔
698
      {
699
        newline = false;
61✔
700
        stripline( line );
61✔
701
        if ( !line.empty() )
61✔
702
          finallines.emplace_back( std::move( line ) );
56✔
703
        line = alignmentSpacing( alignmentspace[part.firstgroup] );
61✔
704
        line += indent;
61✔
705
      }
706
      line += part.text;
67✔
707
#ifdef DEBUG_FORMAT_BREAK
708
      INFO_PRINTLN( "same {} - {} {} {}", line, alignmentspace, groupdiffered, part.text );
709
#endif
710
      if ( line.size() > compilercfg.FormatterLineWidth )
67✔
711
      {
712
        stripline( line );
×
713
        if ( !line.empty() )
×
714
          finallines.emplace_back( std::move( line ) );
×
715
        line = alignmentSpacing( alignmentspace[part.firstgroup] );
×
716
        line += indent;
×
717
        // dont keep spacing over var,
718
        if ( part.style & FmtToken::PREFERRED_BREAK_VAR )
×
719
          alignmentspace = initial_alignmentspace;
×
720
      }
721
    }
722

723
    lastgroup = part.firstgroup;
104✔
724
    ++i;
104✔
725
  }
726
  if ( !line.empty() )
13✔
727
  {
728
#ifdef DEBUG_FORMAT_BREAK
729
    INFO_PRINTLN( "remaining {}", line );
730
#endif
731
    // look for something like ); and add it to the last line
732
    if ( auto nonspace = line.find_first_not_of( " \t" );
1✔
733
         nonspace != std::string::npos && line.size() - nonspace < 4 )
1✔
734
    {
UNCOV
735
      if ( lines.size() >= 2 )
×
736
      {
UNCOV
737
        if ( !( lines[lines.size() - 2].style & FmtToken::FORCED_BREAK ) )
×
738
        {
UNCOV
739
          if ( lines[lines.size() - 2].style & FmtToken::SPACE )
×
UNCOV
740
            finallines.back() += ' ';  // needed since already stripped
×
UNCOV
741
          finallines.back() += line.erase( 0, nonspace );
×
UNCOV
742
          stripline( finallines.back() );
×
UNCOV
743
          return finallines;
×
744
        }
745
      }
746
    }
747
    stripline( line );
1✔
748
    finallines.emplace_back( std::move( line ) );
1✔
749
  }
750
  return finallines;
13✔
751
}
13✔
752

753
// helper to find the alignment of the last open parenthesis and use this as alignment for the
754
// next line
755
bool PrettifyLineBuilder::parenthesisAlign( const std::vector<std::string>& finallines,
11,698✔
756
                                            std::string& line ) const
757
{
758
  std::vector<size_t> parenthesisalign;
11,698✔
759
  for ( const auto& finalline : finallines )
11,712✔
760
  {
761
    size_t i = 0;
14✔
762
    for ( auto c : finalline )
1,235✔
763
    {
764
      if ( c == '(' )
1,221✔
765
        parenthesisalign.push_back( i );
15✔
766
      else if ( c == ')' && !parenthesisalign.empty() )
1,206✔
767
        parenthesisalign.pop_back();
4✔
768
      if ( c == '\t' )
1,221✔
769
        i += compilercfg.FormatterTabWidth;
×
770
      else
771
        ++i;
1,221✔
772
    }
773
  }
774
  if ( parenthesisalign.empty() )
11,698✔
775
    return false;
11,688✔
776

777
  // if its at the end of last line start at the beginning
778
  if ( parenthesisalign.back() >= ( finallines.back().size() - 1 ) )
10✔
779
    return true;
×
780
  size_t alignment = line.find_first_not_of( " \t" );
10✔
781
  if ( alignment == std::string::npos )
10✔
782
    alignment = line.size();
×
783
  if ( compilercfg.FormatterUseTabs )
10✔
784
    alignment *= compilercfg.FormatterTabWidth;
×
785
  if ( parenthesisalign.back() + ( compilercfg.FormatterBracketSpacing ? 2 : 1 ) > alignment )
10✔
786
  {
787
    auto newspace = alignmentSpacing( parenthesisalign.back() +
3✔
788
                                      ( compilercfg.FormatterBracketSpacing ? 2 : 1 ) );
3✔
789
    auto space = line.find_first_not_of( " \t" );
3✔
790
    line.erase( 0, space );
3✔
791
    line = newspace + line;
3✔
792
  }
3✔
793
  // if its a operator +-*/<> align the actual "data" so subtract 2
794
  auto space = line.find_first_not_of( " \t" );
10✔
795
  if ( space != std::string::npos && space > 1 && space + 1 < line.size() &&
10✔
796
       line[space + 1] == ' ' &&
23✔
797
       ( line[space] == '+' || line[space] == '-' || line[space] == '*' || line[space] == '/' ||
3✔
798
         line[space] == '<' || line[space] == '>' ) )
×
799
  {
800
    // TODO tabs...
801
    if ( !compilercfg.FormatterUseTabs )
3✔
802
      line.erase( 0, 2 );
3✔
803
  }
804
  return true;
10✔
805
}
11,698✔
806

807
std::vector<std::string> PrettifyLineBuilder::createBasedOnPreferredBreaks(
669✔
808
    const std::vector<FmtToken>& lines, bool logical ) const
809
{
810
  std::vector<std::string> finallines;
669✔
811
  std::string line;
669✔
812
  // first join parts until a forced/preferred break
813
  size_t alignmentspace = 0;
669✔
814
  size_t assignpos = std::string::npos;
669✔
815
  std::vector<std::tuple<std::string, int, FmtContext>> parts;
669✔
816
  std::string tmp;
669✔
817
  // if breaks exists from logical "and/or" we only split based on them
818
  // in a long if-statement we want to break line on the logical points and not mixed with
819
  // commas from functions
820
  int breakflag = logical ? FmtToken::PREFERRED_BREAK_LOGICAL : FmtToken::PREFERRED_BREAK;
669✔
821
  std::vector<size_t> parenthesisalign;
669✔
822
  FmtContext tmpcontext = FmtContext::NONE;
669✔
823
  for ( const auto& part : lines )
3,968✔
824
  {
825
    if ( !alignmentspace )
3,299✔
826
    {
827
      auto indent = indentSpacing();
669✔
828
      alignmentspace = part.text.size() + indent.size();
669✔
829
      line += indent;
669✔
830
      parts.push_back( { part.text, FmtToken::NONE, part.context } );
669✔
831
      assignpos = part.text.find( ":=" );
669✔
832
      if ( assignpos != std::string::npos )
669✔
833
        assignpos += indent.size() + 2;
110✔
834
      continue;
669✔
835
    }
669✔
836
    // if it gets to long we still have to split the line
837
    if ( tmp.size() + part.text.size() > compilercfg.FormatterLineWidth )
2,630✔
838
    {
839
      parts.push_back( { std::move( tmp ), FmtToken::NONE, tmpcontext } );
×
840
      tmp.clear();
×
841
      tmpcontext = part.context;
×
842
    }
843
    size_t i = 0;
2,630✔
844
    for ( auto c : tmp )
9,195✔
845
    {
846
      if ( c == '(' )
6,565✔
847
        parenthesisalign.push_back( i );
268✔
848
      else if ( c == ')' && !parenthesisalign.empty() )
6,297✔
849
        parenthesisalign.pop_back();
231✔
850
      ++i;
6,565✔
851
    }
852
    // something closed split it
853
    if ( parenthesisalign.empty() )
2,630✔
854
    {
855
      parts.push_back( { std::move( tmp ), FmtToken::NONE, tmpcontext } );
2,012✔
856
      tmp.clear();
2,012✔
857
      tmpcontext = part.context;
2,012✔
858
    }
859
    tmp += part.text;
2,630✔
860
    if ( tmpcontext == FmtContext::NONE )
2,630✔
861
      tmpcontext = part.context;
1,913✔
862
    else if ( tmpcontext == FmtContext::PREFERRED_BREAK_START &&
717✔
863
              part.context == FmtContext::PREFERRED_BREAK_END )
218✔
864
      tmpcontext = FmtContext::NONE;  // start and stop so clear
5✔
865

866
    if ( ( part.style & FmtToken::FORCED_BREAK ) ||
2,630✔
867
         ( part.style & FmtToken::PREFERRED_BREAK_VAR ) || ( part.style & breakflag ) )
2,612✔
868
    {
869
      parts.push_back( { std::move( tmp ), part.style, tmpcontext } );
1,311✔
870
      tmp.clear();
1,311✔
871
      if ( part.context != FmtContext::PREFERRED_BREAK_END )
1,311✔
872
        tmpcontext = part.context;
1,310✔
873
      else
874
        tmpcontext = FmtContext::NONE;
1✔
875
    }
876
  }
877
  if ( !tmp.empty() )
669✔
878
    parts.push_back( { tmp, FmtToken::NONE, tmpcontext } );
651✔
879
  // now build the actual line(s)
880
  bool alignpart = false;
669✔
881
  size_t parti = 0;
669✔
882
  for ( auto& [l, style, context] : parts )
5,312✔
883
  {
884
#ifdef DEBUG_FORMAT_BREAK
885
    INFO_PRINTLN( "'{}'{} -{} {} FILTERED", l, l.size(), style, (int)context );
886
#endif
887
    if ( line.empty() && alignmentspace && alignpart )
4,643✔
888
      line = alignmentSpacing( alignmentspace );
×
889

890
    if ( !logical )  // align line directly to have the correct linelen
4,643✔
891
      parenthesisAlign( finallines, line );
4,598✔
892

893
    alignpart = true;  // otherwise first part would get spacing
4,643✔
894
    bool newcontext_longer = false;
4,643✔
895
    // if we start a new parenthesis group check if it fit completely
896
    auto currlinespace = line.find_first_not_of( " \t" );
4,643✔
897
    if ( context == FmtContext::PREFERRED_BREAK_START && currlinespace != std::string::npos )
4,643✔
898
    {
899
      size_t prefferedsize = l.size();
104✔
900
      for ( size_t j = parti + 1; j < parts.size(); ++j )
192✔
901
      {
902
        prefferedsize += std::get<0>( parts[j] ).size();
190✔
903
        if ( std::get<2>( parts[j] ) == FmtContext::PREFERRED_BREAK_END )
190✔
904
        {
905
          // does not fit into current line, start a new one
906
          if ( prefferedsize + line.size() > compilercfg.FormatterLineWidth )
102✔
907
            newcontext_longer = true;
1✔
908
          break;
102✔
909
        }
910
      }
911
    }
912
    // with a margin of 75% start a new line before
913
    if ( ( ( line.size() + ( l.size() * 0.75 ) ) > compilercfg.FormatterLineWidth ) ||
4,643✔
914
         newcontext_longer )
915
    {
916
      // TODO if next is linecomment dont split now, but split comment
917
      std::string origline = line;  // parenthesisAlign modifies
6✔
918
      auto space = line.find_first_not_of( " \t" );
6✔
919
      if ( space != std::string::npos )  // line contained only alignment
6✔
920
      {
921
        if ( !logical )
6✔
922
        {
923
          if ( !parenthesisAlign( finallines, line ) )
6✔
924
          {
925
            if ( assignpos != std::string::npos )
6✔
926
            {
927
              space = line.find_first_not_of( " \t" );
×
928
              if ( space > assignpos )  // TODO operator shift of 2
×
929
                line.erase( 0, space - assignpos );
×
930
            }
931
          }
932
        }
933
        // line shrinks due to align, check again
934
        if ( newcontext_longer ||
11✔
935
             ( line.size() + ( l.size() * 0.75 ) ) > compilercfg.FormatterLineWidth )
5✔
936
        {
937
          bool skip = false;
6✔
938
          if ( parti == parts.size() - 1 )  // last
6✔
939
          {
940
            if ( l.size() < 4 ||
7✔
941
                 line.size() + l.size() <= compilercfg.FormatterLineWidth )  // small
3✔
942
              skip = true;
1✔
943
            else if ( !l.empty() && l[0] == '/' )  // comment
3✔
944
              skip = true;
×
945
          }
946
          else if ( auto c = line.find_last_not_of( " \t" );
2✔
947
                    c != std::string::npos &&
4✔
948
                    ( line[c] == '(' || ( finallines.empty() &&
2✔
949
                                          line.size() < compilercfg.FormatterLineWidth * 0.3 ) ) )
2✔
950
          {
951
            // a bracket just started, dont directly start a newline
952
            // or no completed line (prevents eg empty "var" line)
953
            skip = true;
×
954
          }
955
          if ( !skip )
6✔
956
          {
957
            stripline( line );
5✔
958
            finallines.emplace_back( std::move( line ) );
5✔
959
            line = alignmentSpacing( alignmentspace );
5✔
960
          }
961
          else
962
            line = origline;
1✔
963
        }
964
        else
965
          line = origline;
×
966
      }
967
    }
6✔
968
    line += l;
4,643✔
969
    // linewidth reached add current line, start a new one
970
    if ( line.size() > compilercfg.FormatterLineWidth || style & FmtToken::FORCED_BREAK ||
9,267✔
971
         style & FmtToken::PREFERRED_BREAK_VAR )
4,624✔
972
    {
973
      // TODO if next is linecomment dont split now, but split comment
974
      std::string origline = line;  // parenthesisAlign modifies
19✔
975
      if ( !logical )
19✔
976
      {
977
        if ( !parenthesisAlign( finallines, line ) )
19✔
978
        {
979
          if ( assignpos != std::string::npos )
19✔
980
          {
981
            auto space = line.find_first_not_of( " \t" );
×
982
            if ( space > assignpos )  // TODO operator shift of 2
×
983
              line.erase( 0, space - assignpos );
×
984
          }
985
        }
986
      }
987
      bool startnew = true;
19✔
988
      if ( style & FmtToken::FORCED_BREAK || style & FmtToken::PREFERRED_BREAK_VAR )
19✔
989
        startnew = true;
18✔
990
      else if ( line.size() < compilercfg.FormatterLineWidth )
1✔
991
        startnew = false;
×
992
      else if ( parti + 1 == parts.size() - 1 && std::get<0>( parts[parti + 1] ).size() < 4 )
1✔
993
        startnew = false;  // next is last and small so add it
×
994
      else if ( parti + 1 == parts.size() - 1 && !std::get<0>( parts[parti + 1] ).empty() &&
1✔
995
                std::get<0>( parts[parti + 1] )[0] == '/' )
×
996
        startnew = false;  // next is last and comment so add it
×
997
      if ( startnew )
19✔
998
      {
999
        stripline( line );
19✔
1000
        finallines.emplace_back( std::move( line ) );
19✔
1001
        line.clear();
19✔
1002
      }
1003
      else
1004
        line = origline;
×
1005
    }
19✔
1006
    ++parti;
4,643✔
1007
  }
1008
  if ( !line.empty() )
669✔
1009
  {
1010
    stripline( line );
650✔
1011
    if ( !logical )
650✔
1012
      parenthesisAlign( finallines, line );
641✔
1013
    finallines.emplace_back( std::move( line ) );
650✔
1014
  }
1015
  return finallines;
1,338✔
1016
}
669✔
1017

1018
std::vector<std::string> PrettifyLineBuilder::createSimple(
6,430✔
1019
    const std::vector<FmtToken>& lines ) const
1020
{
1021
  std::vector<std::string> finallines;
6,430✔
1022
  std::string line;
6,430✔
1023
  size_t alignmentspace = 0;
6,430✔
1024
  size_t i = 0;
6,430✔
1025
  for ( const auto& part : lines )
17,631✔
1026
  {
1027
    // following lines need to be aligned
1028
    if ( line.empty() && alignmentspace )
11,201✔
1029
      line = alignmentSpacing( alignmentspace );
4✔
1030
    // first breakpoint defines the alignment and add initial indent level
1031
    if ( !alignmentspace )
11,201✔
1032
    {
1033
      auto indent = indentSpacing();
6,430✔
1034
      alignmentspace = part.text.size() + indent.size();
6,430✔
1035
      line += indent;
6,430✔
1036
    }
6,430✔
1037
    line += part.text;
11,201✔
1038
    bool forcebreakVarToLong{ false };
11,201✔
1039
    // if its a var definition, check if the next var would fit in the line,
1040
    // otherwise start directly a new line
1041
    if ( part.style & FmtToken::PREFERRED_BREAK_VAR && i > 0 )
11,201✔
1042
    {
1043
      std::string next_var;
73✔
1044
      for ( size_t j = i + 1; j < lines.size(); ++j )
110✔
1045
      {
1046
        next_var += lines[j].text;
73✔
1047
        if ( lines[j].style & FmtToken::PREFERRED_BREAK_VAR )
73✔
1048
          break;
36✔
1049
      }
1050
      if ( ( line.size() + next_var.size() + alignmentSpacing( alignmentspace ).size() ) >
146✔
1051
           compilercfg.FormatterLineWidth )
73✔
1052
        forcebreakVarToLong = true;
×
1053
    }
73✔
1054
    // linewidth reached add current line, start a new one
1055
    if ( line.size() > compilercfg.FormatterLineWidth || part.style & FmtToken::FORCED_BREAK ||
11,201✔
1056
         forcebreakVarToLong )
1057
    {
1058
      // TODO if next is linecomment dont split now, but split comment
1059
      // be extra generous if we only have a few chars left
1060
      if ( ( part.style & FmtToken::FORCED_BREAK ) == 0 )
207✔
1061
      {
1062
        if ( i + 1 == lines.size() - 1 && lines[i + 1].text.size() < 4 )
13✔
1063
        {
1064
          ++i;
3✔
1065
          continue;
3✔
1066
        }
1067
      }
1068
      stripline( line );
204✔
1069
      parenthesisAlign( finallines, line );
204✔
1070
      finallines.emplace_back( std::move( line ) );
204✔
1071
      line.clear();
204✔
1072
    }
1073
    ++i;
11,198✔
1074
  }
1075
  if ( !line.empty() )
6,430✔
1076
  {
1077
    stripline( line );
6,230✔
1078
    parenthesisAlign( finallines, line );
6,230✔
1079
    finallines.emplace_back( std::move( line ) );
6,230✔
1080
  }
1081
  return finallines;
12,860✔
1082
}
6,430✔
1083

1084
void PrettifyLineBuilder::stripline( std::string& line ) const
7,442✔
1085
{
1086
  if ( line.empty() )
7,442✔
1087
    return;
×
1088
  auto lastchar = line.find_last_not_of( ' ' );
7,442✔
1089
  line.erase( line.begin() + lastchar + 1, line.end() );
7,442✔
1090
}
1091

1092
void PrettifyLineBuilder::buildLine( size_t current_indent )
7,245✔
1093
{
1094
  _currindent = current_indent;
7,245✔
1095
  if ( _line_parts.empty() )
7,245✔
1096
  {
1097
    packLines();
133✔
1098
    return;
133✔
1099
  }
1100
  mergeComments();
7,112✔
1101

1102
  // fill lines with final strings splitted at breakpoints
1103
  auto lines = buildLineSplits();
7,112✔
1104
#ifdef DEBUG_FORMAT_BREAK
1105
  INFO_PRINTLN( "BREAK" );
1106
  for ( const auto& part : lines )
1107
    INFO_PRINTLN( "\"{}\" {}-{} ->{} :{}-{}", part.text, part.group, part.firstgroup, part.style,
1108
                  (int)part.scope, (int)part.context );
1109
#endif
1110
  // add newline from original sourcecode
1111
  addEmptyLines( _line_parts.front().pos.line_number );
7,112✔
1112

1113
  // sum up linelength, are groups inside or preferred breaks
1114
  bool groups = false;
7,112✔
1115
  bool has_preferred = false;
7,112✔
1116
  bool has_preferred_logical = false;
7,112✔
1117
  size_t linelength = 0;
7,112✔
1118
  for ( const auto& part : lines )
21,811✔
1119
  {
1120
    if ( part.group != 0 )
14,699✔
1121
      groups = true;
1,479✔
1122
    if ( part.style & FmtToken::PREFERRED_BREAK )
14,699✔
1123
      has_preferred = true;
1,342✔
1124
    if ( part.style & FmtToken::PREFERRED_BREAK_LOGICAL )
14,699✔
1125
      has_preferred_logical = true;
9✔
1126
    linelength += part.text.size();
14,699✔
1127
  }
1128

1129
  std::vector<std::string> finallines;
7,112✔
1130
  // split based on groups
1131
  if ( groups && linelength > compilercfg.FormatterLineWidth )
7,112✔
1132
  {
1133
#ifdef DEBUG_FORMAT_BREAK
1134
    INFO_PRINTLN( "split groupbased" );
1135
#endif
1136
    finallines = createBasedOnGroups( lines );
13✔
1137
  }
1138
  else  // split based on parts
1139
  {
1140
    // with preferred breaks
1141
    if ( has_preferred || has_preferred_logical )
7,099✔
1142
    {
1143
#ifdef DEBUG_FORMAT_BREAK
1144
      INFO_PRINTLN( "split preferred" );
1145
#endif
1146
      finallines = createBasedOnPreferredBreaks( lines, has_preferred_logical );
669✔
1147
    }
1148
    else  // simple splitting
1149
    {
1150
#ifdef DEBUG_FORMAT_BREAK
1151
      INFO_PRINTLN( "split simple" );
1152
#endif
1153
      finallines = createSimple( lines );
6,430✔
1154
    }
1155
  }
1156
  alignComments( finallines );
7,112✔
1157
  _lines.insert( _lines.end(), std::make_move_iterator( finallines.begin() ),
7,112✔
1158
                 std::make_move_iterator( finallines.end() ) );
1159
  _last_line = _line_parts.back().pos.line_number;
7,112✔
1160
  _line_parts.clear();
7,112✔
1161

1162
  packLines();
7,112✔
1163
}
7,112✔
1164

1165
void PrettifyLineBuilder::packLines()
7,245✔
1166
{
1167
  if ( !_packableline_allowed || !_packablelineend )
7,245✔
1168
    return;
7,142✔
1169
  _packableline_allowed = false;
133✔
1170
  _packablelineend = false;
133✔
1171

1172
  // 3 lines max
1173
  // for case its "label: blubb; break;
1174
  // for funcrefs its @(){ blubb; };
1175
  if ( _lines.size() - _packablelinestart >= 4 )
133✔
1176
    return;
30✔
1177
  std::string packedline;
103✔
1178
  for ( size_t i = _packablelinestart; i < _lines.size(); ++i )
327✔
1179
  {
1180
    auto l = _lines[i];
224✔
1181
    if ( l.find( "//" ) != std::string::npos )
224✔
1182
    {
1183
      // if the line comment is not at the end we cannot pack
1184
      if ( i < _lines.size() - 1 )
12✔
1185
        return;
×
1186
    }
1187
    stripline( l );
224✔
1188
    if ( packedline.empty() )
224✔
1189
      packedline += l;
103✔
1190
    else
1191
    {
1192
      auto begin = _lines[i].find_first_not_of( " \t" );
121✔
1193
      if ( begin > 0 && begin != std::string::npos )
121✔
1194
        l.erase( 0, begin );
110✔
1195
      packedline += " " + l;
121✔
1196
    }
1197
  }
224✔
1198
  if ( packedline.empty() )
103✔
1199
    return;
×
1200

1201
  if ( packedline.size() <= compilercfg.FormatterLineWidth )
103✔
1202
  {
1203
    _lines.resize( _packablelinestart );
101✔
1204
    _lines.push_back( std::move( packedline ) );
101✔
1205
  }
1206
}
103✔
1207

1208
void PrettifyLineBuilder::alignComments( std::vector<std::string>& finallines )
7,112✔
1209
{
1210
  if ( !compilercfg.FormatterAlignTrailingComments )
7,112✔
1211
    return;
6,901✔
1212
  std::vector<size_t> commentstart;
7,112✔
1213
  // collect comment columns
1214
  for ( size_t i = 0; i < finallines.size(); ++i )
14,318✔
1215
  {
1216
    auto linecomment = finallines[i].find( "//" );
7,206✔
1217
    if ( linecomment == std::string::npos )
7,206✔
1218
      commentstart.push_back( 0 );
6,994✔
1219
    else
1220
    {
1221
      // if the line only contains a comment dont align it
1222
      auto other = finallines[i].find_first_not_of( " \t" );
212✔
1223
      if ( other == linecomment )
212✔
1224
        commentstart.push_back( 0 );
×
1225
      else
1226
        commentstart.push_back( linecomment );
212✔
1227
    }
1228
  }
1229
  if ( commentstart.empty() )
7,112✔
1230
    return;
×
1231

1232
  // add spaces at comment start foreach comment other then max
1233
  auto max = *std::max_element( commentstart.begin(), commentstart.end() );
7,112✔
1234
  if ( max == 0 )
7,112✔
1235
    return;
6,901✔
1236
  for ( size_t i = 0; i < finallines.size(); ++i )
423✔
1237
  {
1238
    if ( !commentstart[i] || max == commentstart[i] )
212✔
1239
      continue;
211✔
1240
    finallines[i].insert( commentstart[i], max - commentstart[i], ' ' );
1✔
1241
  }
1242
}
7,112✔
1243

1244
void PrettifyLineBuilder::addEmptyLines( size_t line_number )
7,672✔
1245
{
1246
  if ( compilercfg.FormatterMergeEmptyLines )
7,672✔
1247
  {
1248
    if ( line_number > _last_line + 1 )
7,672✔
1249
      _lines.emplace_back( "" );
1,551✔
1250
  }
1251
  else
1252
  {
1253
    while ( line_number > _last_line + 1 )
×
1254
    {
1255
      _lines.emplace_back( "" );
×
1256
      ++_last_line;
×
1257
    }
1258
  }
1259
}
7,672✔
1260

1261
int PrettifyLineBuilder::closingParenthesisStyle( bool args )
4,986✔
1262
{
1263
  auto style = FmtToken::SPACE | FmtToken::BREAKPOINT;
4,986✔
1264
  if ( !args )
4,986✔
1265
  {
1266
    if ( !compilercfg.FormatterEmptyParenthesisSpacing )
472✔
1267
      style |= FmtToken::ATTACHED;
472✔
1268
    else if ( !_line_parts.empty() )
×
1269
      _line_parts.back().style |= FmtToken::SPACE;
×
1270
  }
1271
  else if ( !compilercfg.FormatterParenthesisSpacing )
4,514✔
1272
    style |= FmtToken::ATTACHED;
×
1273
  return style;
4,986✔
1274
}
1275

1276
int PrettifyLineBuilder::closingBracketStyle( size_t begin_size )
573✔
1277
{
1278
  auto style = FmtToken::SPACE | FmtToken::BREAKPOINT;
573✔
1279
  if ( _line_parts.size() == begin_size )
573✔
1280
  {
1281
    if ( !compilercfg.FormatterEmptyBracketSpacing )
198✔
1282
      style |= FmtToken::ATTACHED;
198✔
1283
    else if ( !_line_parts.empty() )
×
1284
      _line_parts.back().style |= FmtToken::SPACE;
×
1285
  }
1286
  else if ( !compilercfg.FormatterBracketSpacing )
375✔
1287
    style |= FmtToken::ATTACHED;
×
1288
  return style;
573✔
1289
}
1290

1291
int PrettifyLineBuilder::openingParenthesisStyle() const
4,986✔
1292
{
1293
  return FmtToken::ATTACHED | FmtToken::BREAKPOINT |
4,986✔
1294
         ( compilercfg.FormatterParenthesisSpacing ? FmtToken::SPACE : FmtToken::NONE );
9,972✔
1295
}
1296

1297
int PrettifyLineBuilder::openingBracketStyle( bool typeinit, bool force_unattached )
573✔
1298
{
1299
  if ( ( typeinit && !compilercfg.FormatterBracketAttachToType ) || force_unattached )
573✔
1300
    return FmtToken::BREAKPOINT |
58✔
1301
           ( compilercfg.FormatterBracketSpacing ? FmtToken::SPACE : FmtToken::NONE );
116✔
1302
  return FmtToken::ATTACHED | FmtToken::BREAKPOINT |
515✔
1303
         ( compilercfg.FormatterBracketSpacing ? FmtToken::SPACE : FmtToken::NONE );
1,030✔
1304
}
1305

1306
int PrettifyLineBuilder::delimiterStyle() const
1,911✔
1307
{
1308
  return FmtToken::ATTACHED | FmtToken::BREAKPOINT |
1,911✔
1309
         ( compilercfg.FormatterDelimiterSpacing ? FmtToken::SPACE : FmtToken::NONE );
3,822✔
1310
}
1311

1312
int PrettifyLineBuilder::terminatorStyle() const
4,894✔
1313
{
1314
  return FmtToken::SPACE | FmtToken::ATTACHED | FmtToken::BREAKPOINT;
4,894✔
1315
}
1316

1317
int PrettifyLineBuilder::assignmentStyle() const
1,682✔
1318
{
1319
  if ( !compilercfg.FormatterAssignmentSpacing )
1,682✔
1320
    return FmtToken::ATTACHED;
×
1321
  return FmtToken::SPACE;
1,682✔
1322
}
1323

1324
int PrettifyLineBuilder::comparisonStyle() const
385✔
1325
{
1326
  if ( !compilercfg.FormatterComparisonSpacing )
385✔
1327
    return FmtToken::ATTACHED;
×
1328
  return FmtToken::SPACE;
385✔
1329
}
1330

1331
int PrettifyLineBuilder::operatorStyle() const
826✔
1332
{
1333
  if ( !compilercfg.FormatterOperatorSpacing )
826✔
1334
    return FmtToken::ATTACHED;
×
1335
  return FmtToken::SPACE;
826✔
1336
}
1337

1338
std::string PrettifyLineBuilder::indentSpacing() const
7,692✔
1339
{
1340
  if ( !compilercfg.FormatterUseTabs )
7,692✔
1341
    return std::string( _currindent * compilercfg.FormatterIndentLevel, ' ' );
7,692✔
1342
  size_t total = _currindent * compilercfg.FormatterIndentLevel;
×
1343
  size_t tabs = total / compilercfg.FormatterTabWidth;
×
1344
  size_t remaining = total % compilercfg.FormatterTabWidth;
×
1345
  return std::string( tabs, '\t' ) + std::string( remaining, ' ' );
×
1346
}
1347

1348
std::string PrettifyLineBuilder::alignmentSpacing( size_t count ) const
175✔
1349
{
1350
  if ( !count )
175✔
1351
    return {};
×
1352
  if ( !compilercfg.FormatterUseTabs )
175✔
1353
    return std::string( count, ' ' );
175✔
1354
  size_t tabs = count / compilercfg.FormatterTabWidth;
×
1355
  size_t remaining = count % compilercfg.FormatterTabWidth;
×
1356
  return std::string( tabs, '\t' ) + std::string( remaining, ' ' );
×
1357
}
1358

1359
void PrettifyLineBuilder::mergeEOFNonTokens()
594✔
1360
{
1361
  mergeRawContent( _last_line );
594✔
1362
  while ( !_comments.empty() )
644✔
1363
  {
1364
    addEmptyLines( _comments.front().pos.line_number );
50✔
1365
    _lines.push_back( indentSpacing() + _comments.front().text );
50✔
1366
    _last_line = _comments.front().pos_end.line_number;
50✔
1367
    mergeRawContent( _last_line );
50✔
1368
    _comments.erase( _comments.begin() );
50✔
1369
  }
1370
  // purge raw content
1371
  mergeRawContent( std::numeric_limits<size_t>::max() );
594✔
1372
  // if the original file ends with a newline, make sure to also end
1373
  if ( !_lines.empty() && !_lines.back().empty() && !_rawlines.empty() && _rawlines.back().empty() )
594✔
1374
    _lines.push_back( "" );
×
1375
}
594✔
1376

1377
void PrettifyLineBuilder::markPackableLineStart()
179✔
1378
{
1379
  _packablelinestart = _lines.size();
179✔
1380
  _packableline_allowed = true;
179✔
1381
  _packablelineend = false;
179✔
1382
}
179✔
1383
void PrettifyLineBuilder::markPackableLineEnd()
180✔
1384
{
1385
  _packablelineend = true;
180✔
1386
}
180✔
1387

1388
void PrettifyLineBuilder::markLastTokensAsSwitchLabel()
90✔
1389
{
1390
  if ( _line_parts.size() < 2 )
90✔
1391
    return;
×
1392
  _packable_switch_labels.push_back( _line_parts[_line_parts.size() - 2].text +
90✔
1393
                                     _line_parts[_line_parts.size() - 1].text );
90✔
1394
}
1395
void PrettifyLineBuilder::alignSingleLineSwitchStatements( size_t start )
27✔
1396
{
1397
  if ( !compilercfg.FormatterAlignConsecutiveShortCaseStatements ||
27✔
1398
       !compilercfg.FormatterAllowShortCaseLabelsOnASingleLine )
27✔
1399
    return;
2✔
1400
  size_t l_index = 0;
27✔
1401
  std::vector<std::pair<size_t, size_t>> statementstart;
27✔
1402
  std::optional<std::pair<size_t, size_t>> default_start;
27✔
1403
  // collect label starts based on stored labels
1404
  // default: is collected extra
1405
  for ( size_t i = start; i < _lines.size(); ++i )
135✔
1406
  {
1407
    if ( _packable_switch_labels.size() <= l_index )
111✔
1408
      break;
3✔
1409
    const auto& label = _packable_switch_labels[l_index];
108✔
1410
    auto labelend = _lines[i].find( label );
108✔
1411
    if ( labelend == std::string::npos )
108✔
1412
      continue;
21✔
1413
    // there should be only whitespace before (could be in a comment)
1414
    bool empty{ true };
87✔
1415
    for ( size_t w = 0; w < labelend; ++w )
405✔
1416
    {
1417
      if ( _lines[i][w] != ' ' && _lines[i][w] != '\t' )
318✔
1418
      {
1419
        empty = false;
×
1420
        break;
×
1421
      }
1422
    }
1423
    if ( !empty )
87✔
1424
      continue;
×
1425

1426
    ++l_index;  // ~valid match, so next one
87✔
1427
    // nothing following no need to align or consider as max
1428
    auto next = _lines[i].find_first_not_of( " \t", labelend + label.size() );
87✔
1429
    if ( next == std::string::npos )
87✔
1430
      continue;
8✔
1431

1432
    if ( label == "default:" )
79✔
1433
      default_start = std::make_pair( i, labelend + label.size() );
13✔
1434
    else
1435
      statementstart.push_back( { i, labelend + label.size() } );
66✔
1436
  }
1437
  _packable_switch_labels.clear();
27✔
1438
  if ( statementstart.empty() )
27✔
1439
    return;
2✔
1440

1441
  auto max = std::max_element( statementstart.begin(), statementstart.end(),
25✔
1442
                               []( auto& a, auto& b ) { return a.second < b.second; } )
41✔
1443
                 ->second;
25✔
1444
  if ( max == 0 )
25✔
1445
    return;
×
1446
  for ( auto& [line, pos] : statementstart )
91✔
1447
  {
1448
    if ( pos == max )
66✔
1449
      continue;
40✔
1450
    _lines[line].insert( pos, max - pos, ' ' );
26✔
1451
  }
1452
  // we have a default, but only align it if its not the "biggest"
1453
  if ( default_start )
25✔
1454
  {
1455
    if ( max > default_start->second )
13✔
1456
      _lines[default_start->first].insert( default_start->second, max - default_start->second,
6✔
1457
                                           ' ' );
1458
  }
1459
}
27✔
1460

1461
}  // namespace Pol::Bscript::Compiler
1462

1463
fmt::format_context::iterator fmt::formatter<Pol::Bscript::Compiler::FmtToken>::format(
×
1464
    const Pol::Bscript::Compiler::FmtToken& t, fmt::format_context& ctx ) const
1465
{
1466
  return fmt::formatter<std::string>::format(
×
1467
      fmt::format( "{} ({}:{}:{})", t.text, t.style, t.pos.line_number, t.pos.token_index ), ctx );
×
1468
}
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