/*
 *  yosys -- Yosys Open SYnthesis Suite
 *
 *  Copyright (C) 2021  Marcelina Kościelnicka <mwk@0x04.net>
 *
 *  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 "memlib.h"

#include <ctype.h>

USING_YOSYS_NAMESPACE

using namespace MemLibrary;

PRIVATE_NAMESPACE_BEGIN

typedef dict<std::string, Const> Options;

struct ClockDef {
	ClkPolKind kind;
	std::string name;
};

struct RawWrTransDef {
	WrTransTargetKind target_kind;
	std::string target_group;
	WrTransKind kind;
};

struct PortWidthDef {
	bool tied;
	std::vector<int> wr_widths;
	std::vector<int> rd_widths;
};

struct SrstDef {
	ResetValKind val;
	SrstKind kind;
	bool block_wr;
};

struct Empty {};

template<typename T> struct Capability {
	T val;
	Options opts, portopts;

	Capability(T val, Options opts, Options portopts) : val(val), opts(opts), portopts(portopts) {}
};

template<typename T> using Caps = std::vector<Capability<T>>;

struct PortGroupDef {
	PortKind kind;
	dict<std::string, pool<Const>> portopts;
	std::vector<std::string> names;
	Caps<Empty> forbid;
	Caps<ClockDef> clock;
	Caps<Empty> clken;
	Caps<Empty> wrbe_separate;
	Caps<PortWidthDef> width;
	Caps<Empty> rden;
	Caps<RdWrKind> rdwr;
	Caps<ResetValKind> rdinit;
	Caps<ResetValKind> rdarst;
	Caps<SrstDef> rdsrst;
	Caps<std::string> wrprio;
	Caps<RawWrTransDef> wrtrans;
	Caps<Empty> optional;
	Caps<Empty> optional_rw;
};

struct WidthsDef {
	std::vector<int> widths;
	WidthMode mode;
};

struct ResourceDef {
	std::string name;
	int count;
};

struct RamDef {
	IdString id;
	dict<std::string, pool<Const>> opts;
	RamKind kind;
	Caps<Empty> forbid;
	Caps<Empty> prune_rom;
	Caps<PortGroupDef> ports;
	Caps<int> abits;
	Caps<WidthsDef> widths;
	Caps<ResourceDef> resource;
	Caps<double> cost;
	Caps<double> widthscale;
	Caps<int> byte;
	Caps<MemoryInitKind> init;
	Caps<std::string> style;
};

struct Parser {
	std::string filename;
	std::ifstream infile;
	int line_number = 0;
	Library &lib;
	const pool<std::string> &defines;
	pool<std::string> &defines_unused;
	std::vector<std::string> tokens;
	int token_idx = 0;
	bool eof = false;

	std::vector<std::pair<std::string, Const>> option_stack;
	std::vector<std::pair<std::string, Const>> portoption_stack;
	RamDef ram;
	PortGroupDef port;
	bool active = true;

	Parser(std::string filename, Library &lib, const pool<std::string> &defines, pool<std::string> &defines_unused) : filename(filename), lib(lib), defines(defines), defines_unused(defines_unused) {
		// Note: this rewrites the filename we're opening, but not
		// the one we're storing — this is actually correct, so that
		// we keep the original filename for diagnostics.
		rewrite_filename(filename);
		infile.open(filename);
		if (infile.fail()) {
			log_error("failed to open %s\n", filename);
		}
		parse();
		infile.close();
	}

	std::string peek_token() {
		if (eof)
			return "";

		if (token_idx < GetSize(tokens))
			return tokens[token_idx];

		tokens.clear();
		token_idx = 0;

		std::string line;
		while (std::getline(infile, line)) {
			line_number++;
			for (string tok = next_token(line); !tok.empty(); tok = next_token(line)) {
				if (tok[0] == '#')
					break;
				if (tok[tok.size()-1] == ';') {
					tokens.push_back(tok.substr(0, tok.size()-1));
					tokens.push_back(";");
				} else {
					tokens.push_back(tok);
				}
			}
			if (!tokens.empty())
				return tokens[token_idx];
		}

		eof = true;
		return "";
	}

	std::string get_token() {
		std::string res = peek_token();
		if (!eof)
			token_idx++;
		return res;
	}

	void eat_token(std::string expected) {
		std::string token = get_token();
		if (token != expected) {
			log_error("%s:%d: expected `%s`, got `%s`.\n", filename, line_number, expected, token);
		}
	}

	IdString get_id() {
		std::string token = get_token();
		if (token.empty() || (token[0] != '$' && token[0] != '\\')) {
			log_error("%s:%d: expected id string, got `%s`.\n", filename, line_number, token);
		}
		return IdString(token);
	}

	std::string get_name() {
		std::string res = get_token();
		bool valid = true;
		// Basic sanity check.
		if (res.empty() || (!isalpha(res[0]) && res[0] != '_'))
			valid = false;
		for (char c: res)
			if (!isalnum(c) && c != '_')
				valid = false;
		if (!valid)
			log_error("%s:%d: expected name, got `%s`.\n", filename, line_number, res);
		return res;
	}

	std::string get_string() {
		std::string token = get_token();
		if (token.size() < 2 || token[0] != '"' || token[token.size()-1] != '"') {
			log_error("%s:%d: expected string, got `%s`.\n", filename, line_number, token);
		}
		return token.substr(1, token.size()-2);
	}

	bool peek_string() {
		std::string token = peek_token();
		return !token.empty() && token[0] == '"';
	}

	int get_int() {
		std::string token = get_token();
		char *endptr;
		long res = strtol(token.c_str(), &endptr, 0);
		if (token.empty() || *endptr || res > INT_MAX) {
			log_error("%s:%d: expected int, got `%s`.\n", filename, line_number, token);
		}
		return res;
	}

	double get_double() {
		std::string token = get_token();
		char *endptr;
		double res = strtod(token.c_str(), &endptr);
		if (token.empty() || *endptr) {
			log_error("%s:%d: expected float, got `%s`.\n", filename, line_number, token);
		}
		return res;
	}

	bool peek_int() {
		std::string token = peek_token();
		return !token.empty() && isdigit(token[0]);
	}

	void get_semi() {
		std::string token = get_token();
		if (token != ";") {
			log_error("%s:%d: expected `;`, got `%s`.\n", filename, line_number, token);
		}
	}

	Const get_value() {
		std::string token = peek_token();
		if (!token.empty() && token[0] == '"') {
			std::string s = get_string();
			return Const(s);
		} else {
			return Const(get_int());
		}
	}

	bool enter_ifdef(bool polarity) {
		bool res = active;
		std::string name = get_name();
		defines_unused.erase(name);
		if (active) {
			if (defines.count(name)) {
				active = polarity;
			} else {
				active = !polarity;
			}
		}
		return res;
	}

	void enter_else(bool save) {
		get_token();
		active = !active && save;
	}

	void enter_option() {
		std::string name = get_string();
		Const val = get_value();
		if (active) {
			ram.opts[name].insert(val);
		}
		option_stack.push_back({name, val});
	}

	void exit_option() {
		option_stack.pop_back();
	}

	Options get_options() {
		Options res;
		for (auto it: option_stack)
			res[it.first] = it.second;
		return res;
	}

	void enter_portoption() {
		std::string name = get_string();
		Const val = get_value();
		if (active) {
			port.portopts[name].insert(val);
		}
		portoption_stack.push_back({name, val});
	}

	void exit_portoption() {
		portoption_stack.pop_back();
	}

	Options get_portoptions() {
		Options res;
		for (auto it: portoption_stack)
			res[it.first] = it.second;
		return res;
	}

	template<typename T> void add_cap(Caps<T> &caps, T val) {
		if (active)
			caps.push_back(Capability<T>(val, get_options(), get_portoptions()));
	}

	void parse_port_block() {
		if (peek_token() == "{") {
			get_token();
			while (peek_token() != "}")
				parse_port_item();
			get_token();
		} else {
			parse_port_item();
		}
	}

	void parse_ram_block() {
		if (peek_token() == "{") {
			get_token();
			while (peek_token() != "}")
				parse_ram_item();
			get_token();
		} else {
			parse_ram_item();
		}
	}

	void parse_top_block() {
		if (peek_token() == "{") {
			get_token();
			while (peek_token() != "}")
				parse_top_item();
			get_token();
		} else {
			parse_top_item();
		}
	}

	void parse_port_item() {
		std::string token = get_token();
		if (token == "ifdef") {
			bool save = enter_ifdef(true);
			parse_port_block();
			if (peek_token() == "else") {
				enter_else(save);
				parse_port_block();
			}
			active = save;
		} else if (token == "ifndef") {
			bool save = enter_ifdef(false);
			parse_port_block();
			if (peek_token() == "else") {
				enter_else(save);
				parse_port_block();
			}
			active = save;
		} else if (token == "option") {
			enter_option();
			parse_port_block();
			exit_option();
		} else if (token == "portoption") {
			enter_portoption();
			parse_port_block();
			exit_portoption();
		} else if (token == "clock") {
			if (port.kind == PortKind::Ar) {
				log_error("%s:%d: `clock` not allowed in async read port.\n", filename, line_number);
			}
			ClockDef def;
			token = get_token();
			if (token == "anyedge") {
				def.kind = ClkPolKind::Anyedge;
			} else if (token == "posedge") {
				def.kind = ClkPolKind::Posedge;
			} else if (token == "negedge") {
				def.kind = ClkPolKind::Negedge;
			} else {
				log_error("%s:%d: expected `posedge`, `negedge`, or `anyedge`, got `%s`.\n", filename, line_number, token);
			}
			if (peek_string()) {
				def.name = get_string();
			}
			get_semi();
			add_cap(port.clock, def);
		} else if (token == "clken") {
			if (port.kind == PortKind::Ar) {
				log_error("%s:%d: `clken` not allowed in async read port.\n", filename, line_number);
			}
			get_semi();
			add_cap(port.clken, {});
		} else if (token == "wrbe_separate") {
			if (port.kind == PortKind::Ar || port.kind == PortKind::Sr) {
				log_error("%s:%d: `wrbe_separate` not allowed in read port.\n", filename, line_number);
			}
			get_semi();
			add_cap(port.wrbe_separate, {});
		} else if (token == "width") {
			PortWidthDef def;
			token = peek_token();
			bool is_rw = port.kind == PortKind::Srsw || port.kind == PortKind::Arsw;
			if (token == "tied") {
				get_token();
				if (!is_rw)
					log_error("%s:%d: `tied` only makes sense for read+write ports.\n", filename, line_number);
				while (peek_int())
					def.wr_widths.push_back(get_int());
				def.tied = true;
			} else if (token == "mix") {
				get_token();
				if (!is_rw)
					log_error("%s:%d: `mix` only makes sense for read+write ports.\n", filename, line_number);
				while (peek_int())
					def.wr_widths.push_back(get_int());
				def.rd_widths = def.wr_widths;
				def.tied = false;
			} else if (token == "rd") {
				get_token();
				if (!is_rw)
					log_error("%s:%d: `rd` only makes sense for read+write ports.\n", filename, line_number);
				do {
					def.rd_widths.push_back(get_int());
				} while (peek_int());
				eat_token("wr");
				do {
					def.wr_widths.push_back(get_int());
				} while (peek_int());
				def.tied = false;
			} else if (token == "wr") {
				get_token();
				if (!is_rw)
					log_error("%s:%d: `wr` only makes sense for read+write ports.\n", filename, line_number);
				do {
					def.wr_widths.push_back(get_int());
				} while (peek_int());
				eat_token("rd");
				do {
					def.rd_widths.push_back(get_int());
				} while (peek_int());
				def.tied = false;
			} else {
				do {
					def.wr_widths.push_back(get_int());
				} while (peek_int());
				def.tied = true;
			}
			get_semi();
			add_cap(port.width, def);
		} else if (token == "rden") {
			if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
				log_error("%s:%d: `rden` only allowed on sync read ports.\n", filename, line_number);
			get_semi();
			add_cap(port.rden, {});
		} else if (token == "rdwr") {
			if (port.kind != PortKind::Srsw)
				log_error("%s:%d: `rdwr` only allowed on sync read+write ports.\n", filename, line_number);
			RdWrKind kind;
			token = get_token();
			if (token == "undefined") {
				kind = RdWrKind::Undefined;
			} else if (token == "no_change") {
				kind = RdWrKind::NoChange;
			} else if (token == "new") {
				kind = RdWrKind::New;
			} else if (token == "old") {
				kind = RdWrKind::Old;
			} else if (token == "new_only") {
				kind = RdWrKind::NewOnly;
			} else {
				log_error("%s:%d: expected `undefined`, `new`, `old`, `new_only`, or `no_change`, got `%s`.\n", filename, line_number, token);
			}
			get_semi();
			add_cap(port.rdwr, kind);
		} else if (token == "rdinit") {
			if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
				log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename, line_number, token);
			ResetValKind kind;
			token = get_token();
			if (token == "none") {
				kind = ResetValKind::None;
			} else if (token == "zero") {
				kind = ResetValKind::Zero;
			} else if (token == "any") {
				kind = ResetValKind::Any;
			} else if (token == "no_undef") {
				kind = ResetValKind::NoUndef;
			} else {
				log_error("%s:%d: expected `none`, `zero`, `any`, or `no_undef`, got `%s`.\n", filename, line_number, token);
			}
			get_semi();
			add_cap(port.rdinit, kind);
		} else if (token == "rdarst") {
			if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
				log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename, line_number, token);
			ResetValKind kind;
			token = get_token();
			if (token == "none") {
				kind = ResetValKind::None;
			} else if (token == "zero") {
				kind = ResetValKind::Zero;
			} else if (token == "any") {
				kind = ResetValKind::Any;
			} else if (token == "no_undef") {
				kind = ResetValKind::NoUndef;
			} else if (token == "init") {
				kind = ResetValKind::Init;
			} else {
				log_error("%s:%d: expected `none`, `zero`, `any`, `no_undef`, or `init`, got `%s`.\n", filename, line_number, token);
			}
			get_semi();
			add_cap(port.rdarst, kind);
		} else if (token == "rdsrst") {
			if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
				log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename, line_number, token);
			SrstDef def;
			token = get_token();
			if (token == "none") {
				def.val = ResetValKind::None;
			} else if (token == "zero") {
				def.val = ResetValKind::Zero;
			} else if (token == "any") {
				def.val = ResetValKind::Any;
			} else if (token == "no_undef") {
				def.val = ResetValKind::NoUndef;
			} else if (token == "init") {
				def.val = ResetValKind::Init;
			} else {
				log_error("%s:%d: expected `none`, `zero`, `any`, `no_undef`, or `init`, got `%s`.\n", filename, line_number, token);
			}
			if (def.val == ResetValKind::None) {
				def.kind = SrstKind::None;
			} else {
				token = get_token();
				if (token == "ungated") {
					def.kind = SrstKind::Ungated;
				} else if (token == "gated_clken") {
					def.kind = SrstKind::GatedClkEn;
				} else if (token == "gated_rden") {
					def.kind = SrstKind::GatedRdEn;
				} else {
					log_error("%s:%d: expected `ungated`, `gated_clken` or `gated_rden`, got `%s`.\n", filename, line_number, token);
				}
			}
			def.block_wr = false;
			if (peek_token() == "block_wr") {
				get_token();
				def.block_wr = true;
			}
			get_semi();
			add_cap(port.rdsrst, def);
		} else if (token == "wrprio") {
			if (port.kind == PortKind::Ar || port.kind == PortKind::Sr)
				log_error("%s:%d: `wrprio` only allowed on write ports.\n", filename, line_number);
			do {
				add_cap(port.wrprio, get_string());
			} while (peek_string());
			get_semi();
		} else if (token == "wrtrans") {
			if (port.kind == PortKind::Ar || port.kind == PortKind::Sr)
				log_error("%s:%d: `wrtrans` only allowed on write ports.\n", filename, line_number);
			token = peek_token();
			RawWrTransDef def;
			if (token == "all") {
				def.target_kind = WrTransTargetKind::All;
				get_token();
			} else {
				def.target_kind = WrTransTargetKind::Group;
				def.target_group = get_string();
			}
			token = get_token();
			if (token == "new") {
				def.kind = WrTransKind::New;
			} else if (token == "old") {
				def.kind = WrTransKind::Old;
			} else {
				log_error("%s:%d: expected `new` or `old`, got `%s`.\n", filename, line_number, token);
			}
			get_semi();
			add_cap(port.wrtrans, def);
		} else if (token == "forbid") {
			get_semi();
			add_cap(port.forbid, {});
		} else if (token == "optional") {
			get_semi();
			add_cap(port.optional, {});
		} else if (token == "optional_rw") {
			get_semi();
			add_cap(port.optional_rw, {});
		} else if (token == "") {
			log_error("%s:%d: unexpected EOF while parsing port item.\n", filename, line_number);
		} else {
			log_error("%s:%d: unknown port-level item `%s`.\n", filename, line_number, token);
		}
	}

	void parse_ram_item() {
		std::string token = get_token();
		if (token == "ifdef") {
			bool save = enter_ifdef(true);
			parse_ram_block();
			if (peek_token() == "else") {
				enter_else(save);
				parse_ram_block();
			}
			active = save;
		} else if (token == "ifndef") {
			bool save = enter_ifdef(false);
			parse_ram_block();
			if (peek_token() == "else") {
				enter_else(save);
				parse_ram_block();
			}
			active = save;
		} else if (token == "option") {
			enter_option();
			parse_ram_block();
			exit_option();
		} else if (token == "prune_rom") {
			get_semi();
			add_cap(ram.prune_rom, {});
		} else if (token == "forbid") {
			get_semi();
			add_cap(ram.forbid, {});
		} else if (token == "abits") {
			int val = get_int();
			if (val < 0)
				log_error("%s:%d: abits %d nagative.\n", filename, line_number, val);
			get_semi();
			add_cap(ram.abits, val);
		} else if (token == "width") {
			WidthsDef def;
			int w = get_int();
			if (w <= 0)
				log_error("%s:%d: width %d not positive.\n", filename, line_number, w);
			def.widths.push_back(w);
			def.mode = WidthMode::Single;
			get_semi();
			add_cap(ram.widths, def);
		} else if (token == "widths") {
			WidthsDef def;
			int last = 0;
			do {
				int w = get_int();
				if (w <= 0)
					log_error("%s:%d: width %d not positive.\n", filename, line_number, w);
				if (w < last * 2)
					log_error("%s:%d: width %d smaller than %d required for progression.\n", filename, line_number, w, last * 2);
				last = w;
				def.widths.push_back(w);
			} while(peek_int());
			token = get_token();
			if (token == "global") {
				def.mode = WidthMode::Global;
			} else if (token == "per_port") {
				def.mode = WidthMode::PerPort;
			} else {
				log_error("%s:%d: expected `global`, or `per_port`, got `%s`.\n", filename, line_number, token);
			}
			get_semi();
			add_cap(ram.widths, def);
		} else if (token == "resource") {
			ResourceDef def;
			def.name = get_string();
			if (peek_int())
				def.count = get_int();
			else
				def.count = 1;
			get_semi();
			add_cap(ram.resource, def);
		} else if (token == "cost") {
			add_cap(ram.cost, get_double());
			get_semi();
		} else if (token == "widthscale") {
			if (peek_int()) {
				add_cap(ram.widthscale, get_double());
			} else {
				add_cap(ram.widthscale, 0.0);
			}
			get_semi();
		} else if (token == "byte") {
			int val = get_int();
			if (val <= 0)
				log_error("%s:%d: byte width %d not positive.\n", filename, line_number, val);
			add_cap(ram.byte, val);
			get_semi();
		} else if (token == "init") {
			MemoryInitKind kind;
			token = get_token();
			if (token == "zero") {
				kind = MemoryInitKind::Zero;
			} else if (token == "any") {
				kind = MemoryInitKind::Any;
			} else if (token == "no_undef") {
				kind = MemoryInitKind::NoUndef;
			} else if (token == "none") {
				kind = MemoryInitKind::None;
			} else {
				log_error("%s:%d: expected `zero`, `any`, `none`, or `no_undef`, got `%s`.\n", filename, line_number, token);
			}
			get_semi();
			add_cap(ram.init, kind);
		} else if (token == "style") {
			do {
				std::string val = get_string();
				for (auto &c: val)
					c = std::tolower(c);
				add_cap(ram.style, val);
			} while (peek_string());
			get_semi();
		} else if (token == "port") {
			port = PortGroupDef();
			token = get_token();
			if (token == "ar") {
				port.kind = PortKind::Ar;
			} else if (token == "sr") {
				port.kind = PortKind::Sr;
			} else if (token == "sw") {
				port.kind = PortKind::Sw;
			} else if (token == "arsw") {
				port.kind = PortKind::Arsw;
			} else if (token == "srsw") {
				port.kind = PortKind::Srsw;
			} else {
				log_error("%s:%d: expected `ar`, `sr`, `sw`, `arsw`, or `srsw`, got `%s`.\n", filename, line_number, token);
			}
			do {
				port.names.push_back(get_string());
			} while (peek_string());
			parse_port_block();
			if (active)
				add_cap(ram.ports, port);
		} else if (token == "") {
			log_error("%s:%d: unexpected EOF while parsing ram item.\n", filename, line_number);
		} else {
			log_error("%s:%d: unknown ram-level item `%s`.\n", filename, line_number, token);
		}
	}

	void parse_top_item() {
		std::string token = get_token();
		if (token == "ifdef") {
			bool save = enter_ifdef(true);
			parse_top_block();
			if (peek_token() == "else") {
				enter_else(save);
				parse_top_block();
			}
			active = save;
		} else if (token == "ifndef") {
			bool save = enter_ifdef(false);
			parse_top_block();
			if (peek_token() == "else") {
				enter_else(save);
				parse_top_block();
			}
			active = save;
		} else if (token == "ram") {
			int orig_line = line_number;
			ram = RamDef();
			token = get_token();
			if (token == "distributed") {
				ram.kind = RamKind::Distributed;
			} else if (token == "block") {
				ram.kind = RamKind::Block;
			} else if (token == "huge") {
				ram.kind = RamKind::Huge;
			} else {
				log_error("%s:%d: expected `distributed`, `block`, or `huge`, got `%s`.\n", filename, line_number, token);
			}
			ram.id = get_id();
			parse_ram_block();
			if (active) {
				compile_ram(orig_line);
			}
		} else if (token == "") {
			log_error("%s:%d: unexpected EOF while parsing top item.\n", filename, line_number);
		} else {
			log_error("%s:%d: unknown top-level item `%s`.\n", filename, line_number, token);
		}
	}

	bool opts_ok(const Options &def, const Options &var) {
		for (auto &it: def)
			if (var.at(it.first) != it.second)
				return false;
		return true;
	}

	template<typename T> const T *find_single_cap(const Caps<T> &caps, const Options &opts, const Options &portopts, const char *name) {
		const T *res = nullptr;
		for (auto &cap: caps) {
			if (!opts_ok(cap.opts, opts))
				continue;
			if (!opts_ok(cap.portopts, portopts))
				continue;
			if (res)
				log_error("%s:%d: duplicate %s cap.\n", filename, line_number, name);
			res = &cap.val;
		}
		return res;
	}

	std::vector<Options> make_opt_combinations(const dict<std::string, pool<Const>> &opts) {
		std::vector<Options> res;
		res.push_back(Options());
		for (auto &it: opts) {
			std::vector<Options> new_res;
			for (auto &val: it.second) {
				for (Options o: res) {
					o[it.first] = val;
					new_res.push_back(o);
				}
			}
			res = new_res;
		}
		return res;
	}

	void compile_portgroup(Ram &cram, PortGroupDef &pdef, dict<std::string, int> &clk_ids, const dict<std::string, int> &port_ids, int orig_line) {
		PortGroup grp;
		grp.optional = find_single_cap(pdef.optional, cram.options, Options(), "optional");
		grp.optional_rw = find_single_cap(pdef.optional_rw, cram.options, Options(), "optional_rw");
		grp.names = pdef.names;
		for (auto portopts: make_opt_combinations(pdef.portopts)) {
			bool forbidden = false;
			for (auto &fdef: ram.forbid) {
				if (opts_ok(fdef.opts, cram.options) && opts_ok(fdef.portopts, portopts)) {
					forbidden = true;
				}
			}
			if (forbidden)
				continue;
			PortVariant var;
			var.options = portopts;
			var.kind = pdef.kind;
			if (pdef.kind == PortKind::Ar) {
				var.clk_en = false;
			} else {
				const ClockDef *cdef = find_single_cap(pdef.clock, cram.options, portopts, "clock");
				if (!cdef)
					log_error("%s:%d: missing clock capability.\n", filename, orig_line);
				var.clk_pol = cdef->kind;
				if (cdef->name.empty()) {
					var.clk_shared = -1;
				} else {
					auto it = clk_ids.find(cdef->name);
					bool anyedge = cdef->kind == ClkPolKind::Anyedge;
					if (it == clk_ids.end()) {
						clk_ids[cdef->name] = var.clk_shared = GetSize(cram.shared_clocks);
						RamClock clk;
						clk.name = cdef->name;
						clk.anyedge = anyedge;
						cram.shared_clocks.push_back(clk);
					} else {
						var.clk_shared = it->second;
						if (cram.shared_clocks[var.clk_shared].anyedge != anyedge) {
							log_error("%s:%d: named clock \"%s\" used with both posedge/negedge and anyedge clocks.\n", filename, orig_line, cdef->name);
						}
					}
				}
				var.clk_en = find_single_cap(pdef.clken, cram.options, portopts, "clken") != nullptr;
			}
			const PortWidthDef *wdef = find_single_cap(pdef.width, cram.options, portopts, "width");
			if (wdef) {
				if (cram.width_mode != WidthMode::PerPort)
					log_error("%s:%d: per-port width doesn't make sense for tied dbits.\n", filename, orig_line);
				compile_widths(var, cram.dbits, *wdef);
			} else {
				var.width_tied = true;
				var.min_wr_wide_log2 = 0;
				var.min_rd_wide_log2 = 0;
				var.max_wr_wide_log2 = GetSize(cram.dbits) - 1;
				var.max_rd_wide_log2 = GetSize(cram.dbits) - 1;
			}
			if (pdef.kind == PortKind::Srsw || pdef.kind == PortKind::Sr) {
				const RdWrKind *rdwr = find_single_cap(pdef.rdwr, cram.options, portopts, "rdwr");
				var.rdwr = rdwr ? *rdwr : RdWrKind::Undefined;
				var.rd_en = find_single_cap(pdef.rden, cram.options, portopts, "rden");
				const ResetValKind *iv = find_single_cap(pdef.rdinit, cram.options, portopts, "rdinit");
				var.rdinitval = iv ? *iv : ResetValKind::None;
				const ResetValKind *arv = find_single_cap(pdef.rdarst, cram.options, portopts, "rdarst");
				var.rdarstval = arv ? *arv : ResetValKind::None;
				const SrstDef *srv = find_single_cap(pdef.rdsrst, cram.options, portopts, "rdsrst");
				if (srv) {
					var.rdsrstval = srv->val;
					var.rdsrstmode = srv->kind;
					var.rdsrst_block_wr = srv->block_wr;
					if (srv->kind == SrstKind::GatedClkEn && !var.clk_en)
						log_error("%s:%d: `gated_clken` used without `clken`.\n", filename, orig_line);
					if (srv->kind == SrstKind::GatedRdEn && !var.rd_en)
						log_error("%s:%d: `gated_rden` used without `rden`.\n", filename, orig_line);
				} else {
					var.rdsrstval = ResetValKind::None;
					var.rdsrstmode = SrstKind::None;
					var.rdsrst_block_wr = false;
				}
				if (var.rdarstval == ResetValKind::Init || var.rdsrstval == ResetValKind::Init) {
					if (var.rdinitval != ResetValKind::Any && var.rdinitval != ResetValKind::NoUndef) {
						log_error("%s:%d: reset value `init` has to be paired with `any` or `no_undef` initial value.\n", filename, orig_line);
					}
				}
			}
			var.wrbe_separate = find_single_cap(pdef.wrbe_separate, cram.options, portopts, "wrbe_separate");
			if (var.wrbe_separate && cram.byte == 0) {
				log_error("%s:%d: `wrbe_separate` used without `byte`.\n", filename, orig_line);
			}
			for (auto &def: pdef.wrprio) {
				if (!opts_ok(def.opts, cram.options))
					continue;
				if (!opts_ok(def.portopts, portopts))
					continue;
				var.wrprio.push_back(port_ids.at(def.val));
			}
			for (auto &def: pdef.wrtrans) {
				if (!opts_ok(def.opts, cram.options))
					continue;
				if (!opts_ok(def.portopts, portopts))
					continue;
				WrTransDef tdef;
				tdef.target_kind = def.val.target_kind;
				if (def.val.target_kind == WrTransTargetKind::Group)
					tdef.target_group = port_ids.at(def.val.target_group);
				tdef.kind = def.val.kind;
				var.wrtrans.push_back(tdef);
			}
			grp.variants.push_back(var);
		}
		if (grp.variants.empty()) {
			log_error("%s:%d: all port option combinations are forbidden.\n", filename, orig_line);
		}
		cram.port_groups.push_back(grp);
	}

	void compile_ram(int orig_line) {
		if (ram.abits.empty())
			log_error("%s:%d: `dims` capability should be specified.\n", filename, orig_line);
		if (ram.widths.empty())
			log_error("%s:%d: `widths` capability should be specified.\n", filename, orig_line);
		if (ram.ports.empty())
			log_error("%s:%d: at least one port group should be specified.\n", filename, orig_line);
		for (auto opts: make_opt_combinations(ram.opts)) {
			bool forbidden = false;
			for (auto &fdef: ram.forbid) {
				if (opts_ok(fdef.opts, opts)) {
					forbidden = true;
				}
			}
			if (forbidden)
				continue;
			Ram cram;
			cram.id = ram.id;
			cram.kind = ram.kind;
			cram.options = opts;
			cram.prune_rom = find_single_cap(ram.prune_rom, opts, Options(), "prune_rom");
			const int *abits = find_single_cap(ram.abits, opts, Options(), "abits");
			if (!abits)
				continue;
			cram.abits = *abits;
			const WidthsDef *widths = find_single_cap(ram.widths, opts, Options(), "widths");
			if (!widths)
				continue;
			cram.dbits = widths->widths;
			cram.width_mode = widths->mode;
			const ResourceDef *resource = find_single_cap(ram.resource, opts, Options(), "resource");
			if (resource) {
				cram.resource_name = resource->name;
				cram.resource_count = resource->count;
			} else {
				cram.resource_count = 1;
			}
			cram.cost = 0;
			for (auto &cap: ram.cost) {
				if (opts_ok(cap.opts, opts))
					cram.cost += cap.val;
			}
			const double *widthscale = find_single_cap(ram.widthscale, opts, Options(), "widthscale");
			if (widthscale)
				cram.widthscale = *widthscale ? *widthscale : cram.cost;
			else
				cram.widthscale = 0;
			const int *byte = find_single_cap(ram.byte, opts, Options(), "byte");
			cram.byte = byte ? *byte : 0;
			if (GetSize(cram.dbits) - 1 > cram.abits)
				log_error("%s:%d: abits %d too small for dbits progression.\n", filename, line_number, cram.abits);
			validate_byte(widths->widths, cram.byte);
			const MemoryInitKind *ik = find_single_cap(ram.init, opts, Options(), "init");
			cram.init = ik ? *ik : MemoryInitKind::None;
			for (auto &sdef: ram.style)
				if (opts_ok(sdef.opts, opts))
					cram.style.push_back(sdef.val);
			dict<std::string, int> port_ids;
			int ctr = 0;
			for (auto &pdef: ram.ports) {
				if (!opts_ok(pdef.opts, opts))
					continue;
				for (auto &name: pdef.val.names)
					port_ids[name] = ctr;
				ctr++;
			}
			dict<std::string, int> clk_ids;
			for (auto &pdef: ram.ports) {
				if (!opts_ok(pdef.opts, opts))
					continue;
				compile_portgroup(cram, pdef.val, clk_ids, port_ids, orig_line);
			}
			lib.rams.push_back(cram);
		}
	}

	void validate_byte(const std::vector<int> &widths, int byte) {
		if (byte == 0)
			return;
		if (byte >= widths.back())
			return;
		if (widths[0] % byte == 0) {
			for (int j = 1; j < GetSize(widths); j++)
				if (widths[j] % byte != 0)
					log_error("%s:%d: width progression past byte width %d is not divisible.\n", filename, line_number, byte);
			return;
		}
		for (int i = 0; i < GetSize(widths); i++) {
			if (widths[i] == byte) {
				for (int j = i + 1; j < GetSize(widths); j++)
					if (widths[j] % byte != 0)
						log_error("%s:%d: width progression past byte width %d is not divisible.\n", filename, line_number, byte);
				return;
			}
		}
		log_error("%s:%d: byte width %d invalid for dbits.\n", filename, line_number, byte);
	}

	void compile_widths(PortVariant &var, const std::vector<int> &widths, const PortWidthDef &width) {
		var.width_tied = width.tied;
		auto wr_widths = compile_widthdef(widths, width.wr_widths);
		var.min_wr_wide_log2 = wr_widths.first;
		var.max_wr_wide_log2 = wr_widths.second;
		if (width.tied) {
			var.min_rd_wide_log2 = wr_widths.first;
			var.max_rd_wide_log2 = wr_widths.second;
		} else {
			auto rd_widths = compile_widthdef(widths, width.rd_widths);
			var.min_rd_wide_log2 = rd_widths.first;
			var.max_rd_wide_log2 = rd_widths.second;
		}
	}

	std::pair<int, int> compile_widthdef(const std::vector<int> &dbits, const std::vector<int> &widths) {
		if (widths.empty())
			return {0, GetSize(dbits) - 1};
		for (int i = 0; i < GetSize(dbits); i++) {
			if (dbits[i] == widths[0]) {
				for (int j = 0; j < GetSize(widths); j++) {
					if (i+j >= GetSize(dbits) || dbits[i+j] != widths[j]) {
						log_error("%s:%d: port width %d doesn't match dbits progression.\n", filename, line_number, widths[j]);
					}
				}
				return {i, i + GetSize(widths) - 1};
			}
		}
		log_error("%s:%d: port width %d invalid for dbits.\n", filename, line_number, widths[0]);
	}

	void parse() {
		while (peek_token() != "")
			parse_top_item();
	}
};

PRIVATE_NAMESPACE_END

Library MemLibrary::parse_library(const std::vector<std::string> &filenames, const pool<std::string> &defines) {
	Library res;
	pool<std::string> defines_unused = defines;
	for (auto &file: filenames) {
		Parser(file, res, defines, defines_unused);
	}
	for (auto def: defines_unused) {
		log_warning("define %s not used in the library.\n", def);
	}
	return res;
}
