Follow-up Comment #6, bug #67408 (group groff): I think I have it cracked.
It was about as big a pain in the ass as I thought it would be, and indeed demanded intrusive changes to the formatter's code, because in AT&T troff, the set of valid delimiters was contextual. I still need to flesh out some diagnostic messages and write a change log entry. commit 20962902250f484c5ebcf7e3c082c1d7505bd7ba Author: G. Branden Robinson <[email protected]> Date: Fri Oct 31 22:27:29 2025 -0500 Revert "[troff]: Allow more delimiters in compat mode." This reverts commit fb6fdbd9d174262ab57572bf095100459b589a66. The test script "src/roff/groff/tests/check-delimiter-validity.sh" fails at this commit. This change begins resolving Savannah #67408. diff --git a/ChangeLog b/ChangeLog index 4322caf41..9e1a63b42 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9034,21 +9034,6 @@ * tmac/tests/doc_vertical-margins-are-correct.sh: Work around macOS wc(1)'s right-alignment of its integer output field. -2024-11-25 G. Branden Robinson <[email protected]> - - * src/roff/troff/input.cpp (token::is_usable_as_delimiter): If - in compatibility mode, accept any ordinary character as a - delimiter. - - * doc/groff.texi.in (Delimiters, Compatibility Mode): - * man/groff_diff.7.man (Compatibility mode): Document it. - - * src/roff/groff/tests/\ - allow-wacky-delimiters-in-compatibility-mode.sh: Test it. - * src/roff/groff/groff.am (groff_TESTS): Run test. - - * NEWS: Add item. - 2024-11-25 G. Branden Robinson <[email protected]> * src/roff/troff/input.cpp (is_char_usable_as_delimiter): diff --git a/NEWS b/NEWS index b533efb1d..cfb2c8694 100644 --- a/NEWS +++ b/NEWS @@ -261,11 +261,6 @@ troff * The `-c` command-line option now also removes the `color` request's ability to enable multi-color output. -* In compatibility mode, GNU troff now accepts delimiters that it - rejects when not in compatibility mode--namely, ordinary characters - that can validly begin numeric expressions (which are often - delimited). This change improves compatibility with AT&T troff. - eqn --- diff --git a/doc/groff.texi.in b/doc/groff.texi.in index 9a3be9e6d..1641b44ac 100644 --- a/doc/groff.texi.in +++ b/doc/groff.texi.in @@ -7817,14 +7817,10 @@ @node Delimiters @ifinfo @cindex <colon>, as delimiter @end ifinfo -@c @cindex @code{|}, as delimiter +@cindex @code{|}, as delimiter @cindex @code{(}, as delimiter @cindex @code{)}, as delimiter -the (single-character) operators @samp{+-/*%<>=&:()}@footnote{GNU -@command{troff} accepts @samp{|} as a delimiter in spite of its -meaningfulness in numeric expressions because it occasionally sees use -in man pages. Future @code{groff} releases may deprecate and -subsequently withdraw such support.} +the (single-character) operators @samp{+-/*%<>=&:()|} @item @cindex space character, as delimiter @@ -19680,17 +19676,6 @@ @node Compatibility Mode choose a register name that is unlikely to collide with other uses. @endDefreq -@cindex additional delimiters accepted by @acronym{AT&T} @code{troff} -@cindex delimiters, additional, accepted by @acronym{AT&T} @code{troff} -In compatibility mode, -GNU -@command{troff} @c GNU -accepts several characters as delimiters that it ordinarily rejects -because they can begin numeric expressions and therefore -may be ambiguous to the document maintainer. -This set of additional delimiters comprises -@samp{0123456789+-(.|}. - @cindex input level @cindex level, input @cindex interpolation depth diff --git a/man/groff_diff.7.man b/man/groff_diff.7.man index a1058a5b4..8953f22d3 100644 --- a/man/groff_diff.7.man +++ b/man/groff_diff.7.man @@ -6169,18 +6169,6 @@ .SH "Compatibility mode" while it interprets its argument list. . . -.P -In compatibility mode, -GNU -.I troff \" GNU -accepts several characters as delimiters that it ordinarily rejects, -because they can begin numeric expressions and therefore -may be ambiguous to the document maintainer. -. -The set of additional delimiters comprises -.RB \[lq] 0123456789+\-(.| \[rq]. -. -. .\" ==================================================================== .SH "Other differences" .\" ==================================================================== diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp index 49c7a5217..08f3f645d 100644 --- a/src/roff/troff/input.cpp +++ b/src/roff/troff/input.cpp @@ -2625,9 +2625,17 @@ bool token::operator!=(const token &t) // doesn't tokenize it) and accepts a user-specified delimiter. static bool is_char_usable_as_delimiter(int c) { - if (csdigit(c)) - return false; switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': case '+': case '-': // case '/': @@ -2654,10 +2662,6 @@ bool token::is_usable_as_delimiter(bool report_error) bool is_valid = false; switch (type) { case TOKEN_CHAR: - // AT&T troff accepted any character as a delimiter, even perverse - // choices in cases like `\l91n+2n\&*9`. See Savannah #66481. - if (want_att_compat) - return true; is_valid = is_char_usable_as_delimiter(c); if (!is_valid && report_error) error("character '%1' is not allowed as a delimiter", commit 4477aea29d69a33f4e28a36307655c067291909f Author: G. Branden Robinson <[email protected]> Date: Fri Oct 31 22:38:11 2025 -0500 [groff]: Expand delimiter tests in compat mode. * src/roff/groff/tests/check-delimiter-validity.sh: Expand test coverage of delimiter usage in compatibility mode to account for the three different contexts in which delimiters can occur (and consequently the three different subsets of Unicode Basic Latin that are valid as delimiters in AT&T troff). Test still fails at this commit. groff 1.22.3, 1.22.4, and 1.23.0 fail this test too. Continues fixing Savannah #67408. diff --git a/ChangeLog b/ChangeLog index 9e1a63b42..ff1ebd086 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2025-10-31 G. Branden Robinson <[email protected]> + + * src/roff/groff/tests/check-delimiter-validity.sh: Expand test + coverage of delimiter usage in compatibility mode to account for + the three different contexts in which delimiters can occur (and + consequently the three different subsets of Unicode Basic Latin + that are valid as delimiters in AT&T troff). + 2025-10-30 G. Branden Robinson <[email protected]> * src/devices/grotty/tests/h-option-works.sh: Add unit test for diff --git a/src/roff/groff/tests/check-delimiter-validity.sh b/src/roff/groff/tests/check-delimiter-validity.sh index ad4aefbe1..1275944c7 100755 --- a/src/roff/groff/tests/check-delimiter-validity.sh +++ b/src/roff/groff/tests/check-delimiter-validity.sh @@ -48,14 +48,41 @@ do echo "$output" | grep -qx 1n+2n_. || wail done -# All of these work in DWB nroff. +# Now test the context-dependent sets of delimiters of AT&T troff. + for c in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \ a b c d e f g h i j k l m n o p q r s t u v w x y z \ 0 1 2 3 4 5 6 7 8 9 + - / '*' % '<' '>' = '&' : '(' ')' . '|' do - echo "checking validity of '$c' as delimiter in compatibility mode" \ - >&2 - output=$(printf '\\l%c1n+2n\\&_%c\n' "$c" "$c" \ + echo "checking validity of '$c' as string expression delimiter" \ + "in compatibility mode" >&2 + output=$(printf '\\o%c__%c__\n' "$c" "$c" \ + | "$groff" -C -T ascii -P -cbou | sed '/^$/d') + echo "$output" + echo "$output" | grep -Fqx ___ || wail +done + +for c in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \ + a b c d e f g h i j k l m n o p q r s t u v w x y z \ + 0 1 2 3 4 5 6 7 8 9 . '|' +do + echo "checking validity of '$c' as numeric expression delimiter" \ + "in compatibility mode" >&2 + output=$(printf '_\\h%c1n+2n%c_\n' "$c" "$c" \ + | "$groff" -C -T ascii | sed '/^$/d') + echo "$output" + echo "$output" | grep -Fqx '_ _' || wail +done + +# 'v' as a conditional expression operator is a vtroff extension, not a +# GNU one. vtroff is also unobtainium in the 21st century. DWB 3.3, +# Plan 9, and Solaris 10 troffs don't treat 'v' specially. +for c in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \ + a b c d f g h i j k l m p q r s u v w x y z +do + echo "checking validity of '$c' as output comparison delimiter" \ + "in compatibility mode" >&2 + output=$(printf '.if %c@@@%c@@@%c ___\n' "$c" "$c" "$c" \ | "$groff" -C -T ascii | sed '/^$/d') echo "$output" echo "$output" | grep -Fqx ___ || wail commit feb8dce9cf9b6bf21174b8cf811e5d037ef945f9 (HEAD -> master) Author: G. Branden Robinson <[email protected]> Date: Sat Nov 1 05:19:11 2025 -0500 XXX [troff]: Fix Savannah #67408. diff --git a/src/roff/troff/env.cpp b/src/roff/troff/env.cpp index 1f0533229..e65e2ba29 100644 --- a/src/roff/troff/env.cpp +++ b/src/roff/troff/env.cpp @@ -1717,7 +1717,7 @@ void number_lines() curenv->numbering_nodes = nd; curenv->line_number_digit_width = env_digit_width(curenv); int n; - if (!tok.is_usable_as_delimiter()) { + if (!tok.is_usable_as_delimiter()) { // XXX abuse of function if (get_integer(&n, next_line_number)) { next_line_number = n; if (next_line_number < 0) { @@ -1730,7 +1730,7 @@ void number_lines() while (!tok.is_space() && !tok.is_newline() && !tok.is_eof()) tok.next(); if (has_arg()) { - if (!tok.is_usable_as_delimiter()) { + if (!tok.is_usable_as_delimiter()) { // XXX abuse of function if (get_integer(&n)) { if (n <= 0) { warning(WARN_RANGE, "output line number multiple cannot" @@ -1744,14 +1744,14 @@ void number_lines() while (!tok.is_space() && !tok.is_newline() && !tok.is_eof()) tok.next(); if (has_arg()) { - if (!tok.is_usable_as_delimiter()) { + if (!tok.is_usable_as_delimiter()) { // XXX abuse of function if (get_integer(&n)) curenv->number_text_separation = n; } else while (!tok.is_space() && !tok.is_newline() && !tok.is_eof()) tok.next(); - if (has_arg() && !tok.is_usable_as_delimiter() + if (has_arg() && !tok.is_usable_as_delimiter() // XXX abuse of function && get_integer(&n)) curenv->line_number_indent = n; } diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp index 08f3f645d..132e64150 100644 --- a/src/roff/troff/input.cpp +++ b/src/roff/troff/input.cpp @@ -1625,10 +1625,14 @@ node *do_overstrike() // \o int start_level = input_stack::get_level(); token start_token; start_token.next(); - if (!start_token.is_usable_as_delimiter()) + if (!want_att_compat && !start_token.is_usable_as_delimiter()) warning(WARN_DELIM, "interpreting %1 as an escape sequence" " delimiter; it is ambiguous because it can also begin a" " numeric expression", start_token.description()); + else if (want_att_compat + && !start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_STRING_EXPRESSION)) + warning(WARN_DELIM, "your \\o diagnostic here: %1", start_token.description()); // TODO: groff 1.24.0 release + 2 years? #if 0 if (!start_token.is_usable_as_delimiter(true /* report error */)) { @@ -1677,10 +1681,14 @@ static node *do_bracket() // \b int start_level = input_stack::get_level(); token start_token; start_token.next(); - if (!start_token.is_usable_as_delimiter()) + if (!want_att_compat && !start_token.is_usable_as_delimiter()) warning(WARN_DELIM, "interpreting %1 as an escape sequence" " delimiter; it is ambiguous because it can also begin a" " numeric expression", start_token.description()); + else if (want_att_compat + && !start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_STRING_EXPRESSION)) + warning(WARN_DELIM, "your \\b diagnostic here: %1", start_token.description()); // TODO: groff 1.24.0 release + 2 years? #if 0 if (!start_token.is_usable_as_delimiter(true /* report error */)) { @@ -1719,10 +1727,14 @@ static const char *do_name_test() // \A int start_level = input_stack::get_level(); token start_token; start_token.next(); - if (!start_token.is_usable_as_delimiter()) + if (!want_att_compat && !start_token.is_usable_as_delimiter()) warning(WARN_DELIM, "interpreting %1 as an escape sequence" " delimiter; it is ambiguous because it can also begin a" " numeric expression", start_token.description()); + else if (want_att_compat + && !start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_STRING_EXPRESSION)) + warning(WARN_DELIM, "your \\A diagnostic here: %1", start_token.description()); // TODO: groff 1.24.0 release + 2 years? #if 0 if (!start_token.is_usable_as_delimiter(true /* report error */)) @@ -1758,8 +1770,13 @@ static const char *do_expr_test() // \B token start_token; start_token.next(); int start_level = input_stack::get_level(); - if (!start_token.is_usable_as_delimiter(true /* report error */)) + if (!want_att_compat + && !start_token.is_usable_as_delimiter(true /* report error */)) return 0 /* nullptr */; + else if (want_att_compat + && !start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_NUMERIC_EXPRESSION)) + warning(WARN_DELIM, "your \\B diagnostic here: %1", start_token.description()); tok.next(); // disable all warning and error messages temporarily unsigned int saved_warning_mask = warning_mask; @@ -1836,10 +1853,14 @@ static node *do_zero_width_output() // \Z int start_level = input_stack::get_level(); token start_token; start_token.next(); - if (!start_token.is_usable_as_delimiter()) + if (!want_att_compat && !start_token.is_usable_as_delimiter()) warning(WARN_DELIM, "interpreting %1 as an escape sequence" " delimiter; it is ambiguous because it can also begin a" " numeric expression", start_token.description()); + else if (want_att_compat + && !start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_STRING_EXPRESSION)) + warning(WARN_DELIM, "your \\Z diagnostic here: %1", start_token.description()); // TODO: groff 1.24.0 release + 2 years? #if 0 if (!start_token.is_usable_as_delimiter(true /* report error */)) { @@ -2657,12 +2678,38 @@ static bool is_char_usable_as_delimiter(int c) } // Is the token a valid delimiter (like `'`)? -bool token::is_usable_as_delimiter(bool report_error) +bool token::is_usable_as_delimiter(bool report_error, + enum delimiter_context context) { bool is_valid = false; switch (type) { case TOKEN_CHAR: is_valid = is_char_usable_as_delimiter(c); + if (want_att_compat) { + assert(context != DELIMITER_GROFF); + switch (context) { + case DELIMITER_ATT_STRING_EXPRESSION: + if (csgraph(c)) + is_valid = true; + break; + case DELIMITER_ATT_NUMERIC_EXPRESSION: + if (csalnum(c) || ('.' == c) || ('|' == c)) + is_valid = true; + break; + case DELIMITER_ATT_OUTPUT_COMPARISON_EXPRESSION: + if (csupper(c) + || (cslower(c) + && (c != 'e') + && (c != 'n') + && (c != 'o') + && (c != 't'))) + is_valid = true; + break; + default: + assert(0 == "unhandled case of `context` (enum dcontext)"); + break; + } + } if (!is_valid && report_error) error("character '%1' is not allowed as a delimiter", static_cast<char>(c)); @@ -5644,20 +5691,29 @@ static bool read_delimited_number(units *n, { token start_token; start_token.next(); - if (start_token.is_usable_as_delimiter(true /* report error */)) { - tok.next(); - if (read_measurement(n, si, prev_value)) { - if (start_token != tok) { - // token::description() writes to static, class-wide storage, so - // we must allocate a copy of it before issuing the next - // diagnostic. - char *delimdesc = strdup(start_token.description()); - warning(WARN_DELIM, "closing delimiter does not match;" - " expected %1, got %2", delimdesc, tok.description()); - free(delimdesc); - } - return true; + bool is_valid = false; + if (!want_att_compat && start_token.is_usable_as_delimiter()) + is_valid = true; + else if (want_att_compat + && start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_NUMERIC_EXPRESSION)) + is_valid = true; + if (!is_valid) { + error("your read_delimited_number diagnostic 'A' here: %1", start_token.description()); + return false; + } + tok.next(); + if (read_measurement(n, si, prev_value)) { + if (start_token != tok) { + // token::description() writes to static, class-wide storage, so + // we must allocate a copy of it before issuing the next + // diagnostic. + char *delimdesc = strdup(start_token.description()); + warning(WARN_DELIM, "closing delimiter does not match;" + " expected %1, got %2", delimdesc, tok.description()); + free(delimdesc); } + return true; } return false; } @@ -5666,20 +5722,29 @@ static bool read_delimited_number(units *n, unsigned char si) { token start_token; start_token.next(); - if (start_token.is_usable_as_delimiter(true /* report error */)) { - tok.next(); - if (read_measurement(n, si)) { - if (start_token != tok) { - // token::description() writes to static, class-wide storage, so - // we must allocate a copy of it before issuing the next - // diagnostic. - char *delimdesc = strdup(start_token.description()); - warning(WARN_DELIM, "closing delimiter does not match;" - " expected %1, got %2", delimdesc, tok.description()); - free(delimdesc); - } - return true; + bool is_valid = false; + if (!want_att_compat && start_token.is_usable_as_delimiter()) + is_valid = true; + else if (want_att_compat + && start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_NUMERIC_EXPRESSION)) + is_valid = true; + if (!is_valid) { + error("your read_delimited_number diagnostic 'B' here: %1", start_token.description()); + return false; + } + tok.next(); + if (read_measurement(n, si)) { + if (start_token != tok) { + // token::description() writes to static, class-wide storage, so + // we must allocate a copy of it before issuing the next + // diagnostic. + char *delimdesc = strdup(start_token.description()); + warning(WARN_DELIM, "closing delimiter does not match;" + " expected %1, got %2", delimdesc, tok.description()); + free(delimdesc); } + return true; } return false; } @@ -5689,7 +5754,12 @@ static bool get_line_arg(units *n, unsigned char si, charinfo **cp) token start_token; start_token.next(); int start_level = input_stack::get_level(); - if (!start_token.is_usable_as_delimiter(true /* report error */)) + if (!want_att_compat + && !start_token.is_usable_as_delimiter(true /* report error */)) + return false; + else if (want_att_compat + && start_token.is_usable_as_delimiter(true /* report error */, + DELIMITER_ATT_NUMERIC_EXPRESSION)) return false; tok.next(); if (read_measurement(n, si)) { @@ -5779,10 +5849,14 @@ static bool read_size(int *x) } val *= sizescale; } - else if (!tok.is_usable_as_delimiter()) + else if (!want_att_compat && !tok.is_usable_as_delimiter()) warning(WARN_DELIM, "interpreting %1 as an escape sequence" " delimiter; it is ambiguous because it can also begin a" " numeric expression", tok.description()); + else if (want_att_compat + && !tok.is_usable_as_delimiter(false, + DELIMITER_ATT_NUMERIC_EXPRESSION)) + warning(WARN_DELIM, "your \\s diagnostic here: %1", tok.description()); // TODO: groff 1.24.0 release + 2 years? #if 0 else if (!tok.is_usable_as_delimiter(true /* report error */)) @@ -5916,10 +5990,14 @@ static void do_register() // \R { token start_token; start_token.next(); - if (!start_token.is_usable_as_delimiter()) + if (!want_att_compat && !start_token.is_usable_as_delimiter()) warning(WARN_DELIM, "interpreting %1 as an escape sequence" " delimiter; it is ambiguous because it can also begin a" " numeric expression", start_token.description()); + else if (want_att_compat + && !start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_STRING_EXPRESSION)) + warning(WARN_DELIM, "your \\R diagnostic here: %1", start_token.description()); // TODO: groff 1.24.0 release + 2 years? #if 0 if (!start_token.is_usable_as_delimiter(true /* report error */)) { @@ -5956,10 +6034,14 @@ static void do_width() // \w int start_level = input_stack::get_level(); token start_token; start_token.next(); - if (!start_token.is_usable_as_delimiter()) + if (!want_att_compat && !start_token.is_usable_as_delimiter()) warning(WARN_DELIM, "interpreting %1 as an escape sequence" " delimiter; it is ambiguous because it is also meaningful" " in a numeric expression", start_token.description()); + else if (want_att_compat + && !start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_STRING_EXPRESSION)) + warning(WARN_DELIM, "your \\w diagnostic here: %1", start_token.description()); // TODO: groff 1.24.0 release + 2 years? #if 0 if (!start_token.is_usable_as_delimiter(true /* report error */)) @@ -6258,10 +6340,14 @@ static node *do_device_extension() // \X int start_level = input_stack::get_level(); token start_token; start_token.next(); - if (!start_token.is_usable_as_delimiter()) + if (!want_att_compat && !start_token.is_usable_as_delimiter()) warning(WARN_DELIM, "interpreting %1 as an escape sequence" " delimiter; it is ambiguous because it can also begin a" " numeric expression", start_token.description()); + else if (want_att_compat + && !start_token.is_usable_as_delimiter(false, + DELIMITER_ATT_STRING_EXPRESSION)) + warning(WARN_DELIM, "your \\X diagnostic here: %1", start_token.description()); // TODO: groff 1.24.0 release + 2 years? #if 0 if (!start_token.is_usable_as_delimiter(true /* report error */)) @@ -6620,6 +6706,51 @@ static void nop_request() tok.next(); } +static bool compare_output() +{ + token delim = tok; + int delim_level = input_stack::get_level(); + environment env1(curenv); + environment env2(curenv); + environment *oldenv = curenv; + curenv = &env1;+ suppress_push = true; + for (int i = 0; i < 2; i++) { + for (;;) { + tok.next(); + if (tok.is_newline() || tok.is_eof()) { + // token::description() writes to static, class-wide storage, + // so we must allocate a copy of it before issuing the next + // diagnostic. + char *delimdesc = strdup(delim.description()); + warning(WARN_DELIM, "missing closing delimiter in output" + " comparison operator; expected %1, got %2", + delimdesc, tok.description()); + free(delimdesc); + tok.next(); + curenv = oldenv; + return false; + } + if (tok == delim + && (want_att_compat + || input_stack::get_level() == delim_level)) + break; + tok.process(); + } + curenv = &env2; + } + node *n1 = env1.extract_output_line(); + node *n2 = env2.extract_output_line(); + bool result = same_node_list(n1, n2); + delete_node_list(n1); + delete_node_list(n2); + curenv = oldenv; + have_formattable_input = false; + suppress_push = false; + tok.next(); + return result; +} + static std::stack<bool> if_else_stack; static bool is_conditional_expression_true() @@ -6641,9 +6772,11 @@ static bool is_conditional_expression_true() case 'd': case 'm': case 'r': + case 'v': warning(WARN_SYNTAX, "conditional operator '%1' used in compatibility mode", c); + // TODO: "; treating as output comparison delimiter", c); break; default: break; @@ -6656,10 +6789,6 @@ static bool is_conditional_expression_true() tok.next(); result = in_nroff_mode; } - else if (c == 'v') { - tok.next(); - result = false; - } else if (c == 'o') { result = (topdiv->get_page_number() & 1); tok.next(); @@ -6668,14 +6797,16 @@ static bool is_conditional_expression_true() result = !(topdiv->get_page_number() & 1); tok.next(); } - else if (c == 'd' || c == 'r') { + // TODO: else if (!want_att_compat) { + // Check for GNU troff extension conditional operators. + else if ((c == 'd') || (c == 'r')) { tok.next(); symbol nm = get_name(true /* required */); if (nm.is_null()) { skip_branch(); return false; } - result = (c == 'd' + result = ((c == 'd') ? request_dictionary.lookup(nm) != 0 /* nullptr */ : register_dictionary.lookup(nm) != 0 /* nullptr */); } @@ -6686,7 +6817,7 @@ static bool is_conditional_expression_true() skip_branch(); return false; } - result = (nm == default_symbol + result = ((nm == default_symbol) || color_dictionary.lookup(nm) != 0 /* nullptr */); } else if (c == 'c') { @@ -6718,52 +6849,22 @@ static bool is_conditional_expression_true() } result = is_abstract_style(nm); } - else if (tok.is_space()) - result = false; - else if (tok.is_usable_as_delimiter()) { - // Perform (formatted) output comparison. - token delim = tok; - int delim_level = input_stack::get_level(); - environment env1(curenv); - environment env2(curenv); - environment *oldenv = curenv; - curenv = &env1; - suppress_push = true; - for (int i = 0; i < 2; i++) { - for (;;) { - tok.next(); - if (tok.is_newline() || tok.is_eof()) { - // token::description() writes to static, class-wide storage, - // so we must allocate a copy of it before issuing the next - // diagnostic. - char *delimdesc = strdup(delim.description()); - warning(WARN_DELIM, "missing closing delimiter in output" - " comparison operator; expected %1, got %2", - delimdesc, tok.description()); - free(delimdesc); - tok.next(); - curenv = oldenv; - return false; - } - if (tok == delim - && (want_att_compat - || input_stack::get_level() == delim_level)) - break; - tok.process(); - } - curenv = &env2; - } - node *n1 = env1.extract_output_line(); - node *n2 = env2.extract_output_line(); - result = same_node_list(n1, n2); - delete_node_list(n1); - delete_node_list(n2); - curenv = oldenv; - have_formattable_input = false; - suppress_push = false; + // vtroff extension + else if (c == 'v') { tok.next(); + result = false; } + else if (tok.is_space()) + result = false; + else if (!want_att_compat + && tok.is_usable_as_delimiter()) + result = compare_output(); + else if (want_att_compat + && tok.is_usable_as_delimiter(false /* report error */, + DELIMITER_ATT_OUTPUT_COMPARISON_EXPRESSION)) + result = compare_output(); else { + // Evaluate numeric expression. units n; if (!read_measurement(&n, 'u')) { skip_branch(); diff --git a/src/roff/troff/token.h b/src/roff/troff/token.h index 290d1387f..4b0163e98 100644 --- a/src/roff/troff/token.h +++ b/src/roff/troff/token.h @@ -26,6 +26,13 @@ class charinfo; struct node; class vunits; +enum delimiter_context { + DELIMITER_GROFF, + DELIMITER_ATT_STRING_EXPRESSION, + DELIMITER_ATT_NUMERIC_EXPRESSION, + DELIMITER_ATT_OUTPUT_COMPARISON_EXPRESSION, +}; + class token { symbol nm; node *nd; @@ -86,7 +93,8 @@ public: bool is_tab(); bool is_leader(); bool is_backspace(); - bool is_usable_as_delimiter(bool /* report_error */ = false); + bool is_usable_as_delimiter(bool /* report_error */ = false, + enum delimiter_context /* context */ = DELIMITER_GROFF); bool is_dummy(); bool is_transparent_dummy(); bool is_transparent(); _______________________________________________________ Reply to this item at: <https://savannah.gnu.org/bugs/?67408> _______________________________________________ Message sent via Savannah https://savannah.gnu.org/
signature.asc
Description: PGP signature
