diff --git a/misc/_murphi2c b/misc/_murphi2c index ae3d32a3..c1c9517b 100644 --- a/misc/_murphi2c +++ b/misc/_murphi2c @@ -3,6 +3,7 @@ # Zsh completion script for Murphi2C _arguments \ + '--colour[enable or disable ANSI colour codes]: :(auto off on)' \ '--header[generate a C header]' \ '--help[display help information]' \ {--output,-o}'[path to write source/header to]:filename:_files' \ diff --git a/misc/_murphi2murphi b/misc/_murphi2murphi index 262941f7..f297176e 100644 --- a/misc/_murphi2murphi +++ b/misc/_murphi2murphi @@ -3,6 +3,7 @@ # Zsh completion script for Murphi2Murphi _arguments \ + '--colour[enable or disable ANSI colour codes]: :(auto off on)' \ '--decompose-complex-comparisons[expand array and record equality tests]' \ '--explicit-semicolons[add omitted semicolons]' \ '--help[display help information]' \ diff --git a/misc/_murphi2smv b/misc/_murphi2smv index d9f86412..94ff54bc 100644 --- a/misc/_murphi2smv +++ b/misc/_murphi2smv @@ -3,6 +3,7 @@ # Zsh completion script for Murphi2SMV _arguments \ + '--colour[enable or disable ANSI colour codes]: :(auto off on)' \ '--help[display help information]' \ {--numeric-type,-n}'[target numeric type]:TYPE' \ {--output,-o}'[path to write SMV to]:filename:_files' \ diff --git a/misc/_murphi2xml b/misc/_murphi2xml index 58d75614..eee56e5d 100644 --- a/misc/_murphi2xml +++ b/misc/_murphi2xml @@ -3,6 +3,7 @@ # Zsh completion script for Murphi2XML _arguments \ + '--colour[enable or disable ANSI colour codes]: :(auto off on)' \ '--help[display help information]' \ {--output,-o}'[path to write XML to]:filename:_files' \ '--version[output version information]' \ diff --git a/murphi2c/doc/murphi2c.1 b/murphi2c/doc/murphi2c.1 index 8d1a081b..b713b94c 100644 --- a/murphi2c/doc/murphi2c.1 +++ b/murphi2c/doc/murphi2c.1 @@ -23,6 +23,12 @@ See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS +\fB\-\-colour\fR [\fBauto\fR | \fBoff\fR | \fBon\fR] +.RS +Enable or disable the use of ANSI colour codes in error messages. The default is +\fBauto\fR, to auto-detect based on whether the stderr is a TTY. +.RE +.PP \fB\-\-header\fR .RS Generate a C header, as opposed to a source file. diff --git a/murphi2c/src/check.cc b/murphi2c/src/check.cc index f7231a86..e7e68080 100644 --- a/murphi2c/src/check.cc +++ b/murphi2c/src/check.cc @@ -11,76 +11,46 @@ namespace { class Check : public ConstTraversal { public: - bool ok = true; - - void visit_choose(const Choose &) final { - if (ok) { - std::cerr << "choose rules are not supported\n"; - ok = false; - } + void visit_choose(const Choose &n) final { + throw Error("choose rules are not supported", n.loc); } - void visit_ismember(const IsMember &) final { - if (ok) { - std::cerr << "ismember expressions are not supported\n"; - ok = false; - } + void visit_ismember(const IsMember &n) final { + throw Error("ismember expressions are not supported", n.loc); } - void visit_isundefined(const IsUndefined &) final { - if (ok) { - std::cerr << "isundefined expressions are not supported\n"; - ok = false; - } + void visit_isundefined(const IsUndefined &n) final { + throw Error("isundefined expressions are not supported", n.loc); } - void visit_multiset(const Multiset &) final { - if (ok) { - std::cerr << "multiset types are not supported\n"; - ok = false; - } + void visit_multiset(const Multiset &n) final { + throw Error("multiset types are not supported", n.loc); } - void visit_multisetadd(const MultisetAdd &) final { - if (ok) { - std::cerr << "multiset types are not supported\n"; - ok = false; - } + void visit_multisetadd(const MultisetAdd &n) final { + throw Error("multiset types are not supported", n.loc); } - void visit_multisetcount(const MultisetCount &) final { - if (ok) { - std::cerr << "multiset types are not supported\n"; - ok = false; - } + void visit_multisetcount(const MultisetCount &n) final { + throw Error("multiset types are not supported", n.loc); } - void visit_multisetremove(const MultisetRemove &) final { - if (ok) { - std::cerr << "multiset types are not supported\n"; - ok = false; - } + void visit_multisetremove(const MultisetRemove &n) final { + throw Error("multiset types are not supported", n.loc); } - void visit_multisetremovepred(const MultisetRemovePred &) final { - if (ok) { - std::cerr << "multiset types are not supported\n"; - ok = false; - } + void visit_multisetremovepred(const MultisetRemovePred &n) final { + throw Error("multiset types are not supported", n.loc); } - void visit_union(const Union &) final { - if (ok) { - std::cerr << "union types are not supported\n"; - ok = false; - } + void visit_union(const Union &n) final { + throw Error("union types are not supported", n.loc); } }; } // namespace -bool check(const Node &n) { +void check(const Node &n) { Check c; c.dispatch(n); - return c.ok; } diff --git a/murphi2c/src/check.h b/murphi2c/src/check.h index 069c69a7..41a6cb31 100644 --- a/murphi2c/src/check.h +++ b/murphi2c/src/check.h @@ -2,6 +2,7 @@ #include -// validate the given AST contains no idioms that cannot be handled by murphi2c, -// and return false if any are found -bool check(const rumur::Node &n); +/// validate the given AST contains no idioms that cannot be handled by murphi2c +/// +/// Throws `rumur::Error` if anything is found that cannot be handled. +void check(const rumur::Node &n); diff --git a/murphi2c/src/main.cc b/murphi2c/src/main.cc index cdea1b7c..fbd3284a 100644 --- a/murphi2c/src/main.cc +++ b/murphi2c/src/main.cc @@ -7,7 +7,9 @@ #include "resources.h" #include #include +#include #include +#include #include #include #include @@ -17,25 +19,25 @@ #include #include #include -#include #include -// a pair of input streams -using dup_t = - std::pair, std::shared_ptr>; - -static std::string in_filename = ""; -static dup_t in; +static const char *in_filename = ""; +static std::shared_ptr in; static std::shared_ptr out; // output C source? (as opposed to C header) static bool source = true; +/// use colour in error messages? +static enum { AUTO, ON, OFF } color = AUTO; + static void parse_args(int argc, char **argv) { for (;;) { static struct option options[] = { // clang-format off + { "color", required_argument, 0, 132 }, + { "colour", required_argument, 0, 132 }, { "header", no_argument, 0, 128 }, { "help", no_argument, 0, 'h' }, { "output", required_argument, 0, 'o' }, @@ -89,6 +91,20 @@ static void parse_args(int argc, char **argv) { std::cout << "Murphi2C version " << rumur_get_version() << '\n'; exit(EXIT_SUCCESS); + case 132: // --color, --colour + if (strcmp(optarg, "auto") == 0) { + color = AUTO; + } else if (strcmp(optarg, "on") == 0) { + color = ON; + } else if (strcmp(optarg, "off") == 0) { + color = OFF; + } else { + std::cerr << "invalid --colour argument \"" << optarg << "\"\n" + << "valid arguments are \"auto\", \"off\", and \"on\"\n"; + exit(EXIT_FAILURE); + } + break; + default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); @@ -112,25 +128,93 @@ static void parse_args(int argc, char **argv) { in_filename = argv[optind]; auto i = std::make_shared(in_filename); - auto j = std::make_shared(in_filename); - if (!i->is_open() || !j->is_open()) { + if (!i->is_open()) { std::cerr << "failed to open " << in_filename << '\n'; exit(EXIT_FAILURE); } - in = dup_t(i, j); + in = i; } } -static dup_t make_stdin_dup() { +static std::shared_ptr make_stdin_buf() { // read stdin into memory auto buffer = std::make_shared(); *buffer << std::cin.rdbuf(); - // duplicate the buffer - auto copy = std::make_shared(buffer->str()); + return buffer; +} + +static bool use_colors() { + if (color == AUTO) + color = isatty(STDERR_FILENO) ? ON : OFF; + return color == ON; +} + +static const char *bold() { + if (use_colors()) + return "\033[1m"; + return ""; +} + +static const char *green() { + if (use_colors()) + return "\033[32m"; + return ""; +} + +static const char *red() { + if (use_colors()) + return "\033[31m"; + return ""; +} + +static const char *reset() { + if (use_colors()) + return "\033[0m"; + return ""; +} - return dup_t(buffer, copy); +static const char *white() { + if (use_colors()) + return "\033[37m"; + return ""; +} + +static void print_location(std::istream &src, const rumur::location &location) { + + // the type of position.line and position.column changes across Bison + // releases, so avoid some -Wsign-compare warnings by casting them in advance + auto loc_line = static_cast(location.begin.line); + auto loc_col = static_cast(location.begin.column); + + std::string line; + unsigned long lineno = 0; + while (lineno < loc_line) { + if (!std::getline(src, line)) + return; + lineno++; + } + + // print the line, and construct an underline indicating the column location + std::ostringstream buf; + unsigned long col = 1; + for (const char &c : line) { + if (col == loc_col) { + buf << green() << bold() << '^' << reset(); + } else if (col < loc_col) { + if (c == '\t') { + buf << '\t'; + } else { + buf << ' '; + } + } + std::cerr << c; + col++; + } + std::cerr << '\n'; + + std::cerr << buf.str() << '\n'; } int main(int argc, char **argv) { @@ -138,17 +222,20 @@ int main(int argc, char **argv) { // parse command line options parse_args(argc, argv); - // if we are reading from stdin, duplicate it so that we can parse it both as - // Murphi and for comments - if (in.first == nullptr) - in = make_stdin_dup(); + // if we are reading from stdin, duplicate it so that we can seek it + if (in == nullptr) + in = make_stdin_buf(); // parse input model rumur::Ptr m; try { - m = rumur::parse_model(*in.first); + m = rumur::parse_model(*in); } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } @@ -162,13 +249,25 @@ int main(int argc, char **argv) { resolve_symbols(*m); validate(*m); } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } // validate that this model is OK to translate - if (!check(*m)) + try { + check(*m); + } catch (rumur::Error &e) { + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; + } // name any rules that are unnamed, so they get valid C symbols rumur::sanitise_rule_names(*m); @@ -178,7 +277,8 @@ int main(int argc, char **argv) { bool pack = compares_complex_values(*m); // parse comments from the source code - std::vector comments = rumur::parse_comments(*in.second); + in->seekg(0); + std::vector comments = rumur::parse_comments(*in); // output code if (source) { diff --git a/murphi2murphi/doc/murphi2murphi.1 b/murphi2murphi/doc/murphi2murphi.1 index 20c4732f..80190d00 100644 --- a/murphi2murphi/doc/murphi2murphi.1 +++ b/murphi2murphi/doc/murphi2murphi.1 @@ -23,6 +23,12 @@ See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS +\fB\-\-colour\fR [\fBauto\fR | \fBoff\fR | \fBon\fR] +.RS +Enable or disable the use of ANSI colour codes in error messages. The default is +\fBauto\fR, to auto-detect based on whether the stderr is a TTY. +.RE +.PP \fB\-\-decompose\-complex\-comparisons\fR .RS Rumur supports comparing values of complex type (records and arrays) with each diff --git a/murphi2murphi/src/main.cc b/murphi2murphi/src/main.cc index 580bcc5e..f77a0bc2 100644 --- a/murphi2murphi/src/main.cc +++ b/murphi2murphi/src/main.cc @@ -11,6 +11,7 @@ #include "resources.h" #include #include +#include #include #include #include @@ -20,13 +21,17 @@ #include #include #include +#include using namespace rumur; +static const char *in_filename = ""; static std::shared_ptr in; -static std::shared_ptr in_replay; static std::shared_ptr out; +/// use colour in error messages? +static enum { AUTO, ON, OFF } color = AUTO; + // buffer the contents of stdin so we can read it twice static void buffer_stdin() { @@ -35,9 +40,8 @@ static void buffer_stdin() { buf << std::cin.rdbuf(); buf.flush(); - // put this into two buffers we can read from + // put this into a buffer we can read from in = std::make_shared(buf.str()); - in_replay = std::make_shared(buf.str()); } static void parse_args(int argc, char **argv) { @@ -46,6 +50,8 @@ static void parse_args(int argc, char **argv) { static struct option opts[] = { // clang-format off + { "color", required_argument, 0, 139 }, + { "colour", required_argument, 0, 139 }, { "decompose-complex-comparisons", no_argument, 0, 128 }, { "explicit-semicolons", no_argument, 0, 129 }, { "help", no_argument, 0, 'h' }, @@ -134,6 +140,20 @@ static void parse_args(int argc, char **argv) { std::cout << "Murphi2Murphi version " << rumur_get_version() << '\n'; exit(EXIT_SUCCESS); + case 139: // --color, --colour + if (strcmp(optarg, "auto") == 0) { + color = AUTO; + } else if (strcmp(optarg, "on") == 0) { + color = ON; + } else if (strcmp(optarg, "off") == 0) { + color = OFF; + } else { + std::cerr << "invalid --colour argument \"" << optarg << "\"\n" + << "valid arguments are \"auto\", \"off\", and \"on\"\n"; + exit(EXIT_FAILURE); + } + break; + default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); @@ -141,6 +161,8 @@ static void parse_args(int argc, char **argv) { } if (optind == argc - 1) { + in_filename = argv[optind]; + struct stat buf; if (stat(argv[optind], &buf) < 0) { std::cerr << "failed to open " << argv[optind] << ": " << strerror(errno) @@ -160,20 +182,84 @@ static void parse_args(int argc, char **argv) { exit(EXIT_FAILURE); } in = i; - - // open the input again that we need for replay during XML output - auto i2 = std::make_shared(argv[optind]); - if (!i2->is_open()) { - std::cerr << "failed to open " << argv[optind] << '\n'; - exit(EXIT_FAILURE); - } - in_replay = i2; } else { // we are going to read data from stdin buffer_stdin(); } } +static bool use_colors() { + if (color == AUTO) + color = isatty(STDERR_FILENO) ? ON : OFF; + return color == ON; +} + +static const char *bold() { + if (use_colors()) + return "\033[1m"; + return ""; +} + +static const char *green() { + if (use_colors()) + return "\033[32m"; + return ""; +} + +static const char *red() { + if (use_colors()) + return "\033[31m"; + return ""; +} + +static const char *reset() { + if (use_colors()) + return "\033[0m"; + return ""; +} + +static const char *white() { + if (use_colors()) + return "\033[37m"; + return ""; +} + +static void print_location(std::istream &src, const rumur::location &location) { + + // the type of position.line and position.column changes across Bison + // releases, so avoid some -Wsign-compare warnings by casting them in advance + auto loc_line = static_cast(location.begin.line); + auto loc_col = static_cast(location.begin.column); + + std::string line; + unsigned long lineno = 0; + while (lineno < loc_line) { + if (!std::getline(src, line)) + return; + lineno++; + } + + // print the line, and construct an underline indicating the column location + std::ostringstream buf; + unsigned long col = 1; + for (const char &c : line) { + if (col == loc_col) { + buf << green() << bold() << '^' << reset(); + } else if (col < loc_col) { + if (c == '\t') { + buf << '\t'; + } else { + buf << ' '; + } + } + std::cerr << c; + col++; + } + std::cerr << '\n'; + + std::cerr << buf.str() << '\n'; +} + int main(int argc, char **argv) { // parse command line options @@ -186,7 +272,11 @@ int main(int argc, char **argv) { try { m = parse_model(*in); } catch (Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } @@ -200,7 +290,11 @@ int main(int argc, char **argv) { resolve_symbols(*m); validate(*m); } catch (Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } @@ -208,7 +302,8 @@ int main(int argc, char **argv) { Pipeline pipe; // add output generator - Printer p(*in_replay, out == nullptr ? std::cout : *out); + in->seekg(0); + Printer p(*in, out == nullptr ? std::cout : *out); pipe.add_stage(p); // are we adding semi-colons? @@ -239,7 +334,11 @@ int main(int argc, char **argv) { pipe.finalise(); } catch (Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } diff --git a/murphi2smv/doc/murphi2smv.1 b/murphi2smv/doc/murphi2smv.1 index 1b0e139a..b360027b 100644 --- a/murphi2smv/doc/murphi2smv.1 +++ b/murphi2smv/doc/murphi2smv.1 @@ -10,6 +10,12 @@ checker. See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS +\fB\-\-colour\fR [\fBauto\fR | \fBoff\fR | \fBon\fR] +.RS +Enable or disable the use of ANSI colour codes in error messages. The default is +\fBauto\fR, to auto-detect based on whether the stderr is a TTY. +.RE +.PP \fB\-\-help\fR or \fB\-?\fR .RS Display usage information. diff --git a/murphi2smv/src/main.cc b/murphi2smv/src/main.cc index 807865e4..6228a9e0 100644 --- a/murphi2smv/src/main.cc +++ b/murphi2smv/src/main.cc @@ -3,7 +3,9 @@ #include "pick_numeric_type.h" #include "resources.h" #include +#include #include +#include #include #include #include @@ -12,30 +14,31 @@ #include #include #include -#include +#include -// a pair of input streams -using dup_t = - std::pair, std::shared_ptr>; - -static std::string in_filename{""}; -static dup_t in; -static std::string out_filename{"-"}; +static const char *in_filename = ""; +static std::shared_ptr in; +static const char *out_filename = "-"; static std::shared_ptr out; std::string numeric_type; +/// use colour in error messages? +static enum { AUTO, ON, OFF } color = AUTO; + static void parse_args(int argc, char **argv) { for (;;) { static struct option options[] = { // clang-format off + { "color", required_argument, 0, 129 }, + { "colour", required_argument, 0, 129 }, { "help", no_argument, 0, 'h' }, { "numeric-type", required_argument, 0, 'n' }, { "output", required_argument, 0, 'o' }, { "version", no_argument, 0, 128 }, { 0, 0, 0, 0 }, - // clange-format on + // clang-format on }; int option_index = 0; @@ -70,6 +73,20 @@ static void parse_args(int argc, char **argv) { std::cout << "Murphi2SMV version " << rumur_get_version() << '\n'; exit(EXIT_SUCCESS); + case 129: // --color, --colour + if (strcmp(optarg, "auto") == 0) { + color = AUTO; + } else if (strcmp(optarg, "on") == 0) { + color = ON; + } else if (strcmp(optarg, "off") == 0) { + color = OFF; + } else { + std::cerr << "invalid --colour argument \"" << optarg << "\"\n" + << "valid arguments are \"auto\", \"off\", and \"on\"\n"; + exit(EXIT_FAILURE); + } + break; + default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); @@ -93,29 +110,95 @@ static void parse_args(int argc, char **argv) { in_filename = argv[optind]; auto i = std::make_shared(in_filename); - auto j = std::make_shared(in_filename); - if (!i->is_open() || !j->is_open()) { + if (!i->is_open()) { std::cerr << "failed to open " << in_filename << '\n'; exit(EXIT_FAILURE); } - in = dup_t{i, j}; + in = i; } } -static dup_t make_stdin_dup() { +static std::shared_ptr make_stdin_buf() { // read stdin into memory auto buffer = std::make_shared(); *buffer << std::cin.rdbuf(); - // duplicate the buffer - auto copy = std::make_shared(buffer->str()); + return buffer; +} + +static std::ostream &output() { return out == nullptr ? std::cout : *out; } + +static bool use_colors() { + if (color == AUTO) + color = isatty(STDERR_FILENO) ? ON : OFF; + return color == ON; +} + +static const char *bold() { + if (use_colors()) + return "\033[1m"; + return ""; +} + +static const char *green() { + if (use_colors()) + return "\033[32m"; + return ""; +} + +static const char *red() { + if (use_colors()) + return "\033[31m"; + return ""; +} - return dup_t{buffer, copy}; +static const char *reset() { + if (use_colors()) + return "\033[0m"; + return ""; } -static std::ostream &output() { - return out == nullptr ? std::cout : *out; +static const char *white() { + if (use_colors()) + return "\033[37m"; + return ""; +} + +static void print_location(std::istream &src, const rumur::location &location) { + + // the type of position.line and position.column changes across Bison + // releases, so avoid some -Wsign-compare warnings by casting them in advance + auto loc_line = static_cast(location.begin.line); + auto loc_col = static_cast(location.begin.column); + + std::string line; + unsigned long lineno = 0; + while (lineno < loc_line) { + if (!std::getline(src, line)) + return; + lineno++; + } + + // print the line, and construct an underline indicating the column location + std::ostringstream buf; + unsigned long col = 1; + for (const char &c : line) { + if (col == loc_col) { + buf << green() << bold() << '^' << reset(); + } else if (col < loc_col) { + if (c == '\t') { + buf << '\t'; + } else { + buf << ' '; + } + } + std::cerr << c; + col++; + } + std::cerr << '\n'; + + std::cerr << buf.str() << '\n'; } int main(int argc, char **argv) { @@ -123,17 +206,20 @@ int main(int argc, char **argv) { // parse command line options parse_args(argc, argv); - // if we are reading from stdin, duplicate it so that we can parse it both as - // Murphi and for comments - if (in.first == nullptr) - in = make_stdin_dup(); + // if we are reading from stdin, buffer it so that we can seek it + if (in == nullptr) + in = make_stdin_buf(); // parse input rumur::Ptr parsed; try { - parsed = rumur::parse(*in.first); + parsed = rumur::parse(*in); } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } @@ -151,7 +237,11 @@ int main(int argc, char **argv) { resolve_symbols(*model); validate(*model); } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() + << ' ' << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } } @@ -161,11 +251,12 @@ int main(int argc, char **argv) { numeric_type = pick_numeric_type(*parsed); // parse comments from the source code - std::vector comments = rumur::parse_comments(*in.second); + in->seekg(0); + std::vector comments = rumur::parse_comments(*in); // only *now* open the output file, to avoid creating an empty file if any of // the preceding steps fail - if (out_filename != "-") { + if (strcmp(out_filename, "-") != 0) { auto o = std::make_shared(out_filename); if (!o->is_open()) { std::cerr << "failed to open " << out_filename << '\n'; diff --git a/murphi2uclid/doc/murphi2uclid.1 b/murphi2uclid/doc/murphi2uclid.1 index b3896f64..44d58adb 100644 --- a/murphi2uclid/doc/murphi2uclid.1 +++ b/murphi2uclid/doc/murphi2uclid.1 @@ -9,6 +9,12 @@ be used to translate a Murphi model into a Uclid5 model. See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS +\fB\-\-colour\fR [\fBauto\fR | \fBoff\fR | \fBon\fR] +.RS +Enable or disable the use of ANSI colour codes in error messages. The default is +\fBauto\fR, to auto-detect based on whether the stderr is a TTY. +.RE +.PP \fB\-\-help\fR or \fB\-?\fR .RS Display usage information. diff --git a/murphi2uclid/src/main.cc b/murphi2uclid/src/main.cc index d5694bb6..cb562a8a 100644 --- a/murphi2uclid/src/main.cc +++ b/murphi2uclid/src/main.cc @@ -18,22 +18,21 @@ #include #include #include -#include +#include #include -// a pair of input streams -using dup_t = - std::pair, std::shared_ptr>; - -static std::string in_filename = ""; -static dup_t in; -static std::string out_filename = "-"; +static const char *in_filename = ""; +static std::shared_ptr in; +static const char *out_filename = "-"; static std::shared_ptr out; std::string module_name = "main"; std::string numeric_type; +/// use colour in error messages? +static enum { AUTO, ON, OFF } color = AUTO; + static bool is_valid_numeric_type(const char *s) { assert(s != NULL); if (strcmp(s, "integer") == 0) @@ -54,6 +53,8 @@ static void parse_args(int argc, char **argv) { for (;;) { static struct option options[] = { // clang-format off + { "color", required_argument, 0, 129 }, + { "colour", required_argument, 0, 129 }, { "help", no_argument, 0, 'h' }, { "module", required_argument, 0, 'm' }, { "numeric-type", required_argument, 0, 'n' }, @@ -62,7 +63,7 @@ static void parse_args(int argc, char **argv) { { "verbose", no_argument, 0, 'v' }, { "version", no_argument, 0, 128 }, { 0, 0, 0, 0 }, - // clange-format on + // clang-format on }; int option_index = 0; @@ -109,6 +110,20 @@ static void parse_args(int argc, char **argv) { std::cout << "Murphi2Uclid version " << rumur_get_version() << '\n'; exit(EXIT_SUCCESS); + case 129: // --color, --colour + if (strcmp(optarg, "auto") == 0) { + color = AUTO; + } else if (strcmp(optarg, "on") == 0) { + color = ON; + } else if (strcmp(optarg, "off") == 0) { + color = OFF; + } else { + std::cerr << "invalid --colour argument \"" << optarg << "\"\n" + << "valid arguments are \"auto\", \"off\", and \"on\"\n"; + exit(EXIT_FAILURE); + } + break; + default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); @@ -132,29 +147,95 @@ static void parse_args(int argc, char **argv) { in_filename = argv[optind]; auto i = std::make_shared(in_filename); - auto j = std::make_shared(in_filename); - if (!i->is_open() || !j->is_open()) { + if (!i->is_open()) { std::cerr << "failed to open " << in_filename << '\n'; exit(EXIT_FAILURE); } - in = dup_t(i, j); + in = i; } } -static dup_t make_stdin_dup() { +static std::shared_ptr make_stdin_buf() { // read stdin into memory auto buffer = std::make_shared(); *buffer << std::cin.rdbuf(); - // duplicate the buffer - auto copy = std::make_shared(buffer->str()); + return buffer; +} + +static std::ostream &output() { return out == nullptr ? std::cout : *out; } + +static bool use_colors() { + if (color == AUTO) + color = isatty(STDERR_FILENO) ? ON : OFF; + return color == ON; +} + +static const char *bold() { + if (use_colors()) + return "\033[1m"; + return ""; +} + +static const char *green() { + if (use_colors()) + return "\033[32m"; + return ""; +} + +static const char *red() { + if (use_colors()) + return "\033[31m"; + return ""; +} + +static const char *reset() { + if (use_colors()) + return "\033[0m"; + return ""; +} - return dup_t(buffer, copy); +static const char *white() { + if (use_colors()) + return "\033[37m"; + return ""; } -static std::ostream &output() { - return out == nullptr ? std::cout : *out; +static void print_location(std::istream &src, const rumur::location &location) { + + // the type of position.line and position.column changes across Bison + // releases, so avoid some -Wsign-compare warnings by casting them in advance + auto loc_line = static_cast(location.begin.line); + auto loc_col = static_cast(location.begin.column); + + std::string line; + unsigned long lineno = 0; + while (lineno < loc_line) { + if (!std::getline(src, line)) + return; + lineno++; + } + + // print the line, and construct an underline indicating the column location + std::ostringstream buf; + unsigned long col = 1; + for (const char &c : line) { + if (col == loc_col) { + buf << green() << bold() << '^' << reset(); + } else if (col < loc_col) { + if (c == '\t') { + buf << '\t'; + } else { + buf << ' '; + } + } + std::cerr << c; + col++; + } + std::cerr << '\n'; + + std::cerr << buf.str() << '\n'; } int main(int argc, char **argv) { @@ -164,15 +245,19 @@ int main(int argc, char **argv) { // if we are reading from stdin, duplicate it so that we can parse it both as // Murphi and for comments - if (in.first == nullptr) - in = make_stdin_dup(); + if (in == nullptr) + in = make_stdin_buf(); // parse input rumur::Ptr parsed; try { - parsed = rumur::parse(*in.first); + parsed = rumur::parse(*in); } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } @@ -182,17 +267,21 @@ int main(int argc, char **argv) { auto model = dynamic_cast(parsed.get()); if (model != nullptr) { - // update unique identifiers within the model - model->reindex(); - - // check the model is valid - try { - resolve_symbols(*model); - validate(*model); - } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; - return EXIT_FAILURE; - } + // update unique identifiers within the model + model->reindex(); + + // check the model is valid + try { + resolve_symbols(*model); + validate(*model); + } catch (rumur::Error &e) { + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() + << ' ' << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); + return EXIT_FAILURE; + } } // name any rules that are unnamed, so they get valid Uclid5 symbols @@ -202,7 +291,11 @@ int main(int argc, char **argv) { try { check(*parsed); } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } @@ -211,11 +304,12 @@ int main(int argc, char **argv) { numeric_type = pick_numeric_type(*parsed); // parse comments from the source code - std::vector comments = rumur::parse_comments(*in.second); + in->seekg(0); + std::vector comments = rumur::parse_comments(*in); // only *now* open the output file, to avoid creating an empty file if any of // the preceding steps fail - if (out_filename != "-") { + if (strcmp(out_filename, "-") != 0) { auto o = std::make_shared(out_filename); if (!o->is_open()) { std::cerr << "failed to open " << out_filename << '\n'; diff --git a/murphi2xml/doc/murphi2xml.1 b/murphi2xml/doc/murphi2xml.1 index de726099..6eb0117f 100644 --- a/murphi2xml/doc/murphi2xml.1 +++ b/murphi2xml/doc/murphi2xml.1 @@ -10,6 +10,12 @@ format. See .BR rumur(1) for more information about Rumur or Murphi. .SH OPTIONS +\fB\-\-colour\fR [\fBauto\fR | \fBoff\fR | \fBon\fR] +.RS +Enable or disable the use of ANSI colour codes in error messages. The default is +\fBauto\fR, to auto-detect based on whether the stderr is a TTY. +.RE +.PP \fB\-\-help\fR or \fB\-?\fR .RS Display usage information. diff --git a/murphi2xml/src/main.cc b/murphi2xml/src/main.cc index 1b74ce65..4ddacf9b 100644 --- a/murphi2xml/src/main.cc +++ b/murphi2xml/src/main.cc @@ -2,7 +2,9 @@ #include "XMLPrinter.h" #include "resources.h" #include +#include #include +#include #include #include #include @@ -13,11 +15,13 @@ #include #include -static std::string in_filename = ""; +static const char *in_filename = ""; static std::shared_ptr in; -static std::shared_ptr in_replay; static std::shared_ptr out; +/// use colour in error messages? +static enum { AUTO, ON, OFF } color = AUTO; + // buffer the contents of stdin so we can read it twice static void buffer_stdin() { @@ -26,15 +30,16 @@ static void buffer_stdin() { buf << std::cin.rdbuf(); buf.flush(); - // put this into two buffers we can read from + // put this into a buffer we can read from in = std::make_shared(buf.str()); - in_replay = std::make_shared(buf.str()); } static void parse_args(int argc, char **argv) { for (;;) { static struct option options[] = { + {"color", required_argument, 0, 129}, + {"colour", required_argument, 0, 129}, {"help", no_argument, 0, '?'}, {"output", required_argument, 0, 'o'}, {"version", no_argument, 0, 128}, @@ -67,6 +72,20 @@ static void parse_args(int argc, char **argv) { std::cout << "Rumur version " << rumur_get_version() << '\n'; exit(EXIT_SUCCESS); + case 129: // --color, --colour + if (strcmp(optarg, "auto") == 0) { + color = AUTO; + } else if (strcmp(optarg, "on") == 0) { + color = ON; + } else if (strcmp(optarg, "off") == 0) { + color = OFF; + } else { + std::cerr << "invalid --colour argument \"" << optarg << "\"\n" + << "valid arguments are \"auto\", \"off\", and \"on\"\n"; + exit(EXIT_FAILURE); + } + break; + default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); @@ -95,20 +114,84 @@ static void parse_args(int argc, char **argv) { exit(EXIT_FAILURE); } in = i; - - // open the input again that we need for replay during XML output - auto i2 = std::make_shared(in_filename); - if (!i2->is_open()) { - std::cerr << "failed to open " << in_filename << '\n'; - exit(EXIT_FAILURE); - } - in_replay = i2; } else { // we are going to read data from stdin buffer_stdin(); } } +static bool use_colors() { + if (color == AUTO) + color = isatty(STDERR_FILENO) ? ON : OFF; + return color == ON; +} + +static const char *bold() { + if (use_colors()) + return "\033[1m"; + return ""; +} + +static const char *green() { + if (use_colors()) + return "\033[32m"; + return ""; +} + +static const char *red() { + if (use_colors()) + return "\033[31m"; + return ""; +} + +static const char *reset() { + if (use_colors()) + return "\033[0m"; + return ""; +} + +static const char *white() { + if (use_colors()) + return "\033[37m"; + return ""; +} + +static void print_location(std::istream &src, const rumur::location &location) { + + // the type of position.line and position.column changes across Bison + // releases, so avoid some -Wsign-compare warnings by casting them in advance + auto loc_line = static_cast(location.begin.line); + auto loc_col = static_cast(location.begin.column); + + std::string line; + unsigned long lineno = 0; + while (lineno < loc_line) { + if (!std::getline(src, line)) + return; + lineno++; + } + + // print the line, and construct an underline indicating the column location + std::ostringstream buf; + unsigned long col = 1; + for (const char &c : line) { + if (col == loc_col) { + buf << green() << bold() << '^' << reset(); + } else if (col < loc_col) { + if (c == '\t') { + buf << '\t'; + } else { + buf << ' '; + } + } + std::cerr << c; + col++; + } + std::cerr << '\n'; + + std::cerr << buf.str() << '\n'; +} + int main(int argc, char **argv) { // Parse command line options @@ -121,7 +204,11 @@ int main(int argc, char **argv) { try { m = rumur::parse_model(*in); } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } @@ -134,14 +221,19 @@ int main(int argc, char **argv) { resolve_symbols(*m); validate(*m); } catch (rumur::Error &e) { - std::cerr << e.loc << ":" << e.what() << '\n'; + std::cerr << white() << bold() << in_filename << ':' << e.loc << ':' + << reset() << ' ' << red() << bold() << "error:" << reset() << ' ' + << white() << bold() << e.what() << reset() << '\n'; + in->seekg(0); + print_location(*in, e.loc); return EXIT_FAILURE; } assert(m != nullptr); + in->seekg(0); { - XMLPrinter p(in_filename, *in_replay, out == nullptr ? std::cout : *out); + XMLPrinter p(in_filename, *in, out == nullptr ? std::cout : *out); p.dispatch(*m); } diff --git a/rumur/src/main.cc b/rumur/src/main.cc index 7725c37d..9eeaf666 100644 --- a/rumur/src/main.cc +++ b/rumur/src/main.cc @@ -544,8 +544,9 @@ static void parse_args(int argc, char **argv) { } static bool use_colors() { - return options.color == Color::ON || - (options.color == Color::AUTO && isatty(STDERR_FILENO)); + if (options.color == Color::AUTO) + options.color = isatty(STDERR_FILENO) ? Color::ON : Color::OFF; + return options.color == Color::ON; } static std::string bold() {