/*
 *  yosys -- Yosys Open SYnthesis Suite
 *
 *  Copyright (C) 2012  Claire Xenia Wolf <claire@yosyshq.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
 *  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.
 *
 *  ---
 *
 *  A handwritten recursive-descent parser for the RTLIL text representation.
 *
 */

#include "kernel/register.h"
#include "kernel/log.h"
#include "kernel/utils.h"
#include <charconv>
#include <deque>
#include <optional>

YOSYS_NAMESPACE_BEGIN

struct RTLILFrontendWorker {
	// Forbid constants of more than 1 Gb.
	// This will help us not explode on malicious RTLIL.
	static constexpr int MAX_CONST_WIDTH = 1024 * 1024 * 1024;

	std::istream *f = nullptr;
	RTLIL::Design *design;
	bool flag_nooverwrite = false;
	bool flag_overwrite = false;
	bool flag_lib = false;
	bool flag_legalize = false;

	int line_num;
	std::string line_buf;
	// Substring of line_buf. Always newline-terminated, thus never empty.
	std::string_view line;

	RTLIL::Module *current_module;
	dict<RTLIL::IdString, RTLIL::Const> attrbuf;
	std::vector<std::vector<RTLIL::SwitchRule*>*> switch_stack;
	std::vector<RTLIL::CaseRule*> case_stack;

	template <typename... Args>
	[[noreturn]]
	void error(FmtString<TypeIdentity<Args>...> fmt, const Args &... args)
	{
		log_error("Parser error in line %d: %s\n", line_num, fmt.format(args...));
	}

	template <typename... Args>
	void warning(FmtString<TypeIdentity<Args>...> fmt, const Args &... args)
	{
		log_warning("In line %d: %s\n", line_num, fmt.format(args...));
	}

	// May return an empty line if the stream is not good().
	void advance_to_next_nonempty_line()
	{
		if (!f->good()) {
			line = "\n";
			return;
		}
		while (true) {
			std::getline(*f, line_buf);
			line_num++;
			if (line_buf.empty() || line_buf[line_buf.size() - 1] != '\n')
				line_buf += '\n';
			line = line_buf;
			consume_whitespace_and_comments();
			if (line[0] != '\n' || !f->good())
				break;
		}
	}

	void consume_whitespace_and_comments()
	{
		while (true) {
			switch (line[0]) {
			case ' ':
			case '\t':
				line = line.substr(1);
				break;
			case '#':
				line = "\n";
				return;
			default:
				return;
			}
		}
	}

	bool try_parse_keyword(std::string_view keyword)
	{
		int keyword_size = keyword.size();
		if (keyword != line.substr(0, keyword_size))
			return false;
		// This index is safe because `line` is always newline-terminated
		// and `keyword` never contains a newline.
		char ch = line[keyword_size];
		if (ch >= 'a' && ch <= 'z')
			return false;
		line = line.substr(keyword_size);
		consume_whitespace_and_comments();
		return true;
	}

	std::string error_token()
	{
		std::string result;
		for (char ch : line) {
			if (ch == '\n' || ch == ' ' || ch == '\t')
				break;
			result += ch;
		}
		return result;
	}

	void expect_keyword(std::string_view keyword)
	{
		if (!try_parse_keyword(keyword))
			error("Expected token `%s', got `%s'.", keyword, error_token());
	}

	bool try_parse_char(char ch)
	{
		if (line[0] != ch)
			return false;
		line = line.substr(1);
		consume_whitespace_and_comments();
		return true;
	}

	void expect_char(char ch)
	{
		if (!try_parse_char(ch))
			error("Expected `%c', got `%s'.", ch, error_token());
	}

	bool try_parse_eol()
	{
		if (line[0] != '\n')
			return false;
		advance_to_next_nonempty_line();
		return true;
	}

	void expect_eol()
	{
		if (!try_parse_eol())
			error("Expected EOL, got `%s'.", error_token());
	}

	std::optional<RTLIL::IdString> try_parse_id()
	{
		char ch = line[0];
		if (ch != '\\' && ch != '$')
			return std::nullopt;
		int idx = 1;
		while (true) {
			ch = line[idx];
			if (ch <= ' ' && (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'))
				break;
			++idx;
		}
		IdString result(line.substr(0, idx));
		line = line.substr(idx);
		consume_whitespace_and_comments();
		return result;
	}

	RTLIL::IdString parse_id()
	{
		std::optional<RTLIL::IdString> id = try_parse_id();
		if (!id.has_value())
			error("Expected ID, got `%s'.", error_token());
		return std::move(*id);
	}

	long long parse_integer()
	{
		long long result = parse_integer_alone();
		consume_whitespace_and_comments();
		return result;
	}

	long long parse_integer_alone()
	{
		int idx = 0;
		if (line[idx] == '-')
			++idx;
		while (true) {
			char ch = line[idx];
			if (ch < '0' || ch > '9')
				break;
			++idx;
		}
		long long result;
		if (std::from_chars(line.data(), line.data() + idx, result, 10).ec != std::errc{})
			error("Invalid integer `%s'.", error_token());
		line = line.substr(idx);
		return result;
	}

	std::string parse_string()
	{
		if (line[0] != '\"')
			error("Expected string, got `%s'.", error_token());
		std::string str;
		int idx = 1;
		while (true) {
			int start_idx = idx;
			char ch;
			while (true) {
				ch = line[idx];
				if (ch == '"' || ch == '\n' || ch == '\\' || ch == 0)
					break;
				++idx;
			}
			str.append(line.data() + start_idx, line.data() + idx);
			++idx;
			if (ch == '"')
				break;
			if (ch == 0)
				error("Null byte in string literal: `%s'.", line);
			if (ch == '\n')
				error("Unterminated string literal: `%s'.", line);
			ch = line[idx++];
			if (ch == 'n') {
				ch = '\n';
			} else if (ch == 't') {
				ch = '\t';
			} else if (ch >= '0' && ch <= '7') {
				int v = ch - '0';
				char next_ch = line[idx + 1];
				if (next_ch >= '0' && next_ch <= '7') {
					++idx;
					v = v*8 + (next_ch - '0');
					next_ch = line[idx + 1];
					if (next_ch >= '0' && next_ch <= '7') {
						++idx;
						v = v*8 + (next_ch - '0');
					}
				}
				ch = v;
			}
			str += ch;
		}
		line = line.substr(idx);
		consume_whitespace_and_comments();
		return str;
	}

	RTLIL::Const parse_const()
	{
		if (line[0] == '"')
			return RTLIL::Const(parse_string());

		bool negative_value = line[0] == '-';
		long long width = parse_integer_alone();
		// Can't test value<0 here because we need to stop parsing after '-0'
		if (negative_value || line[0] != '\'') {
			if (width < INT_MIN || width > INT_MAX)
				error("Integer %lld out of range before `%s'.", width, error_token());
			consume_whitespace_and_comments();
			return RTLIL::Const(width);
		}

		int idx = 1;
		bool is_signed = line[1] == 's';
		if (is_signed)
			++idx;

		std::vector<RTLIL::State> bits;
		if (width > MAX_CONST_WIDTH)
			error("Constant width %lld out of range before `%s`.", width, error_token());
		bits.reserve(width);
		int start_idx = idx;
		while (true) {
			RTLIL::State bit;
			switch (line[idx]) {
			case '0': bit = RTLIL::S0; break;
			case '1': bit = RTLIL::S1; break;
			case 'x': bit = RTLIL::Sx; break;
			case 'z': bit = RTLIL::Sz; break;
			case 'm': bit = RTLIL::Sm; break;
			case '-': bit = RTLIL::Sa; break;
			default: goto done;
			}
			bits.push_back(bit);
			++idx;
		}
	done:
		if (start_idx < idx)
			std::reverse(bits.begin(), bits.end());

		if (GetSize(bits) > width)
			bits.resize(width);
		else if (GetSize(bits) < width) {
			RTLIL::State extbit = RTLIL::Sx;
			if (!bits.empty()) {
				extbit = bits.back();
				if (extbit == RTLIL::S1)
					extbit = RTLIL::S0;
			}
			bits.resize(width, extbit);
		}

		RTLIL::Const val(std::move(bits));
		if (is_signed)
			val.flags |= RTLIL::CONST_FLAG_SIGNED;
		line = line.substr(idx);
		consume_whitespace_and_comments();
		return val;
	}

	RTLIL::Wire *legalize_wire(RTLIL::IdString id)
	{
		int wires_size = current_module->wires_size();
		if (wires_size == 0)
			error("No wires found for legalization");
		int hash = hash_ops<RTLIL::IdString>::hash(id).yield();
		RTLIL::Wire *wire = current_module->wire_at(abs(hash % wires_size));
		log("Legalizing wire `%s' to `%s'.\n", log_id(id), log_id(wire->name));
		return wire;
	}

	RTLIL::SigSpec parse_sigspec()
	{
		RTLIL::SigSpec sig;

		if (try_parse_char('{')) {
			std::vector<SigSpec> parts;
			while (!try_parse_char('}'))
				parts.push_back(parse_sigspec());
			for (auto it = parts.rbegin(); it != parts.rend(); ++it)
				sig.append(std::move(*it));
		} else {
			// We could add a special path for parsing IdStrings that must already exist,
			// as here.
			// We don't need to addref/release in this case.
			std::optional<RTLIL::IdString> id = try_parse_id();
			if (id.has_value()) {
				RTLIL::Wire *wire = current_module->wire(*id);
				if (wire == nullptr) {
					if (flag_legalize)
						wire = legalize_wire(*id);
					else
						error("Wire `%s' not found.", *id);
				}
				sig = RTLIL::SigSpec(wire);
			} else {
				sig = RTLIL::SigSpec(parse_const());
			}
		}

		while (try_parse_char('[')) {
			int left = parse_integer();
			if (left >= sig.size() || left < 0) {
				if (flag_legalize) {
					int legalized;
					if (sig.size() == 0)
						legalized = 0;
					else
						legalized = std::max(0, std::min(left, sig.size() - 1));
					log("Legalizing bit index %d to %d.\n", left, legalized);
					left = legalized;
				} else {
					error("bit index %d out of range", left);
				}
			}
			if (try_parse_char(':')) {
				int right = parse_integer();
				if (right < 0) {
					if (flag_legalize) {
						log("Legalizing bit index %d to %d.\n", right, 0);
						right = 0;
					} else
						error("bit index %d out of range", right);
				}
				if (left < right) {
					if (flag_legalize) {
						log("Legalizing bit index %d to %d.\n", left, right);
						left = right;
					} else
						error("invalid slice [%d:%d]", left, right);
				}
				if (flag_legalize && left >= sig.size())
					log("Legalizing slice %d:%d by igoring it\n", left, right);
				else
					sig = sig.extract(right, left - right + 1);
			} else {
				if (flag_legalize && left >= sig.size())
					log("Legalizing slice %d by igoring it\n", left);
				else
					sig = sig.extract(left);
			}
			expect_char(']');
		}

		return sig;
	}

	void parse_module()
	{
		RTLIL::IdString module_name = parse_id();
		expect_eol();

		bool delete_current_module = false;
		if (design->has(module_name)) {
			RTLIL::Module *existing_mod = design->module(module_name);
			if (!flag_overwrite && (flag_lib || (attrbuf.count(ID::blackbox) && attrbuf.at(ID::blackbox).as_bool()))) {
				log("Ignoring blackbox re-definition of module %s.\n", module_name);
				delete_current_module = true;
			} else if (!flag_nooverwrite && !flag_overwrite && !existing_mod->get_bool_attribute(ID::blackbox)) {
				error("RTLIL error: redefinition of module %s.", module_name);
			} else if (flag_nooverwrite) {
				log("Ignoring re-definition of module %s.\n", module_name);
				delete_current_module = true;
			} else {
				log("Replacing existing%s module %s.\n", existing_mod->get_bool_attribute(ID::blackbox) ? " blackbox" : "", module_name);
				design->remove(existing_mod);
			}
		}

		current_module = new RTLIL::Module;
		current_module->name = std::move(module_name);
		current_module->attributes = std::move(attrbuf);
		if (!delete_current_module)
			design->add(current_module);

		while (true)
		{
			if (try_parse_keyword("attribute")) {
				parse_attribute();
				continue;
			}
			if (try_parse_keyword("parameter")) {
				parse_parameter();
				continue;
			}
			if (try_parse_keyword("connect")) {
				parse_connect();
				continue;
			}
			if (try_parse_keyword("wire")) {
				parse_wire();
				continue;
			}
			if (try_parse_keyword("cell")) {
				parse_cell();
				continue;
			}
			if (try_parse_keyword("memory")) {
				parse_memory();
				continue;
			}
			if (try_parse_keyword("process")) {
				parse_process();
				continue;
			}
			if (try_parse_keyword("end")) {
				expect_eol();
				break;
			}
			error("Unexpected token in module body: %s", error_token());
		}

		if (attrbuf.size() != 0)
			error("dangling attribute");
		current_module->fixup_ports();
		if (delete_current_module)
			delete current_module;
		else if (flag_lib)
			current_module->makeblackbox();
		current_module = nullptr;
	}

	void parse_attribute()
	{
		RTLIL::IdString id = parse_id();
		RTLIL::Const c = parse_const();
		attrbuf.insert({std::move(id), std::move(c)});
		expect_eol();
	}

	void parse_parameter()
	{
		RTLIL::IdString id = parse_id();
		current_module->avail_parameters(id);
		if (try_parse_eol())
			return;
		RTLIL::Const c = parse_const();
		current_module->parameter_default_values.insert({std::move(id), std::move(c)});
		expect_eol();
	}

	void parse_wire()
	{
		RTLIL::Wire *wire;
		int width = 1;
		int start_offset = 0;
		int port_id = 0;
		bool port_input = false;
		bool port_output = false;
		bool upto = false;
		bool is_signed = false;

		while (true)
		{
			std::optional<RTLIL::IdString> id = try_parse_id();
			if (id.has_value()) {
				if (current_module->wire(*id) != nullptr) {
				  if (flag_legalize) {
						log("Legalizing redefinition of wire %s.\n", *id);
						pool<RTLIL::Wire*> wires = {current_module->wire(*id)};
						current_module->remove(wires);
					} else
						error("RTLIL error: redefinition of wire %s.", *id);
				}
				wire = current_module->addWire(std::move(*id));
				break;
			}
			if (try_parse_keyword("width"))
				width = parse_integer();
			else if (try_parse_keyword("upto"))
				upto = true;
			else if (try_parse_keyword("signed"))
				is_signed = true;
			else if (try_parse_keyword("offset"))
				start_offset = parse_integer();
			else if (try_parse_keyword("input")) {
				port_id = parse_integer();
				port_input = true;
			} else if (try_parse_keyword("output")) {
				port_id = parse_integer();
				port_output = true;
			} else if (try_parse_keyword("inout")) {
				port_id = parse_integer();
				port_input = true;
				port_output = true;
			} else if (try_parse_eol())
				error("Missing wire ID");
			else
				error("Unexpected wire option: %s", error_token());
		}

		wire->attributes = std::move(attrbuf);
		wire->width = width;
		wire->upto = upto;
		wire->start_offset = start_offset;
		wire->is_signed = is_signed;
		wire->port_id = port_id;
		wire->port_input = port_input;
		wire->port_output = port_output;
		expect_eol();
	}

	void parse_memory()
	{
		RTLIL::Memory *memory = new RTLIL::Memory;
		memory->attributes = std::move(attrbuf);

		int width = 1;
		int start_offset = 0;
		int size = 0;
		while (true)
		{
			std::optional<RTLIL::IdString> id = try_parse_id();
			if (id.has_value()) {
				if (current_module->memories.count(*id) != 0) {
					if (flag_legalize) {
						log("Legalizing redefinition of memory %s.\n", *id);
						current_module->remove(current_module->memories.at(*id));
					} else
						error("RTLIL error: redefinition of memory %s.", *id);
				}
				memory->name = std::move(*id);
				break;
			}
			if (try_parse_keyword("width"))
				width = parse_integer();
			else if (try_parse_keyword("size"))
				size = parse_integer();
			else if (try_parse_keyword("offset"))
				start_offset = parse_integer();
			else if (try_parse_eol())
				error("Missing memory ID");
			else
				error("Unexpected memory option: %s", error_token());
		}
		memory->width = width;
		memory->start_offset = start_offset;
		memory->size = size;
		current_module->memories.insert({memory->name, memory});
		expect_eol();
	}

	void legalize_width_parameter(RTLIL::Cell *cell, RTLIL::IdString port_name)
	{
		std::string width_param_name = port_name.str() + "_WIDTH";
		if (cell->parameters.count(width_param_name) == 0)
			return;
		RTLIL::Const &param = cell->parameters.at(width_param_name);
		if (param.as_int() != 0)
			return;
		cell->parameters[width_param_name] = RTLIL::Const(cell->getPort(port_name).size());
	}

	void parse_cell()
	{
		RTLIL::IdString cell_type = parse_id();
		RTLIL::IdString cell_name = parse_id();
		expect_eol();

		if (current_module->cell(cell_name) != nullptr) {
			if (flag_legalize) {
				RTLIL::IdString new_name;
				int suffix = 1;
				do {
					new_name = RTLIL::IdString(cell_name.str() + "_" + std::to_string(suffix));
					++suffix;
				} while (current_module->cell(new_name) != nullptr);
				log("Legalizing redefinition of cell %s by renaming to %s.\n", cell_name, new_name);
				cell_name = new_name;
			} else
				error("RTLIL error: redefinition of cell %s.", cell_name);
		}
		RTLIL::Cell *cell = current_module->addCell(cell_name, cell_type);
		cell->attributes = std::move(attrbuf);

		while (true)
		{
			if (try_parse_keyword("parameter")) {
				bool is_signed = false;
				bool is_real = false;
				bool is_unsized = false;
				if (try_parse_keyword("signed")) {
					is_signed = true;
				} else if (try_parse_keyword("real")) {
					is_real = true;
				} else if (try_parse_keyword("unsized")) {
					is_unsized = true;
				}
				RTLIL::IdString param_name = parse_id();
				RTLIL::Const val = parse_const();
				if (is_signed)
					val.flags |= RTLIL::CONST_FLAG_SIGNED;
				if (is_real)
					val.flags |= RTLIL::CONST_FLAG_REAL;
				if (is_unsized)
					val.flags |= RTLIL::CONST_FLAG_UNSIZED;
				cell->parameters.insert({std::move(param_name), std::move(val)});
				expect_eol();
			} else if (try_parse_keyword("connect")) {
				RTLIL::IdString port_name = parse_id();
				if (cell->hasPort(port_name)) {
					if (flag_legalize)
						log("Legalizing redefinition of cell port %s.", port_name);
					else
						error("RTLIL error: redefinition of cell port %s.", port_name);
				}
				cell->setPort(std::move(port_name), parse_sigspec());
				if (flag_legalize)
					legalize_width_parameter(cell, port_name);
				expect_eol();
			} else if (try_parse_keyword("end")) {
				expect_eol();
				break;
			} else {
				error("Unexpected token in cell body: %s", error_token());
			}
		}
	}

	void parse_connect()
	{
		if (attrbuf.size() != 0)
			error("dangling attribute");
		RTLIL::SigSpec s1 = parse_sigspec();
		RTLIL::SigSpec s2 = parse_sigspec();
		if (flag_legalize) {
			int min_size = std::min(s1.size(), s2.size());
			s1 = s1.extract(0, min_size);
			s2 = s2.extract(0, min_size);
		}
		current_module->connect(std::move(s1), std::move(s2));
		expect_eol();
	}

	void parse_case_body(RTLIL::CaseRule *current_case)
	{
		while (true)
		{
			if (try_parse_keyword("attribute"))
				parse_attribute();
			else if (try_parse_keyword("switch"))
				parse_switch();
			else if (try_parse_keyword("assign")) {
				if (attrbuf.size() != 0)
					error("dangling attribute");
				// See https://github.com/YosysHQ/yosys/pull/4765 for discussion on this
				// warning
				if (!switch_stack.back()->empty())
					warning("case rule assign statements after switch statements may cause unexpected behaviour. "
						"The assign statement is reordered to come before all switch statements.");
				RTLIL::SigSpec s1 = parse_sigspec();
				RTLIL::SigSpec s2 = parse_sigspec();
				current_case->actions.push_back(RTLIL::SigSig(std::move(s1), std::move(s2)));
				expect_eol();
			} else
				return;
		}
	}

	void parse_switch()
	{
		RTLIL::SwitchRule *rule = new RTLIL::SwitchRule;
		rule->signal = parse_sigspec();
		rule->attributes = std::move(attrbuf);
		switch_stack.back()->push_back(rule);
		expect_eol();

		while (true) {
			if (try_parse_keyword("attribute")) {
				parse_attribute();
				continue;
			}

			if (try_parse_keyword("end")) {
				expect_eol();
				break;
			}

			expect_keyword("case");
			RTLIL::CaseRule *case_rule = new RTLIL::CaseRule;
			case_rule->attributes = std::move(attrbuf);
			rule->cases.push_back(case_rule);
			switch_stack.push_back(&case_rule->switches);
			case_stack.push_back(case_rule);

			if (!try_parse_eol()) {
				while (true) {
					case_rule->compare.push_back(parse_sigspec());
					if (try_parse_eol())
						break;
					expect_char(',');
				}
			}

			parse_case_body(case_rule);

			switch_stack.pop_back();
			case_stack.pop_back();
		}
	}

	void parse_process()
	{
		RTLIL::IdString proc_name = parse_id();
		expect_eol();

		if (current_module->processes.count(proc_name) != 0) {
			if (flag_legalize) {
				log("Legalizing redefinition of process %s.\n", proc_name);
				current_module->remove(current_module->processes.at(proc_name));
			} else
				error("RTLIL error: redefinition of process %s.", proc_name);
		}
		RTLIL::Process *proc = current_module->addProcess(std::move(proc_name));
		proc->attributes = std::move(attrbuf);

		switch_stack.clear();
		switch_stack.push_back(&proc->root_case.switches);
		case_stack.clear();
		case_stack.push_back(&proc->root_case);

		parse_case_body(&proc->root_case);

		while (try_parse_keyword("sync"))
		{
			RTLIL::SyncRule *rule = new RTLIL::SyncRule;

			if (try_parse_keyword("low")) rule->type = RTLIL::ST0;
			else if (try_parse_keyword("high")) rule->type = RTLIL::ST1;
			else if (try_parse_keyword("posedge")) rule->type = RTLIL::STp;
			else if (try_parse_keyword("negedge")) rule->type = RTLIL::STn;
			else if (try_parse_keyword("edge")) rule->type = RTLIL::STe;
			else if (try_parse_keyword("always")) rule->type = RTLIL::STa;
			else if (try_parse_keyword("global")) rule->type = RTLIL::STg;
			else if (try_parse_keyword("init")) rule->type = RTLIL::STi;
			else error("Unexpected sync type: %s", error_token());

			if (rule->type != RTLIL::STa && rule->type != RTLIL::STg && rule->type != RTLIL::STi)
				rule->signal = parse_sigspec();
			proc->syncs.push_back(rule);
			expect_eol();

			bool attributes_in_update_list = false;
			while (true)
			{
				if (try_parse_keyword("update")) {
					RTLIL::SigSpec s1 = parse_sigspec();
					RTLIL::SigSpec s2 = parse_sigspec();
					rule->actions.push_back(RTLIL::SigSig(std::move(s1), std::move(s2)));
					expect_eol();
					continue;
				}

				if (try_parse_keyword("attribute")) {
					attributes_in_update_list = true;
					parse_attribute();
					continue;
				}

				if (!try_parse_keyword("memwr"))
					break;

				RTLIL::MemWriteAction act;
				act.attributes = std::move(attrbuf);
				act.memid = parse_id();
				act.address = parse_sigspec();
				act.data = parse_sigspec();
				act.enable = parse_sigspec();
				act.priority_mask = parse_const();
				rule->mem_write_actions.push_back(std::move(act));
				expect_eol();
			}
			// The old parser allowed dangling attributes before a "sync" to carry through
			// the "sync", so we will too, for now.
			if (attributes_in_update_list && attrbuf.size() > 0)
				error("dangling attribute");
		}

		expect_keyword("end");
		expect_eol();
	}

	RTLILFrontendWorker(RTLIL::Design *design) : design(design) {}

	void parse(std::istream *f)
	{
		this->f = f;
		line_num = 0;
		advance_to_next_nonempty_line();
		while (f->good())
		{
			if (try_parse_keyword("attribute")) {
				parse_attribute();
				continue;
			}
			if (try_parse_keyword("module")) {
				parse_module();
				continue;
			}
			if (try_parse_keyword("autoidx")) {
				autoidx = std::max<int>(autoidx, parse_integer());
				expect_eol();
				continue;
			}
			error("Unexpected token: %s", error_token());
		}
		if (attrbuf.size() != 0)
			error("dangling attribute");
	}
};

struct RTLILFrontend : public Frontend {
	RTLILFrontend() : Frontend("rtlil", "read modules from RTLIL file") { }
	void help() override
	{
		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
		log("\n");
		log("    read_rtlil [filename]\n");
		log("\n");
		log("Load modules from an RTLIL file to the current design. (RTLIL is a text\n");
		log("representation of a design in yosys's internal format.)\n");
		log("\n");
		log("    -nooverwrite\n");
		log("        ignore re-definitions of modules. (the default behavior is to\n");
		log("        create an error message if the existing module is not a blackbox\n");
		log("        module, and overwrite the existing module if it is a blackbox module.)\n");
		log("\n");
		log("    -overwrite\n");
		log("        overwrite existing modules with the same name\n");
		log("\n");
		log("    -lib\n");
		log("        only create empty blackbox modules\n");
		log("\n");
		log("    -legalize\n");
		log("        prevent semantic errors (e.g. reference to unknown wire, redefinition of wire/cell)\n");
		log("        by deterministically rewriting the input into something valid. Useful when using\n");
		log("        fuzzing to generate random but valid RTLIL.\n");
		log("\n");
	}
	void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
	{
		RTLILFrontendWorker worker(design);

		log_header(design, "Executing RTLIL frontend.\n");

		size_t argidx;
		for (argidx = 1; argidx < args.size(); argidx++) {
			std::string arg = args[argidx];
			if (arg == "-nooverwrite") {
				worker.flag_nooverwrite = true;
				worker.flag_overwrite = false;
				continue;
			}
			if (arg == "-overwrite") {
				worker.flag_nooverwrite = false;
				worker.flag_overwrite = true;
				continue;
			}
			if (arg == "-lib") {
				worker.flag_lib = true;
				continue;
			}
			if (arg == "-legalize") {
				worker.flag_legalize = true;
				continue;
			}
			break;
		}
		extra_args(f, filename, args, argidx);

		log("Input filename: %s\n", filename);

		worker.parse(f);
	}
} RTLILFrontend;

YOSYS_NAMESPACE_END
