1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2024 Google LLC. All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7
8 // Author: kenton@google.com (Kenton Varda)
9 // Based on original Protocol Buffers design by
10 // Sanjay Ghemawat, Jeff Dean, and others.
11
12 #include "google/protobuf/io/printer.h"
13
14 #include <stdlib.h>
15
16 #include <cstddef>
17 #include <functional>
18 #include <string>
19 #include <utility>
20 #include <vector>
21
22 #include "absl/container/flat_hash_map.h"
23 #include "absl/log/absl_check.h"
24 #include "absl/log/absl_log.h"
25 #include "absl/strings/ascii.h"
26 #include "absl/strings/escaping.h"
27 #include "absl/strings/match.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/str_format.h"
30 #include "absl/strings/str_split.h"
31 #include "absl/strings/string_view.h"
32 #include "absl/strings/strip.h"
33 #include "absl/types/optional.h"
34 #include "absl/types/span.h"
35 #include "absl/types/variant.h"
36
37 namespace google {
38 namespace protobuf {
39 namespace io {
40 namespace {
41 template <typename T>
LookupInFrameStack(absl::string_view var,absl::Span<std::function<absl::optional<T> (absl::string_view)>> frames)42 absl::optional<T> LookupInFrameStack(
43 absl::string_view var,
44 absl::Span<std::function<absl::optional<T>(absl::string_view)>> frames) {
45 for (size_t i = frames.size(); i >= 1; --i) {
46 auto val = frames[i - 1](var);
47 if (val.has_value()) {
48 return val;
49 }
50 }
51 return absl::nullopt;
52 }
53 } // namespace
54
55 struct Printer::Format {
56 struct Chunk {
57 // The chunk's text; if this is a variable, it does not include the $...$.
58 absl::string_view text;
59
60 // Whether or not this is a variable name, i.e., a $...$.
61 bool is_var;
62 };
63
64 struct Line {
65 // Chunks to emit, split along $ and annotates as to whether it is a
66 // variable name.
67 std::vector<Chunk> chunks;
68
69 // The indentation for this chunk.
70 size_t indent;
71 };
72
73 std::vector<Line> lines;
74
75 // Whether this is a multiline raw string, according to internal heuristics.
76 bool is_raw_string = false;
77 };
78
TokenizeFormat(absl::string_view format_string,const PrintOptions & options)79 Printer::Format Printer::TokenizeFormat(absl::string_view format_string,
80 const PrintOptions& options) {
81 Format format;
82 size_t raw_string_indent = 0;
83 if (options.strip_raw_string_indentation) {
84 // We are processing a call that looks like
85 //
86 // p->Emit(R"cc(
87 // class Foo {
88 // int x, y, z;
89 // };
90 // )cc");
91 //
92 // or
93 //
94 // p->Emit(R"cc(
95 //
96 // class Foo {
97 // int x, y, z;
98 // };
99 // )cc");
100 //
101 // To compute the indent, we need:
102 // 1. Iterate over each line.
103 // 2. Find the first line that contains non-whitespace characters.
104 // 3. Count the number of leading spaces on that line.
105 //
106 // The following pairs of loops assume the current line is the first line
107 // with non-whitespace characters; if we consume all the spaces and
108 // then immediately hit a newline, this means this wasn't the right line and
109 // we should start over.
110 //
111 // Note that the very first character *must* be a newline; that is how we
112 // detect that this is a multi-line raw string template, and as such this is
113 // a while loop, not a do/while loop.
114
115 absl::string_view orig = format_string;
116 while (absl::ConsumePrefix(&format_string, "\n")) {
117 raw_string_indent = 0;
118 format.is_raw_string = true;
119 while (absl::ConsumePrefix(&format_string, " ")) {
120 ++raw_string_indent;
121 }
122 }
123
124 // If we consume the entire string, this probably wasn't a raw string and
125 // was probably something like a couple of explicit newlines.
126 if (format_string.empty()) {
127 format_string = orig;
128 format.is_raw_string = false;
129 raw_string_indent = 0;
130 }
131
132 // This means we have a preprocessor directive and we should not have eaten
133 // the newline.
134 if (!at_start_of_line_ && absl::StartsWith(format_string, "#")) {
135 format_string = orig;
136 }
137 }
138
139 // We now split the remaining format string into lines and discard:
140 // 1. A trailing Printer-discarded comment, if this is a raw string.
141 //
142 // 2. All leading spaces to compute that line's indent.
143 // We do not do this for the first line, so that Emit(" ") works
144 // correctly. We do this *regardless* of whether we are processing
145 // a raw string, because existing non-raw-string calls to cpp::Formatter
146 // rely on this. There is a test that validates this behavior.
147 //
148 // 3. Set the indent for that line to max(0, line_indent -
149 // raw_string_indent), if this is not a raw string.
150 //
151 // 4. Trailing empty lines, if we know this is a raw string, except for
152 // a single extra newline at the end.
153 //
154 // Each line is itself split into chunks along the variable delimiters, e.g.
155 // $...$.
156 bool is_first = true;
157 for (absl::string_view line_text : absl::StrSplit(format_string, '\n')) {
158 if (format.is_raw_string) {
159 size_t comment_index = line_text.find(options_.ignored_comment_start);
160 if (comment_index != absl::string_view::npos) {
161 line_text = line_text.substr(0, comment_index);
162 if (absl::StripLeadingAsciiWhitespace(line_text).empty()) {
163 continue;
164 }
165 }
166 }
167
168 size_t line_indent = 0;
169 while (!is_first && absl::ConsumePrefix(&line_text, " ")) {
170 ++line_indent;
171 }
172 is_first = false;
173
174 format.lines.emplace_back();
175 auto& line = format.lines.back();
176 line.indent =
177 line_indent > raw_string_indent ? line_indent - raw_string_indent : 0;
178
179 bool is_var = false;
180 size_t total_len = 0;
181 for (absl::string_view chunk :
182 absl::StrSplit(line_text, options_.variable_delimiter)) {
183 // The special _start and _end variables should actually glom the next
184 // chunk into themselves, so as to be of the form _start$foo and _end$foo.
185 if (!line.chunks.empty() && !is_var) {
186 auto& prev = line.chunks.back();
187 if (prev.text == "_start" || prev.text == "_end") {
188 // The +1 below is to account for the $ in between them.
189 // This string is safe, because prev.text and chunk are contiguous
190 // by construction.
191 prev.text = absl::string_view(prev.text.data(),
192 prev.text.size() + 1 + chunk.size());
193
194 // Account for the foo$ part of $_start$foo$.
195 total_len += chunk.size() + 1;
196 continue;
197 }
198 }
199
200 if (is_var || !chunk.empty()) {
201 line.chunks.push_back(Format::Chunk{chunk, is_var});
202 }
203
204 total_len += chunk.size();
205 if (is_var) {
206 // This accounts for the $s around a variable.
207 total_len += 2;
208 }
209
210 is_var = !is_var;
211 }
212
213 // To ensure there are no unclosed $...$, we check that the computed length
214 // above equals the actual length of the string. If it's off, that means
215 // that there are missing or extra $ characters.
216 Validate(total_len == line_text.size(), options, [&line] {
217 if (line.chunks.empty()) {
218 return std::string("wrong number of variable delimiters");
219 }
220
221 return absl::StrFormat("unclosed variable name: `%s`",
222 absl::CHexEscape(line.chunks.back().text));
223 });
224
225 // Trim any empty, non-variable chunks.
226 while (!line.chunks.empty()) {
227 auto& last = line.chunks.back();
228 if (last.is_var || !last.text.empty()) {
229 break;
230 }
231
232 line.chunks.pop_back();
233 }
234 }
235
236 // Discard any trailing newlines (i.e., lines which contain no chunks.)
237 if (format.is_raw_string) {
238 while (!format.lines.empty() && format.lines.back().chunks.empty()) {
239 format.lines.pop_back();
240 }
241 }
242
243 #if 0 // Use this to aid debugging tokenization.
244 LOG(INFO) << "--- " << format.lines.size() << " lines";
245 for (size_t i = 0; i < format.lines.size(); ++i) {
246 const auto& line = format.lines[i];
247
248 auto log_line = absl::StrFormat("[\" \" x %d]", line.indent);
249 for (const auto& chunk : line.chunks) {
250 absl::StrAppendFormat(&log_line, " %s\"%s\"", chunk.is_var ? "$" : "",
251 absl::CHexEscape(chunk.text));
252 }
253 LOG(INFO) << log_line;
254 }
255 LOG(INFO) << "---";
256 #endif
257
258 return format;
259 }
260
261 constexpr absl::string_view Printer::kProtocCodegenTrace;
262
Printer(ZeroCopyOutputStream * output)263 Printer::Printer(ZeroCopyOutputStream* output) : Printer(output, Options{}) {}
264
Printer(ZeroCopyOutputStream * output,Options options)265 Printer::Printer(ZeroCopyOutputStream* output, Options options)
266 : sink_(output), options_(options) {
267 if (!options_.enable_codegen_trace.has_value()) {
268 // Trace-by-default is threaded through via an env var, rather than a
269 // global, so that child processes can pick it up as well. The flag
270 // --enable_codegen_trace setenv()'s this in protoc's startup code.
271 static const bool kEnableCodegenTrace =
272 ::getenv(kProtocCodegenTrace.data()) != nullptr;
273 options_.enable_codegen_trace = kEnableCodegenTrace;
274 }
275 }
276
Printer(ZeroCopyOutputStream * output,char variable_delimiter,AnnotationCollector * annotation_collector)277 Printer::Printer(ZeroCopyOutputStream* output, char variable_delimiter,
278 AnnotationCollector* annotation_collector)
279 : Printer(output, Options{variable_delimiter, annotation_collector}) {}
280
LookupVar(absl::string_view var)281 absl::string_view Printer::LookupVar(absl::string_view var) {
282 auto result = LookupInFrameStack(var, absl::MakeSpan(var_lookups_));
283 ABSL_CHECK(result.has_value()) << "could not find " << var;
284
285 auto* view = result->AsString();
286 ABSL_CHECK(view != nullptr)
287 << "could not find " << var << "; found callback instead";
288
289 return *view;
290 }
291
Validate(bool cond,Printer::PrintOptions opts,absl::FunctionRef<std::string ()> message)292 bool Printer::Validate(bool cond, Printer::PrintOptions opts,
293 absl::FunctionRef<std::string()> message) {
294 if (!cond) {
295 if (opts.checks_are_debug_only) {
296 ABSL_DLOG(FATAL) << message();
297 } else {
298 ABSL_LOG(FATAL) << message();
299 }
300 }
301 return cond;
302 }
303
Validate(bool cond,Printer::PrintOptions opts,absl::string_view message)304 bool Printer::Validate(bool cond, Printer::PrintOptions opts,
305 absl::string_view message) {
306 return Validate(cond, opts, [=] { return std::string(message); });
307 }
308
309 // This function is outlined to isolate the use of
310 // ABSL_CHECK into the .cc file.
Outdent()311 void Printer::Outdent() {
312 PrintOptions opts;
313 opts.checks_are_debug_only = true;
314 if (!Validate(indent_ >= options_.spaces_per_indent, opts,
315 "Outdent() without matching Indent()")) {
316 return;
317 }
318 indent_ -= options_.spaces_per_indent;
319 }
320
Emit(absl::Span<const Sub> vars,absl::string_view format,SourceLocation loc)321 void Printer::Emit(absl::Span<const Sub> vars, absl::string_view format,
322 SourceLocation loc) {
323 PrintOptions opts;
324 opts.strip_raw_string_indentation = true;
325 opts.loc = loc;
326
327 auto defs = WithDefs(vars, /*allow_callbacks=*/true);
328
329 PrintImpl(format, {}, opts);
330 }
331
GetSubstitutionRange(absl::string_view varname,PrintOptions opts)332 absl::optional<std::pair<size_t, size_t>> Printer::GetSubstitutionRange(
333 absl::string_view varname, PrintOptions opts) {
334 auto it = substitutions_.find(varname);
335 if (!Validate(it != substitutions_.end(), opts, [varname] {
336 return absl::StrCat("undefined variable in annotation: ", varname);
337 })) {
338 return absl::nullopt;
339 }
340
341 std::pair<size_t, size_t> range = it->second;
342 if (!Validate(range.first <= range.second, opts, [range, varname] {
343 return absl::StrFormat(
344 "variable used for annotation used multiple times: %s (%d..%d)",
345 varname, range.first, range.second);
346 })) {
347 return absl::nullopt;
348 }
349
350 return range;
351 }
352
Annotate(absl::string_view begin_varname,absl::string_view end_varname,absl::string_view file_path,const std::vector<int> & path,absl::optional<AnnotationCollector::Semantic> semantic)353 void Printer::Annotate(absl::string_view begin_varname,
354 absl::string_view end_varname,
355 absl::string_view file_path,
356 const std::vector<int>& path,
357 absl::optional<AnnotationCollector::Semantic> semantic) {
358 if (options_.annotation_collector == nullptr) {
359 return;
360 }
361
362 PrintOptions opts;
363 opts.checks_are_debug_only = true;
364 auto begin = GetSubstitutionRange(begin_varname, opts);
365 auto end = GetSubstitutionRange(end_varname, opts);
366 if (!begin.has_value() || !end.has_value()) {
367 return;
368 }
369 if (begin->first > end->second) {
370 ABSL_DLOG(FATAL) << "annotation has negative length from " << begin_varname
371 << " to " << end_varname;
372 return;
373 }
374 options_.annotation_collector->AddAnnotation(
375 begin->first, end->second, std::string(file_path), path, semantic);
376 }
377
WriteRaw(const char * data,size_t size)378 void Printer::WriteRaw(const char* data, size_t size) {
379 if (failed_ || size == 0) {
380 return;
381 }
382
383 if (at_start_of_line_ && data[0] != '\n') {
384 IndentIfAtStart();
385 if (failed_) {
386 return;
387 }
388
389 // Fix up empty variables (e.g., "{") that should be annotated as
390 // coming after the indent.
391 for (const std::string& var : line_start_variables_) {
392 auto& pair = substitutions_[var];
393 pair.first += indent_;
394 pair.second += indent_;
395 }
396 }
397
398 // If we're going to write any data, clear line_start_variables_, since
399 // we've either updated them in the block above or they no longer refer to
400 // the current line.
401 line_start_variables_.clear();
402
403 if (paren_depth_to_omit_.empty()) {
404 sink_.Append(data, size);
405 } else {
406 for (size_t i = 0; i < size; ++i) {
407 char c = data[i];
408 switch (c) {
409 case '(':
410 paren_depth_++;
411 if (!paren_depth_to_omit_.empty() &&
412 paren_depth_to_omit_.back() == paren_depth_) {
413 break;
414 }
415
416 sink_.Append(&c, 1);
417 break;
418 case ')':
419 if (!paren_depth_to_omit_.empty() &&
420 paren_depth_to_omit_.back() == paren_depth_) {
421 paren_depth_to_omit_.pop_back();
422 paren_depth_--;
423 break;
424 }
425
426 paren_depth_--;
427 sink_.Append(&c, 1);
428 break;
429 default:
430 sink_.Append(&c, 1);
431 break;
432 }
433 }
434 }
435 failed_ |= sink_.failed();
436 }
437
IndentIfAtStart()438 void Printer::IndentIfAtStart() {
439 if (!at_start_of_line_) {
440 return;
441 }
442
443 for (size_t i = 0; i < indent_; ++i) {
444 sink_.Write(" ");
445 }
446 at_start_of_line_ = false;
447 }
448
PrintCodegenTrace(absl::optional<SourceLocation> loc)449 void Printer::PrintCodegenTrace(absl::optional<SourceLocation> loc) {
450 if (!options_.enable_codegen_trace.value_or(false) || !loc.has_value()) {
451 return;
452 }
453
454 if (!at_start_of_line_) {
455 at_start_of_line_ = true;
456 line_start_variables_.clear();
457 sink_.Write("\n");
458 }
459
460 PrintRaw(absl::StrFormat("%s @%s:%d\n", options_.comment_start,
461 loc->file_name(), loc->line()));
462 at_start_of_line_ = true;
463 }
464
ValidateIndexLookupInBounds(size_t index,size_t current_arg_index,size_t args_len,PrintOptions opts)465 bool Printer::ValidateIndexLookupInBounds(size_t index,
466 size_t current_arg_index,
467 size_t args_len, PrintOptions opts) {
468 if (!Validate(index < args_len, opts, [this, index] {
469 return absl::StrFormat("annotation %c{%d%c is out of bounds",
470 options_.variable_delimiter, index + 1,
471 options_.variable_delimiter);
472 })) {
473 return false;
474 }
475 if (!Validate(
476 index <= current_arg_index, opts, [this, index, current_arg_index] {
477 return absl::StrFormat(
478 "annotation arg must be in correct order as given; expected "
479 "%c{%d%c but got %c{%d%c",
480 options_.variable_delimiter, current_arg_index + 1,
481 options_.variable_delimiter, options_.variable_delimiter,
482 index + 1, options_.variable_delimiter);
483 })) {
484 return false;
485 }
486 return true;
487 }
488
PrintImpl(absl::string_view format,absl::Span<const std::string> args,PrintOptions opts)489 void Printer::PrintImpl(absl::string_view format,
490 absl::Span<const std::string> args, PrintOptions opts) {
491 // Inside of this function, we set indentation as we print new lines from
492 // the format string. No matter how we exit this function, we should fix up
493 // the indent to what it was before we entered; a cleanup makes it easy to
494 // avoid this mistake.
495 size_t original_indent = indent_;
496 auto unindent =
497 absl::MakeCleanup([this, original_indent] { indent_ = original_indent; });
498
499 absl::string_view original = format;
500
501 line_start_variables_.clear();
502
503 if (opts.use_substitution_map) {
504 substitutions_.clear();
505 }
506
507 auto fmt = TokenizeFormat(format, opts);
508 PrintCodegenTrace(opts.loc);
509
510 size_t arg_index = 0;
511 bool skip_next_newline = false;
512 std::vector<AnnotationCollector::Annotation> annot_stack;
513 std::vector<std::pair<absl::string_view, size_t>> annot_records;
514 for (size_t line_idx = 0; line_idx < fmt.lines.size(); ++line_idx) {
515 const auto& line = fmt.lines[line_idx];
516
517 // We only print a newline for lines that follow the first; a loop iteration
518 // can also hint that we should not emit another newline through the
519 // `skip_next_newline` variable.
520 //
521 // We also assume that double newlines are undesirable, so we
522 // do not emit a newline if we are at the beginning of a line, *unless* the
523 // previous format line is actually empty. This behavior is specific to
524 // raw strings.
525 if (line_idx > 0) {
526 bool prev_was_empty = fmt.lines[line_idx - 1].chunks.empty();
527 bool should_skip_newline =
528 skip_next_newline ||
529 (fmt.is_raw_string && (at_start_of_line_ && !prev_was_empty));
530 if (!should_skip_newline) {
531 line_start_variables_.clear();
532 sink_.Write("\n");
533 at_start_of_line_ = true;
534 }
535 }
536 skip_next_newline = false;
537
538 indent_ = original_indent + line.indent;
539
540 for (size_t chunk_idx = 0; chunk_idx < line.chunks.size(); ++chunk_idx) {
541 auto chunk = line.chunks[chunk_idx];
542
543 if (!chunk.is_var) {
544 PrintRaw(chunk.text);
545 continue;
546 }
547
548 if (chunk.text.empty()) {
549 // `$$` is an escape for just `$`.
550 WriteRaw(&options_.variable_delimiter, 1);
551 continue;
552 }
553
554 // If we get this far, we can conclude the chunk is a substitution
555 // variable; we rename the `chunk` variable to make this clear below.
556 absl::string_view var = chunk.text;
557 if (substitution_listener_ != nullptr) {
558 substitution_listener_(var, opts.loc.value_or(SourceLocation()));
559 }
560 if (opts.use_curly_brace_substitutions &&
561 absl::ConsumePrefix(&var, "{")) {
562 if (!Validate(var.size() == 1u, opts,
563 "expected single-digit variable")) {
564 continue;
565 }
566
567 if (!Validate(absl::ascii_isdigit(var[0]), opts,
568 "expected digit after {")) {
569 continue;
570 }
571
572 size_t idx = var[0] - '1';
573 if (!ValidateIndexLookupInBounds(idx, arg_index, args.size(), opts)) {
574 continue;
575 }
576
577 if (idx == arg_index) {
578 ++arg_index;
579 }
580
581 IndentIfAtStart();
582 annot_stack.push_back({{sink_.bytes_written(), 0}, args[idx]});
583 continue;
584 }
585
586 if (opts.use_curly_brace_substitutions &&
587 absl::ConsumePrefix(&var, "}")) {
588 // The rest of var is actually ignored, and this is apparently
589 // public API now. Oops?
590 if (!Validate(!annot_stack.empty(), opts,
591 "unexpected end of annotation")) {
592 continue;
593 }
594
595 annot_stack.back().first.second = sink_.bytes_written();
596 if (options_.annotation_collector != nullptr) {
597 options_.annotation_collector->AddAnnotationNew(annot_stack.back());
598 }
599 annot_stack.pop_back();
600 continue;
601 }
602
603 absl::string_view prefix, suffix;
604 if (opts.strip_spaces_around_vars) {
605 var = absl::StripLeadingAsciiWhitespace(var);
606 prefix = chunk.text.substr(0, chunk.text.size() - var.size());
607 var = absl::StripTrailingAsciiWhitespace(var);
608 suffix = chunk.text.substr(prefix.size() + var.size());
609 }
610
611 if (!Validate(!var.empty(), opts, "unexpected empty variable")) {
612 continue;
613 }
614
615 bool is_start = absl::ConsumePrefix(&var, "_start$");
616 bool is_end = absl::ConsumePrefix(&var, "_end$");
617 if (opts.use_annotation_frames && (is_start || is_end)) {
618 if (is_start) {
619 IndentIfAtStart();
620 annot_records.push_back({var, sink_.bytes_written()});
621
622 // Skip all whitespace immediately after a _start.
623 ++chunk_idx;
624 if (chunk_idx < line.chunks.size()) {
625 absl::string_view text = line.chunks[chunk_idx].text;
626 while (absl::ConsumePrefix(&text, " ")) {
627 }
628 PrintRaw(text);
629 }
630 } else {
631 // If a line consisted *only* of an _end, this will likely result in
632 // a blank line if we do not zap the newline after it, so we do that
633 // here.
634 if (line.chunks.size() == 1) {
635 skip_next_newline = true;
636 }
637
638 auto record_var = annot_records.back();
639 annot_records.pop_back();
640
641 if (!Validate(record_var.first == var, opts, [record_var, var] {
642 return absl::StrFormat(
643 "_start and _end variables must match, but got %s and %s, "
644 "respectively",
645 record_var.first, var);
646 })) {
647 continue;
648 }
649
650 absl::optional<AnnotationRecord> record =
651 LookupInFrameStack(var, absl::MakeSpan(annotation_lookups_));
652
653 if (!Validate(record.has_value(), opts, [var] {
654 return absl::StrCat("undefined annotation variable: \"",
655 absl::CHexEscape(var), "\"");
656 })) {
657 continue;
658 }
659
660 if (options_.annotation_collector != nullptr) {
661 options_.annotation_collector->AddAnnotation(
662 record_var.second, sink_.bytes_written(), record->file_path,
663 record->path, record->semantic);
664 }
665 }
666
667 continue;
668 }
669
670 absl::optional<ValueView> sub;
671 absl::optional<AnnotationRecord> same_name_record;
672 if (opts.allow_digit_substitutions && absl::ascii_isdigit(var[0])) {
673 if (!Validate(var.size() == 1u, opts,
674 "expected single-digit variable")) {
675 continue;
676 }
677
678 size_t idx = var[0] - '1';
679 if (!ValidateIndexLookupInBounds(idx, arg_index, args.size(), opts)) {
680 continue;
681 }
682 if (idx == arg_index) {
683 ++arg_index;
684 }
685 sub = args[idx];
686 } else {
687 sub = LookupInFrameStack(var, absl::MakeSpan(var_lookups_));
688
689 if (opts.use_annotation_frames) {
690 same_name_record =
691 LookupInFrameStack(var, absl::MakeSpan(annotation_lookups_));
692 }
693 }
694
695 // By returning here in case of empty we also skip possible spaces inside
696 // the $...$, i.e. "void$ dllexpor$ f();" -> "void f();" in the empty
697 // case.
698 if (!Validate(sub.has_value(), opts, [var] {
699 return absl::StrCat("undefined variable: \"", absl::CHexEscape(var),
700 "\"");
701 })) {
702 continue;
703 }
704
705 size_t range_start = sink_.bytes_written();
706 size_t range_end = sink_.bytes_written();
707
708 if (const absl::string_view* str = sub->AsString()) {
709 if (at_start_of_line_ && str->empty()) {
710 line_start_variables_.emplace_back(var);
711 }
712
713 if (!str->empty()) {
714 // If `sub` is empty, we do not print the spaces around it.
715 PrintRaw(prefix);
716 PrintRaw(*str);
717 range_end = sink_.bytes_written();
718 range_start = range_end - str->size();
719 PrintRaw(suffix);
720 }
721 } else {
722 const ValueView::Callback* fnc = sub->AsCallback();
723 ABSL_CHECK(fnc != nullptr);
724
725 Validate(
726 prefix.empty() && suffix.empty(), opts,
727 "substitution that resolves to callback cannot contain whitespace");
728
729 range_start = sink_.bytes_written();
730 ABSL_CHECK((*fnc)())
731 << "recursive call encountered while evaluating \"" << var << "\"";
732 range_end = sink_.bytes_written();
733 }
734
735 if (range_start == range_end && sub->consume_parens_if_empty) {
736 paren_depth_to_omit_.push_back(paren_depth_ + 1);
737 }
738
739 // If we just evaluated a value which specifies end-of-line consume-after
740 // characters, and we're at the start of a line, that means we finished
741 // with a newline.
742 //
743 // We trim a single end-of-line `consume_after` character in this case.
744 //
745 // This helps callback formatting "work as expected" with respect to forms
746 // like
747 //
748 // class Foo {
749 // $methods$;
750 // };
751 //
752 // Without this post-processing, it would turn into
753 //
754 // class Foo {
755 // void Bar() {};
756 // };
757 //
758 // in many cases. Without the `;`, clang-format may format the template
759 // incorrectly.
760 auto next_idx = chunk_idx + 1;
761 if (!sub->consume_after.empty() && next_idx < line.chunks.size() &&
762 !line.chunks[next_idx].is_var) {
763 chunk_idx = next_idx;
764
765 absl::string_view text = line.chunks[chunk_idx].text;
766 for (char c : sub->consume_after) {
767 if (absl::ConsumePrefix(&text, absl::string_view(&c, 1))) {
768 break;
769 }
770 }
771
772 PrintRaw(text);
773 }
774
775 if (same_name_record.has_value() &&
776 options_.annotation_collector != nullptr) {
777 options_.annotation_collector->AddAnnotation(
778 range_start, range_end, same_name_record->file_path,
779 same_name_record->path, same_name_record->semantic);
780 }
781
782 if (opts.use_substitution_map) {
783 auto insertion =
784 substitutions_.emplace(var, std::make_pair(range_start, range_end));
785
786 if (!insertion.second) {
787 // This variable was used multiple times.
788 // Make its span have negative length so
789 // we can detect it if it gets used in an
790 // annotation.
791 insertion.first->second = {1, 0};
792 }
793 }
794 }
795 }
796
797 Validate(arg_index == args.size(), opts,
798 [original] { return absl::StrCat("unused args: ", original); });
799 Validate(annot_stack.empty(), opts, [this, original] {
800 return absl::StrFormat(
801 "annotation range was not closed; expected %c}%c: %s",
802 options_.variable_delimiter, options_.variable_delimiter, original);
803 });
804
805 // For multiline raw strings, we always make sure to end on a newline.
806 if (fmt.is_raw_string && !at_start_of_line_) {
807 PrintRaw("\n");
808 at_start_of_line_ = true;
809 }
810 }
811 } // namespace io
812 } // namespace protobuf
813 } // namespace google
814