From 7863221ec4af93d9c57644be170aa49ca23c8f7a Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 16 Feb 2022 17:15:46 +0300 Subject: Invent quoting modes for to_stream(name) --- libbuild2/adhoc-rule-buildscript.cxx | 2 +- libbuild2/build/script/parser.cxx | 2 +- libbuild2/config/operation.cxx | 6 ++-- libbuild2/functions-builtin.cxx | 2 +- libbuild2/name.cxx | 55 +++++++++++++++++++++++++++--------- libbuild2/name.hxx | 28 +++++++++++++----- libbuild2/name.test.cxx | 16 ++++++++--- libbuild2/parser.cxx | 2 +- libbuild2/script/parser.cxx | 6 ++-- libbuild2/test/script/parser.cxx | 2 +- 10 files changed, 86 insertions(+), 35 deletions(-) diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx index c64dbfb..7174296 100644 --- a/libbuild2/adhoc-rule-buildscript.cxx +++ b/libbuild2/adhoc-rule-buildscript.cxx @@ -183,7 +183,7 @@ namespace build2 { os << " ["; os << "diag="; - to_stream (os, name (*script.diag_name), true /* quote */, '@'); + to_stream (os, name (*script.diag_name), quote_mode::normal, '@'); os << ']'; } } diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index dd6fa2d..cba4b88 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -1439,7 +1439,7 @@ namespace build2 { diag_record dr (fail (l)); dr << "depdb dyndep: invalid string value "; - to_stream (dr.os, n, true /* quote */); + to_stream (dr.os, n, quote_mode::normal); } } } diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index 8ceb4d4..2fb0423 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -42,7 +42,7 @@ namespace build2 ofs << "# Created automatically by the config module." << endl << "#" << endl << "src_root = "; - to_stream (ofs, name (src_root), true /* quote */, '@'); + to_stream (ofs, name (src_root), quote_mode::normal, '@'); ofs << endl; ofs.close (); @@ -71,7 +71,7 @@ namespace build2 ofs << "# Created automatically by the config module." << endl << "#" << endl << "out_root = "; - to_stream (ofs, name (out_root), true /* quote */, '@'); + to_stream (ofs, name (out_root), quote_mode::normal, '@'); ofs << endl; ofs.close (); @@ -539,7 +539,7 @@ namespace build2 if (!p.first.empty ()) { os << ' '; - to_stream (os, p.first, true /* quote */, '@'); + to_stream (os, p.first, quote_mode::normal, '@'); } os << endl; diff --git a/libbuild2/functions-builtin.cxx b/libbuild2/functions-builtin.cxx index c013c3b..93f2d0f 100644 --- a/libbuild2/functions-builtin.cxx +++ b/libbuild2/functions-builtin.cxx @@ -94,7 +94,7 @@ namespace build2 ostringstream os; to_stream (os, v->as (), - true /* quote */, + quote_mode::normal, '@' /* pair */, escape && convert (move (*escape))); return os.str (); diff --git a/libbuild2/name.cxx b/libbuild2/name.cxx index 1081b5c..a285459 100644 --- a/libbuild2/name.cxx +++ b/libbuild2/name.cxx @@ -80,15 +80,21 @@ namespace build2 } ostream& - to_stream (ostream& os, const name& n, bool quote, char pair, bool escape) + to_stream (ostream& os, const name& n, quote_mode q, char pair, bool escape) { using pattern_type = name::pattern_type; - auto write_string = [&os, quote, pair, escape] ( + auto write_string = [&os, q, pair, escape] ( const string& v, optional pat = nullopt, bool curly = false) { + // We don't expect the effective quoting mode to be specified for the + // name patterns or names that need curly braces in their + // representations. + // + assert (q != quote_mode::effective || (!pat && !curly)); + // Special characters, path pattern characters, and regex pattern // characters. The latter only need to be quoted in the first position // and if followed by a non-alphanumeric delimiter. If that's the only @@ -97,7 +103,7 @@ namespace build2 // escape leading `+` in the curly braces which is also recognized as a // path pattern. // - char sc[] = { + char nsc[] = { '{', '}', '[', ']', '$', '(', ')', // Token endings. ' ', '\t', '\n', '#', // Spaces. '\\', '"', // Escaping and quoting. @@ -114,6 +120,26 @@ namespace build2 return (v[0] == '~' || v[0] == '^') && v[1] != '\0' && !alnum (v[1]); }; + char esc[] = { + '$', '(', // Token endings. + ' ', '\t', '\n', '#', // Spaces. + '"', // Quoting. + pair, // Pair separator, if any. + '\0'}; + + auto ec = [&esc] (const string& v) + { + for (size_t i (0); i < v.size (); ++i) + { + char c (v[i]); + + if (strchr (esc, c) != nullptr || (c == '\\' && v[i + 1] == '\\')) + return true; + } + + return false; + }; + if (pat) { switch (*pat) @@ -124,7 +150,7 @@ namespace build2 } } - if (quote && v.find ('\'') != string::npos) + if (q != quote_mode::none && v.find ('\'') != string::npos) { // Quote the string with the double quotes rather than with the single // one. Escape some of the special characters. @@ -148,8 +174,10 @@ namespace build2 // pattern character but not vice-verse. See the parsing logic for // details. // - else if (quote && (v.find_first_of (sc) != string::npos || - (!pat && v.find_first_of (pc) != string::npos))) + else if ((q == quote_mode::normal && + (v.find_first_of (nsc) != string::npos || + (!pat && v.find_first_of (pc) != string::npos))) || + (q == quote_mode::effective && ec (v))) { if (escape) os << '\\'; os << '\''; @@ -164,8 +192,9 @@ namespace build2 // details). So we escape it both if it's not a pattern or is a path // pattern. // - else if (quote && ((!pat || *pat == pattern_type::path) && - ((v[0] == '+' && curly) || rc (v)))) + else if (q == quote_mode::normal && + (!pat || *pat == pattern_type::path) && + ((v[0] == '+' && curly) || rc (v))) { if (escape) os << '\\'; os << '\\' << v; @@ -176,12 +205,12 @@ namespace build2 uint16_t dv (stream_verb (os).path); // Directory verbosity. - auto write_dir = [&os, quote, &write_string, dv] ( + auto write_dir = [&os, q, &write_string, dv] ( const dir_path& d, optional pat = nullopt, bool curly = false) { - if (quote) + if (q != quote_mode::none) write_string (dv < 1 ? diag_relative (d) : d.representation (), pat, curly); @@ -194,7 +223,7 @@ namespace build2 // If quoted then print empty name as '' rather than {}. // - if (quote && n.empty ()) + if (q != quote_mode::none && n.empty ()) return os << (escape ? "\\'\\'" : "''"); if (n.proj) @@ -255,7 +284,7 @@ namespace build2 ostream& to_stream (ostream& os, const names_view& ns, - bool quote, + quote_mode q, char pair, bool escape) { @@ -263,7 +292,7 @@ namespace build2 { const name& n (*i); ++i; - to_stream (os, n, quote, pair, escape); + to_stream (os, n, q, pair, escape); if (n.pair) os << n.pair; diff --git a/libbuild2/name.hxx b/libbuild2/name.hxx index 1dd5a9f..795cd5c 100644 --- a/libbuild2/name.hxx +++ b/libbuild2/name.hxx @@ -184,8 +184,8 @@ namespace build2 to_name (string); // Serialize the name to the stream. If requested, the name components - // containing special characters are quoted and/or escaped. The special - // characters are: + // containing special characters are quoted and/or escaped. In the normal + // quoting mode the special characters are: // // {}[]$() \t\n#\"'% // @@ -199,8 +199,14 @@ namespace build2 // // As well as leading `+` if in the curly braces. // + // In the effective quoting mode the special characters are: + // + // $( \t\n#"' + // + // As well as `\` if followed by any of the above characters or itself. + // // If the pair argument is not '\0', then it is added to the above special - // characters set. If the quote character is present in the component then + // characters sets. If the quote character is present in the component then // it is double quoted rather than single quoted. In this case the following // characters are escaped: // @@ -213,15 +219,23 @@ namespace build2 // Note that in the quoted mode empty unqualified name is printed as '', // not {}. // + enum class quote_mode + { + none, + normal, + effective + }; + LIBBUILD2_SYMEXPORT ostream& to_stream (ostream&, const name&, - bool quote, + quote_mode, char pair = '\0', bool escape = false); inline ostream& - operator<< (ostream& os, const name& n) {return to_stream (os, n, false);} + operator<< (ostream& os, const name& n) { + return to_stream (os, n, quote_mode::none);} // Vector of names. // @@ -240,13 +254,13 @@ namespace build2 LIBBUILD2_SYMEXPORT ostream& to_stream (ostream&, const names_view&, - bool quote, + quote_mode, char pair = '\0', bool escape = false); inline ostream& operator<< (ostream& os, const names_view& ns) { - return to_stream (os, ns, false);} + return to_stream (os, ns, quote_mode::none);} inline ostream& operator<< (ostream& os, const names& ns) {return os << names_view (ns);} diff --git a/libbuild2/name.test.cxx b/libbuild2/name.test.cxx index 80b830e..c404503 100644 --- a/libbuild2/name.test.cxx +++ b/libbuild2/name.test.cxx @@ -46,7 +46,7 @@ namespace build2 // Test stream representation. // { - auto ts = [] (const name& n, bool quote = true) + auto ts = [] (const name& n, quote_mode quote = quote_mode::normal) { ostringstream os; stream_verb (os, stream_verbosity (0, 1)); @@ -54,8 +54,8 @@ namespace build2 return os.str (); }; - assert (ts (name ()) == "''"); - assert (ts (name (), false) == "{}"); + assert (ts (name ()) == "''"); + assert (ts (name (), quote_mode::none) == "{}"); assert (ts (name ("foo")) == "foo"); @@ -70,10 +70,18 @@ namespace build2 assert (ts (name (dir ("bar/"), "dir", "foo")) == "bar/dir{foo}"); assert (ts (name (dir ("bar/baz/"), "dir", "foo")) == "bar/baz/dir{foo}"); - // Quoting. + // Normal quoting. // assert (ts (name (dir ("bar baz/"), "dir", "foo fox")) == "'bar baz/'dir{'foo fox'}"); + // Effective quoting. + // + assert (ts (name ("bar\\baz"), quote_mode::effective) == "bar\\baz"); + assert (ts (name ("bar[baz]"), quote_mode::effective) == "bar[baz]"); + assert (ts (name ("bar$baz"), quote_mode::effective) == "'bar$baz'"); + assert (ts (name ("bar\\\\baz"), quote_mode::effective) == "'bar\\\\baz'"); + assert (ts (name ("bar\\$baz"), quote_mode::effective) == "'bar\\$baz'"); + // Relative logic. // #ifndef _WIN32 diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index 9fbcd2b..bec3230 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -42,7 +42,7 @@ namespace build2 { o << '='; names storage; - to_stream (o, reverse (a.value, storage), true /* quote */, '@'); + to_stream (o, reverse (a.value, storage), quote_mode::normal, '@'); } return o; diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx index 7722002..82eb9c8 100644 --- a/libbuild2/script/parser.cxx +++ b/libbuild2/script/parser.cxx @@ -1115,7 +1115,7 @@ namespace build2 { diag_record dr (fail (l)); dr << "invalid string value "; - to_stream (dr.os, n, true /* quote */); + to_stream (dr.os, n, quote_mode::normal); } // If it is a quoted chunk, then we add the word as is. @@ -1351,7 +1351,7 @@ namespace build2 { diag_record dr (fail (l)); dr << "invalid string value "; - to_stream (dr.os, n, true /* quote */); + to_stream (dr.os, n, quote_mode::normal); } } @@ -1537,7 +1537,7 @@ namespace build2 diag_record dr; dr << fail (l) << "expected exit status instead of "; - to_stream (dr.os, ns, true /* quote */); + to_stream (dr.os, ns, quote_mode::normal); dr << info << "exit status is an unsigned integer less than 256"; } diff --git a/libbuild2/test/script/parser.cxx b/libbuild2/test/script/parser.cxx index 9e92f3b..bf04a30 100644 --- a/libbuild2/test/script/parser.cxx +++ b/libbuild2/test/script/parser.cxx @@ -1057,7 +1057,7 @@ namespace build2 diag_record dr (fail (dl)); dr << "invalid testscript include path "; - to_stream (dr.os, n, true); // Quote. + to_stream (dr.os, n, quote_mode::normal); } } -- cgit v1.1