From b61e9e2ba8e625a598427cc2990806b69d104a18 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 25 Oct 2016 16:47:00 +0300 Subject: Add support of file redirects to testscript parser --- build2/test/script/lexer.cxx | 25 ++++++-- build2/test/script/parser.cxx | 91 ++++++++++++++++++++++++----- build2/test/script/runner.cxx | 13 +++-- build2/test/script/script | 39 +++++++++++-- build2/test/script/script.cxx | 132 ++++++++++++++++++++++++++++++++++++++++-- build2/test/script/token | 5 +- build2/test/script/token.cxx | 49 ++++++++-------- 7 files changed, 296 insertions(+), 58 deletions(-) (limited to 'build2') diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx index 7ced2b9..e5514bd 100644 --- a/build2/test/script/lexer.cxx +++ b/build2/test/script/lexer.cxx @@ -264,10 +264,13 @@ namespace build2 { p = peek (); - if (p == ':') + if (p == ':' || p == '<') { get (); - return make_token (type::in_doc_nn); + + return make_token (p == ':' + ? type::in_doc_nn + : type::in_file); } else return make_token (type::in_doc); @@ -297,10 +300,24 @@ namespace build2 { p = peek (); - if (p == ':') + if (p == ':' || p == '>') { get (); - return make_token (type::out_doc_nn); + + if (p == ':') + return make_token (type::out_doc_nn); + + // File redirect. + // + p = peek (); + + if (p == '&') + { + get (); + return make_token (type::out_file_app); + } + else + return make_token (type::out_file); } else return make_token (type::out_doc); diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx index 424029f..450b5ac 100644 --- a/build2/test/script/parser.cxx +++ b/build2/test/script/parser.cxx @@ -409,13 +409,17 @@ namespace build2 program, in_string, in_document, + in_file, out_string, out_document, + out_file, err_string, - err_document + err_document, + err_file }; pending p (pending::program); - bool nn (false); // True if pending here-{str,doc} is "no-newline". + bool nn (false); // True if pending here-{str,doc} is "no-newline". + bool app (false); // True if to append to pending file. // Ordered sequence of here-document redirects that we can expect to // see after the command line. @@ -432,12 +436,12 @@ namespace build2 // to program arguments by default. // auto add_word = - [&c, &p, &nn, &hd, this] (string&& w, const location& l) + [&c, &p, &nn, &app, &hd, this] (string&& w, const location& l) { - auto add_here_str = [&hd, &nn] (redirect& r, string&& w) + auto add_here_str = [&nn] (redirect& r, string&& w) { if (!nn) w += '\n'; - r.value = move (w); + r.str = move (w); }; auto add_here_end = [&hd, &nn] (redirect& r, string&& w) @@ -445,6 +449,26 @@ namespace build2 hd.push_back (here_doc {&r, move (w), nn}); }; + auto add_file = + [&app, &l, this] (redirect& r, const char* n, string&& w) + { + try + { + r.file.path = path (move (w)); + + if (r.file.path.empty ()) + fail (l) << "empty " << n << " redirect file path"; + + } + catch (const invalid_path& e) + { + fail (l) << "invalid " << n << "redirect file path '" << e.path + << "'"; + } + + r.file.append = app; + }; + switch (p) { case pending::none: c.arguments.push_back (move (w)); break; @@ -471,10 +495,15 @@ namespace build2 case pending::in_string: add_here_str (c.in, move (w)); break; case pending::out_string: add_here_str (c.out, move (w)); break; case pending::err_string: add_here_str (c.err, move (w)); break; + + case pending::in_file: add_file (c.in, "stdin", move (w)); break; + case pending::out_file: add_file (c.out, "stdout", move (w)); break; + case pending::err_file: add_file (c.err, "stderr", move (w)); break; } p = pending::none; nn = false; + app = false; }; // Make sure we don't have any pending positions to fill. @@ -489,10 +518,13 @@ namespace build2 case pending::program: what = "program"; break; case pending::in_string: what = "stdin here-string"; break; case pending::in_document: what = "stdin here-document end"; break; + case pending::in_file: what = "stdin file"; break; case pending::out_string: what = "stdout here-string"; break; case pending::out_document: what = "stdout here-document end"; break; + case pending::out_file: what = "stdout file"; break; case pending::err_string: what = "stderr here-string"; break; case pending::err_document: what = "stderr here-document end"; break; + case pending::err_file: what = "stderr file"; break; } if (what != nullptr) @@ -502,11 +534,11 @@ namespace build2 // Parse the redirect operator. // auto parse_redirect = - [&c, &p, &nn, this] (const token& t, const location& l) + [&c, &p, &nn, &app, this] (const token& t, const location& l) { // Our semantics is the last redirect seen takes effect. // - assert (p == pending::none && !nn); + assert (p == pending::none && !nn && !app); // See if we have the file descriptor. // @@ -546,6 +578,7 @@ namespace build2 case type::in_str_nn: case type::in_doc: case type::in_doc_nn: + case type::in_file: { if ((fd = fd == 3 ? 0 : fd) != 0) fail (l) << "invalid in redirect file descriptor " << fd; @@ -558,6 +591,8 @@ namespace build2 case type::out_str_nn: case type::out_doc: case type::out_doc_nn: + case type::out_file: + case type::out_file_app: { if ((fd = fd == 3 ? 1 : fd) == 0) fail (l) << "invalid out redirect file descriptor " << fd; @@ -570,24 +605,28 @@ namespace build2 switch (tt) { case type::in_pass: - case type::out_pass: rt = redirect_type::pass; break; + case type::out_pass: rt = redirect_type::pass; break; case type::in_null: - case type::out_null: rt = redirect_type::null; break; + case type::out_null: rt = redirect_type::null; break; case type::in_str_nn: - case type::out_str_nn: nn = true; // Fall through. + case type::out_str_nn: nn = true; // Fall through. case type::in_str: - case type::out_str: rt = redirect_type::here_string; break; + case type::out_str: rt = redirect_type::here_string; break; case type::in_doc_nn: - case type::out_doc_nn: nn = true; // Fall through. + case type::out_doc_nn: nn = true; // Fall through. case type::in_doc: - case type::out_doc: rt = redirect_type::here_document; break; + case type::out_doc: rt = redirect_type::here_document; break; + + case type::out_file_app: app = true; // Fall through. + case type::in_file: + case type::out_file: rt = redirect_type::file; break; } redirect& r (fd == 0 ? c.in : fd == 1 ? c.out : c.err); - r.type = rt; + r = redirect (rt); switch (rt) { @@ -611,6 +650,14 @@ namespace build2 case 2: p = pending::err_document; break; } break; + case redirect_type::file: + switch (fd) + { + case 0: p = pending::in_file; break; + case 1: p = pending::out_file; break; + case 2: p = pending::err_file; break; + } + break; } }; @@ -650,6 +697,10 @@ namespace build2 case type::in_doc_nn: case type::out_str_nn: case type::out_doc_nn: + + case type::in_file: + case type::out_file: + case type::out_file_app: { if (pre_parse_) { @@ -709,6 +760,10 @@ namespace build2 case type::out_str_nn: case type::out_doc_nn: + case type::in_file: + case type::out_file: + case type::out_file_app: + parse_redirect (t, l); next (t, tt); break; @@ -846,6 +901,10 @@ namespace build2 case type::in_str_nn: case type::out_str_nn: + + case type::in_file: + case type::out_file: + case type::out_file_app: { parse_redirect (t, l); break; @@ -916,8 +975,8 @@ namespace build2 if (!pre_parse_) { redirect& r (*h.redir); - r.value = move (v); - r.here_end = move (h.end); + r.doc.doc = move (v); + r.doc.end = move (h.end); } expire_mode (); diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx index 995da4f..f065389 100644 --- a/build2/test/script/runner.cxx +++ b/build2/test/script/runner.cxx @@ -64,7 +64,11 @@ namespace build2 { ofdstream os (orp); sp.cleanups.emplace_back (orp); - os << rd.value; + + os << (rd.type == redirect_type::here_string + ? rd.str + : rd.doc.doc); + os.close (); } catch (const io_error& e) @@ -298,11 +302,12 @@ namespace build2 so.close (); se.close (); - if (c.in.type == redirect_type::here_string || - c.in.type == redirect_type::here_document) + const redirect& r (c.in); + if (r.type == redirect_type::here_string || + r.type == redirect_type::here_document) { ofdstream os (pr.out_fd); - os << c.in.value; + os << (r.type == redirect_type::here_string ? r.str : r.doc.doc); os.close (); } diff --git a/build2/test/script/script b/build2/test/script/script index 2d74b16..79ada8e 100644 --- a/build2/test/script/script +++ b/build2/test/script/script @@ -43,15 +43,44 @@ namespace build2 none, pass, null, - here_string, // Value is the string. - here_document // Value is the document. + here_string, + here_document, + file }; struct redirect { - redirect_type type = redirect_type::none; - string value; // Note: includes trailing newline, if required. - string here_end; // Only for here-documents. + redirect_type type; + + struct doc_type + { + string doc; // Note: includes trailing newline, if required. + string end; + }; + + struct file_type + { + using path_type = build2::path; + path_type path; + bool append = false; + }; + + union + { + string str; // Note: includes trailing newline, if required. + doc_type doc; + file_type file; + }; + + explicit + redirect (redirect_type = redirect_type::none); + + redirect (redirect&&); + redirect (const redirect&); + redirect& operator= (redirect&&); + redirect& operator= (const redirect&); + + ~redirect (); }; enum class exit_comparison {eq, ne}; diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx index 6f303d5..ac11546 100644 --- a/build2/test/script/script.cxx +++ b/build2/test/script/script.cxx @@ -14,6 +14,8 @@ namespace build2 { namespace script { + // Utility functions + // // Quote if empty or contains spaces or any of the special characters. // // @@ What if it contains quotes, escapes? @@ -37,8 +39,7 @@ namespace build2 size_t n (string::traits_type::length (prefix)); assert (n > 0); - const string& v (r.value); - bool nl (!v.empty () && v.back () == '\n'); + char d (prefix[n - 1]); // Redirect direction. switch (r.type) { @@ -48,6 +49,9 @@ namespace build2 case redirect_type::here_string: { + const string& v (r.str); + bool nl (!v.empty () && v.back () == '\n'); + if (!nl) o << ':'; @@ -56,10 +60,22 @@ namespace build2 } case redirect_type::here_document: { + const string& v (r.doc.doc); + bool nl (!v.empty () && v.back () == '\n'); + // Add another '>' or '<'. Note that here end marker never // needs to be quoted. // - o << prefix[n - 1] << (nl ? "" : ":") << r.here_end; + o << d << (nl ? "" : ":") << r.doc.end; + break; + } + case redirect_type::file: + { + using build2::operator<<; + + // Add '>>' or '<<' (and so make it '<<<' or '>>>'). + // + o << d << d << (r.file.append ? "&" : "") << r.file.path; break; } } @@ -67,9 +83,9 @@ namespace build2 auto print_doc = [&o] (const redirect& r) { - const string& v (r.value); + const string& v (r.doc.doc); bool nl (!v.empty () && v.back () == '\n'); - o << endl << v << (nl ? "" : "\n") << r.here_end; + o << endl << v << (nl ? "" : "\n") << r.doc.end; }; if ((m & command_to_stream::header) == command_to_stream::header) @@ -114,6 +130,108 @@ namespace build2 } } + // redirect + // + redirect:: + redirect (redirect_type t) + : type (t) + { + switch (type) + { + case redirect_type::none: + case redirect_type::pass: + case redirect_type::null: break; + + case redirect_type::here_string: new (&str) string (); break; + case redirect_type::here_document: new (&doc) doc_type (); break; + case redirect_type::file: new (&file) file_type (); break; + } + } + + redirect:: + redirect (redirect&& r) + : type (r.type) + { + switch (type) + { + case redirect_type::none: + case redirect_type::pass: + case redirect_type::null: break; + + case redirect_type::here_string: + { + new (&str) string (move (r.str)); + break; + } + case redirect_type::here_document: + { + new (&doc) doc_type (move (r.doc)); + break; + } + case redirect_type::file: + { + new (&file) file_type (move (r.file)); + break; + } + } + } + + redirect:: + redirect (const redirect& r) + : type (r.type) + { + switch (type) + { + case redirect_type::none: + case redirect_type::pass: + case redirect_type::null: break; + + case redirect_type::here_string: new (&str) string (r.str); break; + case redirect_type::here_document: new (&doc) doc_type (r.doc); break; + case redirect_type::file: + { + new (&file) file_type (r.file); + break; + } + } + } + + redirect:: + ~redirect () + { + switch (type) + { + case redirect_type::none: + case redirect_type::pass: + case redirect_type::null: break; + + case redirect_type::here_string: str.~string (); break; + case redirect_type::here_document: doc.~doc_type (); break; + case redirect_type::file: file.~file_type (); break; + } + } + + redirect& redirect:: + operator= (redirect&& r) + { + if (this != &r) + { + this->~redirect (); + new (this) redirect (move (r)); // Assume noexcept move-constructor. + } + return *this; + } + + redirect& redirect:: + operator= (const redirect& r) + { + if (this != &r) + *this = redirect (r); // Reduce to move-assignment. + return *this; + } + + // scope + // scope:: scope (const string& id, scope* p) : parent (p), @@ -143,6 +261,8 @@ namespace build2 const_cast (wd_path) = dir_path (p->wd_path) /= id; } + // script_base + // script_base:: script_base () : // Enter the test* variables with the same variable types as in @@ -156,6 +276,8 @@ namespace build2 wd_var (var_pool.insert ("~")), id_var (var_pool.insert ("@")) {} + // script + // static inline string script_id (const path& p) { diff --git a/build2/test/script/token b/build2/test/script/token index f9352d2..d22b758 100644 --- a/build2/test/script/token +++ b/build2/test/script/token @@ -40,13 +40,16 @@ namespace build2 in_str_nn, // <: in_doc, // << in_doc_nn, // <<: + in_file, // <<< out_pass, // >+ out_null, // >- out_str, // > out_str_nn, // >: out_doc, // >> - out_doc_nn // >>: + out_doc_nn, // >>: + out_file, // >>> + out_file_app // >>>& }; token_type () = default; diff --git a/build2/test/script/token.cxx b/build2/test/script/token.cxx index 549fe13..376a92e 100644 --- a/build2/test/script/token.cxx +++ b/build2/test/script/token.cxx @@ -21,29 +21,32 @@ namespace build2 switch (t.type) { - case token_type::semi: os << q << ';' << q; break; - - case token_type::plus: os << q << '+' << q; break; - case token_type::minus: os << q << '-' << q; break; - - case token_type::pipe: os << q << '|' << q; break; - case token_type::clean: os << q << '&' << q; break; - case token_type::log_and: os << q << "&&" << q; break; - case token_type::log_or: os << q << "||" << q; break; - - case token_type::in_pass: os << q << "<+" << q; break; - case token_type::in_null: os << q << "<-" << q; break; - case token_type::in_str: os << q << '<' << q; break; - case token_type::in_str_nn: os << q << "<:" << q; break; - case token_type::in_doc: os << q << "<<" << q; break; - case token_type::in_doc_nn: os << q << "<<:" << q; break; - - case token_type::out_pass: os << q << ">+" << q; break; - case token_type::out_null: os << q << ">-" << q; break; - case token_type::out_str: os << q << '>' << q; break; - case token_type::out_str_nn: os << q << ">:" << q; break; - case token_type::out_doc: os << q << ">>" << q; break; - case token_type::out_doc_nn: os << q << ">>:" << q; break; + case token_type::semi: os << q << ';' << q; break; + + case token_type::plus: os << q << '+' << q; break; + case token_type::minus: os << q << '-' << q; break; + + case token_type::pipe: os << q << '|' << q; break; + case token_type::clean: os << q << '&' << q; break; + case token_type::log_and: os << q << "&&" << q; break; + case token_type::log_or: os << q << "||" << q; break; + + case token_type::in_pass: os << q << "<+" << q; break; + case token_type::in_null: os << q << "<-" << q; break; + case token_type::in_str: os << q << '<' << q; break; + case token_type::in_str_nn: os << q << "<:" << q; break; + case token_type::in_doc: os << q << "<<" << q; break; + case token_type::in_doc_nn: os << q << "<<:" << q; break; + case token_type::in_file: os << q << "<<<" << q; break; + + case token_type::out_pass: os << q << ">+" << q; break; + case token_type::out_null: os << q << ">-" << q; break; + case token_type::out_str: os << q << '>' << q; break; + case token_type::out_str_nn: os << q << ">:" << q; break; + case token_type::out_doc: os << q << ">>" << q; break; + case token_type::out_doc_nn: os << q << ">>:" << q; break; + case token_type::out_file: os << q << ">>>" << q; break; + case token_type::out_file_app: os << q << ">>>&" << q; break; default: build2::token_printer (os, t, d); } -- cgit v1.1