diff options
Diffstat (limited to 'libbuild2/parser.hxx')
-rw-r--r-- | libbuild2/parser.hxx | 323 |
1 files changed, 234 insertions, 89 deletions
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx index 1e924f0..3e1d0a0 100644 --- a/libbuild2/parser.hxx +++ b/libbuild2/parser.hxx @@ -4,6 +4,10 @@ #ifndef LIBBUILD2_PARSER_HXX #define LIBBUILD2_PARSER_HXX +#include <exception> // uncaught_exception[s]() + +#include <libbutl/ft/exception.hxx> // uncaught_exceptions + #include <libbuild2/types.hxx> #include <libbuild2/forward.hxx> #include <libbuild2/utility.hxx> @@ -44,9 +48,19 @@ namespace build2 explicit parser (context& c, stage s = stage::rest) : fail ("error", &path_), info ("info", &path_), - ctx (c), + ctx (&c), stage_ (s) {} + // Pattern expansion mode. + // + enum class pattern_mode + { + ignore, // Treat as literals. + preserve, // Preserve as name pattern. + expand, // Expand to non-pattern names. + detect // Implementation detail mode (see code for more information). + }; + // Issue diagnostics and throw failed in case of an error. // void @@ -55,14 +69,20 @@ namespace build2 scope* root, scope& base, target* = nullptr, - prerequisite* = nullptr); + prerequisite* = nullptr, + bool enter_buildfile = true); void parse_buildfile (lexer&, scope* root, scope& base, target* = nullptr, - prerequisite* = nullptr); + prerequisite* = nullptr, + bool enter_buildfile = true); + + names + parse_export_stub (istream& is, const path_name& name, + const scope& rs, scope& gs, scope& ts); buildspec parse_buildspec (istream&, const path_name&); @@ -73,12 +93,10 @@ namespace build2 pair<value, token> parse_variable_value (lexer&, scope&, const dir_path*, const variable&); - names - parse_export_stub (istream& is, const path_name& name, scope& r, scope& b) - { - parse_buildfile (is, name, &r, b); - return move (export_value); - } + // Parse an evaluation context (`(...)`). + // + value + parse_eval (lexer&, scope& rs, scope& bs, pattern_mode); // The above functions may be called again on the same parser instance // after a reset. @@ -86,6 +104,25 @@ namespace build2 void reset (); + // Special, context-less mode that can only be used to parse literal + // names. + // + public: + static const string name_separators; + + explicit + parser (context* c) + : fail ("error", &path_), info ("info", &path_), + ctx (c), + stage_ (stage::rest) {} + + names + parse_names (lexer&, + const dir_path* base, + pattern_mode pmode, + const char* what = "name", + const string* separators = &name_separators); + // Ad hoc parsing results for some cases. // // Note that these are not touched by reset(). @@ -97,21 +134,34 @@ namespace build2 // config directive result. // - vector<pair<lookup, string>> config_report; // Config value and format. - bool config_report_new = false; // One of values is new. + struct config_report + { + struct value + { + lookup val; // Value. + string fmt; // Format. + string org; // Original variable if config.report.variable. + }; + + project_name module; // Reporting module name. + vector<value> values; + bool new_value; // One of values is new. + }; + small_vector<config_report, 1> config_reports; - // Recursive descent parser. + // Misc utilities. // - protected: + public: + // Return the value type corresponding to the type name or NULL if the + // type name is unknown. Pass project's root scope if known. + // + static const value_type* + find_value_type (const scope* rs, const string& name); - // Pattern expansion mode. + // Recursive descent parser. // - enum class pattern_mode - { - ignore, // Treat as ordinary names. - detect, // Ignore pair/dir/type if the first name is a pattern. - expand // Expand to ordinary names. - }; + protected: + using pattern_type = name::pattern_type; // If one is true then parse a single (logical) line (logical means it // can actually be several lines, e.g., an if-block). Return false if @@ -128,35 +178,53 @@ namespace build2 void parse_variable_block (token&, token_type&, - const target_type* = nullptr, - string = string ()); + optional<pattern_type> = {}, + const target_type* = nullptr, + string = {}, + const location& = {}); void parse_recipe (token&, token_type&, const token&, - small_vector<shared_ptr<adhoc_rule>, 1>&); + small_vector<shared_ptr<adhoc_rule>, 1>&, + const target_type* = nullptr, + const string& = {}); - // Ad hoc target names inside < ... >. + // Group target names inside < ... >. // - struct adhoc_names_loc + struct group_names_loc { + bool expl = false; // True -- explicit group, fase -- ad hoc. + location group_loc; // Group/primary target location. + location member_loc; // Members location. names ns; - location loc; }; - using adhoc_names = small_vector<adhoc_names_loc, 1>; + using group_names = small_vector<group_names_loc, 1>; - void - enter_adhoc_members (adhoc_names_loc&&, bool); + vector<reference_wrapper<target>> + enter_explicit_members (group_names_loc&&, bool); + + vector<reference_wrapper<target>> + enter_adhoc_members (group_names_loc&&, bool); - small_vector<reference_wrapper<target>, 1> - enter_targets (names&&, const location&, adhoc_names&&, size_t); + small_vector<pair<reference_wrapper<target>, // Target. + vector<reference_wrapper<target>>>, // Ad hoc members. + 1> + enter_targets (names&&, const location&, + group_names&&, + size_t, + const attributes&); + + void + apply_target_attributes (target&, const attributes&); void parse_dependency (token&, token_type&, names&&, const location&, - adhoc_names&&, - names&&, const location&); + group_names&&, + names&&, const location&, + const attributes&); void parse_assert (token&, token_type&); @@ -183,6 +251,9 @@ namespace build2 parse_config (token&, token_type&); void + parse_config_environment (token&, token_type&); + + void parse_import (token&, token_type&); void @@ -201,7 +272,9 @@ namespace build2 parse_if_else (token&, token_type&, bool, const function<void ( - token&, token_type&, bool, const string&)>&); + token&, token_type&, bool, const string&)>&, + const function<void ( + token&, token_type&, const string&)>&); void parse_switch (token&, token_type&); @@ -210,7 +283,9 @@ namespace build2 parse_switch (token&, token_type&, bool, const function<void ( - token&, token_type&, bool, const string&)>&); + token&, token_type&, bool, const string&)>&, + const function<void ( + token&, token_type&, const string&)>&); void parse_for (token&, token_type&); @@ -219,17 +294,25 @@ namespace build2 parse_variable (token&, token_type&, const variable&, token_type); void - parse_type_pattern_variable (token&, token_type&, - const target_type&, string, - const variable&, token_type, const location&); + parse_type_pattern_variable ( + token&, token_type&, + pattern_type, const target_type&, string, const location&, + const variable&, token_type, const location&); + + const variable& + parse_variable_name (string&&, const location&); const variable& parse_variable_name (names&&, const location&); // Note: calls attributes_push() that the caller must pop. // + // If mode is false, assume the appropriate mode has already been switched + // to (value, `@` as pair separator, with attributes recognition). This + // can be useful, for example, if need to call peek(). + // value - parse_variable_value (token&, token_type&); + parse_variable_value (token&, token_type&, bool mode = true); void apply_variable_attributes (const variable&); @@ -280,15 +363,25 @@ namespace build2 // Push a new entry into the attributes_ stack. If the next token is `[` // then parse the attribute sequence until ']' storing the result in the - // new stack entry. Then get the next token and, if standalone is false, - // verify it is not newline/eos (i.e., there is something after it). - // Return the indication of whether we have seen `[` (even if it's the - // `[]` empty list) and its location. + // new stack entry. Then, if next_token is true, get the next token and, + // if standalone is false, verify it is not newline/eos (i.e., there is + // something after it). If the next token is read and it is a word or a + // "word-producing" token (`$` for variable expansions/function calls, `(` + // for eval contexts, and `{` for name generation), then verify that it is + // separated to reduce the possibility of confusing it with a wildcard + // pattern. Consider: + // + // ./: [abc]-foo.txt + // + // Return the indication of whether we have seen any attributes (note that + // the `[]` empty list does not count) and the location of `[`. // // Note that during pre-parsing nothing is pushed into the stack. // pair<bool, location> - attributes_push (token&, token_type&, bool standalone = false); + attributes_push (token&, token_type&, + bool standalone = false, + bool next_token = true); attributes attributes_pop () @@ -302,15 +395,21 @@ namespace build2 attributes& attributes_top () {return attributes_.back ();} - // Source a stream optionnaly performing the default target processing. - // If the specified path name has a real path, then also enter it as a - // buildfile. + // Source a buildfile as a stream optionally performing the default target + // processing. If the specified path name has a real path, then also enter + // it as a buildfile. + // + // If default_target is nullopt, then disable the default target semantics + // as when loading boostrap.build or root.build. If it is false, then + // continue with the existing default_target value. If it is true, then + // start with a new default_value and call process_default_target() at + // the end. // void - source (istream&, - const path_name&, - const location&, - bool default_target); + source_buildfile (istream&, + const path_name&, + const location&, + optional<bool> default_target); // The what argument is used in diagnostics (e.g., "expected <what> // instead of ...". @@ -320,9 +419,6 @@ namespace build2 // project separator. Note that even if it is NULL, the result may still // contain non-simple names due to variable expansions. // - - static const string name_separators; - names parse_names (token& t, token_type& tt, pattern_mode pmode, @@ -345,14 +441,7 @@ namespace build2 const string* separators = &name_separators) { names ns; - parse_names (t, tt, - ns, - pmode, - chunk, - what, - separators, - 0, - nullopt, nullptr, nullptr); + parse_names (t, tt, ns, pmode, chunk, what, separators); return ns; } @@ -374,14 +463,7 @@ namespace build2 bool chunk = false) { names ns; - auto r (parse_names (t, tt, - ns, - pmode, - chunk, - what, - separators, - 0, - nullopt, nullptr, nullptr)); + auto r (parse_names (t, tt, ns, pmode, chunk, what, separators)); value v (r.type); // Potentially typed NULL value. @@ -397,8 +479,8 @@ namespace build2 // As above but also handle value attributes. // value - parse_value_with_attributes (token& t, token_type& tt, - pattern_mode pmode, + parse_value_with_attributes (token&, token_type&, + pattern_mode, const char* what = "name", const string* separators = &name_separators, bool chunk = false); @@ -416,15 +498,16 @@ namespace build2 // those places. Still it may make sense to look into redesigning the // whole thing one day. // - // Currently the only way for the result to be NULL or have a type is if + // Currently the only way for the result to be NULL or to have type is if // it is the result of a sole, unquoted variable expansion, function call, - // or context evaluation. + // or context evaluation. In these cases value is set to true. // // In the pre-parse mode no names are appended and the result is always // {true, nullptr, nullopt}. // struct parse_names_result { + bool value; bool not_null; const value_type* type; optional<const target_type*> pattern; @@ -500,8 +583,12 @@ namespace build2 // Customization hooks. // protected: - // If qual is not empty, then its pair member should indicate the kind - // of qualification: ':' -- target, '/' -- scope. + // If qual is not empty, then first element's pair member indicates the + // kind of qualification: + // + // '\0' -- target + // '@' -- out-qualified target + // '/' -- scope // // Note that this function is called even during pre-parse with the result // unused. In this case a valid name will only be provided for variables @@ -509,8 +596,12 @@ namespace build2 // example, $($x ? X : Y)) it will be empty (along with qual, which can // only be non-empty for a computed variable). // + // Note also that this function is (currently) not called by some lookup- + // like functions ($defined(), $config.origin()). But we should be careful + // if/when extending this and audit all the existing use-cases. + // virtual lookup - lookup_variable (name&& qual, string&& name, const location&); + lookup_variable (names&& qual, string&& name, const location&); // This function is only called during pre-parse and is the continuation // of the similar logic in lookup_variable() above (including the fact @@ -528,18 +619,22 @@ namespace build2 // Switch to a new current scope. Note that this function might also have // to switch to a new root scope if the new current scope is in another - // project. So both must be saved and restored. + // project. So both must be saved and restored. In case of a new root, it + // also switches to the new project's environment. // - void - switch_scope (const dir_path&); + auto_project_env + switch_scope (const dir_path& out_base); void - process_default_target (token&); + process_default_target (token&, const buildfile*); - // Enter buildfile as a target. + private: + // Enter buildfile or buildfile-file like file (e.g., a recipe file) as a + // target. // - void - enter_buildfile (const path&); + template <typename T> + const T& + enter_buildfile (const path&, optional<dir_path> out = nullopt); // Lexer. // @@ -625,15 +720,24 @@ namespace build2 replay_data_[replay_i_].mode == m); } + // In the replay mode return the lexing mode of the token returned by the + // subsequent next() or peek() call. + // lexer_mode mode () const { if (replay_ != replay::play) + { return lexer_->mode (); + } else { - assert (replay_i_ != replay_data_.size ()); - return replay_data_[replay_i_].mode; + assert (!peeked_ || replay_i_ != 0); + + size_t i (!peeked_ ? replay_i_ : replay_i_ - 1); + assert (i != replay_data_.size ()); + + return replay_data_[i].mode; } } @@ -688,6 +792,16 @@ namespace build2 } void + replay_pop () + { + assert (replay_ == replay::save); + + assert (!peeked_ && !replay_data_.empty ()); + + replay_data_.pop_back (); + } + + void replay_play () { assert ((replay_ == replay::save && !replay_data_.empty ()) || @@ -703,10 +817,21 @@ namespace build2 } void - replay_stop () + replay_skip () { + assert (replay_ == replay::play); + assert (!peeked_); + replay_i_ = replay_data_.size () - 1; + } + + void + replay_stop (bool verify = true) + { + if (verify) + assert (!peeked_); + if (replay_ == replay::play) path_ = replay_path_; // Restore old path. @@ -733,10 +858,23 @@ namespace build2 ~replay_guard () { if (p_ != nullptr) - p_->replay_stop (); + p_->replay_stop (!uncaught_exception ()); } private: + // C++17 deprecated uncaught_exception() so use uncaught_exceptions() if + // available. + // + static bool + uncaught_exception () + { +#ifdef __cpp_lib_uncaught_exceptions + return std::uncaught_exceptions () != 0; +#else + return std::uncaught_exception (); +#endif + } + parser* p_; }; @@ -806,7 +944,7 @@ namespace build2 // NOTE: remember to update reset() if adding anything here. // protected: - context& ctx; + context* ctx; stage stage_; bool pre_parse_ = false; @@ -823,6 +961,13 @@ namespace build2 small_vector<attributes, 2> attributes_; + // Innermost if/switch (but excluding recipes). + // + // Note also that this is cleared/restored when crossing the include + // (but not source) boundary. + // + optional<location> condition_; + target* default_target_ = nullptr; replay_token peek_; |