From facdb4c90f01c762df4dcc87ab9cddf834fe41f2 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 14 Jan 2011 17:11:56 -0800 Subject: [PATCH] add an include statement --- manual.asciidoc | 15 ++++++++++++--- src/parsers.cc | 30 ++++++++++++++++++++---------- src/parsers.h | 5 ++++- src/parsers_test.cc | 11 +++++++++++ 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/manual.asciidoc b/manual.asciidoc index 2ca38672e9..258782c0c7 100644 --- a/manual.asciidoc +++ b/manual.asciidoc @@ -224,7 +224,9 @@ A file is a series of declarations. A declaration can be one of: 3. Variable declarations, which look like +_variable_ = _value_+. -4. References to more files, which look like +subninja _path_+. +4. References to more files, which look like +subninja _path_+ or + +include _path_+. The difference between these is explained below + <>. Comments begin with `#` and extend to the end of the line. @@ -299,11 +301,18 @@ introduces a new scope. The included `subninja` file may use the variables from the parent file, and shadow their values for the file's scope, but it won't affect values of the variables in the parent. +To include another `.ninja` file in the current scope, much like a C +`#include` statement, use `include` instead of `subninja`. + Variable declarations indented in a `build` block are scoped to the `build` block. This scope is inherited by the `rule`. The full lookup order for a variable referenced in a rule is: 1. Rule-level variables (i.e. `$in`, `$command`). + 2. Build-level variables from the `build` that references this rule. -3. File-level variables from the current file. -4. File-level variables from any file that included this file. + +3. File-level variables from the file that the `build` line was in. + +4. Variables from the file that included that file using the + `subninja` keyword. diff --git a/src/parsers.cc b/src/parsers.cc index 8b594e50ea..7b810fec35 100644 --- a/src/parsers.cc +++ b/src/parsers.cc @@ -15,6 +15,7 @@ string Token::AsString() const { case RULE: return "'rule'"; case BUILD: return "'build'"; case SUBNINJA: return "'subninja'"; + case INCLUDE: return "'include'"; case NEWLINE: return "newline"; case EQUALS: return "'='"; case COLON: return "':'"; @@ -22,11 +23,10 @@ string Token::AsString() const { case TEOF: return "eof"; case INDENT: return "indenting in"; case OUTDENT: return "indenting out"; - case NONE: - default: - assert(false); - return ""; + case NONE: break; } + assert(false); + return ""; } void Tokenizer::Start(const char* start, const char* end) { @@ -168,6 +168,8 @@ Token::Type Tokenizer::PeekToken() { token_.type_ = Token::RULE; else if (len == 5 && memcmp(token_.pos_, "build", 5) == 0) token_.type_ = Token::BUILD; + else if (len == 7 && memcmp(token_.pos_, "include", 7) == 0) + token_.type_ = Token::INCLUDE; else if (len == 8 && memcmp(token_.pos_, "subninja", 8) == 0) token_.type_ = Token::SUBNINJA; else @@ -250,7 +252,8 @@ bool ManifestParser::Parse(const string& input, string* err) { return false; break; case Token::SUBNINJA: - if (!ParseSubNinja(err)) + case Token::INCLUDE: + if (!ParseFileInclude(tokenizer_.PeekToken(), err)) return false; break; case Token::IDENT: { @@ -469,12 +472,12 @@ bool ManifestParser::ParseEdge(string* err) { return true; } -bool ManifestParser::ParseSubNinja(string* err) { - if (!tokenizer_.ExpectToken(Token::SUBNINJA, err)) +bool ManifestParser::ParseFileInclude(Token::Type type, string* err) { + if (!tokenizer_.ExpectToken(type, err)) return false; string path; if (!tokenizer_.ReadIdent(&path)) - return tokenizer_.Error("expected subninja path", err); + return tokenizer_.Error("expected path to ninja file", err); if (!tokenizer_.Newline(err)) return false; @@ -483,8 +486,15 @@ bool ManifestParser::ParseSubNinja(string* err) { return false; ManifestParser subparser(state_, file_reader_); - subparser.env_ = new BindingEnv; - subparser.env_->parent_ = env_; + if (type == Token::SUBNINJA) { + // subninja: Construct a new scope for the new parser. + subparser.env_ = new BindingEnv; + subparser.env_->parent_ = env_; + } else { + // include: Reuse the current scope. + subparser.env_ = env_; + } + string sub_err; if (!subparser.Parse(contents, &sub_err)) return tokenizer_.Error("in '" + path + "': " + sub_err, err); diff --git a/src/parsers.h b/src/parsers.h index 3c6fcc2989..ec9a6772db 100644 --- a/src/parsers.h +++ b/src/parsers.h @@ -16,6 +16,7 @@ struct Token { RULE, BUILD, SUBNINJA, + INCLUDE, NEWLINE, EQUALS, COLON, @@ -87,7 +88,9 @@ struct ManifestParser { // within the value immediately. bool ParseLet(string* key, string* val, bool expand, string* err); bool ParseEdge(string* err); - bool ParseSubNinja(string* err); + + // Parse either a 'subninja' or 'include' line. + bool ParseFileInclude(Token::Type type, string* err); State* state_; BindingEnv* env_; diff --git a/src/parsers_test.cc b/src/parsers_test.cc index 28ba61990f..82ef8f3c95 100644 --- a/src/parsers_test.cc +++ b/src/parsers_test.cc @@ -278,6 +278,17 @@ TEST_F(ParserTest, SubNinja) { EXPECT_EQ("varref outer", state.edges_[2]->EvaluateCommand()); } +TEST_F(ParserTest, Include) { + files_["include.ninja"] = "var = inner\n"; + ASSERT_NO_FATAL_FAILURE(AssertParse( +"var = outer\n" +"include include.ninja\n")); + + ASSERT_EQ(1, files_read_.size()); + EXPECT_EQ("include.ninja", files_read_[0]); + EXPECT_EQ("inner", state.bindings_.LookupVariable("var")); +} + TEST_F(ParserTest, OrderOnly) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule cat\n command = cat $in > $out\n"