mirror of
https://github.com/uffejakobsen/acme.git
synced 2024-11-26 15:49:18 +00:00
00d0462f74
git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@214 4df02467-bbd4-4a76-a152-e7ce94205b78
363 lines
13 KiB
C
363 lines
13 KiB
C
// 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 MACRONAME_DYNABUF_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 *user_macro_name; // original macro title
|
|
static struct dynabuf *internal_name; // 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;
|
|
arg_table = realloc(arg_table, argtable_size * sizeof(*arg_table));
|
|
if (arg_table == NULL)
|
|
Throw_serious_error(exception_no_memory_left);
|
|
}
|
|
|
|
// create dynamic buffers and arg table
|
|
void Macro_init(void)
|
|
{
|
|
user_macro_name = DynaBuf_create(MACRONAME_DYNABUF_INITIALSIZE);
|
|
internal_name = DynaBuf_create(MACRONAME_DYNABUF_INITIALSIZE);
|
|
enlarge_arg_table();
|
|
}
|
|
|
|
// 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(¯o_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(¯o_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;
|
|
|
|
// 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(¯o_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
|
|
}
|