Skip to content

Commit

Permalink
add an include statement
Browse files Browse the repository at this point in the history
  • Loading branch information
evmar committed Jan 15, 2011
1 parent e5e511a commit facdb4c
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 14 deletions.
15 changes: 12 additions & 3 deletions manual.asciidoc
Expand Up @@ -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
<<ref_scope,in the discussion about scoping>>.
Comments begin with `#` and extend to the end of the line.

Expand Down Expand Up @@ -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.
30 changes: 20 additions & 10 deletions src/parsers.cc
Expand Up @@ -15,18 +15,18 @@ 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 "':'";
case PIPE: return "'|'";
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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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;

Expand All @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion src/parsers.h
Expand Up @@ -16,6 +16,7 @@ struct Token {
RULE,
BUILD,
SUBNINJA,
INCLUDE,
NEWLINE,
EQUALS,
COLON,
Expand Down Expand Up @@ -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_;
Expand Down
11 changes: 11 additions & 0 deletions src/parsers_test.cc
Expand Up @@ -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"
Expand Down

0 comments on commit facdb4c

Please sign in to comment.