| //===-- MakefileDepsParser.cpp --------------------------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llbuild/Core/MakefileDepsParser.h" |
| |
| #include "llvm/ADT/SmallString.h" |
| |
| using namespace llvm; |
| using namespace llbuild; |
| using namespace llbuild::core; |
| |
| MakefileDepsParser::ParseActions::~ParseActions() {} |
| |
| #pragma mark - MakefileDepsParser Implementation |
| |
| static bool isWordChar(int c) { |
| switch (c) { |
| case '\0': |
| case '\t': |
| case '\n': |
| case ' ': |
| case '$': |
| case ':': |
| case ';': |
| case '=': |
| case '|': |
| case '%': |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| static void skipWhitespaceAndComments(const char*& cur, const char* end) { |
| for (; cur != end; ++cur) { |
| int c = *cur; |
| |
| // Skip comments. |
| if (c == '#') { |
| // Skip to the next newline. |
| while (cur + 1 != end && cur[1] == '\n') |
| ++cur; |
| continue; |
| } |
| |
| if (c == ' ' || c == '\t' || c == '\n') |
| continue; |
| |
| break; |
| } |
| } |
| |
| static void skipNonNewlineWhitespace(const char*& cur, const char* end) { |
| for (; cur != end; ++cur) { |
| int c = *cur; |
| |
| // Skip regular whitespace. |
| if (c == ' ' || c == '\t') |
| continue; |
| |
| // If this is an escaped newline, also skip it. |
| if (c == '\\' && cur + 1 != end && cur[1] == '\n') { |
| ++cur; |
| continue; |
| } |
| |
| // Otherwise, stop scanning. |
| break; |
| } |
| } |
| |
| static void skipToEndOfLine(const char*& cur, const char* end) { |
| for (; cur != end; ++cur) { |
| int c = *cur; |
| |
| if (c == '\n') { |
| ++cur; |
| break; |
| } |
| } |
| } |
| |
| static void lexWord(const char*& cur, const char* end, |
| SmallVectorImpl<char> &unescapedWord) { |
| unescapedWord.clear(); |
| for (; cur != end; ++cur) { |
| int c = *cur; |
| |
| // Check if this is an escape sequence. |
| if (c == '\\') { |
| // If this is a line continuation, it ends the word. |
| if (cur + 1 != end && cur[1] == '\n') |
| break; |
| |
| // Otherwise, skip the escaped character. |
| ++cur; |
| int c = *cur; |
| |
| // Honor the escaping rules as generated by Clang and GCC, which are *not |
| // necessarily* the actual escaping rules of BSD Make or GNU Make. Due to |
| // incompatibilities in the escape syntax between those two makes, and the |
| // GNU make behavior of retrying an escaped string with the original |
| // input, GCC/Clang adopt a strategy around escaping ' ' and '#', but not |
| // r"\\" itself, or any other characters. However, there are some |
| // situations where Clang *will* generate an escaped '\\' using the |
| // r"\\\\" sequence, so we also honor that. |
| // |
| // FIXME: Make this more complete, or move to a better dependency format. |
| if (c == ' ' || c == '#' || c == '\\') { |
| unescapedWord.push_back(c); |
| } else { |
| unescapedWord.push_back('\\'); |
| unescapedWord.push_back(c); |
| } |
| continue; |
| } else if (c == '$' && cur + 1 != end && cur[1] == '$') { |
| // "$$" is an escaped '$'. |
| unescapedWord.push_back(c); |
| ++cur; |
| continue; |
| } |
| |
| // Otherwise, if this is not a valid word character then skip it. |
| if (!isWordChar(c)) |
| break; |
| unescapedWord.push_back(c); |
| } |
| } |
| |
| void MakefileDepsParser::parse() { |
| const char* cur = data; |
| const char* end = data + length; |
| // Storage for currently begin lexed unescaped word. |
| SmallString<256> unescapedWord; |
| |
| // While we have input data... |
| while (cur != end) { |
| // Skip leading whitespace and comments. |
| skipWhitespaceAndComments(cur, end); |
| |
| // If we have reached the end of the input, we are done. |
| if (cur == end) |
| break; |
| |
| // The next token should be a word. |
| const char* wordStart = cur; |
| lexWord(cur, end, unescapedWord); |
| if (cur == wordStart) { |
| actions.error("unexpected character in file", cur - data); |
| skipToEndOfLine(cur, end); |
| continue; |
| } |
| actions.actOnRuleStart(wordStart, cur - wordStart, unescapedWord.str()); |
| |
| // The next token should be a colon. |
| skipNonNewlineWhitespace(cur, end); |
| if (cur == end || *cur != ':') { |
| actions.error("missing ':' following rule", cur - data); |
| actions.actOnRuleEnd(); |
| skipToEndOfLine(cur, end); |
| continue; |
| } |
| |
| // Skip the colon. |
| ++cur; |
| |
| // Consume dependency words until we reach the end of a line. |
| while (cur != end) { |
| // Skip forward and check for EOL. |
| skipNonNewlineWhitespace(cur, end); |
| if (cur == end || *cur == '\n') |
| break; |
| |
| // Otherwise, we should have a word. |
| const char* wordStart = cur; |
| lexWord(cur, end, unescapedWord); |
| if (cur == wordStart) { |
| actions.error("unexpected character in prerequisites", cur - data); |
| skipToEndOfLine(cur, end); |
| continue; |
| } |
| actions.actOnRuleDependency(wordStart, cur - wordStart, |
| unescapedWord.str()); |
| } |
| actions.actOnRuleEnd(); |
| } |
| } |