#include "PrettifyLineBuilder.h" #include "bscript/compilercfg.h" #include "clib/filecont.h" #include "clib/logfacility.h" #include #include #include #include // #define DEBUG_FORMAT_BREAK namespace Pol::Bscript::Compiler { using namespace EscriptGrammar; const std::vector& PrettifyLineBuilder::formattedLines() const { return _lines; } void PrettifyLineBuilder::setRawLines( std::vector rawlines ) { _rawlines = std::move( rawlines ); } void PrettifyLineBuilder::setComments( std::vector comments ) { _comments = std::move( comments ); } void PrettifyLineBuilder::setSkipLines( std::vector skiplines ) { _skiplines = std::move( skiplines ); } void PrettifyLineBuilder::addPart( FmtToken part ) { for ( const auto& skip : _skiplines ) if ( skip.contains( part.pos ) ) return; _line_parts.emplace_back( std::move( part ) ); } bool PrettifyLineBuilder::finalize() { mergeEOFNonTokens(); return _line_parts.empty() && _comments.empty() && _skiplines.empty(); } const std::vector& PrettifyLineBuilder::currentTokens() const { return _line_parts; } void PrettifyLineBuilder::mergeRawContent( size_t nextlineno ) { for ( auto itr = _skiplines.begin(); itr != _skiplines.end(); ) { if ( itr->start.line_number < nextlineno ) { mergeCommentsBefore( itr->start.line_number ); addEmptyLines( itr->start.line_number ); for ( size_t i = itr->start.line_number - 1; i < itr->end.line_number && i < _rawlines.size(); ++i ) { _lines.emplace_back( _rawlines[i] ); _last_line = i + 1; } itr = _skiplines.erase( itr ); continue; } ++itr; } } void PrettifyLineBuilder::mergeCommentsBefore( size_t nextlineno ) { // add comments before current line while ( !_comments.empty() ) { if ( _comments.front().pos.line_number < nextlineno ) { addEmptyLines( _comments.front().pos.line_number ); _lines.push_back( identSpacing() + _comments.front().text ); _last_line = _comments.front().pos_end.line_number; _comments.erase( _comments.begin() ); } else break; } } void PrettifyLineBuilder::mergeComments() { if ( _line_parts.empty() ) return; mergeRawContent( _line_parts.front().pos.line_number ); mergeCommentsBefore( _line_parts.front().pos.line_number ); // add comments inbetween for ( size_t i = 0; i < _line_parts.size(); ) { if ( _comments.empty() ) break; if ( _line_parts[i].pos.token_index > _comments.front().pos.token_index ) { auto info = _comments.front(); info.group = _currentgroup; _line_parts.insert( _line_parts.begin() + i, std::move( info ) ); _comments.erase( _comments.begin() ); } else ++i; } // add comments at the end of the current line if ( !_comments.empty() ) { if ( _comments.front().pos.line_number == _line_parts.back().pos.line_number && _comments.front().pos.token_index <= _line_parts.back().pos.token_index + 1 ) { addPart( _comments.front() ); _comments.erase( _comments.begin() ); } } } void PrettifyLineBuilder::buildLine( size_t current_ident ) { _currident = current_ident; if ( _line_parts.empty() ) return; auto stripline = []( std::string& line ) { if ( !line.empty() ) { auto lastchar = line.find_last_not_of( ' ' ); line.erase( line.begin() + lastchar + 1, line.end() ); } }; mergeComments(); // fill lines with final strings splitted at breakpoints // std::vector> lines; { std::string line; size_t firstgroup = 0; for ( size_t i = 0; i < _line_parts.size(); ++i ) { if ( line.empty() ) firstgroup = _line_parts[i].group; line += _line_parts[i].text; // add space if set, but not if the following part is attached if ( _line_parts[i].style & FmtToken::SPACE ) { if ( i + 1 < _line_parts.size() ) { if ( !( _line_parts[i + 1].style & FmtToken::ATTACHED ) ) line += ' '; } else line += ' '; } if ( _line_parts[i].style & FmtToken::FORCED_BREAK ) { lines.emplace_back( std::make_tuple( std::move( line ), true, _line_parts[i].group, firstgroup ) ); line.clear(); } // start a new line if breakpoint else if ( _line_parts[i].style & FmtToken::BREAKPOINT ) { // if the next part is attached, dont break now and instead later // eg dont split blubb[1]() bool skip = false; if ( i + 1 < _line_parts.size() ) { // next one is attached if ( _line_parts[i + 1].style & FmtToken::ATTACHED ) { // search for space or breakpoint for ( size_t j = i + 1; j < _line_parts.size(); ++j ) { if ( _line_parts[j].style & FmtToken::ATTACHED ) skip = true; if ( _line_parts[j].style & FmtToken::SPACE || _line_parts[j].style & FmtToken::BREAKPOINT ) { _line_parts[j].style |= FmtToken::BREAKPOINT; skip = true; break; } } } } if ( skip ) continue; lines.emplace_back( std::make_tuple( std::move( line ), false, _line_parts[i].group, firstgroup ) ); line.clear(); } } // add remainings if ( !line.empty() ) { lines.emplace_back( std::make_tuple( std::move( line ), false, _line_parts.back().group, _line_parts.back().group ) ); line.clear(); } } #ifdef DEBUG_FORMAT_BREAK INFO_PRINTLN( "BREAK " ); for ( auto& [l, forced, group, firstgroup] : lines ) INFO_PRINTLN( "\"{}\" {}-{}", l, group, firstgroup ); #endif // add newline from original sourcecode addEmptyLines( _line_parts.front().pos.line_number ); // sum up linelength, are groups inside bool groups = false; size_t linelength = 0; for ( auto& [l, forced, group, firstgroup] : lines ) { if ( group != 0 ) groups = true; linelength += l.size(); } std::string line; // split based on groups if ( groups && linelength > compilercfg.FormatterLineWidth ) { // store for each group thr alignment std::map alignmentspace = {}; size_t lastgroup = 0xffffFFFF; bool newline = false; auto ident = identSpacing(); line = ident; bool groupdiffered = false; for ( auto& [l, forced, group, firstgroup] : lines ) { if ( forced ) { line += l; stripline( line ); _lines.emplace_back( std::move( line ) ); line = ident; continue; } if ( lastgroup == 0xffffFFFF ) { line += l; // first part alignmentspace[firstgroup] = line.size() - ident.size(); alignmentspace[group] = line.size() - ident.size(); newline = true; #ifdef DEBUG_FORMAT_BREAK INFO_PRINTLN( "first {}", line ); #endif } else if ( lastgroup < firstgroup ) // new group { groupdiffered = true; bool newgroup = false; if ( !alignmentspace.count( firstgroup ) ) { newgroup = true; alignmentspace[firstgroup] = line.size() - ident.size(); } // if its not a new group and not a fresh line start a new one // nested structs eg should start at the same line if ( !newgroup && !newline ) { stripline( line ); if ( !line.empty() ) _lines.emplace_back( std::move( line ) ); line = alignmentSpacing( alignmentspace[firstgroup] ); line += ident; line += l; newline = true; #ifdef DEBUG_FORMAT_BREAK INFO_PRINTLN( "!new {} - {}", line, alignmentspace ); #endif } else { line += l; #ifdef DEBUG_FORMAT_BREAK INFO_PRINTLN( "new {} - {}", line, alignmentspace ); #endif } if ( !alignmentspace.count( group ) ) alignmentspace[group] = line.size() - ident.size(); } else if ( lastgroup > firstgroup ) // descending { newline = true; stripline( line ); if ( !line.empty() ) _lines.emplace_back( std::move( line ) ); line = alignmentSpacing( alignmentspace[firstgroup] ); line += ident; line += l; #ifdef DEBUG_FORMAT_BREAK INFO_PRINTLN( "desc {} - {}", line, alignmentspace ); #endif } else // same group { // only if we already had a different group split the line if ( groupdiffered ) { newline = false; stripline( line ); if ( !line.empty() ) _lines.emplace_back( std::move( line ) ); line = alignmentSpacing( alignmentspace[firstgroup] ); line += ident; } line += l; #ifdef DEBUG_FORMAT_BREAK INFO_PRINTLN( "same {} - {}", line, alignmentspace ); #endif if ( line.size() > compilercfg.FormatterLineWidth ) { stripline( line ); if ( !line.empty() ) _lines.emplace_back( std::move( line ) ); line = alignmentSpacing( alignmentspace[firstgroup] ); line += ident; } } lastgroup = firstgroup; } } else // split based on parts { // build final lines size_t alignmentspace = 0; for ( auto& [l, forced, group, firstgroup] : lines ) { // following lines need to be aligned if ( line.empty() && alignmentspace ) line = alignmentSpacing( alignmentspace ); // first breakpoint defines the alignment and add initial ident level if ( !alignmentspace ) { auto ident = identSpacing(); alignmentspace = l.size() + ident.size(); line += ident; } line += l; // linewidth reached add current line, start a new one if ( line.size() > compilercfg.FormatterLineWidth || forced ) { stripline( line ); _lines.emplace_back( std::move( line ) ); line.clear(); } } } if ( !line.empty() ) { stripline( line ); _lines.emplace_back( std::move( line ) ); } _last_line = _line_parts.back().pos.line_number; _line_parts.clear(); } void PrettifyLineBuilder::addEmptyLines( size_t line_number ) { if ( compilercfg.FormatterMergeEmptyLines ) { if ( line_number > _last_line + 1 ) _lines.emplace_back( "" ); } else { while ( line_number > _last_line + 1 ) { _lines.emplace_back( "" ); ++_last_line; } } } int PrettifyLineBuilder::closingParenthesisStyle( size_t begin_size ) { auto style = FmtToken::SPACE | FmtToken::BREAKPOINT; if ( _line_parts.size() == begin_size ) { if ( !compilercfg.FormatterEmptyParenthesisSpacing ) style |= FmtToken::ATTACHED; else if ( !_line_parts.empty() ) _line_parts.back().style |= FmtToken::SPACE; } else if ( !compilercfg.FormatterParenthesisSpacing ) style |= FmtToken::ATTACHED; return style; } int PrettifyLineBuilder::closingBracketStyle( size_t begin_size ) { auto style = FmtToken::SPACE | FmtToken::BREAKPOINT; if ( _line_parts.size() == begin_size ) { if ( !compilercfg.FormatterEmptyBracketSpacing ) style |= FmtToken::ATTACHED; else if ( !_line_parts.empty() ) _line_parts.back().style |= FmtToken::SPACE; } else if ( !compilercfg.FormatterBracketSpacing ) style |= FmtToken::ATTACHED; return style; } int PrettifyLineBuilder::openingParenthesisStyle() { return FmtToken::ATTACHED | FmtToken::BREAKPOINT | ( compilercfg.FormatterParenthesisSpacing ? FmtToken::SPACE : FmtToken::NONE ); } int PrettifyLineBuilder::openingBracketStyle() { return FmtToken::ATTACHED | FmtToken::BREAKPOINT | ( compilercfg.FormatterBracketSpacing ? FmtToken::SPACE : FmtToken::NONE ); } int PrettifyLineBuilder::delimiterStyle() { return FmtToken::ATTACHED | FmtToken::BREAKPOINT | ( compilercfg.FormatterDelimiterSpacing ? FmtToken::SPACE : FmtToken::NONE ); } int PrettifyLineBuilder::terminatorStyle() { return FmtToken::SPACE | FmtToken::ATTACHED | FmtToken::BREAKPOINT; } int PrettifyLineBuilder::assignmentStyle() { if ( !compilercfg.FormatterAssignmentSpacing ) return FmtToken::ATTACHED; return FmtToken::SPACE; } int PrettifyLineBuilder::comparisonStyle() { if ( !compilercfg.FormatterComparisonSpacing ) return FmtToken::ATTACHED; return FmtToken::SPACE; } int PrettifyLineBuilder::operatorStyle() { if ( !compilercfg.FormatterOperatorSpacing ) return FmtToken::ATTACHED; return FmtToken::SPACE; } std::string PrettifyLineBuilder::identSpacing() { if ( !compilercfg.FormatterUseTabs ) return std::string( _currident * compilercfg.FormatterIdentLevel, ' ' ); size_t total = _currident * compilercfg.FormatterIdentLevel; size_t tabs = total / compilercfg.FormatterTabWidth; size_t remaining = total % compilercfg.FormatterTabWidth; return std::string( tabs, '\t' ) + std::string( remaining, ' ' ); } std::string PrettifyLineBuilder::alignmentSpacing( size_t count ) { if ( !count ) return {}; if ( !compilercfg.FormatterUseTabs ) return std::string( count, ' ' ); size_t tabs = count / compilercfg.FormatterTabWidth; size_t remaining = count % compilercfg.FormatterTabWidth; return std::string( tabs, '\t' ) + std::string( remaining, ' ' ); } void PrettifyLineBuilder::mergeEOFNonTokens() { mergeRawContent( _last_line ); while ( !_comments.empty() ) { addEmptyLines( _comments.front().pos.line_number ); _lines.push_back( identSpacing() + _comments.front().text ); _last_line = _comments.front().pos_end.line_number; mergeRawContent( _last_line ); _comments.erase( _comments.begin() ); } // purge raw content mergeRawContent( std::numeric_limits::max() ); // if the original file ends with a newline, make sure to also end if ( !_lines.empty() && !_lines.back().empty() && !_rawlines.empty() && _rawlines.back().empty() ) _lines.push_back( "" ); } } // namespace Pol::Bscript::Compiler fmt::format_context::iterator fmt::formatter::format( const Pol::Bscript::Compiler::FmtToken& t, fmt::format_context& ctx ) const { return fmt::formatter::format( fmt::format( "{} ({}:{}:{})", t.text, t.style, t.pos.line_number, t.pos.token_index ), ctx ); }