Skip to content

Commit bef05a9

Browse files
committed
Account for #if/#else branches that introduce/remove the same net brace count, closes #146
And do better encapsulation of the brace tracking in a `braces_tracker` class
1 parent 41673ef commit bef05a9

File tree

1 file changed

+221
-56
lines changed

1 file changed

+221
-56
lines changed

source/load.h

Lines changed: 221 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -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(
323491
auto 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

Comments
 (0)