From 70317569c6dcd9809ed4a8c425777e653ec6ca08 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 1 May 2017 18:24:31 +0300 Subject: Add hxx extension for headers --- build2/test/script/builtin | 75 ----- build2/test/script/builtin.cxx | 12 +- build2/test/script/builtin.hxx | 75 +++++ build2/test/script/lexer | 90 ------ build2/test/script/lexer.cxx | 2 +- build2/test/script/lexer.hxx | 90 ++++++ build2/test/script/parser | 245 --------------- build2/test/script/parser.cxx | 8 +- build2/test/script/parser.hxx | 245 +++++++++++++++ build2/test/script/regex | 684 ----------------------------------------- build2/test/script/regex.cxx | 2 +- build2/test/script/regex.hxx | 684 +++++++++++++++++++++++++++++++++++++++++ build2/test/script/runner | 90 ------ build2/test/script/runner.cxx | 18 +- build2/test/script/runner.hxx | 90 ++++++ build2/test/script/script | 552 --------------------------------- build2/test/script/script.cxx | 6 +- build2/test/script/script.hxx | 552 +++++++++++++++++++++++++++++++++ build2/test/script/token | 65 ---- build2/test/script/token.cxx | 2 +- build2/test/script/token.hxx | 65 ++++ 21 files changed, 1826 insertions(+), 1826 deletions(-) delete mode 100644 build2/test/script/builtin create mode 100644 build2/test/script/builtin.hxx delete mode 100644 build2/test/script/lexer create mode 100644 build2/test/script/lexer.hxx delete mode 100644 build2/test/script/parser create mode 100644 build2/test/script/parser.hxx delete mode 100644 build2/test/script/regex create mode 100644 build2/test/script/regex.hxx delete mode 100644 build2/test/script/runner create mode 100644 build2/test/script/runner.hxx delete mode 100644 build2/test/script/script create mode 100644 build2/test/script/script.hxx delete mode 100644 build2/test/script/token create mode 100644 build2/test/script/token.hxx (limited to 'build2/test/script') diff --git a/build2/test/script/builtin b/build2/test/script/builtin deleted file mode 100644 index 7d902ea..0000000 --- a/build2/test/script/builtin +++ /dev/null @@ -1,75 +0,0 @@ -// file : build2/test/script/builtin -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_BUILTIN -#define BUILD2_TEST_SCRIPT_BUILTIN - -#include - -#include -#include - -namespace build2 -{ - namespace test - { - namespace script - { - class scope; - - // A process/thread-like object representing a running builtin. - // - // For now, instead of allocating the result storage dynamically, we - // expect it to be provided by the caller. - // - class builtin - { - public: - uint8_t - wait () {if (t_.joinable ()) t_.join (); return r_;} - - ~builtin () {wait ();} - - public: - builtin (uint8_t& r, thread&& t = thread ()): r_ (r), t_ (move (t)) {} - - builtin (builtin&&) = default; - builtin& operator= (builtin&&) = default; - - private: - uint8_t& r_; - thread t_; - }; - - // Start builtin command. Throw system_error on failure. - // - // Note that unlike argc/argv, our args don't include the program name. - // - using builtin_func = builtin (scope&, - uint8_t& result, - const strings& args, - auto_fd in, auto_fd out, auto_fd err); - - class builtin_map: public std::map - { - public: - using base = std::map; - using base::base; - - // Return NULL if not a builtin. - // - builtin_func* - find (const string& n) const - { - auto i (base::find (n)); - return i != end () ? i->second : nullptr; - } - }; - - extern const builtin_map builtins; - } - } -} - -#endif // BUILD2_TEST_SCRIPT_BUILTIN diff --git a/build2/test/script/builtin.cxx b/build2/test/script/builtin.cxx index a2f3107..d45b200 100644 --- a/build2/test/script/builtin.cxx +++ b/build2/test/script/builtin.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #ifndef _WIN32 # include @@ -14,13 +14,13 @@ #include #include -#include // use default operator<< implementation -#include // fdopen_mode, fdstream_mode -#include // mkdir_status +#include // use default operator<< implementation +#include // fdopen_mode, fdstream_mode +#include // mkdir_status -#include +#include -#include +#include // Strictly speaking a builtin which reads/writes from/to standard streams // must be asynchronous so that the caller can communicate with it through diff --git a/build2/test/script/builtin.hxx b/build2/test/script/builtin.hxx new file mode 100644 index 0000000..ae0681f --- /dev/null +++ b/build2/test/script/builtin.hxx @@ -0,0 +1,75 @@ +// file : build2/test/script/builtin.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_BUILTIN_HXX +#define BUILD2_TEST_SCRIPT_BUILTIN_HXX + +#include + +#include +#include + +namespace build2 +{ + namespace test + { + namespace script + { + class scope; + + // A process/thread-like object representing a running builtin. + // + // For now, instead of allocating the result storage dynamically, we + // expect it to be provided by the caller. + // + class builtin + { + public: + uint8_t + wait () {if (t_.joinable ()) t_.join (); return r_;} + + ~builtin () {wait ();} + + public: + builtin (uint8_t& r, thread&& t = thread ()): r_ (r), t_ (move (t)) {} + + builtin (builtin&&) = default; + builtin& operator= (builtin&&) = default; + + private: + uint8_t& r_; + thread t_; + }; + + // Start builtin command. Throw system_error on failure. + // + // Note that unlike argc/argv, our args don't include the program name. + // + using builtin_func = builtin (scope&, + uint8_t& result, + const strings& args, + auto_fd in, auto_fd out, auto_fd err); + + class builtin_map: public std::map + { + public: + using base = std::map; + using base::base; + + // Return NULL if not a builtin. + // + builtin_func* + find (const string& n) const + { + auto i (base::find (n)); + return i != end () ? i->second : nullptr; + } + }; + + extern const builtin_map builtins; + } + } +} + +#endif // BUILD2_TEST_SCRIPT_BUILTIN_HXX diff --git a/build2/test/script/lexer b/build2/test/script/lexer deleted file mode 100644 index 4851e13..0000000 --- a/build2/test/script/lexer +++ /dev/null @@ -1,90 +0,0 @@ -// file : build2/test/script/lexer -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_LEXER -#define BUILD2_TEST_SCRIPT_LEXER - -#include -#include - -#include - -#include - -namespace build2 -{ - namespace test - { - namespace script - { - struct lexer_mode: build2::lexer_mode - { - using base_type = build2::lexer_mode; - - enum - { - command_line = base_type::value_next, - first_token, // Expires at the end of the token. - second_token, // Expires at the end of the token. - variable_line, // Expires at the end of the line. - command_expansion, - here_line_single, - here_line_double, - description_line // Expires at the end of the line. - }; - - lexer_mode () = default; - lexer_mode (value_type v): base_type (v) {} - lexer_mode (base_type v): base_type (v) {} - }; - - class lexer: public build2::lexer - { - public: - using base_lexer = build2::lexer; - using base_mode = build2::lexer_mode; - - lexer (istream& is, - const path& name, - lexer_mode m, - const char* escapes = nullptr) - : base_lexer (is, name, nullptr, false) - { - mode (m, '\0', escapes); - } - - virtual void - mode (base_mode, - char = '\0', - optional = nullopt) override; - - // Number of quoted (double or single) tokens since last reset. - // - size_t - quoted () const {return quoted_;} - - void - reset_quoted (size_t q) {quoted_ = q;} - - virtual token - next () override; - - protected: - token - next_line (); - - token - next_description (); - - virtual token - word (state, bool) override; - - protected: - size_t quoted_; - }; - } - } -} - -#endif // BUILD2_TEST_SCRIPT_LEXER diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx index fab5cc2..4d139ce 100644 --- a/build2/test/script/lexer.cxx +++ b/build2/test/script/lexer.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // strchr() diff --git a/build2/test/script/lexer.hxx b/build2/test/script/lexer.hxx new file mode 100644 index 0000000..a262764 --- /dev/null +++ b/build2/test/script/lexer.hxx @@ -0,0 +1,90 @@ +// file : build2/test/script/lexer.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_LEXER_HXX +#define BUILD2_TEST_SCRIPT_LEXER_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace test + { + namespace script + { + struct lexer_mode: build2::lexer_mode + { + using base_type = build2::lexer_mode; + + enum + { + command_line = base_type::value_next, + first_token, // Expires at the end of the token. + second_token, // Expires at the end of the token. + variable_line, // Expires at the end of the line. + command_expansion, + here_line_single, + here_line_double, + description_line // Expires at the end of the line. + }; + + lexer_mode () = default; + lexer_mode (value_type v): base_type (v) {} + lexer_mode (base_type v): base_type (v) {} + }; + + class lexer: public build2::lexer + { + public: + using base_lexer = build2::lexer; + using base_mode = build2::lexer_mode; + + lexer (istream& is, + const path& name, + lexer_mode m, + const char* escapes = nullptr) + : base_lexer (is, name, nullptr, false) + { + mode (m, '\0', escapes); + } + + virtual void + mode (base_mode, + char = '\0', + optional = nullopt) override; + + // Number of quoted (double or single) tokens since last reset. + // + size_t + quoted () const {return quoted_;} + + void + reset_quoted (size_t q) {quoted_ = q;} + + virtual token + next () override; + + protected: + token + next_line (); + + token + next_description (); + + virtual token + word (state, bool) override; + + protected: + size_t quoted_; + }; + } + } +} + +#endif // BUILD2_TEST_SCRIPT_LEXER_HXX diff --git a/build2/test/script/parser b/build2/test/script/parser deleted file mode 100644 index e018548..0000000 --- a/build2/test/script/parser +++ /dev/null @@ -1,245 +0,0 @@ -// file : build2/test/script/parser -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_PARSER -#define BUILD2_TEST_SCRIPT_PARSER - -#include -#include - -#include -#include - -#include -#include - -namespace build2 -{ - namespace test - { - namespace script - { - class lexer; - class runner; - - class parser: protected build2::parser - { - // Pre-parse. Issue diagnostics and throw failed in case of an error. - // - public: - void - pre_parse (script&); - - void - pre_parse (istream&, script&); - - // Helpers. - // - // Parse attribute string and perform attribute-guided assignment. - // Issue diagnostics and throw failed in case of an error. - // - void - apply_value_attributes (const variable*, // Optional. - value& lhs, - value&& rhs, - const string& attributes, - token_type assign_kind, - const path& name); // For diagnostics. - - // Recursive descent parser. - // - // Usually (but not always) parse functions receive the token/type - // from which it should start consuming and in return the token/type - // should contain the first token that has not been consumed. - // - // Functions that are called parse_*() rather than pre_parse_*() are - // used for both stages. - // - protected: - bool - pre_parse_demote_group_scope (unique_ptr&); - - token - pre_parse_scope_body (); - - unique_ptr - pre_parse_scope_block (token&, token_type&, const string&); - - bool - pre_parse_line (token&, token_type&, - optional&, - lines* = nullptr, - bool one = false); - - bool - pre_parse_if_else (token&, token_type&, - optional&, - lines&); - - bool - pre_parse_if_else_scope (token&, token_type&, - optional&, - lines&); - - bool - pre_parse_if_else_command (token&, token_type&, - optional&, - lines&); - - void - pre_parse_directive (token&, token_type&); - - void - pre_parse_include_line (names, location); - - description - pre_parse_leading_description (token&, token_type&); - - description - parse_trailing_description (token&, token_type&); - - value - parse_variable_line (token&, token_type&); - - command_expr - parse_command_line (token&, token_type&); - - // Ordered sequence of here-document redirects that we can expect to - // see after the command line. - // - struct here_redirect - { - size_t expr; // Index in command_expr. - size_t pipe; // Index in command_pipe. - int fd; // Redirect fd (0 - in, 1 - out, 2 - err). - }; - - struct here_doc - { - // Redirects that share here_doc. Most of the time we will have no - // more than 2 (2 - for the roundtrip test cases). - // - small_vector redirects; - - string end; - bool literal; // Literal (single-quote). - string modifiers; - - // Regex introducer ('\0' if not a regex, so can be used as bool). - // - char regex; - - // Regex global flags. Meaningful if regex != '\0'. - // - string regex_flags; - }; - using here_docs = vector; - - pair - parse_command_expr (token&, token_type&); - - command_exit - parse_command_exit (token&, token_type&); - - void - parse_here_documents (token&, token_type&, - pair&); - - struct parsed_doc - { - union - { - string str; // Here-document literal. - regex_lines regex; // Here-document regex. - }; - - bool re; // True if regex. - uint64_t end_line; // Here-document end marker location. - uint64_t end_column; - - parsed_doc (string, uint64_t line, uint64_t column); - parsed_doc (regex_lines&&, uint64_t line, uint64_t column); - parsed_doc (parsed_doc&&); // Note: move constuctible-only type. - ~parsed_doc (); - }; - - parsed_doc - parse_here_document (token&, token_type&, - const string&, - const string& mode, - char re_intro); // '\0' if not a regex. - - // Execute. Issue diagnostics and throw failed in case of an error. - // - public: - void - execute (script& s, runner& r); - - void - execute (scope&, script&, runner&); - - protected: - void - exec_scope_body (); - - void - exec_lines (lines::iterator, lines::iterator, size_t&, command_type); - - // Customization hooks. - // - protected: - virtual lookup - lookup_variable (name&&, string&&, const location&) override; - - // Number of quoted tokens since last reset. Note that this includes - // the peeked token, if any. - // - protected: - size_t - quoted () const; - - void - reset_quoted (token& current); - - size_t replay_quoted_; - - // Insert id into the id map checking for duplicates. - // - protected: - const string& - insert_id (string, location); - - // Set lexer pointers for both the current and the base classes. - // - protected: - void - set_lexer (lexer* l); - - protected: - using base_parser = build2::parser; - - script* script_; - - // Pre-parse state. - // - using id_map = std::unordered_map; - using include_set = std::set; - - group* group_; - id_map* id_map_; - include_set* include_set_; // Testscripts already included in this - // scope. Must be absolute and normalized. - lexer* lexer_; - string id_prefix_; // Auto-derived id prefix. - - // Execute state. - // - runner* runner_; - scope* scope_; - }; - } - } -} - -#endif // BUILD2_TEST_SCRIPT_PARSER diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx index 350ca44..ae6bdbc 100644 --- a/build2/test/script/parser.cxx +++ b/build2/test/script/parser.cxx @@ -2,14 +2,14 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include -#include // sched, keep_going +#include // sched, keep_going -#include -#include +#include +#include using namespace std; diff --git a/build2/test/script/parser.hxx b/build2/test/script/parser.hxx new file mode 100644 index 0000000..21ea61a --- /dev/null +++ b/build2/test/script/parser.hxx @@ -0,0 +1,245 @@ +// file : build2/test/script/parser.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_PARSER_HXX +#define BUILD2_TEST_SCRIPT_PARSER_HXX + +#include +#include + +#include +#include + +#include +#include + +namespace build2 +{ + namespace test + { + namespace script + { + class lexer; + class runner; + + class parser: protected build2::parser + { + // Pre-parse. Issue diagnostics and throw failed in case of an error. + // + public: + void + pre_parse (script&); + + void + pre_parse (istream&, script&); + + // Helpers. + // + // Parse attribute string and perform attribute-guided assignment. + // Issue diagnostics and throw failed in case of an error. + // + void + apply_value_attributes (const variable*, // Optional. + value& lhs, + value&& rhs, + const string& attributes, + token_type assign_kind, + const path& name); // For diagnostics. + + // Recursive descent parser. + // + // Usually (but not always) parse functions receive the token/type + // from which it should start consuming and in return the token/type + // should contain the first token that has not been consumed. + // + // Functions that are called parse_*() rather than pre_parse_*() are + // used for both stages. + // + protected: + bool + pre_parse_demote_group_scope (unique_ptr&); + + token + pre_parse_scope_body (); + + unique_ptr + pre_parse_scope_block (token&, token_type&, const string&); + + bool + pre_parse_line (token&, token_type&, + optional&, + lines* = nullptr, + bool one = false); + + bool + pre_parse_if_else (token&, token_type&, + optional&, + lines&); + + bool + pre_parse_if_else_scope (token&, token_type&, + optional&, + lines&); + + bool + pre_parse_if_else_command (token&, token_type&, + optional&, + lines&); + + void + pre_parse_directive (token&, token_type&); + + void + pre_parse_include_line (names, location); + + description + pre_parse_leading_description (token&, token_type&); + + description + parse_trailing_description (token&, token_type&); + + value + parse_variable_line (token&, token_type&); + + command_expr + parse_command_line (token&, token_type&); + + // Ordered sequence of here-document redirects that we can expect to + // see after the command line. + // + struct here_redirect + { + size_t expr; // Index in command_expr. + size_t pipe; // Index in command_pipe. + int fd; // Redirect fd (0 - in, 1 - out, 2 - err). + }; + + struct here_doc + { + // Redirects that share here_doc. Most of the time we will have no + // more than 2 (2 - for the roundtrip test cases). + // + small_vector redirects; + + string end; + bool literal; // Literal (single-quote). + string modifiers; + + // Regex introducer ('\0' if not a regex, so can be used as bool). + // + char regex; + + // Regex global flags. Meaningful if regex != '\0'. + // + string regex_flags; + }; + using here_docs = vector; + + pair + parse_command_expr (token&, token_type&); + + command_exit + parse_command_exit (token&, token_type&); + + void + parse_here_documents (token&, token_type&, + pair&); + + struct parsed_doc + { + union + { + string str; // Here-document literal. + regex_lines regex; // Here-document regex. + }; + + bool re; // True if regex. + uint64_t end_line; // Here-document end marker location. + uint64_t end_column; + + parsed_doc (string, uint64_t line, uint64_t column); + parsed_doc (regex_lines&&, uint64_t line, uint64_t column); + parsed_doc (parsed_doc&&); // Note: move constuctible-only type. + ~parsed_doc (); + }; + + parsed_doc + parse_here_document (token&, token_type&, + const string&, + const string& mode, + char re_intro); // '\0' if not a regex. + + // Execute. Issue diagnostics and throw failed in case of an error. + // + public: + void + execute (script& s, runner& r); + + void + execute (scope&, script&, runner&); + + protected: + void + exec_scope_body (); + + void + exec_lines (lines::iterator, lines::iterator, size_t&, command_type); + + // Customization hooks. + // + protected: + virtual lookup + lookup_variable (name&&, string&&, const location&) override; + + // Number of quoted tokens since last reset. Note that this includes + // the peeked token, if any. + // + protected: + size_t + quoted () const; + + void + reset_quoted (token& current); + + size_t replay_quoted_; + + // Insert id into the id map checking for duplicates. + // + protected: + const string& + insert_id (string, location); + + // Set lexer pointers for both the current and the base classes. + // + protected: + void + set_lexer (lexer* l); + + protected: + using base_parser = build2::parser; + + script* script_; + + // Pre-parse state. + // + using id_map = std::unordered_map; + using include_set = std::set; + + group* group_; + id_map* id_map_; + include_set* include_set_; // Testscripts already included in this + // scope. Must be absolute and normalized. + lexer* lexer_; + string id_prefix_; // Auto-derived id prefix. + + // Execute state. + // + runner* runner_; + scope* scope_; + }; + } + } +} + +#endif // BUILD2_TEST_SCRIPT_PARSER_HXX diff --git a/build2/test/script/regex b/build2/test/script/regex deleted file mode 100644 index 1170b99..0000000 --- a/build2/test/script/regex +++ /dev/null @@ -1,684 +0,0 @@ -// file : build2/test/script/regex -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_REGEX -#define BUILD2_TEST_SCRIPT_REGEX - -#include -#include -#include -#include // basic_string -#include // uintptr_t -#include // make_unsigned, enable_if, is_* -#include - -#include -#include - -namespace build2 -{ - namespace test - { - namespace script - { - namespace regex - { - using char_string = std::basic_string; - - enum class char_flags: uint16_t - { - icase = 0x1, // Case-insensitive match. - idot = 0x2, // Invert '.' escaping. - - none = 0 - }; - - // Restricts valid standard flags to just {icase}, extends with custom - // flags {idot}. - // - class char_regex: public std::basic_regex - { - public: - using base_type = std::basic_regex; - - char_regex (const char_string&, char_flags = char_flags::none); - }; - - // Newlines are line separators and are not part of the line: - // - // lineline - // - // Specifically, this means that a customary trailing newline creates a - // trailing blank line. - // - // All characters can inter-compare (though there cannot be regex - // characters in the output, only in line_regex). - // - // Note that we assume that line_regex and the input to regex_match() - // use the same pool. - // - struct line_pool - { - // Note that we assume the pool can be moved without invalidating - // pointers to any already pooled entities. - // - std::unordered_set strings; - std::list regexes; - }; - - enum class line_type - { - special, - literal, - regex - }; - - struct line_char - { - // Steal last two bits from the pointer to store the type. - // - private: - std::uintptr_t data_; - - public: - line_type - type () const {return static_cast (data_ & 0x3);} - - int - special () const - { - // Stored as (shifted) int16_t. Perform steps reversed to those - // that are described in the comment for the corresponding ctor. - // Note that the intermediate cast to uint16_t is required to - // portably preserve the -1 special character. - // - return static_cast (static_cast (data_ >> 2)); - } - - const char_string* - literal () const - { - // Note that 2 rightmost bits are used for packaging line_char - // type. Read the comment for the corresponding ctor for details. - // - return reinterpret_cast ( - data_ & ~std::uintptr_t (0x3)); - } - - const char_regex* - regex () const - { - // Note that 2 rightmost bits are used for packaging line_char - // type. Read the comment for the corresponding ctor for details. - // - return reinterpret_cast ( - data_ & ~std::uintptr_t (0x3)); - } - - static const line_char nul; - static const line_char eof; - - // Note: creates an uninitialized value. - // - line_char () = default; - - // Create a special character. The argument value must be one of the - // following ones: - // - // 0 (nul character) - // -1 (EOF) - // [()|.*+?{}\0123456789,=!] (excluding []) - // - // Note that the constructor is implicit to allow basic_regex to - // implicitly construct line_chars from special char literals (in - // particular libstdc++ appends them to an internal line_string). - // - // Also note that we extend the valid characters set (see above) with - // 'p', 'n' (used by libstdc++ for positive/negative look-ahead - // tokens representation), and '\n', '\r', u'\u2028', u'\u2029' (used - // by libstdc++ for newline/newparagraph matching). - // - line_char (int); - - // Create a literal character. - // - // Don't copy string if already pooled. - // - explicit - line_char (const char_string&, line_pool&); - - explicit - line_char (char_string&&, line_pool&); - - explicit - line_char (const char_string* s) // Assume already pooled. - // - // Steal two bits from the pointer to package line_char type. - // Assume (and statically assert) that char_string address is a - // multiple of four. - // - : data_ (reinterpret_cast (s) | - static_cast (line_type::literal)) {} - - // Create a regex character. - // - explicit - line_char (char_regex, line_pool&); - - explicit - line_char (const char_regex* r) // Assume already pooled. - // - // Steal two bits from the pointer to package line_char type. - // Assume (and statically assert) that char_regex address is a - // multiple of four. - // - : data_ (reinterpret_cast (r) | - static_cast (line_type::regex)) {} - - // Provide basic_regex with the ability to use line_char in a context - // where a char value is expected (e.g., as a function argument). - // - // libstdc++ seems to cast special line_chars only (and such a - // conversion is meanigfull). - // - // msvcrt casts line_chars of arbitrary types instead. The only - // reasonable strategy is to return a value that differs from any - // other that can be encountered in a regex expression and so will - // unlikelly be misinterpreted. - // - operator char () const - { - return type () == line_type::special ? special () : '\a'; // BELL. - } - - // Return true if the character is a syntax (special) one. - // - static bool - syntax (char); - - // Provide basic_regex (such as from msvcrt) with the ability to - // explicitly cast line_chars to implementation-specific enums. - // - template - explicit - operator T () const - { - assert (type () == line_type::special); - return static_cast (special ()); - } - }; - - // Perform "deep" characters comparison (for example match literal - // character with a regex character), rather than just compare them - // literally. At least one argument must be of a type other than regex - // as there is no operator==() defined to compare regexes. Characters - // of the literal type must share the same pool (strings are compared - // by pointers not by values). - // - bool - operator== (const line_char&, const line_char&); - - // Return false if arguments are equal (operator==() returns true). - // Otherwise if types are different return the value implying that - // special < literal < regex. If types are special or literal return - // the result of the respective characters or strings comparison. At - // least one argument must be of a type other than regex as there is no - // operator<() defined to compare regexes. - // - // While not very natural operation for the class we have, we have to - // provide some meaningfull semantics for such a comparison as it is - // required by the char_traits specialization. While we - // could provide it right in that specialization, let's keep it here - // for basic_regex implementations that potentially can compare - // line_chars as they compare them with expressions of other types (see - // below). - // - bool - operator< (const line_char&, const line_char&); - - inline bool - operator!= (const line_char& l, const line_char& r) - { - return !(l == r); - } - - inline bool - operator<= (const line_char& l, const line_char& r) - { - return l < r || l == r; - } - - // Provide basic_regex (such as from msvcrt) with the ability to - // compare line_char to a value of an integral or - // implementation-specific enum type. In the absense of the following - // template operators, such a comparisons would be ambigious for - // integral types (given that there are implicit conversions - // int->line_char and line_char->char) and impossible for enums. - // - // Note that these == and < operators can succeed only for a line_char - // of the special type. For other types they always return false. That - // in particular leads to the following case: - // - // (lc != c) != (lc < c || c < lc). - // - // Note that we can not assert line_char is of the special type as - // basic_regex (such as from libc++) may need the ability to check if - // arbitrary line_char belongs to some special characters range (like - // ['0', '9']). - // - template - struct line_char_cmp - : public std::enable_if::value || - (std::is_enum::value && - !std::is_same::value)> {}; - - template ::type> - bool - operator== (const line_char& l, const T& r) - { - return l.type () == line_type::special && - static_cast (l.special ()) == r; - } - - template ::type> - bool - operator== (const T& l, const line_char& r) - { - return r.type () == line_type::special && - static_cast (r.special ()) == l; - } - - template ::type> - bool - operator!= (const line_char& l, const T& r) - { - return !(l == r); - } - - template ::type> - bool - operator!= (const T& l, const line_char& r) - { - return !(l == r); - } - - template ::type> - bool - operator< (const line_char& l, const T& r) - { - return l.type () == line_type::special && - static_cast (l.special ()) < r; - } - - template ::type> - bool - operator< (const T& l, const line_char& r) - { - return r.type () == line_type::special && - l < static_cast (r.special ()); - } - - template ::type> - inline bool - operator<= (const line_char& l, const T& r) - { - return l < r || l == r; - } - - template ::type> - inline bool - operator<= (const T& l, const line_char& r) - { - return l < r || l == r; - } - - using line_string = std::basic_string; - - // Locale that has ctype facet installed. Used in the - // regex_traits specialization (see below). - // - class line_char_locale: public std::locale - { - public: - // Create a copy of the global C++ locale. - // - line_char_locale (); - }; - } - } - } -} - -// Standard template specializations for line_char that are required for the -// basic_regex instantiation. -// -namespace std -{ - template <> - class char_traits - { - public: - using char_type = build2::test::script::regex::line_char; - using int_type = char_type; - using off_type = char_traits::off_type; - using pos_type = char_traits::pos_type; - using state_type = char_traits::state_type; - - static void - assign (char_type& c1, const char_type& c2) {c1 = c2;} - - static char_type* - assign (char_type*, size_t, char_type); - - // Note that eq() and lt() are not constexpr (as required by C++11) - // because == and < operators for char_type are not constexpr. - // - static bool - eq (const char_type& l, const char_type& r) {return l == r;} - - static bool - lt (const char_type& l, const char_type& r) {return l < r;} - - static char_type* - move (char_type*, const char_type*, size_t); - - static char_type* - copy (char_type*, const char_type*, size_t); - - static int - compare (const char_type*, const char_type*, size_t); - - static size_t - length (const char_type*); - - static const char_type* - find (const char_type*, size_t, const char_type&); - - static constexpr char_type - to_char_type (const int_type& c) {return c;} - - static constexpr int_type - to_int_type (const char_type& c) {return int_type (c);} - - // Note that the following functions are not constexpr (as required by - // C++11) because their return expressions are not constexpr. - // - static bool - eq_int_type (const int_type& l, const int_type& r) {return l == r;} - - static int_type eof () {return char_type::eof;} - - static int_type - not_eof (const int_type& c) - { - return c != char_type::eof ? c : char_type::nul; - } - }; - - // ctype<> must be derived from both ctype_base and locale::facet (the later - // supports ref-counting used by the std::locale implementation internally). - // - // msvcrt for some reason also derives ctype_base from locale::facet which - // produces "already a base-class" warning and effectivelly breaks the - // reference counting. So we derive from ctype_base only in this case. - // - template <> - class ctype: public ctype_base -#if !defined(_MSC_VER) || _MSC_VER > 1910 - , public locale::facet -#endif - { - // Used by the implementation only. - // - using line_type = build2::test::script::regex::line_type; - - public: - using char_type = build2::test::script::regex::line_char; - - static locale::id id; - -#if !defined(_MSC_VER) || _MSC_VER > 1910 - explicit - ctype (size_t refs = 0): locale::facet (refs) {} -#else - explicit - ctype (size_t refs = 0): ctype_base (refs) {} -#endif - - // While unnecessary, let's keep for completeness. - // - virtual - ~ctype () override = default; - - // The C++ standard requires the following functions to call their virtual - // (protected) do_*() counterparts that provide the real implementations. - // The only purpose for this indirection is to provide a user with the - // ability to customize existing (standard) ctype facets. As we do not - // provide such an ability, for simplicity we will omit the do_*() - // functions and provide the implementations directly. This should be safe - // as nobody except us could call those protected functions. - // - bool - is (mask m, char_type c) const - { - return m == - (c.type () == line_type::special && build2::digit (c.special ()) - ? digit - : 0); - } - - const char_type* - is (const char_type*, const char_type*, mask*) const; - - const char_type* - scan_is (mask, const char_type*, const char_type*) const; - - const char_type* - scan_not (mask, const char_type*, const char_type*) const; - - char_type - toupper (char_type c) const {return c;} - - const char_type* - toupper (char_type*, const char_type* e) const {return e;} - - char_type - tolower (char_type c) const {return c;} - - const char_type* - tolower (char_type*, const char_type* e) const {return e;} - - char_type - widen (char c) const {return char_type (c);} - - const char* - widen (const char*, const char*, char_type*) const; - - char - narrow (char_type c, char def) const - { - return c.type () == line_type::special ? c.special () : def; - } - - const char_type* - narrow (const char_type*, const char_type*, char, char*) const; - }; - - // Note: the current application locale must be the POSIX one. Otherwise the - // behavior is undefined. - // - template <> - class regex_traits - { - public: - using char_type = build2::test::script::regex::line_char; - using string_type = build2::test::script::regex::line_string; - using locale_type = build2::test::script::regex::line_char_locale; - using char_class_type = regex_traits::char_class_type; - - // Workaround for msvcrt bugs. For some reason it assumes such a members - // to be present in a regex_traits specialization. - // -#if defined(_MSC_VER) && _MSC_VER <= 1910 - static const ctype_base::mask _Ch_upper = ctype_base::upper; - static const ctype_base::mask _Ch_alpha = ctype_base::alpha; - - // Unsigned char_type. msvcrt statically asserts the _Uelem type is - // unsigned, so we specialize is_unsigned as well (see below). - // - using _Uelem = char_type; -#endif - - regex_traits () = default; // Unnecessary but let's keep for completeness. - - static size_t - length (const char_type* p) {return string_type::traits_type::length (p);} - - char_type - translate (char_type c) const {return c;} - - // Case-insensitive matching is not supported by line_regex. So there is no - // reason for the function to be called. - // - char_type - translate_nocase (char_type c) const {assert (false); return c;} - - // Return a sort-key - the exact copy of [b, e). - // - template - string_type - transform (I b, I e) const {return string_type (b, e);} - - // Return a case-insensitive sort-key. Case-insensitive matching is not - // supported by line_regex. So there is no reason for the function to be - // called. - // - template - string_type - transform_primary (I b, I e) const - { - assert (false); - return string_type (b, e); - } - - // POSIX regex grammar and collating elements (e.g., [.tilde.]) in - // particular are not supported. So there is no reason for the function to - // be called. - // - template - string_type - lookup_collatename (I, I) const {assert (false); return string_type ();} - - // Character classes (e.g., [:lower:]) are not supported. So there is no - // reason for the function to be called. - // - template - char_class_type - lookup_classname (I, I, bool = false) const - { - assert (false); - return char_class_type (); - } - - // Return false as we don't support character classes (e.g., [:lower:]). - // - bool - isctype (char_type, char_class_type) const {return false;} - - int - value (char_type, int) const; - - // Return the locale passed as an argument as we do not expect anything - // other than POSIX locale, that we also assume to be imbued by default. - // - locale_type - imbue (locale_type l) {return l;} - - locale_type - getloc () const {return locale_type ();} - }; - - // We assume line_char to be an unsigned type and express that with the - // following specializations used by basic_regex implementations. - // - // libstdc++ defines unsigned CharT type (regex_traits template parameter) - // to use as an index in some internal cache regardless if the cache is used - // for this specialization (and the cache is used only if CharT is char). - // - template <> - struct make_unsigned - { - using type = build2::test::script::regex::line_char; - }; - - // msvcrt assumes regex_traits::_Uelem to be present (see above) - // and statically asserts it is unsigned. - // - template <> - struct is_unsigned - { - static const bool value = true; - }; - - // When used with libc++ the linker complains that it can't find - // __match_any_but_newline::__exec() function. The problem is - // that the function is only specialized for char and wchar_t - // (LLVM bug #31409). As line_char has no notion of the newline character we - // specialize the class template to behave as the __match_any - // instantiation does (that luckily has all the functions in place). - // -#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 4000 - template <> - class __match_any_but_newline - : public __match_any - { - public: - using base = __match_any; - using base::base; - }; -#endif -} - -namespace build2 -{ - namespace test - { - namespace script - { - namespace regex - { - class line_regex: public std::basic_regex - { - public: - using base_type = std::basic_regex; - - using base_type::base_type; - - line_regex () = default; - - // Move string regex together with the pool used to create it. - // - line_regex (line_string&& s, line_pool&& p) - // No move-string ctor for base_type, so emulate it. - // - : base_type (s), pool (move (p)) {s.clear ();} - - // Move constuctible/assignable-only type. - // - line_regex (line_regex&&) = default; - line_regex (const line_regex&) = delete; - line_regex& operator= (line_regex&&) = default; - line_regex& operator= (const line_regex&) = delete; - - public: - line_pool pool; - }; - } - } - } -} - -#include - -#endif // BUILD2_TEST_SCRIPT_REGEX diff --git a/build2/test/script/regex.cxx b/build2/test/script/regex.cxx index b77f8a5..bf38a62 100644 --- a/build2/test/script/regex.cxx +++ b/build2/test/script/regex.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // copy(), copy_backward() diff --git a/build2/test/script/regex.hxx b/build2/test/script/regex.hxx new file mode 100644 index 0000000..d4f1cf5 --- /dev/null +++ b/build2/test/script/regex.hxx @@ -0,0 +1,684 @@ +// file : build2/test/script/regex.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_REGEX_HXX +#define BUILD2_TEST_SCRIPT_REGEX_HXX + +#include +#include +#include +#include // basic_string +#include // uintptr_t +#include // make_unsigned, enable_if, is_* +#include + +#include +#include + +namespace build2 +{ + namespace test + { + namespace script + { + namespace regex + { + using char_string = std::basic_string; + + enum class char_flags: uint16_t + { + icase = 0x1, // Case-insensitive match. + idot = 0x2, // Invert '.' escaping. + + none = 0 + }; + + // Restricts valid standard flags to just {icase}, extends with custom + // flags {idot}. + // + class char_regex: public std::basic_regex + { + public: + using base_type = std::basic_regex; + + char_regex (const char_string&, char_flags = char_flags::none); + }; + + // Newlines are line separators and are not part of the line: + // + // lineline + // + // Specifically, this means that a customary trailing newline creates a + // trailing blank line. + // + // All characters can inter-compare (though there cannot be regex + // characters in the output, only in line_regex). + // + // Note that we assume that line_regex and the input to regex_match() + // use the same pool. + // + struct line_pool + { + // Note that we assume the pool can be moved without invalidating + // pointers to any already pooled entities. + // + std::unordered_set strings; + std::list regexes; + }; + + enum class line_type + { + special, + literal, + regex + }; + + struct line_char + { + // Steal last two bits from the pointer to store the type. + // + private: + std::uintptr_t data_; + + public: + line_type + type () const {return static_cast (data_ & 0x3);} + + int + special () const + { + // Stored as (shifted) int16_t. Perform steps reversed to those + // that are described in the comment for the corresponding ctor. + // Note that the intermediate cast to uint16_t is required to + // portably preserve the -1 special character. + // + return static_cast (static_cast (data_ >> 2)); + } + + const char_string* + literal () const + { + // Note that 2 rightmost bits are used for packaging line_char + // type. Read the comment for the corresponding ctor for details. + // + return reinterpret_cast ( + data_ & ~std::uintptr_t (0x3)); + } + + const char_regex* + regex () const + { + // Note that 2 rightmost bits are used for packaging line_char + // type. Read the comment for the corresponding ctor for details. + // + return reinterpret_cast ( + data_ & ~std::uintptr_t (0x3)); + } + + static const line_char nul; + static const line_char eof; + + // Note: creates an uninitialized value. + // + line_char () = default; + + // Create a special character. The argument value must be one of the + // following ones: + // + // 0 (nul character) + // -1 (EOF) + // [()|.*+?{}\0123456789,=!] (excluding []) + // + // Note that the constructor is implicit to allow basic_regex to + // implicitly construct line_chars from special char literals (in + // particular libstdc++ appends them to an internal line_string). + // + // Also note that we extend the valid characters set (see above) with + // 'p', 'n' (used by libstdc++ for positive/negative look-ahead + // tokens representation), and '\n', '\r', u'\u2028', u'\u2029' (used + // by libstdc++ for newline/newparagraph matching). + // + line_char (int); + + // Create a literal character. + // + // Don't copy string if already pooled. + // + explicit + line_char (const char_string&, line_pool&); + + explicit + line_char (char_string&&, line_pool&); + + explicit + line_char (const char_string* s) // Assume already pooled. + // + // Steal two bits from the pointer to package line_char type. + // Assume (and statically assert) that char_string address is a + // multiple of four. + // + : data_ (reinterpret_cast (s) | + static_cast (line_type::literal)) {} + + // Create a regex character. + // + explicit + line_char (char_regex, line_pool&); + + explicit + line_char (const char_regex* r) // Assume already pooled. + // + // Steal two bits from the pointer to package line_char type. + // Assume (and statically assert) that char_regex address is a + // multiple of four. + // + : data_ (reinterpret_cast (r) | + static_cast (line_type::regex)) {} + + // Provide basic_regex with the ability to use line_char in a context + // where a char value is expected (e.g., as a function argument). + // + // libstdc++ seems to cast special line_chars only (and such a + // conversion is meanigfull). + // + // msvcrt casts line_chars of arbitrary types instead. The only + // reasonable strategy is to return a value that differs from any + // other that can be encountered in a regex expression and so will + // unlikelly be misinterpreted. + // + operator char () const + { + return type () == line_type::special ? special () : '\a'; // BELL. + } + + // Return true if the character is a syntax (special) one. + // + static bool + syntax (char); + + // Provide basic_regex (such as from msvcrt) with the ability to + // explicitly cast line_chars to implementation-specific enums. + // + template + explicit + operator T () const + { + assert (type () == line_type::special); + return static_cast (special ()); + } + }; + + // Perform "deep" characters comparison (for example match literal + // character with a regex character), rather than just compare them + // literally. At least one argument must be of a type other than regex + // as there is no operator==() defined to compare regexes. Characters + // of the literal type must share the same pool (strings are compared + // by pointers not by values). + // + bool + operator== (const line_char&, const line_char&); + + // Return false if arguments are equal (operator==() returns true). + // Otherwise if types are different return the value implying that + // special < literal < regex. If types are special or literal return + // the result of the respective characters or strings comparison. At + // least one argument must be of a type other than regex as there is no + // operator<() defined to compare regexes. + // + // While not very natural operation for the class we have, we have to + // provide some meaningfull semantics for such a comparison as it is + // required by the char_traits specialization. While we + // could provide it right in that specialization, let's keep it here + // for basic_regex implementations that potentially can compare + // line_chars as they compare them with expressions of other types (see + // below). + // + bool + operator< (const line_char&, const line_char&); + + inline bool + operator!= (const line_char& l, const line_char& r) + { + return !(l == r); + } + + inline bool + operator<= (const line_char& l, const line_char& r) + { + return l < r || l == r; + } + + // Provide basic_regex (such as from msvcrt) with the ability to + // compare line_char to a value of an integral or + // implementation-specific enum type. In the absense of the following + // template operators, such a comparisons would be ambigious for + // integral types (given that there are implicit conversions + // int->line_char and line_char->char) and impossible for enums. + // + // Note that these == and < operators can succeed only for a line_char + // of the special type. For other types they always return false. That + // in particular leads to the following case: + // + // (lc != c) != (lc < c || c < lc). + // + // Note that we can not assert line_char is of the special type as + // basic_regex (such as from libc++) may need the ability to check if + // arbitrary line_char belongs to some special characters range (like + // ['0', '9']). + // + template + struct line_char_cmp + : public std::enable_if::value || + (std::is_enum::value && + !std::is_same::value)> {}; + + template ::type> + bool + operator== (const line_char& l, const T& r) + { + return l.type () == line_type::special && + static_cast (l.special ()) == r; + } + + template ::type> + bool + operator== (const T& l, const line_char& r) + { + return r.type () == line_type::special && + static_cast (r.special ()) == l; + } + + template ::type> + bool + operator!= (const line_char& l, const T& r) + { + return !(l == r); + } + + template ::type> + bool + operator!= (const T& l, const line_char& r) + { + return !(l == r); + } + + template ::type> + bool + operator< (const line_char& l, const T& r) + { + return l.type () == line_type::special && + static_cast (l.special ()) < r; + } + + template ::type> + bool + operator< (const T& l, const line_char& r) + { + return r.type () == line_type::special && + l < static_cast (r.special ()); + } + + template ::type> + inline bool + operator<= (const line_char& l, const T& r) + { + return l < r || l == r; + } + + template ::type> + inline bool + operator<= (const T& l, const line_char& r) + { + return l < r || l == r; + } + + using line_string = std::basic_string; + + // Locale that has ctype facet installed. Used in the + // regex_traits specialization (see below). + // + class line_char_locale: public std::locale + { + public: + // Create a copy of the global C++ locale. + // + line_char_locale (); + }; + } + } + } +} + +// Standard template specializations for line_char that are required for the +// basic_regex instantiation. +// +namespace std +{ + template <> + class char_traits + { + public: + using char_type = build2::test::script::regex::line_char; + using int_type = char_type; + using off_type = char_traits::off_type; + using pos_type = char_traits::pos_type; + using state_type = char_traits::state_type; + + static void + assign (char_type& c1, const char_type& c2) {c1 = c2;} + + static char_type* + assign (char_type*, size_t, char_type); + + // Note that eq() and lt() are not constexpr (as required by C++11) + // because == and < operators for char_type are not constexpr. + // + static bool + eq (const char_type& l, const char_type& r) {return l == r;} + + static bool + lt (const char_type& l, const char_type& r) {return l < r;} + + static char_type* + move (char_type*, const char_type*, size_t); + + static char_type* + copy (char_type*, const char_type*, size_t); + + static int + compare (const char_type*, const char_type*, size_t); + + static size_t + length (const char_type*); + + static const char_type* + find (const char_type*, size_t, const char_type&); + + static constexpr char_type + to_char_type (const int_type& c) {return c;} + + static constexpr int_type + to_int_type (const char_type& c) {return int_type (c);} + + // Note that the following functions are not constexpr (as required by + // C++11) because their return expressions are not constexpr. + // + static bool + eq_int_type (const int_type& l, const int_type& r) {return l == r;} + + static int_type eof () {return char_type::eof;} + + static int_type + not_eof (const int_type& c) + { + return c != char_type::eof ? c : char_type::nul; + } + }; + + // ctype<> must be derived from both ctype_base and locale::facet (the later + // supports ref-counting used by the std::locale implementation internally). + // + // msvcrt for some reason also derives ctype_base from locale::facet which + // produces "already a base-class" warning and effectivelly breaks the + // reference counting. So we derive from ctype_base only in this case. + // + template <> + class ctype: public ctype_base +#if !defined(_MSC_VER) || _MSC_VER > 1910 + , public locale::facet +#endif + { + // Used by the implementation only. + // + using line_type = build2::test::script::regex::line_type; + + public: + using char_type = build2::test::script::regex::line_char; + + static locale::id id; + +#if !defined(_MSC_VER) || _MSC_VER > 1910 + explicit + ctype (size_t refs = 0): locale::facet (refs) {} +#else + explicit + ctype (size_t refs = 0): ctype_base (refs) {} +#endif + + // While unnecessary, let's keep for completeness. + // + virtual + ~ctype () override = default; + + // The C++ standard requires the following functions to call their virtual + // (protected) do_*() counterparts that provide the real implementations. + // The only purpose for this indirection is to provide a user with the + // ability to customize existing (standard) ctype facets. As we do not + // provide such an ability, for simplicity we will omit the do_*() + // functions and provide the implementations directly. This should be safe + // as nobody except us could call those protected functions. + // + bool + is (mask m, char_type c) const + { + return m == + (c.type () == line_type::special && build2::digit (c.special ()) + ? digit + : 0); + } + + const char_type* + is (const char_type*, const char_type*, mask*) const; + + const char_type* + scan_is (mask, const char_type*, const char_type*) const; + + const char_type* + scan_not (mask, const char_type*, const char_type*) const; + + char_type + toupper (char_type c) const {return c;} + + const char_type* + toupper (char_type*, const char_type* e) const {return e;} + + char_type + tolower (char_type c) const {return c;} + + const char_type* + tolower (char_type*, const char_type* e) const {return e;} + + char_type + widen (char c) const {return char_type (c);} + + const char* + widen (const char*, const char*, char_type*) const; + + char + narrow (char_type c, char def) const + { + return c.type () == line_type::special ? c.special () : def; + } + + const char_type* + narrow (const char_type*, const char_type*, char, char*) const; + }; + + // Note: the current application locale must be the POSIX one. Otherwise the + // behavior is undefined. + // + template <> + class regex_traits + { + public: + using char_type = build2::test::script::regex::line_char; + using string_type = build2::test::script::regex::line_string; + using locale_type = build2::test::script::regex::line_char_locale; + using char_class_type = regex_traits::char_class_type; + + // Workaround for msvcrt bugs. For some reason it assumes such a members + // to be present in a regex_traits specialization. + // +#if defined(_MSC_VER) && _MSC_VER <= 1910 + static const ctype_base::mask _Ch_upper = ctype_base::upper; + static const ctype_base::mask _Ch_alpha = ctype_base::alpha; + + // Unsigned char_type. msvcrt statically asserts the _Uelem type is + // unsigned, so we specialize is_unsigned as well (see below). + // + using _Uelem = char_type; +#endif + + regex_traits () = default; // Unnecessary but let's keep for completeness. + + static size_t + length (const char_type* p) {return string_type::traits_type::length (p);} + + char_type + translate (char_type c) const {return c;} + + // Case-insensitive matching is not supported by line_regex. So there is no + // reason for the function to be called. + // + char_type + translate_nocase (char_type c) const {assert (false); return c;} + + // Return a sort-key - the exact copy of [b, e). + // + template + string_type + transform (I b, I e) const {return string_type (b, e);} + + // Return a case-insensitive sort-key. Case-insensitive matching is not + // supported by line_regex. So there is no reason for the function to be + // called. + // + template + string_type + transform_primary (I b, I e) const + { + assert (false); + return string_type (b, e); + } + + // POSIX regex grammar and collating elements (e.g., [.tilde.]) in + // particular are not supported. So there is no reason for the function to + // be called. + // + template + string_type + lookup_collatename (I, I) const {assert (false); return string_type ();} + + // Character classes (e.g., [:lower:]) are not supported. So there is no + // reason for the function to be called. + // + template + char_class_type + lookup_classname (I, I, bool = false) const + { + assert (false); + return char_class_type (); + } + + // Return false as we don't support character classes (e.g., [:lower:]). + // + bool + isctype (char_type, char_class_type) const {return false;} + + int + value (char_type, int) const; + + // Return the locale passed as an argument as we do not expect anything + // other than POSIX locale, that we also assume to be imbued by default. + // + locale_type + imbue (locale_type l) {return l;} + + locale_type + getloc () const {return locale_type ();} + }; + + // We assume line_char to be an unsigned type and express that with the + // following specializations used by basic_regex implementations. + // + // libstdc++ defines unsigned CharT type (regex_traits template parameter) + // to use as an index in some internal cache regardless if the cache is used + // for this specialization (and the cache is used only if CharT is char). + // + template <> + struct make_unsigned + { + using type = build2::test::script::regex::line_char; + }; + + // msvcrt assumes regex_traits::_Uelem to be present (see above) + // and statically asserts it is unsigned. + // + template <> + struct is_unsigned + { + static const bool value = true; + }; + + // When used with libc++ the linker complains that it can't find + // __match_any_but_newline::__exec() function. The problem is + // that the function is only specialized for char and wchar_t + // (LLVM bug #31409). As line_char has no notion of the newline character we + // specialize the class template to behave as the __match_any + // instantiation does (that luckily has all the functions in place). + // +#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 4000 + template <> + class __match_any_but_newline + : public __match_any + { + public: + using base = __match_any; + using base::base; + }; +#endif +} + +namespace build2 +{ + namespace test + { + namespace script + { + namespace regex + { + class line_regex: public std::basic_regex + { + public: + using base_type = std::basic_regex; + + using base_type::base_type; + + line_regex () = default; + + // Move string regex together with the pool used to create it. + // + line_regex (line_string&& s, line_pool&& p) + // No move-string ctor for base_type, so emulate it. + // + : base_type (s), pool (move (p)) {s.clear ();} + + // Move constuctible/assignable-only type. + // + line_regex (line_regex&&) = default; + line_regex (const line_regex&) = delete; + line_regex& operator= (line_regex&&) = default; + line_regex& operator= (const line_regex&) = delete; + + public: + line_pool pool; + }; + } + } + } +} + +#include + +#endif // BUILD2_TEST_SCRIPT_REGEX_HXX diff --git a/build2/test/script/runner b/build2/test/script/runner deleted file mode 100644 index 566b9de..0000000 --- a/build2/test/script/runner +++ /dev/null @@ -1,90 +0,0 @@ -// file : build2/test/script/runner -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_RUNNER -#define BUILD2_TEST_SCRIPT_RUNNER - -#include -#include - -#include // location - -#include - -namespace build2 -{ - namespace test - { - struct common; - - namespace script - { - class runner - { - public: - // Return false if this test/group should be skipped. - // - virtual bool - test (scope&) const = 0; - - // Location is the scope start location (for diagnostics, etc). - // - virtual void - enter (scope&, const location&) = 0; - - // Index is the 1-base index of this command line in the command list - // (e.g., in a compound test). If it is 0 then it means there is only - // one command (e.g., a simple test). This information can be used, - // for example, to derive file names. - // - // Location is the start position of this command line in the - // testscript. It can be used in diagnostics. - // - virtual void - run (scope&, - const command_expr&, command_type, - size_t index, - const location&) = 0; - - virtual bool - run_if (scope&, const command_expr&, size_t, const location&) = 0; - - // Location is the scope end location (for diagnostics, etc). - // - virtual void - leave (scope&, const location&) = 0; - }; - - class default_runner: public runner - { - public: - explicit - default_runner (const common& c): common_ (c) {} - - virtual bool - test (scope& s) const override; - - virtual void - enter (scope&, const location&) override; - - virtual void - run (scope&, - const command_expr&, command_type, - size_t, - const location&) override; - - virtual bool - run_if (scope&, const command_expr&, size_t, const location&) override; - - virtual void - leave (scope&, const location&) override; - - private: - const common& common_; - }; - } - } -} - -#endif // BUILD2_TEST_SCRIPT_RUNNER diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx index 105039a..ce32150 100644 --- a/build2/test/script/runner.cxx +++ b/build2/test/script/runner.cxx @@ -2,22 +2,22 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include #include // streamsize -#include // fdopen_mode, fdnull(), fddup() +#include // fdopen_mode, fdnull(), fddup() -#include -#include -#include +#include +#include +#include -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/test/script/runner.hxx b/build2/test/script/runner.hxx new file mode 100644 index 0000000..7833692 --- /dev/null +++ b/build2/test/script/runner.hxx @@ -0,0 +1,90 @@ +// file : build2/test/script/runner.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_RUNNER_HXX +#define BUILD2_TEST_SCRIPT_RUNNER_HXX + +#include +#include + +#include // location + +#include + +namespace build2 +{ + namespace test + { + struct common; + + namespace script + { + class runner + { + public: + // Return false if this test/group should be skipped. + // + virtual bool + test (scope&) const = 0; + + // Location is the scope start location (for diagnostics, etc). + // + virtual void + enter (scope&, const location&) = 0; + + // Index is the 1-base index of this command line in the command list + // (e.g., in a compound test). If it is 0 then it means there is only + // one command (e.g., a simple test). This information can be used, + // for example, to derive file names. + // + // Location is the start position of this command line in the + // testscript. It can be used in diagnostics. + // + virtual void + run (scope&, + const command_expr&, command_type, + size_t index, + const location&) = 0; + + virtual bool + run_if (scope&, const command_expr&, size_t, const location&) = 0; + + // Location is the scope end location (for diagnostics, etc). + // + virtual void + leave (scope&, const location&) = 0; + }; + + class default_runner: public runner + { + public: + explicit + default_runner (const common& c): common_ (c) {} + + virtual bool + test (scope& s) const override; + + virtual void + enter (scope&, const location&) override; + + virtual void + run (scope&, + const command_expr&, command_type, + size_t, + const location&) override; + + virtual bool + run_if (scope&, const command_expr&, size_t, const location&) override; + + virtual void + leave (scope&, const location&) override; + + private: + const common& common_; + }; + } + } +} + +#endif // BUILD2_TEST_SCRIPT_RUNNER_HXX diff --git a/build2/test/script/script b/build2/test/script/script deleted file mode 100644 index 9a54c71..0000000 --- a/build2/test/script/script +++ /dev/null @@ -1,552 +0,0 @@ -// file : build2/test/script/script -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_SCRIPT -#define BUILD2_TEST_SCRIPT_SCRIPT - -#include - -#include -#include - -#include - -#include - -#include // replay_tokens - -namespace build2 -{ - class target; - - namespace test - { - namespace script - { - class parser; // Required by VC for 'friend class parser' declaration. - - // Pre-parse representation. - // - - enum class line_type - { - var, - cmd, - cmd_if, - cmd_ifn, - cmd_elif, - cmd_elifn, - cmd_else, - cmd_end - }; - - ostream& - operator<< (ostream&, line_type); - - struct line - { - line_type type; - replay_tokens tokens; - - union - { - const variable* var; // Pre-entered for line_type::var. - }; - }; - - // Most of the time we will have just one line (test command). - // - using lines = small_vector; - - // Parse object model. - // - - // redirect - // - enum class redirect_type - { - none, - pass, - null, - trace, - merge, - here_str_literal, - here_str_regex, - here_doc_literal, - here_doc_regex, - here_doc_ref, // Reference to here_doc literal or regex. - file, - }; - - // Pre-parsed (but not instantiated) regex lines. The idea here is that - // we should be able to re-create their (more or less) exact text - // representation for diagnostics but also instantiate without any - // re-parsing. - // - struct regex_line - { - // If regex is true, then value is the regex expression. Otherwise, it - // is a literal. Note that special characters can be present in both - // cases. For example, //+ is a regex, while /+ is a literal, both - // with '+' as a special character. Flags are only valid for regex. - // Literals falls apart into textual (has no special characters) and - // special (has just special characters instead) ones. For example - // foo is a textual literal, while /.+ is a special one. Note that - // literal must not have value and special both non-empty. - // - bool regex; - - string value; - string flags; - string special; - - uint64_t line; - uint64_t column; - - // Create regex with optional special characters. - // - regex_line (uint64_t l, uint64_t c, - string v, string f, string s = string ()) - : regex (true), - value (move (v)), - flags (move (f)), - special (move (s)), - line (l), - column (c) {} - - // Create a literal, either text or special. - // - regex_line (uint64_t l, uint64_t c, string v, bool s) - : regex (false), - value (s ? string () : move (v)), - special (s ? move (v) : string ()), - line (l), - column (c) {} - }; - - struct regex_lines - { - char intro; // Introducer character. - string flags; // Global flags (here-document). - - small_vector lines; - }; - - // Output file redirect mode. - // - enum class redirect_fmode - { - compare, - overwrite, - append - }; - - struct redirect - { - redirect_type type; - - struct file_type - { - using path_type = build2::path; - path_type path; - redirect_fmode mode; // Meaningless for input redirect. - }; - - union - { - int fd; // Merge-to descriptor. - string str; // Note: with trailing newline, if requested. - regex_lines regex; // Note: with trailing blank, if requested. - file_type file; - reference_wrapper ref; // Note: no chains. - }; - - string modifiers; // Redirect modifiers. - string end; // Here-document end marker (no regex intro/flags). - uint64_t end_line; // Here-document end marker location. - uint64_t end_column; - - // Create redirect of a type other than reference. - // - explicit - redirect (redirect_type = redirect_type::none); - - // Create redirect of the reference type. - // - redirect (redirect_type t, const redirect& r) - : type (redirect_type::here_doc_ref), ref (r) - { - // There is no support (and need) for reference chains. - // - assert (t == redirect_type::here_doc_ref && - r.type != redirect_type::here_doc_ref); - } - - // Move constuctible/assignable-only type. - // - redirect (redirect&&); - redirect& operator= (redirect&&); - - ~redirect (); - - const redirect& - effective () const noexcept - { - return type == redirect_type::here_doc_ref ? ref.get () : *this; - } - }; - - // cleanup - // - enum class cleanup_type - { - always, // &foo - cleanup, fail if does not exist. - maybe, // &?foo - cleanup, ignore if does not exist. - never // &!foo - don’t cleanup, ignore if doesn’t exist. - }; - - // File or directory to be automatically cleaned up at the end of the - // scope. If the path ends with a trailing slash, then it is assumed to - // be a directory, otherwise -- a file. A directory that is about to be - // cleaned up must be empty. - // - // The last component in the path may contain a wildcard that have the - // following semantics: - // - // dir/* - remove all immediate files - // dir/*/ - remove all immediate sub-directories (must be empty) - // dir/** - remove all files recursively - // dir/**/ - remove all sub-directories recursively (must be empty) - // dir/*** - remove directory dir with all files and sub-directories - // recursively - // - struct cleanup - { - cleanup_type type; - build2::path path; - }; - using cleanups = vector; - - // command_exit - // - enum class exit_comparison {eq, ne}; - - struct command_exit - { - // C/C++ don't apply constraints on program exit code other than it - // being of type int. - // - // POSIX specifies that only the least significant 8 bits shall be - // available from wait() and waitpid(); the full value shall be - // available from waitid() (read more at _Exit, _exit Open Group - // spec). - // - // While the Linux man page for waitid() doesn't mention any - // deviations from the standard, the FreeBSD implementation (as of - // version 11.0) only returns 8 bits like the other wait*() calls. - // - // Windows supports 32-bit exit codes. - // - // Note that in shells some exit values can have special meaning so - // using them can be a source of confusion. For bash values in the - // [126, 255] range are such a special ones (see Appendix E, "Exit - // Codes With Special Meanings" in the Advanced Bash-Scripting Guide). - // - exit_comparison comparison; - uint8_t status; - }; - - // command - // - struct command - { - path program; - strings arguments; - - redirect in; - redirect out; - redirect err; - - script::cleanups cleanups; - - command_exit exit {exit_comparison::eq, 0}; - }; - - enum class command_to_stream: uint16_t - { - header = 0x01, - here_doc = 0x02, // Note: printed on a new line. - all = header | here_doc - }; - - void - to_stream (ostream&, const command&, command_to_stream); - - ostream& - operator<< (ostream&, const command&); - - // command_pipe - // - using command_pipe = vector; - - void - to_stream (ostream&, const command_pipe&, command_to_stream); - - ostream& - operator<< (ostream&, const command_pipe&); - - // command_expr - // - enum class expr_operator {log_or, log_and}; - - struct expr_term - { - expr_operator op; // OR-ed to an implied false for the first term. - command_pipe pipe; - }; - - using command_expr = vector; - - void - to_stream (ostream&, const command_expr&, command_to_stream); - - ostream& - operator<< (ostream&, const command_expr&); - - // command_type - // - enum class command_type {test, setup, teardown}; - - ostream& - operator<< (ostream&, command_type); - - // description - // - struct description - { - string id; - string summary; - string details; - - bool - empty () const - { - return id.empty () && summary.empty () && details.empty (); - } - }; - - // scope - // - class script; - - enum class scope_state {unknown, passed, failed}; - - class scope - { - public: - scope* const parent; // NULL for the root (script) scope. - script* const root; // Self for the root (script) scope. - - // The chain of if-else scope alternatives. See also if_cond_ below. - // - unique_ptr if_chain; - - // Note that if we pass the variable name as a string, then it will - // be looked up in the wrong pool. - // - variable_map vars; - - const path& id_path; // Id path ($@, relative in POSIX form). - const dir_path& wd_path; // Working dir ($~, absolute and normalized). - - optional desc; - - scope_state state = scope_state::unknown; - test::script::cleanups cleanups; - - // Variables. - // - public: - // Lookup the variable starting from this scope, continuing with outer - // scopes, then the target being tested, then the testscript target, - // and then outer buildfile scopes (including testscript-type/pattern - // specific). - // - lookup - find (const variable&) const; - - // As above but only look for buildfile variables. If target_only is - // false then also look in scopes of the test target (this should only - // be done if the variable's visibility is target). - // - lookup - find_in_buildfile (const string&, bool target_only = true) const; - - // Return a value suitable for assignment. If the variable does not - // exist in this scope's map, then a new one with the NULL value is - // added and returned. Otherwise the existing value is returned. - // - value& - assign (const variable& var) {return vars.assign (var);} - - // Return a value suitable for append/prepend. If the variable does - // not exist in this scope's map, then outer scopes are searched for - // the same variable. If found then a new variable with the found - // value is added to this scope and returned. Otherwise this function - // proceeds as assign() above. - // - value& - append (const variable&); - - // Reset special $*, $N variables based on the test.* values. - // - void - reset_special (); - - // Cleanup. - // - public: - // Register a cleanup. If the cleanup is explicit, then override the - // cleanup type if this path is already registered. Ignore implicit - // registration of a path outside script working directory. - // - void - clean (cleanup, bool implicit); - - public: - virtual - ~scope () = default; - - protected: - scope (const string& id, scope* parent); - - // Pre-parse data. - // - public: - virtual bool - empty () const = 0; - - protected: - friend class parser; - - location start_loc_; - location end_loc_; - - optional if_cond_; - }; - - // group - // - class group: public scope - { - public: - vector> scopes; - - public: - group (const string& id, group& p): scope (id, &p) {} - - protected: - group (const string& id): scope (id, nullptr) {} // For root. - - // Pre-parse data. - // - public: - virtual bool - empty () const override - { - return - setup_.empty () && - tdown_.empty () && - find_if (scopes.begin (), scopes.end (), - [] (const unique_ptr& s) - { - return !s->empty (); - }) == scopes.end (); - } - - private: - friend class parser; - - lines setup_; - lines tdown_; - }; - - // test - // - class test: public scope - { - public: - test (const string& id, group& p): scope (id, &p) {} - - // Pre-parse data. - // - public: - virtual bool - empty () const override - { - return tests_.empty (); - } - - private: - friend class parser; - - lines tests_; - }; - - // script - // - class script_base // Make sure certain things are initialized early. - { - protected: - script_base (); - - public: - variable_pool var_pool; - mutable shared_mutex var_pool_mutex; - - const variable& test_var; // test - const variable& options_var; // test.options - const variable& arguments_var; // test.arguments - const variable& redirects_var; // test.redirects - const variable& cleanups_var; // test.cleanups - - const variable& wd_var; // $~ - const variable& id_var; // $@ - const variable& cmd_var; // $* - const variable* cmdN_var[10]; // $N - }; - - class script: public script_base, public group - { - public: - script (const target& test_target, - const testscript& script_target, - const dir_path& root_wd); - - script (script&&) = delete; - script (const script&) = delete; - script& operator= (script&&) = delete; - script& operator= (const script&) = delete; - - public: - const target& test_target; // Target we are testing. - const testscript& script_target; // Target of the testscript file. - - // Pre-parse data. - // - private: - friend class parser; - - // Testscript file paths. Specifically, replay_token::file points to - // these paths. - // - std::set paths_; - }; - } - } -} - -#include - -#endif // BUILD2_TEST_SCRIPT_SCRIPT diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx index d4945fe..25f72af 100644 --- a/build2/test/script/script.cxx +++ b/build2/test/script/script.cxx @@ -2,13 +2,13 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include #include // find() -#include -#include +#include +#include using namespace std; diff --git a/build2/test/script/script.hxx b/build2/test/script/script.hxx new file mode 100644 index 0000000..4f6fdab --- /dev/null +++ b/build2/test/script/script.hxx @@ -0,0 +1,552 @@ +// file : build2/test/script/script.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_SCRIPT_HXX +#define BUILD2_TEST_SCRIPT_SCRIPT_HXX + +#include + +#include +#include + +#include + +#include + +#include // replay_tokens + +namespace build2 +{ + class target; + + namespace test + { + namespace script + { + class parser; // Required by VC for 'friend class parser' declaration. + + // Pre-parse representation. + // + + enum class line_type + { + var, + cmd, + cmd_if, + cmd_ifn, + cmd_elif, + cmd_elifn, + cmd_else, + cmd_end + }; + + ostream& + operator<< (ostream&, line_type); + + struct line + { + line_type type; + replay_tokens tokens; + + union + { + const variable* var; // Pre-entered for line_type::var. + }; + }; + + // Most of the time we will have just one line (test command). + // + using lines = small_vector; + + // Parse object model. + // + + // redirect + // + enum class redirect_type + { + none, + pass, + null, + trace, + merge, + here_str_literal, + here_str_regex, + here_doc_literal, + here_doc_regex, + here_doc_ref, // Reference to here_doc literal or regex. + file, + }; + + // Pre-parsed (but not instantiated) regex lines. The idea here is that + // we should be able to re-create their (more or less) exact text + // representation for diagnostics but also instantiate without any + // re-parsing. + // + struct regex_line + { + // If regex is true, then value is the regex expression. Otherwise, it + // is a literal. Note that special characters can be present in both + // cases. For example, //+ is a regex, while /+ is a literal, both + // with '+' as a special character. Flags are only valid for regex. + // Literals falls apart into textual (has no special characters) and + // special (has just special characters instead) ones. For example + // foo is a textual literal, while /.+ is a special one. Note that + // literal must not have value and special both non-empty. + // + bool regex; + + string value; + string flags; + string special; + + uint64_t line; + uint64_t column; + + // Create regex with optional special characters. + // + regex_line (uint64_t l, uint64_t c, + string v, string f, string s = string ()) + : regex (true), + value (move (v)), + flags (move (f)), + special (move (s)), + line (l), + column (c) {} + + // Create a literal, either text or special. + // + regex_line (uint64_t l, uint64_t c, string v, bool s) + : regex (false), + value (s ? string () : move (v)), + special (s ? move (v) : string ()), + line (l), + column (c) {} + }; + + struct regex_lines + { + char intro; // Introducer character. + string flags; // Global flags (here-document). + + small_vector lines; + }; + + // Output file redirect mode. + // + enum class redirect_fmode + { + compare, + overwrite, + append + }; + + struct redirect + { + redirect_type type; + + struct file_type + { + using path_type = build2::path; + path_type path; + redirect_fmode mode; // Meaningless for input redirect. + }; + + union + { + int fd; // Merge-to descriptor. + string str; // Note: with trailing newline, if requested. + regex_lines regex; // Note: with trailing blank, if requested. + file_type file; + reference_wrapper ref; // Note: no chains. + }; + + string modifiers; // Redirect modifiers. + string end; // Here-document end marker (no regex intro/flags). + uint64_t end_line; // Here-document end marker location. + uint64_t end_column; + + // Create redirect of a type other than reference. + // + explicit + redirect (redirect_type = redirect_type::none); + + // Create redirect of the reference type. + // + redirect (redirect_type t, const redirect& r) + : type (redirect_type::here_doc_ref), ref (r) + { + // There is no support (and need) for reference chains. + // + assert (t == redirect_type::here_doc_ref && + r.type != redirect_type::here_doc_ref); + } + + // Move constuctible/assignable-only type. + // + redirect (redirect&&); + redirect& operator= (redirect&&); + + ~redirect (); + + const redirect& + effective () const noexcept + { + return type == redirect_type::here_doc_ref ? ref.get () : *this; + } + }; + + // cleanup + // + enum class cleanup_type + { + always, // &foo - cleanup, fail if does not exist. + maybe, // &?foo - cleanup, ignore if does not exist. + never // &!foo - don’t cleanup, ignore if doesn’t exist. + }; + + // File or directory to be automatically cleaned up at the end of the + // scope. If the path ends with a trailing slash, then it is assumed to + // be a directory, otherwise -- a file. A directory that is about to be + // cleaned up must be empty. + // + // The last component in the path may contain a wildcard that have the + // following semantics: + // + // dir/* - remove all immediate files + // dir/*/ - remove all immediate sub-directories (must be empty) + // dir/** - remove all files recursively + // dir/**/ - remove all sub-directories recursively (must be empty) + // dir/*** - remove directory dir with all files and sub-directories + // recursively + // + struct cleanup + { + cleanup_type type; + build2::path path; + }; + using cleanups = vector; + + // command_exit + // + enum class exit_comparison {eq, ne}; + + struct command_exit + { + // C/C++ don't apply constraints on program exit code other than it + // being of type int. + // + // POSIX specifies that only the least significant 8 bits shall be + // available from wait() and waitpid(); the full value shall be + // available from waitid() (read more at _Exit, _exit Open Group + // spec). + // + // While the Linux man page for waitid() doesn't mention any + // deviations from the standard, the FreeBSD implementation (as of + // version 11.0) only returns 8 bits like the other wait*() calls. + // + // Windows supports 32-bit exit codes. + // + // Note that in shells some exit values can have special meaning so + // using them can be a source of confusion. For bash values in the + // [126, 255] range are such a special ones (see Appendix E, "Exit + // Codes With Special Meanings" in the Advanced Bash-Scripting Guide). + // + exit_comparison comparison; + uint8_t status; + }; + + // command + // + struct command + { + path program; + strings arguments; + + redirect in; + redirect out; + redirect err; + + script::cleanups cleanups; + + command_exit exit {exit_comparison::eq, 0}; + }; + + enum class command_to_stream: uint16_t + { + header = 0x01, + here_doc = 0x02, // Note: printed on a new line. + all = header | here_doc + }; + + void + to_stream (ostream&, const command&, command_to_stream); + + ostream& + operator<< (ostream&, const command&); + + // command_pipe + // + using command_pipe = vector; + + void + to_stream (ostream&, const command_pipe&, command_to_stream); + + ostream& + operator<< (ostream&, const command_pipe&); + + // command_expr + // + enum class expr_operator {log_or, log_and}; + + struct expr_term + { + expr_operator op; // OR-ed to an implied false for the first term. + command_pipe pipe; + }; + + using command_expr = vector; + + void + to_stream (ostream&, const command_expr&, command_to_stream); + + ostream& + operator<< (ostream&, const command_expr&); + + // command_type + // + enum class command_type {test, setup, teardown}; + + ostream& + operator<< (ostream&, command_type); + + // description + // + struct description + { + string id; + string summary; + string details; + + bool + empty () const + { + return id.empty () && summary.empty () && details.empty (); + } + }; + + // scope + // + class script; + + enum class scope_state {unknown, passed, failed}; + + class scope + { + public: + scope* const parent; // NULL for the root (script) scope. + script* const root; // Self for the root (script) scope. + + // The chain of if-else scope alternatives. See also if_cond_ below. + // + unique_ptr if_chain; + + // Note that if we pass the variable name as a string, then it will + // be looked up in the wrong pool. + // + variable_map vars; + + const path& id_path; // Id path ($@, relative in POSIX form). + const dir_path& wd_path; // Working dir ($~, absolute and normalized). + + optional desc; + + scope_state state = scope_state::unknown; + test::script::cleanups cleanups; + + // Variables. + // + public: + // Lookup the variable starting from this scope, continuing with outer + // scopes, then the target being tested, then the testscript target, + // and then outer buildfile scopes (including testscript-type/pattern + // specific). + // + lookup + find (const variable&) const; + + // As above but only look for buildfile variables. If target_only is + // false then also look in scopes of the test target (this should only + // be done if the variable's visibility is target). + // + lookup + find_in_buildfile (const string&, bool target_only = true) const; + + // Return a value suitable for assignment. If the variable does not + // exist in this scope's map, then a new one with the NULL value is + // added and returned. Otherwise the existing value is returned. + // + value& + assign (const variable& var) {return vars.assign (var);} + + // Return a value suitable for append/prepend. If the variable does + // not exist in this scope's map, then outer scopes are searched for + // the same variable. If found then a new variable with the found + // value is added to this scope and returned. Otherwise this function + // proceeds as assign() above. + // + value& + append (const variable&); + + // Reset special $*, $N variables based on the test.* values. + // + void + reset_special (); + + // Cleanup. + // + public: + // Register a cleanup. If the cleanup is explicit, then override the + // cleanup type if this path is already registered. Ignore implicit + // registration of a path outside script working directory. + // + void + clean (cleanup, bool implicit); + + public: + virtual + ~scope () = default; + + protected: + scope (const string& id, scope* parent); + + // Pre-parse data. + // + public: + virtual bool + empty () const = 0; + + protected: + friend class parser; + + location start_loc_; + location end_loc_; + + optional if_cond_; + }; + + // group + // + class group: public scope + { + public: + vector> scopes; + + public: + group (const string& id, group& p): scope (id, &p) {} + + protected: + group (const string& id): scope (id, nullptr) {} // For root. + + // Pre-parse data. + // + public: + virtual bool + empty () const override + { + return + setup_.empty () && + tdown_.empty () && + find_if (scopes.begin (), scopes.end (), + [] (const unique_ptr& s) + { + return !s->empty (); + }) == scopes.end (); + } + + private: + friend class parser; + + lines setup_; + lines tdown_; + }; + + // test + // + class test: public scope + { + public: + test (const string& id, group& p): scope (id, &p) {} + + // Pre-parse data. + // + public: + virtual bool + empty () const override + { + return tests_.empty (); + } + + private: + friend class parser; + + lines tests_; + }; + + // script + // + class script_base // Make sure certain things are initialized early. + { + protected: + script_base (); + + public: + variable_pool var_pool; + mutable shared_mutex var_pool_mutex; + + const variable& test_var; // test + const variable& options_var; // test.options + const variable& arguments_var; // test.arguments + const variable& redirects_var; // test.redirects + const variable& cleanups_var; // test.cleanups + + const variable& wd_var; // $~ + const variable& id_var; // $@ + const variable& cmd_var; // $* + const variable* cmdN_var[10]; // $N + }; + + class script: public script_base, public group + { + public: + script (const target& test_target, + const testscript& script_target, + const dir_path& root_wd); + + script (script&&) = delete; + script (const script&) = delete; + script& operator= (script&&) = delete; + script& operator= (const script&) = delete; + + public: + const target& test_target; // Target we are testing. + const testscript& script_target; // Target of the testscript file. + + // Pre-parse data. + // + private: + friend class parser; + + // Testscript file paths. Specifically, replay_token::file points to + // these paths. + // + std::set paths_; + }; + } + } +} + +#include + +#endif // BUILD2_TEST_SCRIPT_SCRIPT_HXX diff --git a/build2/test/script/token b/build2/test/script/token deleted file mode 100644 index 9d3e330..0000000 --- a/build2/test/script/token +++ /dev/null @@ -1,65 +0,0 @@ -// file : build2/test/script/token -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_TOKEN -#define BUILD2_TEST_SCRIPT_TOKEN - -#include -#include - -#include - -namespace build2 -{ - namespace test - { - namespace script - { - struct token_type: build2::token_type - { - using base_type = build2::token_type; - - enum - { - // NOTE: remember to update token_printer()! - - semi = base_type::value_next, // ; - - dot, // . - - plus, // + - minus, // - - - pipe, // | - clean, // &{?!} (modifiers in value) - - in_pass, // <| - in_null, // <- - in_str, // <{:} (modifiers in value) - in_doc, // <<{:} (modifiers in value) - in_file, // <<< - - out_pass, // >| - out_null, // >- - out_trace, // >! - out_merge, // >& - out_str, // >{:~} (modifiers in value) - out_doc, // >>{:~} (modifiers in value) - out_file_cmp, // >>> - out_file_ovr, // >= - out_file_app // >+ - }; - - token_type () = default; - token_type (value_type v): base_type (v) {} - token_type (base_type v): base_type (v) {} - }; - - void - token_printer (ostream&, const token&, bool); - } - } -} - -#endif // BUILD2_TEST_SCRIPT_TOKEN diff --git a/build2/test/script/token.cxx b/build2/test/script/token.cxx index 3d3dc8e..42428c6 100644 --- a/build2/test/script/token.cxx +++ b/build2/test/script/token.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/test/script/token.hxx b/build2/test/script/token.hxx new file mode 100644 index 0000000..0e8a577 --- /dev/null +++ b/build2/test/script/token.hxx @@ -0,0 +1,65 @@ +// file : build2/test/script/token.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_TOKEN_HXX +#define BUILD2_TEST_SCRIPT_TOKEN_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace test + { + namespace script + { + struct token_type: build2::token_type + { + using base_type = build2::token_type; + + enum + { + // NOTE: remember to update token_printer()! + + semi = base_type::value_next, // ; + + dot, // . + + plus, // + + minus, // - + + pipe, // | + clean, // &{?!} (modifiers in value) + + in_pass, // <| + in_null, // <- + in_str, // <{:} (modifiers in value) + in_doc, // <<{:} (modifiers in value) + in_file, // <<< + + out_pass, // >| + out_null, // >- + out_trace, // >! + out_merge, // >& + out_str, // >{:~} (modifiers in value) + out_doc, // >>{:~} (modifiers in value) + out_file_cmp, // >>> + out_file_ovr, // >= + out_file_app // >+ + }; + + token_type () = default; + token_type (value_type v): base_type (v) {} + token_type (base_type v): base_type (v) {} + }; + + void + token_printer (ostream&, const token&, bool); + } + } +} + +#endif // BUILD2_TEST_SCRIPT_TOKEN_HXX -- cgit v1.1