From 8b55e8151bd69e4ef11a67aff57618193f559618 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 7 Dec 2015 12:16:29 +0200 Subject: Add support for specifying minimum required build2 version The syntax is: using build@0.1.0-a1 The idea is that we will later also use it for modules and 'build' is a special, the "build system itself" module. Also fix a problem with peeking and lexer mode switching. --- build/lexer | 13 +++++++++- build/lexer.cxx | 13 ++++++++++ build/parser | 3 +++ build/parser.cxx | 64 ++++++++++++++++++++++++++++++++++++--------- build/types | 2 ++ build/utility | 10 ++++++- build/utility.cxx | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 169 insertions(+), 14 deletions(-) (limited to 'build') diff --git a/build/lexer b/build/lexer index 6880d39..4a50e2a 100644 --- a/build/lexer +++ b/build/lexer @@ -15,6 +15,9 @@ #include +#include +#include + #include #include @@ -43,7 +46,7 @@ namespace build lexer (std::istream& is, const std::string& name, void (*processor) (token&, const lexer&) = nullptr) - : char_scanner (is), fail (name), processor_ (processor) + : char_scanner (is), fail (name), processor_ (processor), sep_ (false) { mode_.push (lexer_mode::normal); } @@ -77,6 +80,13 @@ namespace build token next (); + // Peek at the first character of the next token. Return the character + // or 0 if the next token will be eos. Also return an indicator of + // whether the next token will be separated. + // + pair + peek_char (); + private: token next_impl (); @@ -121,6 +131,7 @@ namespace build std::stack mode_; char pair_separator_; + bool sep_; // True if we skipped spaces in peek(). }; } diff --git a/build/lexer.cxx b/build/lexer.cxx index 79611c6..77e803c 100644 --- a/build/lexer.cxx +++ b/build/lexer.cxx @@ -17,6 +17,16 @@ namespace build return t; } + pair lexer:: + peek_char () + { + // In the quoted mode we don't skip spaces. + // + sep_ = mode_.top () != lexer_mode::quoted && skip_spaces (); + xchar c (peek ()); + return make_pair (eos (c) ? '\0' : char (c), sep_); + } + token lexer:: next_impl () { @@ -385,6 +395,9 @@ namespace build get (); } + r = r || sep_; + sep_ = false; + return r; } diff --git a/build/parser b/build/parser index f3f88d5..08d587a 100644 --- a/build/parser +++ b/build/parser @@ -164,6 +164,9 @@ namespace build token_type next (token&, token_type&); + // Be careful with peeking and switching the lexer mode. See keyword() + // for more information. + // token_type peek (); diff --git a/build/parser.cxx b/build/parser.cxx index a4e2af0..8cfa665 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -492,6 +493,7 @@ namespace build // The rest should be a list of buildfiles. Parse them as names // to get variable expansion and directory prefixes. // + lexer_->mode (lexer_mode::value); next (t, tt); const location l (get_location (t, &path_)); names_type ns (tt != type::newline && tt != type::eos @@ -573,6 +575,7 @@ namespace build // The rest should be a list of buildfiles. Parse them as names // to get variable expansion and directory prefixes. // + lexer_->mode (lexer_mode::value); next (t, tt); const location l (get_location (t, &path_)); names_type ns (tt != type::newline && tt != type::eos @@ -807,20 +810,54 @@ namespace build // The rest should be a list of module names. Parse them as names // to get variable expansion, etc. // + lexer_->mode (lexer_mode::pairs, '@'); next (t, tt); const location l (get_location (t, &path_)); names_type ns (tt != type::newline && tt != type::eos ? names (t, tt) : names_type ()); - for (name& n: ns) + for (auto i (ns.begin ()); i != ns.end (); ++i) { - // For now it should be a simple name. + string n, v; + + if (!i->simple ()) + fail (l) << "module name expected instead of " << *i; + + n = move (i->value); + + if (i->pair) + { + ++i; + if (!i->simple ()) + fail (l) << "module version expected instead of " << *i; + + v = move (i->value); + } + + // Handle the special 'build' module. // - if (!n.simple ()) - fail (l) << "module name expected instead of " << n; + if (n == "build") + { + if (!v.empty ()) + { + unsigned int iv; + try {iv = to_version (v);} + catch (const invalid_argument& e) + { + fail (l) << "invalid version '" << v << "': " << e.what (); + } - load_module (optional, n.value, *root_, *scope_, l); + if (iv > BUILD_VERSION) + fail (l) << "build2 " << v << " required" << + info << "running build2 " << BUILD_VERSION_STR; + } + } + else + { + assert (v.empty ()); // Module versioning not yet implemented. + load_module (optional, n, *root_, *scope_, l); + } } if (tt == type::newline) @@ -1455,7 +1492,7 @@ namespace build if (n.empty ()) fail (loc) << "empty variable/function name"; - // Figure out whether this is a variable expansion of a function + // Figure out whether this is a variable expansion or a function // call. // tt = peek (); @@ -1749,18 +1786,21 @@ namespace build // potential keyword if: // // - it is not quoted [so a keyword can always be escaped] and - // - next token is separated or is '(' [so if(...) will work] and - // - next token is not '=' or '+=' [which means a "directive body" - // can never start with one of them]. + // - next token is eos or '(' [so if(...) will work] or + // - next token is separated and is not '=' or '+=' [which means a + // "directive trailer" can never start with one of them]. // // See tests/keyword. // if (!t.quoted) { - type pt (peek ()); + // We cannot peek at the whole token here since it might have to be + // lexed in a different mode. So peek at its first character. + // + pair p (lexer_->peek_char ()); + char c (p.first); - return pt == type::lparen || - (pt != type::equal && pt != type::plus_equal && peeked ().separated); + return c == '\0' || c == '(' || (p.second && c != '=' && c != '+'); } return false; diff --git a/build/types b/build/types index c119839..2dc9b04 100644 --- a/build/types +++ b/build/types @@ -7,6 +7,7 @@ #include #include +#include // pair #include // unique_ptr, shared_ptr #include // reference_wrapper @@ -19,6 +20,7 @@ namespace build { // Commonly-used types. // + using std::pair; using std::string; using std::unique_ptr; using std::shared_ptr; diff --git a/build/utility b/build/utility index 404b33a..6769f95 100644 --- a/build/utility +++ b/build/utility @@ -7,7 +7,7 @@ #include #include -#include // move() +#include // move(), make_pair() #include // assert() #include #include @@ -17,6 +17,7 @@ namespace build { using std::move; + using std::make_pair; // Empty string and path. // @@ -24,6 +25,13 @@ namespace build extern const path empty_path; extern const dir_path empty_dir_path; + // Parse version string in the X.Y.Z[-{a|b}N] to a version integer in the + // AABBCCDD form as describe in . Throw invalid_argument + // if the passed string is not a valid version. + // + unsigned int + to_version (const string&); + // Call a function if there is an exception. // diff --git a/build/utility.cxx b/build/utility.cxx index d394186..7ee679b 100644 --- a/build/utility.cxx +++ b/build/utility.cxx @@ -4,6 +4,8 @@ #include +#include // strtol() + using namespace std; namespace build @@ -12,5 +14,81 @@ namespace build const path empty_path; const dir_path empty_dir_path; + unsigned int + to_version (const string& s) + { + // See tests/version. + // + + auto parse = [&s] (size_t& p, const char* m, long min = 0, long max = 99) + -> unsigned int + { + if (s[p] == '-' || s[p] == '+') // stoi() allows these. + throw invalid_argument (m); + + const char* b (s.c_str () + p); + char* e; + long r (strtol (b, &e, 10)); + + if (b == e || r < min || r > max) + throw invalid_argument (m); + + p = e - s.c_str (); + return static_cast (r); + }; + + auto bail = [] (const char* m) {throw invalid_argument (m);}; + + unsigned int ma, mi, bf, ab; + + size_t p (0), n (s.size ()); + ma = parse (p, "invalid major version"); + + if (p >= n || s[p] != '.') + bail ("'.' expected after major version"); + + mi = parse (++p, "invalid minor version"); + + if (p >= n || s[p] != '.') + bail ("'.' expected after minor version"); + + bf = parse (++p, "invalid bugfix version"); + + if (p < n) + { + if (s[p] != '-') + bail ("'-' expected after bugfix version"); + + char k (s[++p]); + + if (k != 'a' && k != 'b') + bail ("'a' or 'b' expected in release component"); + + ab = parse (++p, "invalid release component", 1, 49); + + if (p != n) + bail ("junk after release component"); + + if (k == 'b') + ab += 50; + } + + // AABBCCDD + unsigned int r (ma * 1000000U + + mi * 10000U + + bf * 100U); + + if (ab != 0) + { + if (r == 0) + bail ("0.0.0 version with release component"); + + r -= 100; + r += ab; + } + + return r; + } + bool exception_unwinding_dtor = false; } -- cgit v1.1