From e6c09f1e0e8a7c2ca0e455db7f0199cb55f18125 Mon Sep 17 00:00:00 2001
From: Alberto Gonzalez <boqwxp@airmail.cc>
Date: Sun, 15 Mar 2020 09:16:04 +0000
Subject: [PATCH 1/5] Add `exec` command to run shell commands.

---
 passes/cmds/Makefile.inc |   1 +
 passes/cmds/exec.cc      | 156 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 157 insertions(+)
 create mode 100644 passes/cmds/exec.cc

diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc
index 20b38bf8e..60f20fa6d 100644
--- a/passes/cmds/Makefile.inc
+++ b/passes/cmds/Makefile.inc
@@ -1,4 +1,5 @@
 
+OBJS += passes/cmds/exec.o
 OBJS += passes/cmds/add.o
 OBJS += passes/cmds/delete.o
 OBJS += passes/cmds/design.o
diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc
new file mode 100644
index 000000000..9b9f136dc
--- /dev/null
+++ b/passes/cmds/exec.cc
@@ -0,0 +1,156 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *
+ *  Permission to use, copy, modify, and/or distribute this software for any
+ *  purpose with or without fee is hereby granted, provided that the above
+ *  copyright notice and this permission notice appear in all copies.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "kernel/register.h"
+#include "kernel/log.h"
+#include <cstdio>
+#include <sys/wait.h>
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct ExecPass : public Pass {
+	ExecPass() : Pass("exec", "execute commands in the operating system shell") { }
+	void help() YS_OVERRIDE
+	{
+		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+		log("\n");
+		log("    exec [options] -- [command]\n");
+		log("\n");
+		log("Execute a command in the operating system shell.  All supplied arguments are\n");
+		log("concatenated and passed as a command to popen(3).  Whitespace is not guaranteed\n");
+		log("to be preserved, even if quoted.  stdin is not connected, while stdout is\n");
+		log("logged and stderr is logged as a warning.\n");
+		log("\n");
+		log("\n");
+		log("    -q\n");
+		log("        suppress stdout and stderr from subprocess\n");
+		log("\n");
+		log("    -expected-return <int>\n");
+		log("        generates an error if popen() does not return specified value.\n");
+		log("\n");
+		log("    -expected-stdout <regex>\n");
+		log("        generates an error if specified regex does not match any line\n");
+		log("        in subprocess stdout.\n");
+		log("\n");
+		log("\n");
+		log("    Example: exec -q -expected-return 0 -- echo \"bananapie\" | grep \"nana\"\n");
+		log("\n");
+		log("\n");
+	}
+	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
+	{
+		if(args.size() == 0)
+			log_cmd_error("No command provided.\n");
+
+		std::string cmd = "";
+		char buf[4096] = {};
+		std::string linebuf = "";
+		bool flag_cmd = false;
+		bool flag_quiet = false;
+		bool flag_expected_return = false;
+		bool flag_expected_stdout = false;
+		int expected_return_value = 0;
+		YS_REGEX_TYPE expected_stdout_re;
+		std::string expected_stdout_re_str;
+		bool expected_stdout_re_matched = false;
+
+		for(size_t argidx = 1; argidx < args.size(); ++argidx) {
+			if (flag_cmd) {
+				cmd += args[argidx] + (argidx != (args.size() - 1)? " " : "");
+			} else {
+				if (args[argidx] == "--")
+					flag_cmd = true;
+				else if (args[argidx] == "-q")
+					flag_quiet = true;
+				else if (args[argidx] == "-expected-return") {
+					flag_expected_return = true;
+					++argidx;
+					if (argidx >= args.size())
+						log_cmd_error("No expected return value specified.\n");
+
+					expected_return_value = atoi(args[argidx].c_str());
+				} else if (args[argidx] == "-expected-stdout") {
+					flag_expected_stdout = true;
+					++argidx;
+					if (argidx >= args.size())
+						log_cmd_error("No expected regular expression to find in stdout specified.\n");
+
+					try{
+						expected_stdout_re_str = args[argidx];
+						expected_stdout_re = YS_REGEX_COMPILE(args[argidx]);
+					} catch (const YS_REGEX_NS::regex_error& e) {
+						log_cmd_error("Error in regex expression '%s' !\n", expected_stdout_re_str.c_str());
+					}
+
+				} else
+					log_cmd_error("Unknown option \"%s\" or \"--\" doesn\'t precede command.", args[argidx].c_str());
+			}
+		}
+
+		log_header(design, "Executing command \"%s\".\n", cmd.c_str());
+		log_push();
+
+		fflush(stdout);
+		bool keep_reading = true;
+		auto *f = popen(cmd.c_str(), "r");
+		if (f == nullptr)
+			log_cmd_error("errno %d after popen() returned NULL.\n", errno);
+		while (keep_reading) {
+			keep_reading = (fgets(buf, sizeof(buf), f) != nullptr);
+			linebuf += buf;
+			memset(buf, 0, sizeof(buf));
+
+			auto pos = linebuf.find('\n');
+			while (pos != std::string::npos) {
+				std::string line = linebuf.substr(0, pos);
+				linebuf.erase(0, pos + 1);
+				if (!flag_quiet)
+					log("%s\n", line.c_str());
+
+				if(YS_REGEX_NS::regex_search(line, expected_stdout_re))
+					expected_stdout_re_matched = true;
+
+				pos = linebuf.find('\n');
+			}
+		}
+		int status = pclose(f);
+		int retval = -1;
+
+		if(WIFEXITED(status)) {
+		    retval = WEXITSTATUS(status);
+		}
+		else if(WIFSIGNALED(status)) {
+		    retval = WTERMSIG(status);
+		}
+		else if(WIFSTOPPED(status)) {
+		    retval = WSTOPSIG(status);
+		}
+
+		if (flag_expected_return && retval != expected_return_value)
+			log_cmd_error("Return value %d did not match expected return value %d.\n", retval, expected_return_value);
+
+		if (flag_expected_stdout && !expected_stdout_re_matched)
+			log_cmd_error("Command stdout did not have a line matching given regex \"%s\".\n", expected_stdout_re_str.c_str());
+
+		log_pop();
+	}
+} ExecPass;
+
+PRIVATE_NAMESPACE_END

From 8ba49a8462141db4ad715ec7b9e84b0d0d487cd6 Mon Sep 17 00:00:00 2001
From: Alberto Gonzalez <boqwxp@airmail.cc>
Date: Mon, 16 Mar 2020 06:02:14 +0000
Subject: [PATCH 2/5] Allow specifying multiple regexes to match in `exec`
 command output, and also to specify regexes that must _not_ match.

---
 passes/cmds/exec.cc | 100 +++++++++++++++++++++++++++++---------------
 1 file changed, 67 insertions(+), 33 deletions(-)

diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc
index 9b9f136dc..ec7b5b377 100644
--- a/passes/cmds/exec.cc
+++ b/passes/cmds/exec.cc
@@ -35,41 +35,54 @@ struct ExecPass : public Pass {
 		log("\n");
 		log("Execute a command in the operating system shell.  All supplied arguments are\n");
 		log("concatenated and passed as a command to popen(3).  Whitespace is not guaranteed\n");
-		log("to be preserved, even if quoted.  stdin is not connected, while stdout is\n");
-		log("logged and stderr is logged as a warning.\n");
+		log("to be preserved, even if quoted.  stdin and stderr are not connected, while stdout is\n");
+		log("logged unless the \"-q\" option is specified.\n");
 		log("\n");
 		log("\n");
 		log("    -q\n");
-		log("        suppress stdout and stderr from subprocess\n");
+		log("        Suppress stdout and stderr from subprocess\n");
 		log("\n");
-		log("    -expected-return <int>\n");
-		log("        generates an error if popen() does not return specified value.\n");
+		log("    -expect-return <int>\n");
+		log("        Generate an error if popen() does not return specified value.\n");
+		log("        May only be specified once; the final specified value is controlling\n");
+		log("        if specified multiple times.\n");
 		log("\n");
-		log("    -expected-stdout <regex>\n");
-		log("        generates an error if specified regex does not match any line\n");
-		log("        in subprocess stdout.\n");
+		log("    -expect-stdout <regex>\n");
+		log("        Generate an error if the specified regex does not match any line\n");
+		log("        in subprocess's stdout.  May be specified multiple times.\n");
+		log("\n");
+		log("    -not-expect-stdout <regex>\n");
+		log("        Generate an error if the specified regex matches any line\n");
+		log("        in subprocess's stdout.  May be specified multiple times.\n");
 		log("\n");
 		log("\n");
-		log("    Example: exec -q -expected-return 0 -- echo \"bananapie\" | grep \"nana\"\n");
+		log("    Example: exec -q -expect-return 0 -- echo \"bananapie\" | grep \"nana\"\n");
 		log("\n");
 		log("\n");
 	}
 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
 	{
-		if(args.size() == 0)
-			log_cmd_error("No command provided.\n");
-
 		std::string cmd = "";
 		char buf[4096] = {};
 		std::string linebuf = "";
 		bool flag_cmd = false;
 		bool flag_quiet = false;
-		bool flag_expected_return = false;
-		bool flag_expected_stdout = false;
-		int expected_return_value = 0;
-		YS_REGEX_TYPE expected_stdout_re;
-		std::string expected_stdout_re_str;
-		bool expected_stdout_re_matched = false;
+		bool flag_expect_return = false;
+		int expect_return_value = 0;
+		bool flag_expect_stdout = false;
+		struct expect_stdout_elem {
+			bool matched;
+			bool polarity;	//true: this regex must match at least one line
+					//false: this regex must not match any line
+			std::string str;
+			YS_REGEX_TYPE re;
+
+			expect_stdout_elem() : matched(false), polarity(true), str(), re(){};
+		};
+		std::vector<expect_stdout_elem> expect_stdout;
+
+		if(args.size() == 0)
+			log_cmd_error("No command provided.\n");
 
 		for(size_t argidx = 1; argidx < args.size(); ++argidx) {
 			if (flag_cmd) {
@@ -79,24 +92,41 @@ struct ExecPass : public Pass {
 					flag_cmd = true;
 				else if (args[argidx] == "-q")
 					flag_quiet = true;
-				else if (args[argidx] == "-expected-return") {
-					flag_expected_return = true;
+				else if (args[argidx] == "-expect-return") {
+					flag_expect_return = true;
 					++argidx;
 					if (argidx >= args.size())
 						log_cmd_error("No expected return value specified.\n");
 
-					expected_return_value = atoi(args[argidx].c_str());
-				} else if (args[argidx] == "-expected-stdout") {
-					flag_expected_stdout = true;
+					expect_return_value = atoi(args[argidx].c_str());
+				} else if (args[argidx] == "-expect-stdout") {
+					flag_expect_stdout = true;
 					++argidx;
 					if (argidx >= args.size())
-						log_cmd_error("No expected regular expression to find in stdout specified.\n");
+						log_cmd_error("No expected regular expression specified.\n");
 
 					try{
-						expected_stdout_re_str = args[argidx];
-						expected_stdout_re = YS_REGEX_COMPILE(args[argidx]);
+						expect_stdout_elem x;
+						x.str = args[argidx];
+						x.re = YS_REGEX_COMPILE(args[argidx]);
+						expect_stdout.push_back(x);
 					} catch (const YS_REGEX_NS::regex_error& e) {
-						log_cmd_error("Error in regex expression '%s' !\n", expected_stdout_re_str.c_str());
+						log_cmd_error("Error in regex expression '%s' !\n", args[argidx].c_str());
+					}
+				} else if (args[argidx] == "-not-expect-stdout") {
+					flag_expect_stdout = true;
+					++argidx;
+					if (argidx >= args.size())
+						log_cmd_error("No expected regular expression specified.\n");
+
+					try{
+						expect_stdout_elem x;
+						x.str = args[argidx];
+						x.re = YS_REGEX_COMPILE(args[argidx]);
+						x.polarity = false;
+						expect_stdout.push_back(x);
+					} catch (const YS_REGEX_NS::regex_error& e) {
+						log_cmd_error("Error in regex expression '%s' !\n", args[argidx].c_str());
 					}
 
 				} else
@@ -124,8 +154,10 @@ struct ExecPass : public Pass {
 				if (!flag_quiet)
 					log("%s\n", line.c_str());
 
-				if(YS_REGEX_NS::regex_search(line, expected_stdout_re))
-					expected_stdout_re_matched = true;
+				if (flag_expect_stdout)
+					for(auto &x : expect_stdout)
+						if (YS_REGEX_NS::regex_search(line, x.re))
+							x.matched = true;
 
 				pos = linebuf.find('\n');
 			}
@@ -143,11 +175,13 @@ struct ExecPass : public Pass {
 		    retval = WSTOPSIG(status);
 		}
 
-		if (flag_expected_return && retval != expected_return_value)
-			log_cmd_error("Return value %d did not match expected return value %d.\n", retval, expected_return_value);
+		if (flag_expect_return && retval != expect_return_value)
+			log_cmd_error("Return value %d did not match expected return value %d.\n", retval, expect_return_value);
 
-		if (flag_expected_stdout && !expected_stdout_re_matched)
-			log_cmd_error("Command stdout did not have a line matching given regex \"%s\".\n", expected_stdout_re_str.c_str());
+		if (flag_expect_stdout)
+			for (auto &x : expect_stdout)
+				if (x.polarity ^ x.matched)
+					log_cmd_error("Command stdout did%s have a line matching given regex \"%s\".\n", (x.polarity? " not" : ""), x.str.c_str());
 
 		log_pop();
 	}

From a09b260c015457ad9a9cadbe0931b411d748420d Mon Sep 17 00:00:00 2001
From: Alberto Gonzalez <boqwxp@airmail.cc>
Date: Mon, 16 Mar 2020 06:44:21 +0000
Subject: [PATCH 3/5] Add test for `exec` command.

---
 tests/various/exec.ys | 6 ++++++
 1 file changed, 6 insertions(+)
 create mode 100644 tests/various/exec.ys

diff --git a/tests/various/exec.ys b/tests/various/exec.ys
new file mode 100644
index 000000000..0eec00719
--- /dev/null
+++ b/tests/various/exec.ys
@@ -0,0 +1,6 @@
+exec -expect-return 0 -- exit 0
+exec -expect-return 27 -- exit 27
+exec -expect-stdout nana -expect-stdout api -not-expect-stdout giraffe -- echo "bananapie"
+
+logger -expect error "stdout did have a line" 1
+exec -not-expect-stdout giraffe -- echo "giraffe"

From cbc5664d370230803ac25705485ef1c6ed5f8416 Mon Sep 17 00:00:00 2001
From: Alberto Gonzalez <boqwxp@airmail.cc>
Date: Mon, 16 Mar 2020 16:42:04 +0000
Subject: [PATCH 4/5] Clean up `exec` code according to review.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-Authored-By: Miodrag Milanović <mmicko@gmail.com>
---
 passes/cmds/exec.cc | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)

diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc
index ec7b5b377..7b5428f16 100644
--- a/passes/cmds/exec.cc
+++ b/passes/cmds/exec.cc
@@ -20,7 +20,16 @@
 #include "kernel/register.h"
 #include "kernel/log.h"
 #include <cstdio>
-#include <sys/wait.h>
+
+#if defined(_WIN32)
+#  define WIFEXITED(x) 1
+#  define WIFSIGNALED(x) 0
+#  define WIFSTOPPED(x) 0
+#  define WEXITSTATUS(x) ((x) & 0xff)
+#  define WTERMSIG(x) SIGTERM
+#else
+#  include <sys/wait.h>
+#endif
 
 USING_YOSYS_NAMESPACE
 PRIVATE_NAMESPACE_BEGIN
@@ -63,7 +72,7 @@ struct ExecPass : public Pass {
 	void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
 	{
 		std::string cmd = "";
-		char buf[4096] = {};
+		char buf[1024] = {};
 		std::string linebuf = "";
 		bool flag_cmd = false;
 		bool flag_quiet = false;
@@ -139,7 +148,11 @@ struct ExecPass : public Pass {
 
 		fflush(stdout);
 		bool keep_reading = true;
-		auto *f = popen(cmd.c_str(), "r");
+		int status = 0;
+		int retval = 0;
+
+#ifndef EMSCRIPTEN
+		FILE *f = popen(cmd.c_str(), "r");
 		if (f == nullptr)
 			log_cmd_error("errno %d after popen() returned NULL.\n", errno);
 		while (keep_reading) {
@@ -162,8 +175,8 @@ struct ExecPass : public Pass {
 				pos = linebuf.find('\n');
 			}
 		}
-		int status = pclose(f);
-		int retval = -1;
+		status = pclose(f);
+#endif
 
 		if(WIFEXITED(status)) {
 		    retval = WEXITSTATUS(status);

From 7ea7fb700b4e7450b679d8be8a3380ecba721b68 Mon Sep 17 00:00:00 2001
From: Alberto Gonzalez <boqwxp@airmail.cc>
Date: Wed, 18 Mar 2020 09:14:22 +0000
Subject: [PATCH 5/5] Update copyright and license header.

I hereby assign to Claire Wolf the copyright for all work I did on `passes/cmds/exec.cc`.
In the event that this copyright assignment is not legally valid, I offer this work under the ISC license.
---
 passes/cmds/exec.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/passes/cmds/exec.cc b/passes/cmds/exec.cc
index 7b5428f16..399cb0ebb 100644
--- a/passes/cmds/exec.cc
+++ b/passes/cmds/exec.cc
@@ -1,7 +1,7 @@
 /*
  *  yosys -- Yosys Open SYnthesis Suite
  *
- *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *  Copyright (C) 2012 - 2020  Claire Wolf <claire@symbioticeda.com>
  *
  *  Permission to use, copy, modify, and/or distribute this software for any
  *  purpose with or without fee is hereby granted, provided that the above