#pragma once // forliteral.h — MF1-LIT: THE LITERAL ANCHOR of a --for answer whose task names a literal (an error string, an env var, a // config key, an error code). // // WHY. A --for task such as `FZF_DEFAULT_COMMAND` or `"dependency cycle" error` names a STRING, not a symbol. The ranked answer // reads it as words: name-exact finds no definition (`reason="no_candidates"`, empty ) or stops at the one variable spelled // like it, and the place the literal is read, compared, built or raised is never printed. Measured before this code (ORCH // reports/mf1-lit.md Stage 0): on the three MF1-DEV rows whose 12 missing items readout-40 files under LITERAL (fastify-02, // fzf-05, textual-03) the default answer held none of the code that handles the literal, while a byte-exact scan finds it in // one call (`--grep`). // // WHAT. just before , after every byte of the ordinary answer (RIPWIRE_NO_LITERAL_ANCHOR=1 // is the rest of it byte for byte): the grep verb's own byte-exact literal scan (search.h grepCollect + grepEnrich) over the // index, then // * rows: the sites in SOURCE files (filter.h pathTierOf = Source, not a doc/shell/config file), each labelled by the // parse (ingest.h spanTierAt): k="string" inside a string literal, k="code" outside one (an identifier or key use), // k="?" a file past the classification budget or with no grammar (never dropped, never called code). One row per source // line; sites inside a function or method first, then string before code before unclassified, the scan's own path order // within each; in= the innermost definition holding the site and ink= its kind; a string site outside any function (an // error-code table entry) carries the kForLiteralContextLines lines after it (lines=), at most kForLiteralContextRows rows; // * counts beside them for what is NOT a handler: comment= (comment sites in source files, and a Python docstring: a triple-quoted // string that is the first statement of a module, class or def body; a string opening a call-argument line is a site), // test= (test or bench paths), doc= (docs, shell scripts, config files) — counted, never listed as rows here (the grep verb // lists them: next=); // * the whole body of the function or method holding a shown site when it is kForLiteralWholeBodyLines lines or fewer // (at most kForLiteralBodies owners in row order), or an excerpt of a longer one: its signature line plus one window // [site-1, site+5] per shown site (windows merged only where they touch; l= lists the ranges, an ellipsis line sits between // them; of= is the body's lines; next= names the whole body); a body this answer already printed is not repeated // (body="above" on its rows). // Several sites are always listed with counts: the block never picks one. floor="1" when the scan stopped early (budget, // unreadable files): every count is then a floor. No source site, nothing printed (a literal met only in tests, docs or comments // anchors nothing; never a "the literal is unused" claim). // // WHEN. The default regime of the XML dialect, a single root: an explicit --token-budget, --json, --columnar and --candidates // carry none. The literal is read from the task: the first "double-quoted" span of at least 6 bytes holding a letter and a space // (a message fragment; a backticked span is code the ranked answer already reads as names, and measured as noise on S1), else the first CONST_SHAPED word (upper-case letters and digits joined by at least one underscore, 5 bytes or more, // or 10 or more upper-case letters and digits). A task with neither carries no block. The block is not in est_tokens (the // FOR-ROUTE-DECLINED precedent: the bytes are counted on the element instead, bytes=). MCP `for` is not changed. #include "filter.h" // pathTierOf — source / test / doc, the reader's tier #include "layout.h" // layout::lineOf — a byte offset's line #include "hopbody.h" // renderBodyElement — the --expand body element, no callee rows; pythonDocstringQuote #include "langfacts.h" // langFacts(...).leadingDocstring — which languages have a docstring convention #include "lintrules.h" // langOfPath #include "model.h" #include "nextverb.h" // nextFlag / nextAttrXml #include "receiverevidence.h" // ReceiverEvidence::callableKind #include "sarif.h" // rootPrefixOf / rootRelativeUri #include "search.h" // grepCollect / grepEnrich / isBareWordCodePath #include "serialize.h" // escapeXml / appendCdataSafe / truncateUtf8WithEllipsis #include #include #include #include #include #include #include #include #include #include namespace rw { inline constexpr std::size_t kForLiteralSiteRows = 8; // rows shown (a runaway guard: shown=/src= count, next= lists all) inline constexpr std::size_t kForLiteralRankedRows = 256; // source sites ordered for the rows (past it: counted in src=, not ranked) inline constexpr std::size_t kForLiteralContextRows = 2; // string sites outside any function served with the lines after them inline constexpr std::uint32_t kForLiteralContextLines = 2; // ...this many lines after the site inline constexpr std::size_t kForLiteralBodies = 3; // owner bodies served, in row order inline constexpr std::size_t kForLiteralBodyCapBytes = 3000; // a short body over this many bytes is still served as an excerpt inline constexpr std::uint32_t kForLiteralWholeBodyLines = 10; // a body of at most this many lines is served whole; a longer one as an excerpt inline constexpr std::uint32_t kForLiteralExcerptBefore = 1; // lines before a site in its excerpt window inline constexpr std::uint32_t kForLiteralExcerptAfter = 5; // lines after it: what the code does with a literal follows the read (+2/+3 drops fzf R2) inline constexpr std::size_t kForLiteralTierFiles = 64; // source hit files classified (k=); past it k="?" inline constexpr std::size_t kForLiteralLineBytes = 200; // the matched line's text, cut on a UTF-8 boundary inline constexpr std::size_t kForLiteralMinQuoted = 6; inline constexpr std::size_t kForLiteralMinConst = 5; inline constexpr std::size_t kForLiteralMinBareCaps = 10; // the element's legend comment (X3: no double hyphen inside). The compact form is the default --legend=compact posture; the long // text rides --legend=full. Both name every attribute the element prints. inline constexpr std::string_view kForLiteralLegend = ""; inline constexpr std::string_view kForCompactLiteralLegend = ""; static_assert( kForLiteralLegend.find( "--", 4 ) == kForLiteralLegend.size() - 3 && kForCompactLiteralLegend.find( "--", 4 ) == kForCompactLiteralLegend.size() - 3, "the only double hyphen in a legend comment is its closing: one inside is not well-formed XML" ); inline bool forLiteralOn() { return std::getenv( "RIPWIRE_NO_LITERAL_ANCHOR" ) == nullptr; } struct ForLiteral { std::string text; std::string_view shape; // "quoted" | "const" }; namespace lit_detail { // CONST_SHAPED: upper-case letters and digits, starting with a letter, joined by underscores (at least one, none doubled or at an // end) and at least kForLiteralMinConst bytes; or no underscore and at least kForLiteralMinBareCaps bytes (PYTHONASYNCIODEBUG) inline bool isConstShaped( std::string_view w ) noexcept { if( w.empty() || !( w[0] >= 'A' && w[0] <= 'Z' ) || w.back() == '_' ) { return false; } std::size_t underscores = 0; for( std::size_t i = 0; i < w.size(); ++i ) { if( w[i] == '_' ) { if( w[i - 1] == '_' ) { return false; } ++underscores; } else if( !std::isupper( static_cast( w[i] ) ) && !std::isdigit( static_cast( w[i] ) ) ) { return false; } } return underscores > 0 ? w.size() >= kForLiteralMinConst : w.size() >= kForLiteralMinBareCaps; } inline std::optional quotedSpan( std::string_view task, char q ) { for( std::size_t open = task.find( q ); open != std::string_view::npos; open = task.find( q, open + 1 ) ) { const std::size_t close = task.find( q, open + 1 ); if( close == std::string_view::npos ) { return std::nullopt; } const std::string_view inner = task.substr( open + 1, close - open - 1 ); bool letter = false; for( const char c : inner ) { letter = letter || std::isalpha( static_cast( c ) ) != 0; } // a MESSAGE fragment: two words or more (a single quoted word is a value or a name, read as words by the ranked answer) if( inner.size() >= kForLiteralMinQuoted && letter && inner.find_first_not_of( " \t" ) == 0 && inner.back() != ' ' && inner.find( ' ' ) != std::string_view::npos ) { return std::string( inner ); } open = close; } return std::nullopt; } } // namespace lit_detail // The literal the task names, or none. Quoted spans first (the task's own quoting is the strongest evidence), then the first // CONST_SHAPED word (trailing/leading punctuation stripped). inline std::optional forLiteralOf( std::string_view task ) { if( std::optional s = lit_detail::quotedSpan( task, '"' ) ) { return ForLiteral{ std::move( *s ), "quoted" }; } std::size_t i = 0; while( i < task.size() ) { while( i < task.size() && ( task[i] == ' ' || task[i] == '\t' ) ) { ++i; } std::size_t j = i; while( j < task.size() && task[j] != ' ' && task[j] != '\t' ) { ++j; } std::string_view w = task.substr( i, j - i ); while( !w.empty() && std::string_view( "$(['{<,;:.!?)]}>'`" ).find( w.front() ) != std::string_view::npos ) { w.remove_prefix( 1 ); } while( !w.empty() && std::string_view( "(['{<,;:.!?)]}>'`" ).find( w.back() ) != std::string_view::npos ) { w.remove_suffix( 1 ); } if( lit_detail::isConstShaped( w ) ) { return ForLiteral{ std::string( w ), "const" }; } i = j; } return std::nullopt; } struct ForLiteralRender { std::string xml; // "" = nothing rides std::size_t sites = 0; // src= (source code + string + unclassified sites) std::size_t bodies = 0; // / elements served }; namespace lit_detail { struct Site { GrepRawHit raw; char k = '?'; // 's' string, 'c' code, '?' unclassified }; // text-line helpers for the docstring position test: the previous line of the one starting at `ls` (its start moves back to that // line's start), trimmed of '\r'; false at the top of the file inline bool prevLine( const std::string& text, std::size_t& ls, std::string_view& line ) noexcept { if( ls == 0 ) { return false; } const std::size_t nl = ls - 1; // text[nl] == '\n' const std::size_t r = nl == 0 ? std::string::npos : text.rfind( '\n', nl - 1 ); const std::size_t pb = r == std::string::npos ? 0 : r + 1; line = std::string_view( text ).substr( pb, nl - pb ); if( !line.empty() && line.back() == '\r' ) { line.remove_suffix( 1 ); } ls = pb; return true; } // the previous line that holds code (not blank, not a whole-line # comment) inline bool prevCodeLine( const std::string& text, std::size_t& ls, std::string_view& line ) noexcept { while( prevLine( text, ls, line ) ) { const std::string_view t = lintdetail::ltrim( line ); if( !t.empty() && t.front() != '#' ) { return true; } } return false; } // step back through code lines until one is indented LESS than `limit` (at most 200 lines); false when the top of the file comes first inline bool climbBelow( const std::string& text, std::size_t& pos, std::string_view& line, std::size_t limit ) noexcept { for( std::size_t steps = 0; lintdetail::indentOf( line ) >= limit; ++steps ) { if( steps >= 200 || !prevCodeLine( text, pos, line ) ) { return false; } } return true; } // `start` opens a (r/b/u-prefixed) triple-quoted string that is the first thing on its line: ls = that line's start, ind = its indent inline bool opensTripleQuoteLine( const std::string& text, std::size_t start, std::size_t& ls, std::size_t& ind ) noexcept { if( start >= text.size() ) { return false; } for( ls = start; ls > 0 && text[ls - 1] != '\n'; --ls ) { } const std::size_t fs = ls + lintdetail::indentOf( std::string_view( text ).substr( ls, start - ls ) ); ind = fs - ls; return start - fs <= 2 && pythonDocstringQuote( std::string_view( text ).substr( fs, 8 ) ).size() == 3; } // a code line that ends a statement header: a trailing ':' (a trailing # comment aside) inline bool endsWithColon( std::string_view line ) noexcept { // the whole-line form keeps `def f(x="#"):` a header (its '#' is inside a string); only a def/class header line takes it, so a // trailing comment that ends in a colon (`x = x + 1 # note:`) is no header (train 42b, rv-mf1-lit-delta N2) const std::string_view lead = line.substr( std::min( line.find_first_not_of( " \t" ), line.size() ) ); const bool header = lead.starts_with( "def " ) || lead.starts_with( "async def " ) || lead.starts_with( "class " ); return lintdetail::rtrim( line.substr( 0, line.rfind( '#' ) ) ).ends_with( ':' ) || ( header && lintdetail::rtrim( line ).ends_with( ':' ) ); } // A Python DOCSTRING: a triple-quoted string statement that is the FIRST statement of a module, class or def body — the line // before it (comments and blank lines aside) closes a `def` / `async def` / `class` header with a ':' (a signature may span lines), // or there is none (the top of the module). The same string opening a call-argument line (`raise E(\n """msg""")`), sitting in an // `if` body or following other statements is a string SITE: the parse says String, this decides whether it is documentation. The // caller applies it to Python only (langFacts leadingDocstring); another language's triple quote is a raw string, its comments // are the parse's own Comment tier. inline bool isPythonDocstringAt( const std::string& text, std::size_t start ) noexcept { std::size_t ls = 0, ind = 0; if( !opensTripleQuoteLine( text, start, ls, ind ) ) { return false; // not the first thing on its line, or not a triple-quoted string } std::size_t pos = ls; std::string_view line; if( !prevCodeLine( text, pos, line ) ) { return true; // nothing but comments before it: the module docstring } // the header's first line: the nearest code line indented LESS than the string (a multi-line signature closes with `) -> T:` at // the def's own indent; its parameters sit deeper or at the string's indent) if( !endsWithColon( line ) || !climbBelow( text, pos, line, ind ) ) { return false; } std::string_view t = lintdetail::ltrim( line ); if( !t.empty() && std::string_view( ")]}" ).find( t[0] ) != std::string_view::npos ) // `) -> None:` — on to the line that opened it { const std::size_t closeIndent = lintdetail::indentOf( line ); if( !prevCodeLine( text, pos, line ) || !climbBelow( text, pos, line, closeIndent + 1 ) ) { return false; } t = lintdetail::ltrim( line ); } return t.starts_with( "def " ) || t.starts_with( "async def " ) || t.starts_with( "class " ); } using LineRange = std::pair; // inclusive, 1-based // the excerpt of a body: its signature line `first` plus a window [site-before, site+after] per site line, clipped to // [first, last], overlapping or touching windows merged; ascending and disjoint inline std::vector excerptRanges( const std::vector& siteLines, std::uint32_t first, std::uint32_t last ) { std::vector w{ LineRange{ first, first } }; for( const std::uint32_t l : siteLines ) { const std::uint32_t a = std::max( first, l > kForLiteralExcerptBefore ? l - kForLiteralExcerptBefore : 1u ); const std::uint32_t b = std::min( last, l + kForLiteralExcerptAfter ); if( a <= b ) { w.push_back( LineRange{ a, b } ); } } std::sort( w.begin(), w.end() ); std::vector merged; for( const LineRange& r : w ) { if( !merged.empty() && r.first <= merged.back().second + 1 ) { merged.back().second = std::max( merged.back().second, r.second ); } else { merged.push_back( r ); } } return merged; } // the lines of every range of `text`, each range newline-joined (the grep verb's own slicing core, search.h grepLineRangeText), // the ranges joined by a line holding one ellipsis inline std::string linesOfRanges( const std::string& text, const std::vector& ranges ) { std::vector starts{ 0 }; for( std::size_t i = 0; i < text.size(); ++i ) { if( text[i] == '\n' ) { starts.push_back( i + 1 ); } } const std::uint32_t count = grepRealLineCount( text, starts ); std::string out; for( const LineRange& r : ranges ) { const std::uint32_t b = std::min( r.second, count ); if( r.first < 1 || r.first > b ) { continue; } out += out.empty() ? std::string_view() : std::string_view( "\n\xE2\x80\xA6\n" ); out += grepLineRangeText( text, starts, count, r.first, b ); } return out; } // cut every line of `lines` longer than `cap` bytes on a UTF-8 boundary (an ellipsis marks the cut); true when one was cut inline bool capLineBytes( std::string& lines, std::size_t cap ) { if( lines.size() <= cap ) { return false; } std::string out; bool cut = false; for( std::size_t at = 0; at <= lines.size(); ) { const std::size_t end = std::min( lines.find( '\n', at ), lines.size() ); std::string line = lines.substr( at, end - at ); cut = cut || line.size() > cap; truncateUtf8WithEllipsis( line, cap ); out += line; out += end < lines.size() ? "\n" : ""; at = end + 1; } if( cut ) { lines = std::move( out ); } return cut; } inline std::string rangesAttr( const std::vector& ranges ) { std::string out; for( const LineRange& r : ranges ) { out += out.empty() ? "" : ","; out += std::to_string( r.first ); if( r.second > r.first ) { out += "-" + std::to_string( r.second ); } } return out; } // for the body [first, last] of `s` in `text`: the signature line and one window per site line (l= lists the ranges, of= the body, // next= the whole body) inline std::string excerptElement( const std::string& text, const std::string& path, const Symbol& s, std::uint32_t first, std::uint32_t last, const std::vector& siteLines ) { std::vector esc; const std::vector ranges = excerptRanges( siteLines, first, last ); std::string x = " row (a minified one-line body printed 48.9 KB // whole); cut="1" says a line was cut, next= prints the whole body std::string lines = linesOfRanges( text, ranges ); const bool cut = capLineBytes( lines, kForLiteralLineBytes ); x += cut ? " cut=\"1\"" : ""; x += nextAttrXml( nextFlag( "--expand=", path + ":" + std::to_string( s.line ) + ":" + s.name ) ); x += ">"; return x; } } // namespace lit_detail // THE BLOCK. `served` = the answer's own bytes after (bodies, fold, slot, hops): a body whose head is in it // is not printed again. `useMemo` = the span-tier memo (false under --no-cache). `compactLegend` = the default --legend=compact dialect. inline ForLiteralRender renderForLiteral( const IngestResult& ing, const ForLiteral& lit, std::string_view rootArg, std::string_view served, RedactCounts* redact, bool useMemo, bool compactLegend ) { ForLiteralRender out; if( lit.text.empty() || ing.files.empty() ) { return out; } const GrepCollection collected = grepCollect( ing, lit.text, /*regex=*/false, /*noPrefilter=*/false ); if( collected.raw.empty() ) { return out; // nothing found: no claim (a partial index cannot prove the literal unused) } const std::string rootPrefix = rootArg.empty() ? std::string() : rw::sarif::rootPrefixOf( rootArg ); const auto relPath = [ & ]( std::uint32_t f ) -> std::string { return rootArg.empty() ? std::string( ing.files[f] ) : std::string( rw::sarif::rootRelativeUri( ing.files[f], rootPrefix ) ); }; // ── path tiers, then the span tier of every source hit file (bounded, in the scan's order: classified files first) ───────── std::size_t testHits = 0, docHits = 0, commentHits = 0; std::vector src; std::vector srcFiles; for( const GrepRawHit& r : collected.raw ) { const std::string_view p = rootRelPath( ing, r.fileId ); const PathTier t = pathTierOf( p ); if( t == PathTier::TestOrBench ) { ++testHits; } else if( t == PathTier::Doc || isBareWordCodePath( p ) ) { ++docHits; } else { if( srcFiles.empty() || srcFiles.back() != r.fileId ) { srcFiles.push_back( r.fileId ); } src.push_back( lit_detail::Site{ r, '?' } ); } } std::vector tierPaths; for( std::size_t i = 0; i < srcFiles.size() && i < kForLiteralTierFiles; ++i ) { tierPaths.push_back( diskPath( ing, srcFiles[i] ) ); } const SpanTierBatch batch = spanTiersOfFiles( std::span( tierPaths ), useMemo ); std::uint32_t loadedFile = UINT32_MAX; std::string loadedText; std::vector rows; for( lit_detail::Site& s : src ) { std::size_t idx = SIZE_MAX; for( std::size_t i = 0; i < tierPaths.size(); ++i ) { if( srcFiles[i] == s.raw.fileId ) { idx = i; break; } } if( idx != SIZE_MAX && idx < batch.perFile.size() && batch.perFile[idx].isParsed ) { const SpanTierMap& m = batch.perFile[idx]; const SpanTier t = spanTierAt( m, s.raw.byteOffset ); if( t == SpanTier::Comment ) { ++commentHits; continue; } if( t == SpanTier::String && langFacts( langOfPath( ing.files[s.raw.fileId] ) ).leadingDocstring ) { // a Python docstring (the first statement of a module / class / def body): documentation, counted with the comments const auto it = std::upper_bound( m.startByte.begin(), m.startByte.end(), s.raw.byteOffset ); const std::size_t start = it == m.startByte.begin() ? 0 : m.startByte[std::size_t( it - m.startByte.begin() ) - 1]; if( loadedFile != s.raw.fileId ) { loadedFile = s.raw.fileId; loadedText = docparse::detail::readWholeFile( diskPath( ing, s.raw.fileId ) ).value_or( std::string() ); } if( lit_detail::isPythonDocstringAt( loadedText, start ) ) { ++commentHits; continue; } } s.k = t == SpanTier::String ? 's' : 'c'; } rows.push_back( s ); } // one row per source LINE (a line holding the literal twice is one site), enriched for its owner, then ordered: sites inside a // function or method first, string before code before unclassified, the scan's own order within each class std::vector all; { std::vector lines; for( const lit_detail::Site& r : rows ) { if( lines.empty() || lines.back().raw.fileId != r.raw.fileId || lines.back().raw.line != r.raw.line ) { lines.push_back( r ); } else if( r.k == 's' ) { lines.back().k = 's'; } } rows = std::move( lines ); } out.sites = rows.size(); if( rows.empty() ) { return out; // no source site: nothing to anchor on (test/doc/comment counts alone answer nothing; measured as noise, ld2-django-09) } const std::size_t ranked = std::min( rows.size(), kForLiteralRankedRows ); for( std::size_t i = 0; i < ranked; ++i ) { all.push_back( rows[i].raw ); } std::vector enriched = grepEnrich( ing, std::span( all ) ); ASSUME( enriched.size() == all.size(), "grepEnrich returns one row per windowed hit" ); std::vector order( enriched.size() ); for( std::size_t i = 0; i < order.size(); ++i ) { order[i] = i; } const auto rankOf = [ & ]( std::size_t i ) { const bool callable = enriched[i].enclosingId != kNoNode && ReceiverEvidence::callableKind( ing.symbols[enriched[i].enclosingId].kind ); return ( callable ? 0 : 3 ) + ( rows[i].k == 's' ? 0 : ( rows[i].k == 'c' ? 1 : 2 ) ); }; std::stable_sort( order.begin(), order.end(), [ & ]( std::size_t a, std::size_t b ) { return rankOf( a ) < rankOf( b ); } ); const std::size_t shown = std::min( order.size(), kForLiteralSiteRows ); std::vector hits; std::vector shownRows; for( std::size_t i = 0; i < shown; ++i ) { hits.push_back( std::move( enriched[order[i]] ) ); shownRows.push_back( rows[order[i]] ); } rows = std::move( shownRows ); // ── the owner bodies: distinct callable owners of the shown rows, in row order ─────────────────────────────────────────── std::vector esc; std::vector owners; std::vector ownerAbove; // parallel: already printed by the answer std::string bodiesXml; for( const GrepHit& h : hits ) { if( h.enclosingId == kNoNode || !ReceiverEvidence::callableKind( ing.symbols[h.enclosingId].kind ) || std::find( owners.begin(), owners.end(), h.enclosingId ) != owners.end() ) { continue; } const Symbol& s = ing.symbols[h.enclosingId]; const std::string path = relPath( s.fileId ); std::string head = " l=\"" + std::to_string( s.line ) + "\" p=\""; head += escapeXml( path, esc ); head += "\" n=\""; head += escapeXml( s.name, esc ); head += "\""; const bool above = served.find( head ) != std::string_view::npos; owners.push_back( h.enclosingId ); ownerAbove.push_back( above ? 1 : 0 ); if( above || out.bodies >= kForLiteralBodies ) { continue; } const std::string text = docparse::detail::readWholeFile( diskPath( ing, s.fileId ) ).value_or( std::string() ); if( text.empty() ) { continue; } const std::uint32_t first = layout::lineOf( text, s.sigStartByte ); const std::uint32_t last = layout::lineOf( text, s.endByte > 0 ? s.endByte - 1 : 0 ); if( last >= first && last - first < kForLiteralWholeBodyLines ) // a short body is served whole (nothing to leave out) { std::optional body = renderBodyElement( ing, h.enclosingId, /*capBytes=*/0, /*compress=*/false, redact, rootArg, nullptr ); if( body && body->size() <= kForLiteralBodyCapBytes ) { bodiesXml += *body; ++out.bodies; continue; } } // an excerpt of a longer body: its signature line and one window per shown site this owner holds (never the span between) std::vector siteLines; for( const GrepHit& o : hits ) { if( o.enclosingId == h.enclosingId ) { siteLines.push_back( o.line ); } } bodiesXml += lit_detail::excerptElement( text, path, s, first, last, siteLines ); ++out.bodies; } // ── the rows ────────────────────────────────────────────────────────────────────────────────────────────────────────── std::string rowsXml; std::size_t contextRows = 0; for( std::size_t i = 0; i < hits.size(); ++i ) { const GrepHit& h = hits[i]; rowsXml += " h.line ) { line = lit_detail::linesOfRanges( text, { lit_detail::LineRange{ h.line, b } } ); truncateUtf8WithEllipsis( line, kForLiteralLineBytes * ( kForLiteralContextLines + 1 ) ); rowsXml += " lines=\"" + std::to_string( h.line ) + "-" + std::to_string( b ) + "\""; ++contextRows; } } rowsXml += ">"; } std::string& x = out.xml; x = compactLegend ? std::string( kForCompactLiteralLegend ) : std::string( kForLiteralLegend ); x += ""; // bytes= is the element's own size from its open tag on (the legend comment excluded): a fixed point over its own digits const std::size_t openAt = x.find( "" ).size() + inner.size(); std::size_t bytes = fixed; while( fixed + std::to_string( bytes ).size() != bytes ) // the digits count themselves: converges in <= 2 steps { bytes = fixed + std::to_string( bytes ).size(); } x += " bytes=\"" + std::to_string( bytes ) + "\">"; x += inner; ENSURES( x.size() - openAt == bytes, "bytes= is the element's own size" ); return out; } } // namespace rw