Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple slang-tidy port suffixes for EnforcePortSuffix check #787

Merged
merged 2 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions tools/tidy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ checks_config_section ::= "CheckConfigs:" [EOL] config+
config ::= ((config_tuple ",")* | config_tuple) END
config_tuple ::= config_name ":" config_value
config_name ::= identifier
config_value ::= identifier
identifier ::= {A-Za-z}+
config_value ::= expression | "[" ((expression ",")* expression)? "]"
identifier ::= { A-Z | a-z }+
expression ::= { A-Z | a-z | 0-9 | "_" }+
EOL ::= '\n' | '\r' | '\r\n'
END ::= EOL* | EOF
```
Expand Down
40 changes: 16 additions & 24 deletions tools/tidy/include/TidyConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ class TidyConfig {
std::string clkName;
std::string resetName;
bool resetIsActiveHigh;
std::string inputPortSuffix;
std::string outputPortSuffix;
std::string inoutPortSuffix;
std::vector<std::string> inputPortSuffix;
std::vector<std::string> outputPortSuffix;
std::vector<std::string> inoutPortSuffix;
std::string moduleInstantiationPrefix;
};

Expand All @@ -40,6 +40,9 @@ class TidyConfig {
/// Returns the check config object
inline const CheckConfigs& getCheckConfigs() const { return checkConfigs; }

/// Returns the check config object
inline CheckConfigs& getCheckConfigs() { return checkConfigs; }

// Returns a vector containing the file names that won't be checked by slang-tidy
inline const std::vector<std::string>& getSkipFiles() const { return skipFiles; }

Expand Down Expand Up @@ -69,45 +72,34 @@ class TidyConfig {
[[nodiscard]] bool toggleCheck(slang::TidyKind kind, const std::string& checkName,
CheckStatus status);

/// Sets the value of a check config. Will throw an invalid_argument exception if the type of
/// value doesn't match the config type
template<typename T>
void setConfig(const std::string& configName, T value) {
/// Visits the value of a check config. Will throw an invalid_argument exception
/// if the configName is unknown
template<typename Visitor>
void visitConfig(std::string const& configName, Visitor visit) {
if (configName == "clkName") {
innerSetConfig(checkConfigs.clkName, value);
visit(checkConfigs.clkName);
return;
}
else if (configName == "resetName") {
innerSetConfig(checkConfigs.resetName, value);
visit(checkConfigs.resetName);
return;
}
else if (configName == "resetIsActiveHigh") {
innerSetConfig(checkConfigs.resetIsActiveHigh, value);
visit(checkConfigs.resetIsActiveHigh);
return;
}
else if (configName == "inputPortSuffix") {
innerSetConfig(checkConfigs.inputPortSuffix, value);
visit(checkConfigs.inputPortSuffix);
return;
}
else if (configName == "outputPortSuffix") {
innerSetConfig(checkConfigs.outputPortSuffix, value);
visit(checkConfigs.outputPortSuffix);
return;
}
else if (configName == "inoutPortSuffix") {
innerSetConfig(checkConfigs.inoutPortSuffix, value);
visit(checkConfigs.inoutPortSuffix);
return;
}

SLANG_THROW(std::invalid_argument(fmt::format("The check: {} does not exist", configName)));
}

template<typename T, typename U>
void innerSetConfig(T& config, U value) {
if constexpr (std::is_same_v<T, U>)
config = value;
else
SLANG_THROW(
std::invalid_argument(fmt::format("check config expected a {} found {}",
slang::typeName<T>(), slang::typeName<U>())));
}
};
34 changes: 32 additions & 2 deletions tools/tidy/include/TidyConfigParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,21 @@ class TidyConfigParser {
/// Gets the next character from the file stream, skips whitespaces and tabs
char nextChar();

/// Peeks the next character from the file stream
/// Peeks the next character from the file stream, skips whitespaces and tabs
char peekChar();

/// Reads characters from the file stream and adds them to str
/// as long as pred for the extracted character is true.
/// Returns the first character for which pred is false.
template<typename Func, std::enable_if_t<std::is_invocable_r_v<bool, Func, char>, bool> = true>
char readIf(std::string& str, Func pred) {
char c;
while (pred(c = nextChar())) {
str.push_back(c);
}
return c;
}

/// Parses config file and sets the state to parse checks or check configs
void parseInitial();

Expand All @@ -63,6 +75,24 @@ class TidyConfigParser {
/// Parses the check configs regions of the config file
void parseCheckConfigs();

/// Parses multiple configValues by individually
/// parsing every single config values of type T
template<typename T>
void parseConfigValue(std::vector<T>& config, std::vector<std::string>&& configValues) {
config.clear();
for (auto& value : configValues) {
T tmp;
parseConfigValue(tmp, std::move(value));
config.emplace_back(std::move(tmp));
}
}

/// Parses configValue as a single string
void parseConfigValue(std::string& config, std::string&& configValue);

/// Parses configValue as a bool.
void parseConfigValue(bool& config, std::string&& configValue);

/// Toggles all checks with the status provided
void toggleAllChecks(TidyConfig::CheckStatus status);

Expand All @@ -74,7 +104,7 @@ class TidyConfigParser {
TidyConfig::CheckStatus status);

/// Sets the check config with the provided value
void setCheckConfig(const std::string& configName, std::string configValue);
void setCheckConfig(const std::string& configName, std::vector<std::string> configValue);

/// The name format of the checks provided by the user are required to be: this-is-my-check
/// but the registered names in the TidyFactory are ThisIsMyCheck. This function translates from
Expand Down
6 changes: 3 additions & 3 deletions tools/tidy/src/TidyConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ TidyConfig::TidyConfig() {
checkConfigs.clkName = "clk_i";
checkConfigs.resetName = "rst_ni";
checkConfigs.resetIsActiveHigh = true;
checkConfigs.inputPortSuffix = "_i";
checkConfigs.outputPortSuffix = "_o";
checkConfigs.inoutPortSuffix = "_io";
checkConfigs.inputPortSuffix = {"_i"};
checkConfigs.outputPortSuffix = {"_o"};
checkConfigs.inoutPortSuffix = {"_io"};
checkConfigs.moduleInstantiationPrefix = "i_";

auto styleChecks = std::unordered_map<std::string, CheckStatus>();
Expand Down
154 changes: 98 additions & 56 deletions tools/tidy/src/TidyConfigParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@

#include "slang/util/OS.h"

template<typename T>
struct is_vector : std::false_type {};

template<typename T>
struct is_vector<std::vector<T>> : std::true_type {};

template<typename T>
inline constexpr bool is_vector_v = is_vector<T>::value;

TidyConfigParser::TidyConfigParser(const std::filesystem::path& path) {
parserState = ParserState::Initial;
filePath = path.string();
Expand Down Expand Up @@ -56,6 +65,11 @@ char TidyConfigParser::nextChar() {
}

inline char TidyConfigParser::peekChar() {
while (fileStream.peek() == ' ' || fileStream.peek() == '\t') {
fileStream.get();
}
if (fileStream.eof())
return 0;
#if defined(_WIN32)
char ret = fileStream.peek();
if (ret == '\r') {
Expand Down Expand Up @@ -93,7 +107,7 @@ void TidyConfigParser::parseFile() {
}

void TidyConfigParser::parseInitial() {
char currentChar = nextChar();
char currentChar = peekChar();
if (currentChar == 0)
reportErrorAndExit("Unexpected end of file");

Expand All @@ -102,10 +116,7 @@ void TidyConfigParser::parseInitial() {
fmt::format("Unexpected token with ascii_code ({}): {}", +currentChar, currentChar));

std::string str;
while (!fileStream.eof() && isalpha(currentChar)) {
str.push_back(currentChar);
currentChar = nextChar();
}
currentChar = readIf(str, isalpha);

if (currentChar != ':')
reportErrorAndExit(fmt::format("Expected token: ':', found: (ASCIICode: {}){}",
Expand Down Expand Up @@ -138,16 +149,14 @@ void TidyConfigParser::parseChecks() {
char currentChar = nextChar();

// If it is a new line ignore it and get the following char
if (currentChar == '\n')
while (currentChar == '\n')
currentChar = nextChar();

if (currentChar == '-')
newCheckState = TidyConfig::CheckStatus::DISABLED;
else if (isalpha(currentChar))
checkGroup.push_back(currentChar);
else if (currentChar == '*') {
if (checkGroup.size())
reportErrorAndExit("Unexpected '*'");
toggleAllChecks(TidyConfig::CheckStatus::ENABLED);
currentChar = nextChar();
if (currentChar == '\n' || currentChar == 0) {
Expand Down Expand Up @@ -258,49 +267,63 @@ void TidyConfigParser::parseChecks() {

void TidyConfigParser::parseCheckConfigs() {
while (!fileStream.eof()) {
std::string optionName;
std::string optionValue;

// Parse optional newline
if (peekChar() == '\n')
nextChar();

// Parse option name
while (true) {
char currentChar = nextChar();
if (currentChar == ':')
break;
else if (isalpha(currentChar))
optionName.push_back(currentChar);
else
reportErrorAndExit(fmt::format("Expected ':' or '_' or a letter but found ({}){}",
+currentChar, currentChar));
std::string optionName;
char currentChar = readIf(optionName, isalpha);

if (currentChar != ':') {
reportErrorAndExit(fmt::format("Expected ':' or a letter but found ({}){}",
+currentChar, currentChar));
}

// Parse option value
while (true) {
char currentChar = nextChar();
if (currentChar == ',') {
setCheckConfig(optionName, optionValue);
if (nextChar() != '\n') {
reportErrorAndExit(fmt::format("Expected new line but found: ({}){}",
+currentChar, currentChar));
}
break;
}
else if (isalpha(currentChar) || currentChar == '_') {
optionValue.push_back(currentChar);
// Parse multiple option values
std::vector<std::string> optionValues;

auto isOptionValueChar = [](char c) { return isalnum(c) || c == '_'; };

if (peekChar() == '[') {
currentChar = nextChar(); // skip '['

do {
std::string optionValue;
currentChar = readIf(optionValue, isOptionValueChar);
if (!optionValue.empty())
optionValues.emplace_back(optionValue);
} while (currentChar == ',');

if (currentChar != ']') {
reportErrorAndExit(
fmt::format("Expected ']' but found ({}){}", +currentChar, currentChar));
}
else if (currentChar == '\n' || currentChar == 0) {
while (peekChar() == '\n')
nextChar();
setCheckConfig(optionName, optionValue);
parserState = ParserState::Initial;
return;
currentChar = nextChar();
}
else { // Parse single option value
std::string optionValue;
currentChar = readIf(optionValue, isOptionValueChar);
optionValues.emplace_back(optionValue);
}

if (currentChar == ',') {
setCheckConfig(optionName, optionValues);
if (nextChar() != '\n') {
reportErrorAndExit(
fmt::format("Expected new line but found: ({}){}", +currentChar, currentChar));
}
else
reportErrorAndExit(fmt::format("Expected ',' new line or a letter but found ({}){}",
+currentChar, currentChar));
}
else if (currentChar == '\n' || currentChar == 0) {
while (peekChar() == '\n')
nextChar();
setCheckConfig(optionName, optionValues);
parserState = ParserState::Initial;
return;
}
else {
reportErrorAndExit(fmt::format("Expected ',' new line or a letter but found ({}){}",
+currentChar, currentChar));
}
}
}
Expand Down Expand Up @@ -336,25 +359,44 @@ void TidyConfigParser::toggleCheck(const std::string& groupName, const std::stri
fmt::format("Check name {} does not exist in group {}", checkName, groupName));
}

void TidyConfigParser::setCheckConfig(const std::string& configName, std::string configValue) {
auto set_config = [&](auto value) {
SLANG_TRY {
config.setConfig(configName, value);
}
SLANG_CATCH(std::invalid_argument & exception) {
#if __cpp_exceptions
reportWarning(exception.what());
#endif
}
};
void TidyConfigParser::parseConfigValue(std::string& config, std::string&& configValue) {
config = std::move(configValue);
}

void TidyConfigParser::parseConfigValue(bool& config, std::string&& configValue) {
std::transform(configValue.begin(), configValue.end(), configValue.begin(), ::tolower);

if (configValue == "true" || configValue == "false") {
set_config(configValue == "true");
config = configValue == "true";
}
else if (configValue == "1" || configValue == "0") {
config = configValue == "1";
}
else {
set_config(configValue);
reportErrorAndExit(fmt::format("Expected boolean expression but got '{}'", configValue));
}
}

void TidyConfigParser::setCheckConfig(const std::string& configName,
std::vector<std::string> configValues) {
SLANG_TRY {
config.visitConfig(configName, [&](auto& v) {
if constexpr (is_vector_v<std::remove_cvref_t<decltype(v)>>) {
parseConfigValue(v, std::move(configValues));
}
else {
if (configValues.size() != 1) {
reportErrorAndExit(
fmt::format("Expected one configuration value for '{}' but got {}",
configName, configValues.size()));
}
parseConfigValue(v, std::move(configValues.front()));
}
});
}
SLANG_CATCH(std::invalid_argument & exception) {
#if __cpp_exceptions
reportWarning(exception.what());
#endif
}
}

Expand Down
Loading