/*
 *  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.
 *
 *  ---
 *
 *  The Verilog frontend.
 *
 *  This frontend is using the AST frontend library (see frontends/ast/).
 *  Thus this frontend does not generate RTLIL code directly but creates an
 *  AST directly from the Verilog parse tree and then passes this AST to
 *  the AST frontend library.
 *
 *  ---
 *
 *  Ad-hoc implementation of a Verilog preprocessor. The directives `define,
 *  `include, `ifdef, `ifndef, `else and `endif are handled here. All other
 *  directives are handled by the lexer (see verilog_lexer.l).
 *
 */

#include "preproc.h"
#include "verilog_frontend.h"
#include "frontends/verilog/verilog_parser.tab.hh"
#include "kernel/log.h"
#include <assert.h>
#include <stack>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

YOSYS_NAMESPACE_BEGIN
using namespace VERILOG_FRONTEND;

static std::list<std::string> output_code;
static std::list<std::string> input_buffer;
static size_t input_buffer_charp;

static void return_char(char ch)
{
	if (input_buffer_charp == 0)
		input_buffer.push_front(std::string() + ch);
	else
		input_buffer.front()[--input_buffer_charp] = ch;
}

static void insert_input(std::string str)
{
	if (input_buffer_charp != 0) {
		input_buffer.front() = input_buffer.front().substr(input_buffer_charp);
		input_buffer_charp = 0;
	}
	input_buffer.push_front(str);
}

static char next_char()
{
	if (input_buffer.empty())
		return 0;

	log_assert(input_buffer_charp <= input_buffer.front().size());
	if (input_buffer_charp == input_buffer.front().size()) {
		input_buffer_charp = 0;
		input_buffer.pop_front();
		return next_char();
	}

	char ch = input_buffer.front()[input_buffer_charp++];
	return ch == '\r' ? next_char() : ch;
}

static std::string skip_spaces()
{
	std::string spaces;
	while (1) {
		char ch = next_char();
		if (ch == 0)
			break;
		if (ch != ' ' && ch != '\t') {
			return_char(ch);
			break;
		}
		spaces += ch;
	}
	return spaces;
}

static std::string next_token(bool pass_newline = false)
{
	std::string token;

	char ch = next_char();
	if (ch == 0)
		return token;

	token += ch;
	if (ch == '\n') {
		if (pass_newline) {
			output_code.push_back(token);
			return "";
		}
		return token;
	}

	if (ch == ' ' || ch == '\t')
	{
		while ((ch = next_char()) != 0) {
			if (ch != ' ' && ch != '\t') {
				return_char(ch);
				break;
			}
			token += ch;
		}
	}
	else if (ch == '"')
	{
		while ((ch = next_char()) != 0) {
			token += ch;
			if (ch == '"')
				break;
			if (ch == '\\') {
				if ((ch = next_char()) != 0)
					token += ch;
			}
		}
		if (token == "\"\"" && (ch = next_char()) != 0) {
			if (ch == '"')
				token += ch;
			else
				return_char(ch);
		}
	}
	else if (ch == '\\')
	{
		while ((ch = next_char()) != 0) {
			if (ch < 33 || ch > 126) {
				return_char(ch);
				break;
			}
			token += ch;
		}
	}
	else if (ch == '/')
	{
		if ((ch = next_char()) != 0) {
			if (ch == '/') {
				token += '*';
				char last_ch = 0;
				while ((ch = next_char()) != 0) {
					if (ch == '\n') {
						return_char(ch);
						break;
					}
					if (last_ch != '*' || ch != '/') {
						token += ch;
						last_ch = ch;
					}
				}
				token += " */";
			}
			else if (ch == '*') {
				token += '*';
				int newline_count = 0;
				char last_ch = 0;
				while ((ch = next_char()) != 0) {
					if (ch == '\n') {
						newline_count++;
						token += ' ';
					} else
						token += ch;
					if (last_ch == '*' && ch == '/')
						break;
					last_ch = ch;
				}
				while (newline_count-- > 0)
					return_char('\n');
			}
			else
				return_char(ch);
		}
	}
	else
	{
		const char *ok = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ$0123456789";
		if (ch == '`' || strchr(ok, ch) != NULL)
		{
			char first = ch;
			ch = next_char();
			if (first == '`' && (ch == '"' || ch == '`')) {
				token += ch;
			} else do {
					if (strchr(ok, ch) == NULL) {
						return_char(ch);
						break;
					}
					token += ch;
				} while ((ch = next_char()) != 0);
		}
	}
	return token;
}

struct macro_arg_t
{
	macro_arg_t(const std::string &name_, const char *default_value_)
		: name(name_),
		  has_default(default_value_ != nullptr),
		  default_value(default_value_ ? default_value_ : "")
	{}

	std::string name;
	bool        has_default;
	std::string default_value;
};

static bool all_white(const std::string &str)
{
	for (char c : str)
		if (!isspace(c))
			return false;
	return true;
}

struct arg_map_t
{
	arg_map_t()
	{}

	void add_arg(const std::string &name, const char *default_value)
	{
		if (find(name)) {
			log_error("Duplicate macro arguments with name `%s'.\n", name);
		}

		name_to_pos[name] = args.size();
		args.push_back(macro_arg_t(name, default_value));
	}

	// Find an argument by name; return nullptr if it doesn't exist. If pos is not null, write
	// the argument's position to it on success.
	const macro_arg_t *find(const std::string &name, int *pos = nullptr) const
	{
		auto it = name_to_pos.find(name);
		if (it == name_to_pos.end())
			return nullptr;

		if (pos) *pos = it->second;
		return &args[it->second];
	}

	// Construct the name for the local macro definition we use for the given argument
	// (something like macro_foobar_arg2). This doesn't include the leading backtick.
	static std::string str_token(const std::string &macro_name, int pos)
	{
		return stringf("macro_%s_arg%d", macro_name, pos);
	}

	// Return definitions for the macro arguments (so that substituting in the macro body and
	// then performing macro expansion will do argument substitution properly).
	std::vector<std::pair<std::string, std::string>>
	get_vals(const std::string &macro_name, const std::vector<std::string> &arg_vals) const
	{
		std::vector<std::pair<std::string, std::string>> ret;
		for (int i = 0; i < GetSize(args); ++ i) {
			// The SystemVerilog rules are:
			//
			//   - If the call site specifies an argument and it's not whitespace, use
			//     it.
			//
			//   - Otherwise, if the argument has a default value, use it.
			//
			//   - Otherwise, if the call site specified whitespace, use that.
			//
			//   - Otherwise, error.
			const std::string *dflt = nullptr;
			if (args[i].has_default)
				dflt = &args[i].default_value;

			const std::string *given = nullptr;
			if (i < GetSize(arg_vals))
				given = &arg_vals[i];

			const std::string *val = nullptr;
			if (given && (! (dflt && all_white(*given))))
				val = given;
			else if (dflt)
				val = dflt;
			else if (given)
				val = given;
			else
				log_error("Cannot expand macro `%s by giving only %d argument%s "
				          "(argument %d has no default).\n",
				          macro_name.c_str(), GetSize(arg_vals),
				          (GetSize(arg_vals) == 1 ? "" : "s"), i + 1);

			assert(val);
			ret.push_back(std::make_pair(str_token(macro_name, i), * val));
		}
		return ret;
	}


	std::vector<macro_arg_t>   args;
	std::map<std::string, int> name_to_pos;
};

struct define_body_t
{
	define_body_t(const std::string &body, const arg_map_t *args = nullptr)
	  : body(body),
	    has_args(args != nullptr),
	    args(args ? *args : arg_map_t())
	{}

	std::string body;
	bool        has_args;
	arg_map_t   args;
};

define_map_t::define_map_t()
{
	add("YOSYS", "1");
}

// We must define this destructor here (rather than relying on the default), because we need to
// define it somewhere we've got a complete definition of define_body_t.
define_map_t::~define_map_t()
{}

void
define_map_t::add(const std::string &name, const std::string &txt, const arg_map_t *args)
{
	defines[name] = std::unique_ptr<define_body_t>(new define_body_t(txt, args));
}

void define_map_t::add(const std::string &name, const define_body_t &body)
{
	defines[name] = std::unique_ptr<define_body_t>(new define_body_t(body));
}

void define_map_t::merge(const define_map_t &map)
{
	for (const auto &pr : map.defines) {
		// These contortions are so that we take a copy of each definition body in
		// map.defines.
		defines[pr.first] = std::unique_ptr<define_body_t>(new define_body_t(*pr.second));
	}
}

const define_body_t *define_map_t::find(const std::string &name) const
{
	auto it = defines.find(name);
	return (it == defines.end()) ? nullptr : it->second.get();
}

void define_map_t::erase(const std::string &name)
{
	defines.erase(name);
}

void define_map_t::clear()
{
	defines.clear();
}

void define_map_t::log() const
{
	for (auto &it : defines) {
		const std::string &name = it.first;
		const define_body_t &body = *it.second;
		Yosys::log("`define %s%s %s\n",
		           name.c_str(), body.has_args ? "()" : "", body.body.c_str());
	}
}

static void input_file(std::istream &f, std::string filename)
{
	char buffer[513];
	int rc;

	insert_input("");
	auto it = input_buffer.begin();

	input_buffer.insert(it, "`file_push \"" + filename + "\"\n");
	while ((rc = readsome(f, buffer, sizeof(buffer)-1)) > 0) {
		buffer[rc] = 0;
		input_buffer.insert(it, buffer);
	}
	input_buffer.insert(it, "\n`file_pop\n");
}

// Read tokens to get one argument (either a macro argument at a callsite or a default argument in a
// macro definition). Writes the argument to dest. Returns true if we finished with ')' (the end of
// the argument list); false if we finished with ','.
static bool read_argument(std::string &dest)
{
	skip_spaces();
	std::vector<char> openers;
	for (;;) {
		std::string tok = next_token(true);
		if (tok == ")") {
			if (openers.empty()) {
				while (dest.size() && (dest.back() == ' ' || dest.back() == '\t'))
					dest = dest.substr(0, dest.size() - 1);
				return true;
			}
			if (openers.back() != '(')
				log_error("Mismatched brackets in macro argument: %c and %c.\n",
				          openers.back(), tok[0]);

			openers.pop_back();
			dest += tok;
			continue;
		}
		if (tok == "]") {
			char opener = openers.empty() ? '(' : openers.back();
			if (opener != '[')
				log_error("Mismatched brackets in macro argument: %c and %c.\n",
				          opener, tok[0]);

			openers.pop_back();
			dest += tok;
			continue;
		}
		if (tok == "}") {
			char opener = openers.empty() ? '(' : openers.back();
			if (opener != '{')
				log_error("Mismatched brackets in macro argument: %c and %c.\n",
				          opener, tok[0]);

			openers.pop_back();
			dest += tok;
			continue;
		}

		if (tok == "," && openers.empty()) {
			return false;
		}

		if (tok == "(" || tok == "[" || tok == "{")
			openers.push_back(tok[0]);

		dest += tok;
	}
}

using macro_arg_stack_t = std::stack<std::pair<std::string, define_body_t>>;

static void restore_macro_arg(define_map_t &defines, macro_arg_stack_t &macro_arg_stack)
{
	log_assert(!macro_arg_stack.empty());
	auto &overwritten_arg = macro_arg_stack.top();
	defines.add(overwritten_arg.first, overwritten_arg.second);
	macro_arg_stack.pop();
}

static bool try_expand_macro(define_map_t &defines, macro_arg_stack_t &macro_arg_stack, std::string &tok)
{
	if (tok == "`\"") {
		std::string literal("\"");
		// Expand string literal
		while (!input_buffer.empty()) {
			std::string ntok = next_token();
			if (ntok == "`\"") {
				insert_input(literal+"\"");
				return true;
			} else if (!try_expand_macro(defines, macro_arg_stack, ntok)) {
					literal += ntok;
			}
		}
		return false; // error - unmatched `"
	}

	if (tok == "``") {
		// Swallow `` in macro expansion
		return true;
	}

	if (tok.size() <= 1 || tok[0] != '`')
		return false;

	// This token looks like a macro name (`foo).
	std::string macro_name = tok.substr(1);
	const define_body_t *body = defines.find(tok.substr(1));

	if (! body) {
		// Apparently not a name we know.
		return false;
	}

	std::string name = tok.substr(1);
	std::string skipped_spaces = skip_spaces();
	tok = next_token(false);
	if (body->has_args) {
		if (tok != "(") {
			if (tok.size() == 1 && iscntrl(tok[0])) {
				char buf[5];
				snprintf(buf, sizeof(buf), "\\x%02x", tok[0]);
				tok = buf;
			}
			log_error("Expected to find '(' to begin macro arguments for '%s', but instead found '%s'\n",
				name.c_str(), tok.c_str());
		}
		std::vector<std::string> args;
		bool done = false;
		while (!done) {
			std::string arg;
			done = read_argument(arg);
			args.push_back(arg);
		}
		for (const auto &pr : body->args.get_vals(name, args)) {
			if (const define_body_t *existing = defines.find(pr.first)) {
				macro_arg_stack.push({pr.first, *existing});
				insert_input("`__restore_macro_arg ");
			}
			defines.add(pr.first, pr.second);
		}
	} else {
		insert_input(tok);
		insert_input(skipped_spaces);
	}
	insert_input(body->body);
	return true;
}

// Read the arguments for a `define preprocessor directive with formal arguments. This is called
// just after reading the token containing "(". Returns the number of newlines to emit afterwards to
// keep line numbers in sync, together with the map from argument name to data (pos and default
// value).
static std::pair<int, arg_map_t>
read_define_args()
{
	// Each argument looks like one of the following:
	//
	//     identifier
	//     identifier = default_text
	//     identifier =
	//
	// The first example is an argument with no default value. The second is an argument whose
	// default value is default_text. The third is an argument with default value the empty
	// string.

	int newline_count = 0;
	arg_map_t args;

	// FSM state.
	//
	//   0: At start of identifier
	//   1: After identifier (stored in arg_name)
	//   2: After closing paren
	int state = 0;

	std::string arg_name, default_val;

	skip_spaces();
	for (;;) {
		if (state == 2)
			// We've read the closing paren.
			break;

		std::string tok = next_token();

		// Cope with escaped EOLs
		if (tok == "\\") {
			char ch = next_char();
			if (ch == '\n') {
				// Eat the \, the \n and any trailing space and keep going.
				skip_spaces();
				continue;
			} else {
				// There aren't any other situations where a backslash makes sense.
				log_error("Backslash in macro arguments (not at end of line).\n");
			}
		}

		switch (state) {
		case 0:
			// At start of argument. If the token is ')', we've presumably just seen
			// something like "`define foo() ...". Set state to 2 to finish. Otherwise,
			// the token should be a valid simple identifier, but we'll allow anything
			// here.
			if (tok == ")") {
				state = 2;
			} else {
				arg_name = tok;
				state = 1;
			}
			skip_spaces();
			break;

		case 1:
			// After argument. The token should either be an equals sign or a comma or
			// closing paren.
			if (tok == "=") {
				std::string default_val;
				//Read an argument into default_val and set state to 2 if we're at
				// the end; 0 if we hit a comma.
				state = read_argument(default_val) ? 2 : 0;
				args.add_arg(arg_name, default_val.c_str());
				skip_spaces();
				break;
			}
			if (tok == ",") {
				// Take the identifier as an argument with no default value.
				args.add_arg(arg_name, nullptr);
				state = 0;
				skip_spaces();
				break;
			}
			if (tok == ")") {
				// As with comma, but set state to 2 (end of args)
				args.add_arg(arg_name, nullptr);
				state = 2;
				skip_spaces();
				break;
			}
			log_error("Trailing contents after identifier in macro argument `%s': "
				  "expected '=', ',' or ')'.\n",
				  arg_name.c_str());

		default:
			// The only FSM states are 0-2 and we dealt with 2 at the start of the loop.
			log_assert(false);
		}
	}

	return std::make_pair(newline_count, args);
}

// Read a `define preprocessor directive. This is called just after reading the token containing
// "`define".
static void
read_define(const std::string &filename,
            define_map_t      &defines_map,
            define_map_t      &global_defines_cache)
{
	std::string name, value;
	arg_map_t args;

	skip_spaces();
	name = next_token(true);

	bool here_doc_mode = false;
	int newline_count = 0;

	// The FSM state starts at 0. If it sees space (or enters here_doc_mode), it assumes this is
	// a macro without formal arguments and jumps to state 1.
	//
	// In state 0, if it sees an opening parenthesis, it assumes this is a macro with formal
	// arguments. It reads the arguments with read_define_args() and then jumps to state 2.
	//
	// In states 1 or 2, the FSM reads tokens to the end of line (or end of here_doc): this is
	// the body of the macro definition.
	int state = 0;

	if (skip_spaces() != "")
		state = 1;

	for (;;) {
		std::string tok = next_token();
		if (tok.empty())
			break;

		// printf("define-tok: >>%s<<\n", tok != "\n" ? tok.c_str() : "NEWLINE");

		if (tok == "\"\"\"") {
			here_doc_mode = !here_doc_mode;
			continue;
		}

		if (state == 0 && tok == "(") {
			auto pr = read_define_args();
			newline_count += pr.first;
			args = pr.second;

			state = 2;
			continue;
		}

		// This token isn't an opening parenthesis immediately following the macro name, so
		// it's presumably at or after the start of the macro body. If state isn't already 2
		// (which would mean we'd parsed an argument list), set it to 1.
		if (state == 0) {
			state = 1;
		}

		if (tok == "\n") {
			if (here_doc_mode) {
				value += " ";
				newline_count++;
			} else {
				return_char('\n');
				break;
			}
			continue;
		}

		if (tok == "\\") {
			char ch = next_char();
			if (ch == '\n') {
				value += " ";
				newline_count++;
			} else {
				value += std::string("\\");
				return_char(ch);
			}
			continue;
		}

		// Is this token the name of a macro argument? If so, replace it with a magic symbol
		// that we'll replace with the argument value.
		int arg_pos;
		if (args.find(tok, &arg_pos)) {
			value += '`' + args.str_token(name, arg_pos);
			continue;
		}

		// This token is nothing special. Insert it verbatim into the macro body.
		value += tok;
	}

	// Append some newlines so that we don't mess up line counts in error messages.
	while (newline_count-- > 0)
		return_char('\n');

	if (strchr("abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ$0123456789", name[0])) {
		// printf("define: >>%s<< -> >>%s<<\n", name.c_str(), value.c_str());
		defines_map.add(name, value, (state == 2) ? &args : nullptr);
		global_defines_cache.add(name, value, (state == 2) ? &args : nullptr);
	} else {
		log_file_error(filename, 0, "Invalid name for macro definition: >>%s<<.\n", name);
	}
}

std::string
frontend_verilog_preproc(std::istream                 &f,
                         std::string                   filename,
                         const define_map_t           &pre_defines,
                         define_map_t                 &global_defines_cache,
                         const std::list<std::string> &include_dirs,
                         ParseState                   &parse_state,
                         ParseMode                    &parse_mode)
{
	define_map_t defines;
	defines.merge(pre_defines);
	defines.merge(global_defines_cache);

	macro_arg_stack_t macro_arg_stack;
	std::vector<std::string> filename_stack;
	// We are inside pass_level levels of satisfied ifdefs, and then within
	// fail_level levels of unsatisfied ifdefs.  The unsatisfied ones are
	// always within satisfied ones — even if some condition within is true,
	// the parent condition failing renders it moot.
	int ifdef_fail_level = 0;
	int ifdef_pass_level = 0;
	// For the outermost unsatisfied ifdef, true iff that ifdef already
	// had a satisfied branch, and further elsif/else branches should be
	// considered unsatisfied even if the condition is true.
	// Meaningless if ifdef_fail_level == 0.
	bool ifdef_already_satisfied = false;

	output_code.clear();
	input_buffer.clear();
	input_buffer_charp = 0;

	input_file(f, filename);

	while (!input_buffer.empty())
	{
		std::string tok = next_token();
		// printf("token: >>%s<<\n", tok != "\n" ? tok.c_str() : "NEWLINE");

		if (tok == "`endif") {
			if (ifdef_fail_level > 0)
				ifdef_fail_level--;
			else if (ifdef_pass_level > 0)
				ifdef_pass_level--;
			else
				log_error("Found %s outside of macro conditional branch!\n", tok);
			continue;
		}

		if (tok == "`else") {
			if (ifdef_fail_level == 0) {
				if (ifdef_pass_level == 0)
					log_error("Found %s outside of macro conditional branch!\n", tok);
				ifdef_pass_level--;
				ifdef_fail_level = 1;
				ifdef_already_satisfied = true;
			} else if (ifdef_fail_level == 1 && !ifdef_already_satisfied) {
				ifdef_fail_level = 0;
				ifdef_pass_level++;
				ifdef_already_satisfied = true;
			}
			continue;
		}

		if (tok == "`elsif") {
			skip_spaces();
			std::string name = next_token(true);
			if (ifdef_fail_level == 0) {
				if (ifdef_pass_level == 0)
					log_error("Found %s outside of macro conditional branch!\n", tok);
				ifdef_pass_level--;
				ifdef_fail_level = 1;
				ifdef_already_satisfied = true;
			} else if (ifdef_fail_level == 1 && !ifdef_already_satisfied && defines.find(name)) {
				ifdef_fail_level = 0;
				ifdef_pass_level++;
				ifdef_already_satisfied = true;
			}
			continue;
		}

		if (tok == "`ifdef") {
			skip_spaces();
			std::string name = next_token(true);
			if (ifdef_fail_level > 0 || !defines.find(name)) {
				ifdef_fail_level++;
			} else {
				ifdef_pass_level++;
				ifdef_already_satisfied = true;
			}
			if (ifdef_fail_level == 1)
				ifdef_already_satisfied = false;
			continue;
		}

		if (tok == "`ifndef") {
			skip_spaces();
			std::string name = next_token(true);
			if (ifdef_fail_level > 0 || defines.find(name)) {
				ifdef_fail_level++;
			} else {
				ifdef_pass_level++;
				ifdef_already_satisfied = true;
			}
			if (ifdef_fail_level == 1)
				ifdef_already_satisfied = false;
			continue;
		}

		if (ifdef_fail_level > 0) {
			if (tok == "\n")
				output_code.push_back(tok);
			continue;
		}

		if (tok == "`include") {
			skip_spaces();
			std::string fn = next_token(true);
			while (try_expand_macro(defines, macro_arg_stack, fn)) {
				fn = next_token();
			}
			while (1) {
				size_t pos = fn.find('"');
				if (pos == std::string::npos)
					break;
				if (pos == 0)
					fn = fn.substr(1);
				else
					fn = fn.substr(0, pos) + fn.substr(pos+1);
			}
			std::ifstream ff;
			ff.clear();
			std::string fixed_fn = fn;
			ff.open(fixed_fn.c_str());

			bool filename_path_sep_found;
			bool fn_relative;
#ifdef _WIN32
			// Both forward and backslash are acceptable separators on Windows.
			filename_path_sep_found = (filename.find_first_of("/\\") != std::string::npos);
			// Easier just to invert the check for an absolute path (e.g. C:\ or C:/)
			fn_relative = !(fn[1] == ':' && (fn[2] == '/' || fn[2] == '\\'));
#else
			filename_path_sep_found = (filename.find('/') != std::string::npos);
			fn_relative = (fn[0] != '/');
#endif

			if (ff.fail() && fn.size() > 0 && fn_relative && filename_path_sep_found) {
				// if the include file was not found, it is not given with an absolute path, and the
				// currently read file is given with a path, then try again relative to its directory
				ff.clear();
				fixed_fn = parent_from_file_path(filename) + fn;
				ff.open(fixed_fn);
			}
			if (ff.fail() && fn.size() > 0 && fn_relative) {
				// if the include file was not found and it is not given with an absolute path, then
				// search it in the include path
				for (auto incdir : include_dirs) {
					ff.clear();
					fixed_fn = incdir + '/' + fn;
					ff.open(fixed_fn);
					if (!ff.fail()) break;
				}
			}
			if (ff.fail()) {
				output_code.push_back("`file_notfound " + fn);
			} else {
				input_file(ff, fixed_fn);
				yosys_input_files.insert(fixed_fn);
			}
			continue;
		}

		if (tok == "`file_push") {
			skip_spaces();
			std::string fn = next_token(true);
			if (!fn.empty() && fn.front() == '"' && fn.back() == '"')
				fn = fn.substr(1, fn.size()-2);
			output_code.push_back(tok + " \"" + fn + "\"");
			filename_stack.push_back(filename);
			filename = fn;
			continue;
		}

		if (tok == "`file_pop") {
			output_code.push_back(tok);
			filename = filename_stack.back();
			filename_stack.pop_back();
			continue;
		}

		if (tok == "`define") {
			read_define(filename, defines, global_defines_cache);
			continue;
		}

		if (tok == "`undef") {
			std::string name;
			skip_spaces();
			name = next_token(true);
			// printf("undef: >>%s<<\n", name.c_str());
			defines.erase(name);
			global_defines_cache.erase(name);
			continue;
		}

		if (tok == "`timescale") {
			skip_spaces();
			while (!tok.empty() && tok != "\n")
				tok = next_token(true);
			if (tok == "\n")
				return_char('\n');
			continue;
		}

		if (tok == "`resetall") {
			parse_state.default_nettype_wire = true;
			continue;
		}

		if (tok == "`undefineall" && parse_mode.sv) {
			defines.clear();
			global_defines_cache.clear();
			continue;
		}

		if (tok == "`__restore_macro_arg") {
			restore_macro_arg(defines, macro_arg_stack);
			continue;
		}

		if (try_expand_macro(defines, macro_arg_stack, tok))
			continue;

		output_code.push_back(tok);
	}

	if (ifdef_fail_level > 0 || ifdef_pass_level > 0) {
		log_error("Unterminated preprocessor conditional!\n");
	}

	std::string output;
	for (auto &str : output_code)
		output += str;

	output_code.clear();
	input_buffer.clear();
	input_buffer_charp = 0;

	return output;
}

YOSYS_NAMESPACE_END
