// ACME - a crossassembler for producing 6502/65c02/65816/65ce02 code.
// Copyright (C) 1998-2020 Marco Baye
// Have a look at "acme.c" for further info
//
// Macro stuff
#include "macro.h"
#include <string.h>	// needs strlen() + memcpy()
#include "config.h"
#include "platform.h"
#include "acme.h"
#include "alu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "section.h"
#include "symbol.h"
#include "tree.h"


// Constants
#define NAME_INITIALSIZE	128
#define ARG_SEPARATOR	' '	// separates macro title from arg types
#define ARGTYPE_VALUE	'v'
#define ARGTYPE_REF	'r'
#define REFERENCE_CHAR	'~'	// prefix for call-by-reference
#define HALF_INITIAL_ARG_TABLE_SIZE	4
static const char	exception_macro_twice[]	= "Macro already defined.";


// macro struct type definition
struct macro {
	int	def_line_number;	// line number of definition	for error msgs
	char	*def_filename,	// file name of definition	for error msgs
		*original_name,	// user-supplied name		for error msgs
		*parameter_list,	// parameters (whole line)
		*body;	// RAM block containing macro body
};
// there's no need to make this a struct and add a type component:
// when the macro has been found, accessing its parameter_list component
// gives us the possibility to find out which args are call-by-value and
// which ones are call-by-reference.
union macro_arg_t {
	struct object	result;	// value and flags (call by value)
	struct symbol	*symbol;	// pointer to symbol struct (call by reference)
};


// Variables
static	STRUCT_DYNABUF_REF(user_macro_name, NAME_INITIALSIZE);	// original macro title
static	STRUCT_DYNABUF_REF(internal_name, NAME_INITIALSIZE);	// plus param type chars
static struct rwnode	*macro_forest[256];	// trees (because of 8b hash)
// Dynamic argument table
static union macro_arg_t	*arg_table	= NULL;
static int			argtable_size	= HALF_INITIAL_ARG_TABLE_SIZE;


// Functions

// Enlarge the argument table
static void enlarge_arg_table(void)
{
	argtable_size *= 2;
	//printf("Doubling arg table size to %d.\n", argtable_size);
	arg_table = realloc(arg_table, argtable_size * sizeof(*arg_table));
	if (arg_table == NULL)
		Throw_serious_error(exception_no_memory_left);
}

// Read macro scope and title. Title is read to GlobalDynaBuf and then copied
// over to internal_name DynaBuf, where ARG_SEPARATOR is added.
// In user_macro_name DynaBuf, the original name is reconstructed (even with
// LOCAL_PREFIX) so a copy can be linked to the resulting macro struct.
static scope_t get_scope_and_title(void)
{
	scope_t	macro_scope;

	Input_read_scope_and_keyword(&macro_scope);	// skips spaces before
	// now GotByte = illegal character after title
	// copy macro title to private dynabuf and add separator character
	DYNABUF_CLEAR(user_macro_name);
	DYNABUF_CLEAR(internal_name);
	if (macro_scope != SCOPE_GLOBAL) {
		// TODO - allow "cheap macros"?!
		DynaBuf_append(user_macro_name, LOCAL_PREFIX);
	}
	DynaBuf_add_string(user_macro_name, GLOBALDYNABUF_CURRENT);
	DynaBuf_add_string(internal_name, GLOBALDYNABUF_CURRENT);
	DynaBuf_append(user_macro_name, '\0');
	DynaBuf_append(internal_name, ARG_SEPARATOR);
	SKIPSPACE();	// done here once so it's not necessary at two callers
	return macro_scope;
}

// Check for comma. If there, append to GlobalDynaBuf.
static int pipe_comma(void)
{
	int	result;

	result = Input_accept_comma();
	if (result)
		DYNABUF_APPEND(GlobalDynaBuf, ',');
	return result;
}

// Return malloc'd copy of string
static char *get_string_copy(const char *original)
{
	size_t	size;
	char	*copy;

	size = strlen(original) + 1;
	copy = safe_malloc(size);
	memcpy(copy, original, size);
	return copy;
}

// This function is called from both macro definition and macro call.
// Terminate macro name and copy from internal_name to GlobalDynaBuf
// (because that's where Tree_hard_scan() looks for the search string).
// Then try to find macro and return whether it was created.
static int search_for_macro(struct rwnode **result, scope_t scope, int create)
{
	DynaBuf_append(internal_name, '\0');	// terminate macro name
	// now internal_name = macro_title SPC argument_specifiers NUL
	DYNABUF_CLEAR(GlobalDynaBuf);
	DynaBuf_add_string(GlobalDynaBuf, internal_name->buffer);
	DynaBuf_append(GlobalDynaBuf, '\0');
	return Tree_hard_scan(result, macro_forest, scope, create);
}

// This function is called when an already existing macro is re-defined.
// It first outputs a warning and then a serious error, stopping assembly.
// Showing the first message as a warning guarantees that ACME does not reach
// the maximum error limit inbetween.
static void report_redefinition(struct rwnode *macro_node)
{
	struct macro	*original_macro	= macro_node->body;

	// show warning with location of current definition
	Throw_warning(exception_macro_twice);
	// CAUTION, ugly kluge: fiddle with Input_now and section_now
	// data to generate helpful error messages
	Input_now->original_filename = original_macro->def_filename;
	Input_now->line_number = original_macro->def_line_number;
	section_now->type = "original";
	section_now->title = "definition";
	// show serious error with location of original definition
	Throw_serious_error(exception_macro_twice);
}

// This function is only called during the first pass, so there's no need to
// check whether to skip the definition or not.
// Return with GotByte = '}'
void Macro_parse_definition(void)	// Now GotByte = illegal char after "!macro"
{
	char		*formal_parameters;
	struct rwnode	*macro_node;
	struct macro	*new_macro;
	scope_t		macro_scope	= get_scope_and_title();

	// now GotByte = first non-space after title
	DYNABUF_CLEAR(GlobalDynaBuf);	// prepare to hold formal parameters
	// GlobalDynaBuf = "" (will hold formal parameter list)
	// user_macro_name = [LOCAL_PREFIX] MacroTitle NUL
	// internal_name = MacroTitle ARG_SEPARATOR (grows to signature)
	// Accept n>=0 comma-separated formal parameters before CHAR_SOB ('{').
	// Valid argument formats are:
	// .LOCAL_LABEL_BY_VALUE
	// ~.LOCAL_LABEL_BY_REFERENCE
	// @CHEAP_LOCAL_LABEL_BY_VALUE
	// ~@CHEAP_LOCAL_LABEL_BY_REFERENCE
	// GLOBAL_LABEL_BY_VALUE	global args are very uncommon,
	// ~GLOBAL_LABEL_BY_REFERENCE	but not forbidden
	// now GotByte = non-space
	if (GotByte != CHAR_SOB) {	// any at all?
		do {
			// handle call-by-reference character ('~')
			if (GotByte != REFERENCE_CHAR) {
				DynaBuf_append(internal_name, ARGTYPE_VALUE);
			} else {
				DynaBuf_append(internal_name, ARGTYPE_REF);
				DynaBuf_append(GlobalDynaBuf, REFERENCE_CHAR);
				GetByte();
			}
			// handle prefix for (cheap) local symbols ('.'/'@')
			if ((GotByte == LOCAL_PREFIX)
			|| (GotByte == CHEAP_PREFIX)) {
				DynaBuf_append(GlobalDynaBuf, GotByte);
				GetByte();
			}
			// handle symbol name
			Input_append_keyword_to_global_dynabuf();
		} while (pipe_comma());
		// ensure CHAR_SOB ('{')
		if (GotByte != CHAR_SOB)
			Throw_serious_error(exception_no_left_brace);
	}
	DynaBuf_append(GlobalDynaBuf, CHAR_EOS);	// terminate param list
	// now GlobalDynaBuf = comma-separated parameter list without spaces,
	// but terminated with CHAR_EOS.
	formal_parameters = DynaBuf_get_copy(GlobalDynaBuf);
	// now GlobalDynaBuf = unused
	// Reading the macro body would change the line number. To have correct
	// error messages, we're checking for "macro twice" *now*.
	// Search for macro. Create if not found.
	// But if found, complain (macro twice).
	if (search_for_macro(&macro_node, macro_scope, TRUE) == FALSE)
		report_redefinition(macro_node);	// quits with serious error
	// Create new macro struct and set it up. Finally we'll read the body.
	new_macro = safe_malloc(sizeof(*new_macro));
	new_macro->def_line_number = Input_now->line_number;
	new_macro->def_filename = get_string_copy(Input_now->original_filename);
	new_macro->original_name = get_string_copy(user_macro_name->buffer);
	new_macro->parameter_list = formal_parameters;
	new_macro->body = Input_skip_or_store_block(TRUE);	// changes LineNumber
	macro_node->body = new_macro;	// link macro struct to tree node
	// and that about sums it up
}

// Parse macro call ("+MACROTITLE"). Has to be re-entrant.
void Macro_parse_call(void)	// Now GotByte = dot or first char of macro name
{
	char		local_gotbyte;
	struct symbol	*symbol;
	struct section	new_section,
			*outer_section;
	struct input	new_input,
			*outer_input;
	struct macro	*actual_macro;
	struct rwnode	*macro_node,
			*symbol_node;
	scope_t		macro_scope,
			symbol_scope;
	int		arg_count	= 0;
	int		outer_err_count;

	// make sure arg_table is ready (if not yet initialised, do it now)
	if (arg_table == NULL)
		enlarge_arg_table();

	// Enter deeper nesting level
	// Quit program if recursion too deep.
	if (--macro_recursions_left < 0)
		Throw_serious_error("Too deeply nested. Recursive macro calls?");
	macro_scope = get_scope_and_title();
	// now GotByte = first non-space after title
	// internal_name = MacroTitle ARG_SEPARATOR (grows to signature)
	// Accept n>=0 comma-separated arguments before CHAR_EOS.
	// Valid argument formats are:
	//	~SYMBOL		call by ref
	//	EXPRESSION	call by value (everything that does NOT start with '~')
	// now GotByte = non-space
	if (GotByte != CHAR_EOS) {	// any at all?
		do {
			// if arg table cannot take another element, enlarge
			if (argtable_size <= arg_count)
				enlarge_arg_table();
			// Decide whether call-by-reference or call-by-value
			// In both cases, GlobalDynaBuf may be used.
			if (GotByte == REFERENCE_CHAR) {
				// read call-by-reference arg
				DynaBuf_append(internal_name, ARGTYPE_REF);
				GetByte();	// eat '~'
				Input_read_scope_and_keyword(&symbol_scope);
				// GotByte = illegal char
				arg_table[arg_count].symbol = symbol_find(symbol_scope);	// CAUTION, object type may be NULL!
			} else {
				// read call-by-value arg
				DynaBuf_append(internal_name, ARGTYPE_VALUE);
				ALU_any_result(&(arg_table[arg_count].result));
			}
			++arg_count;
		} while (Input_accept_comma());
	}
	// now arg_table contains the arguments
	// now GlobalDynaBuf = unused
	// check for "unknown macro"
	// Search for macro. Do not create if not found.
	search_for_macro(&macro_node, macro_scope, FALSE);
	if (macro_node == NULL) {
		Throw_error("Macro not defined (or wrong signature).");
		Input_skip_remainder();
	} else {
		// make macro_node point to the macro struct
		actual_macro = macro_node->body;
		local_gotbyte = GotByte;	// CAUTION - ugly kluge

		// set up new input
		new_input.original_filename = actual_macro->def_filename;
		new_input.line_number = actual_macro->def_line_number;
		new_input.source = INPUTSRC_RAM;
		new_input.state = INPUTSTATE_NORMAL;	// FIXME - fix others!
		new_input.src.ram_ptr = actual_macro->parameter_list;
		// remember old input
		outer_input = Input_now;
		// activate new input
		Input_now = &new_input;

		outer_err_count = Throw_get_counter();	// remember error count (for call stack decision)

		// remember old section
		outer_section = section_now;
		// start new section (with new scope)
		// FALSE = title mustn't be freed
		section_new(&new_section, "Macro", actual_macro->original_name, FALSE);
		section_new_cheap_scope(&new_section);
		GetByte();	// fetch first byte of parameter list
		// assign arguments
		if (GotByte != CHAR_EOS) {	// any at all?
			arg_count = 0;
			do {
				// Decide whether call-by-reference
				// or call-by-value
				// In both cases, GlobalDynaBuf may be used.
				if (GotByte == REFERENCE_CHAR) {
					// assign call-by-reference arg
					GetByte();	// eat '~'
					Input_read_scope_and_keyword(&symbol_scope);
					// create new tree node and link existing symbol struct from arg list to it
					if ((Tree_hard_scan(&symbol_node, symbols_forest, symbol_scope, TRUE) == FALSE)
					&& (FIRST_PASS))
						Throw_error("Macro parameter twice.");
					symbol_node->body = arg_table[arg_count].symbol;	// CAUTION, object type may be NULL
				} else {
					// assign call-by-value arg
					Input_read_scope_and_keyword(&symbol_scope);
					symbol = symbol_find(symbol_scope);
// FIXME - find out if symbol was just created.
// Then check for the same error message here as above ("Macro parameter twice.").
// TODO - on the other hand, this would rule out globals as args (stupid anyway, but not illegal yet!)
					symbol->object = arg_table[arg_count].result;	// FIXME - this assignment redefines globals/whatever without throwing errors!
				}
				++arg_count;
			} while (Input_accept_comma());
		}
		// and now, finally, parse the actual macro body
		Input_now->state = INPUTSTATE_NORMAL;	// FIXME - fix others!
// maybe call parse_ram_block(actual_macro->def_line_number, actual_macro->body)
		Input_now->src.ram_ptr = actual_macro->body;
		Parse_until_eob_or_eof();
		if (GotByte != CHAR_EOB)
			Bug_found("IllegalBlockTerminator", GotByte);
		// end section (free title memory, if needed)
		section_finalize(&new_section);
		// restore previous section
		section_now = outer_section;
		// restore previous input:
		Input_now = outer_input;
		// restore old Gotbyte context
		GotByte = local_gotbyte;	// CAUTION - ugly kluge

		// if needed, output call stack
		if (Throw_get_counter() != outer_err_count)
			Throw_warning("...called from here.");

		Input_ensure_EOS();
	}
	++macro_recursions_left;	// leave this nesting level
}
