NEWLINE,
EQUALS,
COLON,
+ INDENT,
+ OUTDENT,
TEOF
};
explicit Token(Type type) : type_(type) {}
case EQUALS: return "'='";
case COLON: return "':'";
case TEOF: return "eof";
+ case INDENT: return "indenting in";
+ case OUTDENT: return "indenting out";
case NONE:
default:
assert(false);
};
struct Parser {
- Parser() : token_(Token::NONE), line_number_(1) {}
+ Parser()
+ : token_(Token::NONE), line_number_(1),
+ last_indent_(0), cur_indent_(-1) {}
void Start(const char* start, const char* end);
bool Error(const string& message, string* err);
const Token& token() const { return token_; }
- bool eof() const { return cur_ >= end_; }
void SkipWhitespace(bool newline=false);
bool Newline(string* err);
const char* cur_line_;
Token token_;
int line_number_;
+ int last_indent_, cur_indent_;
};
void Parser::Start(const char* start, const char* end) {
} else if (newline && *cur_ == '\n') {
Newline(NULL);
} else if (*cur_ == '\\' && cur_ + 1 < end_ && cur_[1] == '\n') {
- ++cur_;
- Token token = token_; // XXX hack around newline clearing token.
- token_.Clear();
- Newline(NULL);
- token_ = token;
+ ++cur_; ++cur_;
+ cur_line_ = cur_;
+ ++line_number_;
} else {
break;
}
}
bool Parser::Newline(string* err) {
- PeekToken();
if (!ExpectToken(Token::NEWLINE, err))
return false;
- cur_line_ = cur_;
- ++line_number_;
return true;
}
++cur_;
if (cur_ >= end_)
return Error("unexpected eof", err);
- if (!Newline(err))
- return false;
+ if (*cur_ != '\n')
+ return Error("expected newline after backslash", err);
+ ++cur_;
+ cur_line_ = cur_;
+ ++line_number_;
SkipWhitespace();
// Collapse whitespace, but make sure we get at least one space.
if (text->size() > 0 && text->at(text->size() - 1) != ' ')
return token_.type_;
token_.pos_ = cur_;
+ if (cur_indent_ == -1) {
+ cur_indent_ = cur_ - cur_line_;
+ if (cur_indent_ != last_indent_) {
+ if (cur_indent_ > last_indent_) {
+ token_.type_ = Token::INDENT;
+ } else if (cur_indent_ < last_indent_) {
+ token_.type_ = Token::OUTDENT;
+ }
+ last_indent_ = cur_indent_;
+ return token_.type_;
+ }
+ }
if (cur_ >= end_) {
token_.type_ = Token::TEOF;
} else if (*cur_ == '\n') {
token_.type_ = Token::NEWLINE;
++cur_;
+ cur_line_ = cur_;
+ cur_indent_ = -1;
+ ++line_number_;
}
SkipWhitespace();
if (!parser_.Newline(err))
return false;
- if (!parser_.ExpectToken(Token::COMMAND, err))
- return false;
string command;
- if (!parser_.ReadToNewline(&command, err))
- return false;
+ if (parser_.PeekToken() == Token::INDENT) {
+ parser_.ConsumeToken();
+
+ if (!parser_.ExpectToken(Token::COMMAND, err))
+ return false;
+ if (!parser_.ReadToNewline(&command, err))
+ return false;
+
+ if (!parser_.ExpectToken(Token::OUTDENT, err))
+ return false;
+ }
state_->AddRule(name, command);
State state;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state,
"rule cat\n"
-"command cat @in > $out\n"
+" command cat @in > $out\n"
"\n"
"rule date\n"
-"command date > $out\n"
+" command date > $out\n"
"\n"
"build result: cat in_1.cc in-2.O\n"));
State state;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state,
"rule link\n"
-"command ld $extra $with_under -o $out @in\n"
+" command ld $extra $with_under -o $out @in\n"
"\n"
"extra = -pthread\n"
"with_under = -under\n"
State state;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state,
"rule link\n"
-"command foo bar \\\n"
+" command foo bar \\\n"
" baz\n"
"\n"
"build a: link c \\\n"
{
ManifestParser parser(&state);
string err;
- EXPECT_FALSE(parser.Parse("rule cat\ncommand cat ok\nbuild x: cat \\\n :\n",
+ EXPECT_FALSE(parser.Parse("rule cat\n command cat ok\n"
+ "build x: cat \\\n :\n",
&err));
- EXPECT_EQ("line 4, col 1: expected newline, got ':'", err);
+ EXPECT_EQ("line 4, col 2: expected newline, got ':'", err);
}
}
ASSERT_NO_FATAL_FAILURE(AssertParse(&state,
"builddir = out\n"
"rule cat\n"
-"command cat @in > $out\n"
+" command cat @in > $out\n"
"build @bin: cat @a.o\n"
"build @a.o: cat a.cc\n"));
state.stat_cache()->Dump();
StateTestWithBuiltinRules() {
AssertParse(&state_,
"rule cat\n"
-"command cat @in > $out\n");
+" command cat @in > $out\n");
}
Node* GetNode(const string& path) {