@@ -186,6 +186,183 @@ auto starts_with_identifier_colon(std::string const& line) -> bool
186186}
187187
188188
189+ // ---------------------------------------------------------------------------
190+ // braces_tracker: to track brace depth
191+ //
192+ // Normally we don't emit diagnostics for Cpp1 code, but we do for a
193+ // brace mismatch since we're relying on balanced { } to find Cpp2 code
194+ //
195+ class braces_tracker
196+ {
197+ // to track preprocessor #if brace depth and brace counts
198+ //
199+ class pre_if_depth_info
200+ {
201+ int if_net_braces = 0 ;
202+ bool found_else = false ;
203+ int else_net_braces = 0 ;
204+
205+ public:
206+ auto found_open_brace () -> void {
207+ if (!found_else) { ++if_net_braces; }
208+ else { ++else_net_braces; }
209+ }
210+
211+ auto found_close_brace () -> void {
212+ if (!found_else) { --if_net_braces; }
213+ else { --else_net_braces; }
214+ }
215+
216+ auto found_preprocessor_else () -> void {
217+ assert (!found_else);
218+ found_else = true ;
219+ }
220+
221+ // If the "if" and "else" branches opened/closed the same net number
222+ // of unbalanced braces, they were double-counted in the brace
223+ // matching and to try to keep going we can apply this adjustment
224+ auto braces_to_ignore () -> int {
225+ if (if_net_braces >= 0 && if_net_braces == else_net_braces) {
226+ return if_net_braces;
227+ }
228+ else {
229+ return 0 ;
230+ }
231+ }
232+ };
233+ std::vector<pre_if_depth_info> preprocessor = { {} }; // sentinel
234+ std::vector<lineno_t > open_braces;
235+ std::vector<error>& errors;
236+
237+ public:
238+ braces_tracker ( std::vector<error>& errors ) : errors{errors} { }
239+
240+ // --- Brace matching functions - { and }
241+
242+ auto found_open_brace (lineno_t lineno) -> void {
243+ assert (std::ssize (preprocessor) > 0 );
244+ open_braces.push_back (lineno);
245+ preprocessor.back ().found_open_brace ();
246+ }
247+
248+ auto found_close_brace (source_position pos) -> void {
249+ assert (std::ssize (preprocessor) > 0 );
250+
251+ if (std::ssize (open_braces) < 1 ) {
252+ errors.emplace_back (
253+ pos,
254+ " closing } does not match a prior {"
255+ );
256+ }
257+ else {
258+ open_braces.pop_back ();
259+ }
260+
261+ preprocessor.back ().found_close_brace ();
262+ }
263+
264+ auto found_eof (source_position pos) const -> void {
265+ // Emit diagnostic if braces didn't match
266+ //
267+ if (current_depth () != 0 ) {
268+ std::string unmatched_brace_lines;
269+ for (auto i = 0 ; i < std::ssize (open_braces); ++i) {
270+ if (i > 0 && std::size (open_braces)>2 ) { unmatched_brace_lines += " ," ; };
271+ if (i > 0 && i == std::ssize (open_braces)-1 ) { unmatched_brace_lines += " and" ; };
272+ unmatched_brace_lines += " " + std::to_string (open_braces[i]);
273+ }
274+ errors.emplace_back (
275+ pos,
276+ std::string (" end of file reached with " )
277+ + std::to_string (current_depth ())
278+ + " missing } to match earlier { on line"
279+ + (current_depth () > 1 ? " s" : " " )
280+ + unmatched_brace_lines
281+ );
282+ }
283+ }
284+
285+ auto current_depth () const -> int {
286+ return std::ssize (open_braces);
287+ }
288+
289+ // --- Preprocessor matching functions - #if/#else/#endif
290+
291+ // Entering an #if
292+ auto found_pre_if () -> void {
293+ assert (std::ssize (preprocessor) > 0 );
294+ preprocessor.push_back ({});
295+ }
296+
297+ // Encountered an #else
298+ auto found_pre_else () -> void {
299+ assert (std::ssize (preprocessor) > 1 );
300+ preprocessor.back ().found_preprocessor_else ();
301+ }
302+
303+ // Exiting an #endif
304+ auto found_pre_endif () -> void {
305+ assert (std::ssize (preprocessor) > 1 );
306+
307+ // If the #if/#else/#endif introduced the same net number of braces,
308+ // then we will have recorded that number too many open braces, and
309+ // braces_to_ignore() will be the positive number of those net open braces
310+ // that this loop will now throw away
311+ for (auto i = 0 ; i < preprocessor.back ().braces_to_ignore (); ++i) {
312+ found_close_brace ( source_position{} );
313+ }
314+
315+ preprocessor.pop_back ();
316+ }
317+ };
318+
319+
320+ // ---------------------------------------------------------------------------
321+ // starts_with_whitespace_slash_slash: is this a "// comment" line
322+ //
323+ // line current line being processed
324+ //
325+ enum class preprocessor_conditional {
326+ none = 0 , pre_if, pre_else, pre_endif
327+ };
328+ auto starts_with_preprocessor_if_else_endif (
329+ std::string const & line
330+ )
331+ -> preprocessor_conditional
332+ {
333+ auto i = 0 ;
334+
335+ // find first non-whitespace character
336+ if (!move_next (line, i, isspace)) {
337+ return preprocessor_conditional::none;
338+ }
339+
340+ // if it's not #, this isn't an #if/#else/#endif
341+ if (line[i] != ' #' ) {
342+ return preprocessor_conditional::none;
343+ }
344+
345+ // find next non-whitespace character
346+ ++i;
347+ if (!move_next (line, i, isspace)) {
348+ return preprocessor_conditional::none;
349+ }
350+
351+ if (line.substr (i).starts_with (" if" )) {
352+ return preprocessor_conditional::pre_if;
353+ }
354+ else if (line.substr (i).starts_with (" else" )) {
355+ return preprocessor_conditional::pre_else;
356+ }
357+ else if (line.substr (i).starts_with (" endif" )) {
358+ return preprocessor_conditional::pre_endif;
359+ }
360+ else {
361+ return preprocessor_conditional::none;
362+ }
363+ }
364+
365+
189366// ---------------------------------------------------------------------------
190367// process_cpp_line: just enough to know what to skip over
191368//
@@ -204,19 +381,20 @@ auto process_cpp_line(
204381 bool & in_string_literal,
205382 bool & in_raw_string_literal,
206383 std::string& raw_string_closing_seq,
207- std::vector< int > & brace_depth ,
384+ braces_tracker & braces ,
208385 lineno_t lineno,
209386 std::vector<error>& errors
210387)
211388 -> process_line_ret
212389{
213- if (!in_comment && !in_string_literal && !in_raw_string_literal && starts_with_whitespace_slash_slash (line)) {
214- return { true , false , false };
215- }
216-
217- if (!in_comment && !in_string_literal && !in_raw_string_literal && starts_with_whitespace_slash_star_and_no_star_slash (line)) {
218- in_comment = true ;
219- return { true , false , false };
390+ if (!in_comment && !in_string_literal && !in_raw_string_literal) {
391+ if (starts_with_whitespace_slash_slash (line)) {
392+ return { true , false , false };
393+ }
394+ else if (starts_with_whitespace_slash_star_and_no_star_slash (line)) {
395+ in_comment = true ;
396+ return { true , false , false };
397+ }
220398 }
221399
222400 struct process_line_ret r { in_comment, true , in_raw_string_literal};
@@ -273,22 +451,12 @@ auto process_cpp_line(
273451
274452 break ;case ' {' :
275453 if (!in_literal ()) {
276- brace_depth. push_back (lineno);
454+ braces. found_open_brace (lineno);
277455 }
278456
279457 break ;case ' }' :
280458 if (!in_literal ()) {
281- if (std::ssize (brace_depth) < 1 ) {
282- // Might as well give a diagnostic in Cpp1 code since
283- // we're relying on balanced { } to find Cpp2 code
284- errors.emplace_back (
285- source_position (lineno, i),
286- " closing } does not match a prior {"
287- );
288- }
289- else {
290- brace_depth.pop_back ();
291- }
459+ braces.found_close_brace (source_position (lineno, i));
292460 }
293461
294462 break ;case ' *' :
@@ -323,7 +491,7 @@ auto process_cpp_line(
323491auto process_cpp2_line (
324492 std::string const & line,
325493 bool & in_comment,
326- std::vector< int > & brace_depth ,
494+ braces_tracker & braces ,
327495 lineno_t lineno,
328496 std::vector<error>& errors
329497)
@@ -344,24 +512,16 @@ auto process_cpp2_line(
344512 else {
345513 switch (line[i]) {
346514 break ;case ' {' :
347- brace_depth. push_back (lineno);
515+ braces. found_open_brace (lineno);
348516
349517 break ;case ' }' :
350- if (std::ssize (brace_depth) < 1 ) {
351- errors.emplace_back (
352- source_position (lineno, i),
353- " closing } does not match a prior {"
354- );
355- }
356- else {
357- brace_depth.pop_back ();
358- if (std::ssize (brace_depth) < 1 ) {
359- found_end = true ;
360- }
518+ braces.found_close_brace ( source_position (lineno, i) );
519+ if (braces.current_depth () < 1 ) {
520+ found_end = true ;
361521 }
362522
363523 break ;case ' ;' :
364- if (std::ssize (brace_depth) == 0 ) { found_end = true ; }
524+ if (braces. current_depth () < 1 ) { found_end = true ; }
365525
366526 break ;case ' *' :
367527 if (prev == ' /' ) {
@@ -463,7 +623,23 @@ class source
463623 auto in_raw_string_literal = false ;
464624 std::string raw_string_closing_seq;
465625
466- auto brace_depth = std::vector<int >();
626+ auto braces = braces_tracker (errors);
627+
628+ auto add_preprocessor_line = [&] {
629+ lines.push_back ({ &buf[0 ], source_line::category::preprocessor });
630+ if (auto pre = starts_with_preprocessor_if_else_endif (lines.back ().text );
631+ pre != preprocessor_conditional::none
632+ ) {
633+ switch (pre ) {
634+ break ;case preprocessor_conditional::pre_if:
635+ braces.found_pre_if ();
636+ break ;case preprocessor_conditional::pre_else:
637+ braces.found_pre_else ();
638+ break ;case preprocessor_conditional::pre_endif:
639+ braces.found_pre_endif ();
640+ }
641+ }
642+ };
467643
468644 while (in.getline (&buf[0 ], max_line_len)) {
469645
@@ -472,10 +648,12 @@ class source
472648 if (auto pre = is_preprocessor (buf, true ); pre .is_preprocessor )
473649 {
474650 cpp1_found = true ;
475- lines.push_back ({ &buf[0 ], source_line::category::preprocessor });
651+ // lines.push_back({ &buf[0], source_line::category::preprocessor });
652+ add_preprocessor_line ();
476653 while (pre .has_continuation && in.getline (&buf[0 ], max_line_len))
477654 {
478- lines.push_back ({ &buf[0 ], source_line::category::preprocessor });
655+ // lines.push_back({ &buf[0], source_line::category::preprocessor });
656+ add_preprocessor_line ();
479657 pre = is_preprocessor (buf, false );
480658 }
481659 }
@@ -487,7 +665,9 @@ class source
487665 // Switch to cpp2 mode if we're not in a comment, not inside nested { },
488666 // and the line starts with "nonwhitespace :" but not "::"
489667 //
490- if (!in_comment && !in_raw_string_literal && std::ssize (brace_depth) == 0 && starts_with_identifier_colon (lines.back ().text ))
668+ if (!in_comment && !in_raw_string_literal &&
669+ braces.current_depth () < 1 &&
670+ starts_with_identifier_colon (lines.back ().text ))
491671 {
492672 cpp2_found= true ;
493673
@@ -508,7 +688,7 @@ class source
508688 !process_cpp2_line (
509689 lines.back ().text ,
510690 in_comment,
511- brace_depth ,
691+ braces ,
512692 std::ssize (lines)-1 ,
513693 errors
514694 )
@@ -533,7 +713,7 @@ class source
533713 in_string_literal,
534714 in_raw_string_literal,
535715 raw_string_closing_seq,
536- brace_depth ,
716+ braces ,
537717 std::ssize (lines) - 1 ,
538718 errors
539719 );
@@ -578,22 +758,7 @@ class source
578758 return false ;
579759 }
580760
581- // Emit diagnostic if braces didn't match
582- //
583- if (std::ssize (brace_depth) != 0 ) {
584- std::string unmatched_brace_lines;
585- for (auto line : brace_depth) {
586- unmatched_brace_lines = std::to_string (line) + " " + unmatched_brace_lines;
587- }
588- errors.emplace_back (
589- source_position (lineno_t (std::ssize (lines)), 0 ),
590- std::string (" end of file reached with " )
591- + std::to_string (std::ssize (brace_depth))
592- + " missing } to match earlier { on line"
593- + (std::size (brace_depth)>1 ? " s " : " " )
594- + unmatched_brace_lines
595- );
596- }
761+ braces.found_eof ( source_position (lineno_t (std::ssize (lines)), 0 ) );
597762
598763 return true ;
599764 }
0 commit comments