| // Copyright 2017-2020 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. |
| |
| // verilog_format is a command-line utility to format verilog source code |
| // for a given file. |
| // |
| // Example usage: |
| // verilog_format original-file > new-file |
| // |
| // Exit code: |
| // 0: stdout output can be used to replace original file |
| // nonzero: stdout output (if any) should be discarded |
| |
| #include <fstream> |
| #include <iostream> |
| #include <memory> |
| #include <sstream> // IWYU pragma: keep // for ostringstream |
| #include <string> // for string, allocator, etc |
| #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/str_join.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| #include "common/formatting/align.h" |
| #include "common/strings/position.h" |
| #include "common/util/file_util.h" |
| #include "common/util/init_command_line.h" |
| #include "common/util/interval_set.h" |
| #include "common/util/logging.h" // for operator<<, LOG, LogMessage, etc |
| #include "verilog/formatting/format_style.h" |
| #include "verilog/formatting/format_style_init.h" |
| #include "verilog/formatting/formatter.h" |
| |
| using absl::StatusCode; |
| using verible::LineNumberSet; |
| using verilog::formatter::ExecutionControl; |
| using verilog::formatter::FormatStyle; |
| using verilog::formatter::FormatVerilog; |
| |
| // Pseudo-singleton, so that repeated flag occurrences accumulate values. |
| // --flag x --flag y yields [x, y] |
| struct LineRanges { |
| // need to copy string, cannot just use string_view |
| typedef std::vector<std::string> storage_type; |
| static storage_type values; |
| }; |
| |
| LineRanges::storage_type LineRanges::values; // global initializer |
| |
| bool AbslParseFlag(absl::string_view flag_arg, LineRanges* /* unused */, |
| std::string* error) { |
| auto& values = LineRanges::values; |
| // Pre-split strings, so that "--flag v1,v2" and "--flag v1 --flag v2" are |
| // equivalent. |
| const std::vector<absl::string_view> tokens = absl::StrSplit(flag_arg, ','); |
| values.reserve(values.size() + tokens.size()); |
| for (const auto& token : tokens) { |
| // need to copy string, cannot just use string_view |
| values.push_back(std::string(token.begin(), token.end())); |
| } |
| // Range validation done later. |
| return true; |
| } |
| |
| std::string AbslUnparseFlag(LineRanges /* unused */) { |
| const auto& values = LineRanges::values; |
| return absl::StrJoin(values.begin(), values.end(), ",", |
| absl::StreamFormatter()); |
| } |
| |
| // TODO(fangism): Provide -i alias, as it is canonical to many formatters |
| ABSL_FLAG(bool, inplace, false, |
| "If true, overwrite the input file on successful conditions."); |
| ABSL_FLAG(std::string, stdin_name, "<stdin>", |
| "When using '-' to read from stdin, this gives an alternate name for " |
| "diagnostic purposes. Otherwise this is ignored."); |
| ABSL_FLAG(LineRanges, lines, {}, |
| "Specific lines to format, 1-based, comma-separated, inclusive N-M " |
| "ranges, N is short for N-N. By default, left unspecified, " |
| "all lines are enabled for formatting. (repeatable, cumulative)"); |
| ABSL_FLAG(bool, failsafe_success, true, |
| "If true, always exit with 0 status, even if there were input errors " |
| "or internal errors. In all error conditions, the original text is " |
| "always preserved. This is useful in deploying services where " |
| "fail-safe behaviors should be considered a success."); |
| ABSL_FLAG(bool, verify_convergence, true, |
| "If true, and not incrementally formatting with --lines, " |
| "verify that re-formatting the formatted output yields " |
| "no further changes, i.e. formatting is convergent."); |
| |
| ABSL_FLAG(bool, verbose, false, "Be more verbose."); |
| |
| ABSL_FLAG(int, show_largest_token_partitions, 0, |
| "If > 0, print token partitioning and then " |
| "exit without formatting output."); |
| ABSL_FLAG(bool, show_token_partition_tree, false, |
| "If true, print diagnostics after token partitioning and then " |
| "exit without formatting output."); |
| ABSL_FLAG(bool, show_inter_token_info, false, |
| "If true, along with show_token_partition_tree, include inter-token " |
| "information such as spacing and break penalties."); |
| ABSL_FLAG(bool, show_equally_optimal_wrappings, false, |
| "If true, print when multiple optimal solutions are found (stderr), " |
| "but continue to operate normally."); |
| ABSL_FLAG(int, max_search_states, 100000, |
| "Limits the number of search states explored during " |
| "line wrap optimization."); |
| |
| static std::ostream& FileMsg(absl::string_view filename) { |
| std::cerr << filename << ": "; |
| return std::cerr; |
| } |
| |
| static bool formatOneFile(absl::string_view filename, |
| const LineNumberSet& lines_to_format) { |
| const bool inplace = absl::GetFlag(FLAGS_inplace); |
| const bool is_stdin = filename == "-"; |
| const auto& stdin_name = absl::GetFlag(FLAGS_stdin_name); |
| |
| if (inplace && is_stdin) { |
| FileMsg(filename) |
| << "--inplace is incompatible with stdin. Ignoring --inplace " |
| << "and writing to stdout." << std::endl; |
| } |
| |
| const auto diagnostic_filename = is_stdin ? stdin_name : filename; |
| |
| // Read contents into memory first. |
| std::string content; |
| absl::Status status = verible::file::GetContents(filename, &content); |
| if (!status.ok()) { |
| FileMsg(filename) << status << std::endl; |
| return false; |
| } |
| |
| // TODO(fangism): When requesting --inplace, verify that file |
| // is write-able, and fail-early if it is not. |
| |
| FormatStyle format_style; |
| verilog::formatter::InitializeFromFlags(&format_style); |
| |
| // Handle special debugging modes. |
| ExecutionControl formatter_control; |
| { |
| // execution control flags |
| formatter_control.stream = &std::cout; // for diagnostics only |
| formatter_control.show_largest_token_partitions = |
| absl::GetFlag(FLAGS_show_largest_token_partitions); |
| formatter_control.show_token_partition_tree = |
| absl::GetFlag(FLAGS_show_token_partition_tree); |
| formatter_control.show_inter_token_info = |
| absl::GetFlag(FLAGS_show_inter_token_info); |
| formatter_control.show_equally_optimal_wrappings = |
| absl::GetFlag(FLAGS_show_equally_optimal_wrappings); |
| formatter_control.max_search_states = |
| absl::GetFlag(FLAGS_max_search_states); |
| formatter_control.verify_convergence = |
| absl::GetFlag(FLAGS_verify_convergence); |
| } |
| |
| std::ostringstream stream; |
| const auto format_status = |
| FormatVerilog(content, diagnostic_filename, format_style, stream, |
| lines_to_format, formatter_control); |
| |
| const std::string& formatted_output(stream.str()); |
| if (!format_status.ok()) { |
| if (!inplace) { |
| // Fall back to printing original content regardless of error condition. |
| std::cout << content; |
| } |
| switch (format_status.code()) { |
| case StatusCode::kCancelled: |
| case StatusCode::kInvalidArgument: |
| FileMsg(filename) << format_status.message() << std::endl; |
| break; |
| case StatusCode::kDataLoss: |
| FileMsg(filename) << format_status.message() |
| << "; problematic formatter output is\n" |
| << formatted_output << "<<EOF>>" << std::endl; |
| break; |
| default: |
| FileMsg(filename) << format_status.message() << "[other error status]" |
| << std::endl; |
| break; |
| } |
| |
| return absl::GetFlag(FLAGS_failsafe_success); |
| } |
| |
| // Safe to write out result, having passed above verification. |
| if (inplace && !is_stdin) { |
| // Don't write if the output is exactly as the input, so that we don't mess |
| // with tools that look for timestamp changes (such as make). |
| if (content != formatted_output) { |
| status = verible::file::SetContents(filename, formatted_output); |
| if (!status.ok()) { |
| FileMsg(filename) << "error writing result " << status << std::endl; |
| return false; |
| } |
| } else if (absl::GetFlag(FLAGS_verbose)) { |
| FileMsg(filename) << "Already formatted, no change." << std::endl; |
| } |
| } else { |
| std::cout << formatted_output; |
| } |
| |
| return true; |
| } |
| |
| int main(int argc, char** argv) { |
| const auto usage = absl::StrCat("usage: ", argv[0], |
| " [options] <file> [<file...>]\n" |
| "To pipe from stdin, use '-' as <file>."); |
| const auto file_args = verible::InitCommandLine(usage, &argc, &argv); |
| |
| if (file_args.size() == 1) { |
| std::cerr << absl::ProgramUsageMessage() << std::endl; |
| // TODO(hzeller): how can we append the output of --help here ? |
| return 1; |
| } |
| |
| // Parse LineRanges into a line set, to validate the --lines flag(s) |
| LineNumberSet lines_to_format; |
| if (!verible::ParseInclusiveRanges( |
| &lines_to_format, LineRanges::values.begin(), |
| LineRanges::values.end(), &std::cerr, '-')) { |
| std::cerr << "Error parsing --lines." << std::endl; |
| std::cerr << "Got: --lines=" << AbslUnparseFlag(LineRanges()) << std::endl; |
| return 1; |
| } |
| |
| // Some sanity checks if multiple files are given. |
| if (file_args.size() > 2) { |
| if (!lines_to_format.empty()) { |
| std::cerr << "--lines only works for single files." << std::endl; |
| return 1; |
| } |
| |
| if (!absl::GetFlag(FLAGS_inplace)) { |
| // Dumping all to stdout doesn't really make sense. |
| std::cerr << "--inplace required for multiple files." << std::endl; |
| return 1; |
| } |
| } |
| |
| bool all_success = true; |
| // All positional arguments are file names. Exclude program name. |
| for (const absl::string_view filename : |
| verible::make_range(file_args.begin() + 1, file_args.end())) { |
| all_success &= formatOneFile(filename, lines_to_format); |
| } |
| |
| return all_success ? 0 : 1; |
| } |