aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/parser.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/parser.hxx')
-rw-r--r--libbuild2/parser.hxx323
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_;