| // Copyright 2017-2022 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <functional> |
| #include <iostream> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/flags/flag.h" |
| #include "absl/flags/usage.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "common/util/file_util.h" |
| #include "common/util/init_command_line.h" |
| #include "common/util/status_macros.h" |
| #include "common/util/subcommand.h" |
| #include "verilog/analysis/flow_tree.h" |
| #include "verilog/analysis/verilog_filelist.h" |
| #include "verilog/analysis/verilog_project.h" |
| #include "verilog/parser/verilog_lexer.h" |
| #include "verilog/preprocessor/verilog_preprocess.h" |
| #include "verilog/transform/strip_comments.h" |
| |
| using verible::SubcommandArgsRange; |
| using verible::SubcommandEntry; |
| using FileOpener = verilog::VerilogPreprocess::FileOpener; |
| |
| // TODO(karimtera): Add a boolean flag to configure the macro expansion. |
| ABSL_FLAG(int, limit_variants, 20, "Maximum number of variants printed"); |
| |
| static absl::Status StripComments(const SubcommandArgsRange& args, |
| std::istream&, std::ostream& outs, |
| std::ostream&) { |
| // Parse the arguments into a FileList. |
| std::vector<absl::string_view> cmdline_args(args.begin(), args.end()); |
| verilog::FileList file_list; |
| RETURN_IF_ERROR( |
| verilog::AppendFileListFromCommandline(cmdline_args, &file_list)); |
| const auto& files = file_list.file_paths; |
| |
| if (files.empty()) { |
| return absl::InvalidArgumentError( |
| "Missing file argument. Use '-' for stdin."); |
| } |
| const absl::string_view source_file = files[0]; |
| std::string source_contents; |
| RETURN_IF_ERROR(verible::file::GetContents(source_file, &source_contents)); |
| |
| char replace_char; |
| if (args.size() == 1) { |
| replace_char = ' '; |
| } else if (args.size() == 2) { |
| absl::string_view replace_str(args[1]); |
| if (replace_str.empty()) |
| replace_char = '\0'; |
| else if (replace_str.length() == 1) |
| replace_char = replace_str[0]; |
| else |
| return absl::InvalidArgumentError( |
| "Replacement must be a single character."); |
| } else { |
| return absl::InvalidArgumentError("Too many arguments."); |
| } |
| |
| verilog::StripVerilogComments(source_contents, &outs, replace_char); |
| |
| return absl::OkStatus(); |
| } |
| |
| static absl::Status PreprocessSingleFile( |
| absl::string_view source_file, |
| const verilog::FileList::PreprocessingInfo& preprocessing_info, |
| std::ostream& outs, std::ostream& message_stream) { |
| std::string source_contents; |
| if (auto status = verible::file::GetContents(source_file, &source_contents); |
| !status.ok()) { |
| message_stream << source_file << status; |
| return status; |
| } |
| verilog::VerilogPreprocess::Config config; |
| config.filter_branches = true; |
| config.include_files = true; |
| config.expand_macros = 1; |
| |
| verilog::VerilogProject project(".", preprocessing_info.include_dirs); |
| |
| FileOpener file_opener = |
| [&project]( |
| absl::string_view filename) -> absl::StatusOr<absl::string_view> { |
| auto result = project.OpenIncludedFile(filename); |
| if (!result.status().ok()) return result.status(); |
| return (*result)->GetContent(); |
| }; |
| verilog::VerilogPreprocess preprocessor(config, file_opener); |
| |
| // Setting the preprocessing info (defines, and incdirs) in the preprocessor. |
| preprocessor.setPreprocessingInfo(preprocessing_info); |
| |
| verilog::VerilogLexer lexer(source_contents); |
| verible::TokenSequence lexed_sequence; |
| for (lexer.DoNextToken(); !lexer.GetLastToken().isEOF(); |
| lexer.DoNextToken()) { |
| // For now we will store the syntax tree tokens only, ignoring all the |
| // white-space characters. however that should be stored to output the |
| // source code just like it was, but with conditionals filtered. |
| lexed_sequence.push_back(lexer.GetLastToken()); |
| } |
| verible::TokenStreamView lexed_streamview; |
| // Initializing the lexed token stream view. |
| InitTokenStreamView(lexed_sequence, &lexed_streamview); |
| verilog::VerilogPreprocessData preprocessed_data = |
| preprocessor.ScanStream(lexed_streamview); |
| auto& preprocessed_stream = preprocessed_data.preprocessed_token_stream; |
| for (auto u : preprocessed_stream) outs << u->text(); |
| for (auto& u : preprocessed_data.errors) outs << u.error_message << '\n'; |
| if (!preprocessed_data.errors.empty()) |
| return absl::InvalidArgumentError("Error: The preprocessing has failed."); |
| return absl::OkStatus(); |
| } |
| |
| static absl::Status MultipleCU(const SubcommandArgsRange& args, std::istream&, |
| std::ostream& outs, |
| std::ostream& message_stream) { |
| // Parse the arguments into a FileList. |
| std::vector<absl::string_view> cmdline_args(args.begin(), args.end()); |
| verilog::FileList file_list; |
| RETURN_IF_ERROR( |
| verilog::AppendFileListFromCommandline(cmdline_args, &file_list)); |
| const auto& files = file_list.file_paths; |
| auto& preprocessing_info = file_list.preprocessing; |
| |
| // TODO(karimtera): allow including files with absolute paths. |
| // This is a hacky solution for now. |
| preprocessing_info.include_dirs.push_back("/"); |
| |
| if (files.empty()) { |
| return absl::InvalidArgumentError("ERROR: Missing file argument."); |
| } |
| for (const absl::string_view source_file : files) { |
| RETURN_IF_ERROR(PreprocessSingleFile(source_file, preprocessing_info, outs, |
| message_stream)); |
| } |
| return absl::OkStatus(); |
| } |
| |
| static absl::Status GenerateVariants(const SubcommandArgsRange& args, |
| std::istream&, std::ostream& outs, |
| std::ostream& message_stream) { |
| // Parse the arguments into a FileList. |
| std::vector<absl::string_view> cmdline_args(args.begin(), args.end()); |
| verilog::FileList file_list; |
| RETURN_IF_ERROR( |
| verilog::AppendFileListFromCommandline(cmdline_args, &file_list)); |
| const auto& files = file_list.file_paths; |
| // TODO(karimtera): Pass the +define's to the preprocessor, and only |
| // generate variants with theses defines fixed. |
| |
| const int limit_variants = absl::GetFlag(FLAGS_limit_variants); |
| |
| if (files.empty()) { |
| return absl::InvalidArgumentError("ERROR: Missing file argument."); |
| } |
| if (files.size() > 1) { |
| return absl::InvalidArgumentError( |
| "ERROR: generate-variants only works on one file."); |
| } |
| const auto& source_file = files[0]; |
| std::string source_contents; |
| if (auto status = verible::file::GetContents(source_file, &source_contents); |
| !status.ok()) { |
| message_stream << source_file << status; |
| return status; |
| } |
| |
| // Lexing the input SV source code. |
| verilog::VerilogLexer lexer(source_contents); |
| verible::TokenSequence lexed_sequence; |
| for (lexer.DoNextToken(); !lexer.GetLastToken().isEOF(); |
| lexer.DoNextToken()) { |
| // For now we will store the syntax tree tokens only, ignoring all the |
| // white-space characters. however that should be stored to output the |
| // source code just like it was. |
| if (verilog::VerilogLexer::KeepSyntaxTreeTokens(lexer.GetLastToken())) { |
| lexed_sequence.push_back(lexer.GetLastToken()); |
| } |
| } |
| |
| // Control flow tree constructing. |
| verilog::FlowTree control_flow_tree(lexed_sequence); |
| int counter = 0; |
| return control_flow_tree.GenerateVariants( |
| [limit_variants, &outs, &message_stream, |
| &counter](const verilog::FlowTree::Variant& variant) { |
| if (counter == limit_variants) return false; |
| counter++; |
| message_stream << "Variant number " << counter << ":\n"; |
| for (auto token : variant.sequence) outs << token << '\n'; |
| // TODO(karimtera): Consider creating an output file per vairant, |
| // Such that the files naming reflects which defines are |
| // defined/undefined. |
| return true; |
| }); |
| } |
| |
| static const std::pair<absl::string_view, SubcommandEntry> kCommands[] = { |
| {"preprocess", |
| {&MultipleCU, |
| R"(preprocess [define-include-flags] file [file...] |
| Inputs: |
| Accepts one or more Verilog or SystemVerilog source files to preprocess. |
| Each one of them will be prepropcessed independently which means that |
| declaration scopes will end by the end of each file, and won't be seen from |
| other files (so multiple files will _not_ be treated as compilation unit). |
| The +define+ and +include+ directives on the commandline are honored by |
| the preprocessor. |
| Output: (stdout) |
| The preprocessed files content (same contents with directives interpreted) |
| will be written to stdout, concatenated. |
| )"}}, |
| |
| {"strip-comments", |
| {&StripComments, |
| R"(strip-comments file [replacement-char] |
| Inputs: |
| 'file' is a Verilog or SystemVerilog source file. |
| Use '-' to read from stdin. |
| 'replacement-char' is a character to replace comments with. |
| If not given, or given as a single space character, the comment contents and |
| delimiters are replaced with spaces. |
| If an empty string, the comment contents and delimiters are deleted. Newlines |
| are not deleted. |
| If a single character, the comment contents are replaced with the character. |
| Output: (stdout) |
| Contents of original file with // and /**/ comments removed. |
| )"}}, |
| |
| {"generate-variants", |
| {&GenerateVariants, |
| R"(generate-variants file [-limit_variants number] |
| Inputs: |
| 'file' is a Verilog or SystemVerilog source file. |
| '-limit_variants' flag limits variants to 'number' (20 by default). |
| Output: (stdout) |
| Generates every possible variant of `ifdef blocks considering the |
| conditional directives. |
| )"}}, |
| // TODO(karimtera): We can add another argument to `generate-variants`, |
| // Which allows us to set some defines, as if we are only interested |
| // in the variants in which these defines are set. |
| |
| // TODO(karimtera): Another candidate subcommand is `list-defines`, |
| // Which would be the output of `GetUsedMacros()`. |
| }; |
| |
| int main(int argc, char* argv[]) { |
| // Create a registry of subcommands (locally, rather than as a static global). |
| verible::SubcommandRegistry commands; |
| for (const auto& entry : kCommands) { |
| const auto status = commands.RegisterCommand(entry.first, entry.second); |
| if (!status.ok()) { |
| std::cerr << status.message() << std::endl; |
| return 2; // fatal error |
| } |
| } |
| |
| const std::string usage = absl::StrCat("usage: ", argv[0], |
| " command args...\n" |
| "available commands:\n", |
| commands.ListCommands()); |
| |
| // Process invocation args. |
| const auto args = verible::InitCommandLine(usage, &argc, &argv); |
| if (args.size() == 1) { |
| std::cerr << absl::ProgramUsageMessage() << std::endl; |
| return 1; |
| } |
| // args[0] is the program name |
| // args[1] is the subcommand |
| // subcommand args start at [2] |
| const SubcommandArgsRange command_args(args.cbegin() + 2, args.cend()); |
| |
| const auto& sub = commands.GetSubcommandEntry(args[1]); |
| // Run the subcommand. |
| const auto status = sub.main(command_args, std::cin, std::cout, std::cerr); |
| if (!status.ok()) { |
| std::cerr << status.message() << std::endl; |
| return 1; |
| } |
| return 0; |
| } |