syntax change, part 1: commands are indented
authorEvan Martin <martine@danga.com>
Sun, 24 Oct 2010 16:02:52 +0000 (09:02 -0700)
committerEvan Martin <martine@danga.com>
Sun, 24 Oct 2010 16:02:52 +0000 (09:02 -0700)
manifest_parser.h
ninja_test.cc

index 089312c..f0ebd5c 100644 (file)
@@ -12,6 +12,8 @@ struct Token {
     NEWLINE,
     EQUALS,
     COLON,
+    INDENT,
+    OUTDENT,
     TEOF
   };
   explicit Token(Type type) : type_(type) {}
@@ -27,6 +29,8 @@ struct Token {
       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);
@@ -40,13 +44,14 @@ struct Token {
 };
 
 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);
@@ -63,6 +68,7 @@ struct Parser {
   const char* cur_line_;
   Token token_;
   int line_number_;
+  int last_indent_, cur_indent_;
 };
 
 void Parser::Start(const char* start, const char* end) {
@@ -87,11 +93,9 @@ void Parser::SkipWhitespace(bool newline) {
     } 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;
     }
@@ -99,12 +103,9 @@ void Parser::SkipWhitespace(bool newline) {
 }
 
 bool Parser::Newline(string* err) {
-  PeekToken();
   if (!ExpectToken(Token::NEWLINE, err))
     return false;
 
-  cur_line_ = cur_;
-  ++line_number_;
   return true;
 }
 
@@ -142,8 +143,11 @@ bool Parser::ReadToNewline(string* text, string* err) {
       ++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) != ' ')
@@ -161,6 +165,18 @@ Token::Type Parser::PeekToken() {
     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;
@@ -189,6 +205,9 @@ Token::Type Parser::PeekToken() {
   } else if (*cur_ == '\n') {
     token_.type_ = Token::NEWLINE;
     ++cur_;
+    cur_line_ = cur_;
+    cur_indent_ = -1;
+    ++line_number_;
   }
 
   SkipWhitespace();
@@ -281,11 +300,18 @@ bool ManifestParser::ParseRule(string* err) {
   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);
 
index 8ed448d..2c1c4d5 100644 (file)
@@ -22,10 +22,10 @@ TEST(Parser, Rules) {
   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"));
 
@@ -39,7 +39,7 @@ TEST(Parser, Variables) {
   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"
@@ -54,7 +54,7 @@ TEST(Parser, Continuation) {
   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"
@@ -114,9 +114,10 @@ TEST(Parser, Errors) {
   {
     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);
   }
 }
 
@@ -125,7 +126,7 @@ TEST(Parser, BuildDir) {
   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();
@@ -178,7 +179,7 @@ struct StateTestWithBuiltinRules : public testing::Test {
   StateTestWithBuiltinRules() {
     AssertParse(&state_,
 "rule cat\n"
-"command cat @in > $out\n");
+"  command cat @in > $out\n");
   }
 
   Node* GetNode(const string& path) {