0,0 → 1,1382 |
/* macro.c - macro support for gas |
Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, |
2004, 2005, 2006, 2007, 2008, 2011, 2012, 2013 Free Software Foundation, Inc. |
|
Written by Steve and Judy Chamberlain of Cygnus Support, |
sac@cygnus.com |
|
This file is part of GAS, the GNU Assembler. |
|
GAS is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 3, or (at your option) |
any later version. |
|
GAS is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
|
You should have received a copy of the GNU General Public License |
along with GAS; see the file COPYING. If not, write to the Free |
Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA |
02110-1301, USA. */ |
|
#include "as.h" |
#include "safe-ctype.h" |
#include "sb.h" |
#include "macro.h" |
|
/* The routines in this file handle macro definition and expansion. |
They are called by gas. */ |
|
#define ISWHITE(x) ((x) == ' ' || (x) == '\t') |
|
#define ISSEP(x) \ |
((x) == ' ' || (x) == '\t' || (x) == ',' || (x) == '"' || (x) == ';' \ |
|| (x) == ')' || (x) == '(' \ |
|| ((macro_alternate || macro_mri) && ((x) == '<' || (x) == '>'))) |
|
#define ISBASE(x) \ |
((x) == 'b' || (x) == 'B' \ |
|| (x) == 'q' || (x) == 'Q' \ |
|| (x) == 'h' || (x) == 'H' \ |
|| (x) == 'd' || (x) == 'D') |
|
/* The macro hash table. */ |
|
struct hash_control *macro_hash; |
|
/* Whether any macros have been defined. */ |
|
int macro_defined; |
|
/* Whether we are in alternate syntax mode. */ |
|
static int macro_alternate; |
|
/* Whether we are in MRI mode. */ |
|
static int macro_mri; |
|
/* Whether we should strip '@' characters. */ |
|
static int macro_strip_at; |
|
/* Function to use to parse an expression. */ |
|
static size_t (*macro_expr) (const char *, size_t, sb *, offsetT *); |
|
/* Number of macro expansions that have been done. */ |
|
static int macro_number; |
|
/* Initialize macro processing. */ |
|
void |
macro_init (int alternate, int mri, int strip_at, |
size_t (*exp) (const char *, size_t, sb *, offsetT *)) |
{ |
macro_hash = hash_new (); |
macro_defined = 0; |
macro_alternate = alternate; |
macro_mri = mri; |
macro_strip_at = strip_at; |
macro_expr = exp; |
} |
|
/* Switch in and out of alternate mode on the fly. */ |
|
void |
macro_set_alternate (int alternate) |
{ |
macro_alternate = alternate; |
} |
|
/* Switch in and out of MRI mode on the fly. */ |
|
void |
macro_mri_mode (int mri) |
{ |
macro_mri = mri; |
} |
|
/* Read input lines till we get to a TO string. |
Increase nesting depth if we get a FROM string. |
Put the results into sb at PTR. |
FROM may be NULL (or will be ignored) if TO is "ENDR". |
Add a new input line to an sb using GET_LINE. |
Return 1 on success, 0 on unexpected EOF. */ |
|
int |
buffer_and_nest (const char *from, const char *to, sb *ptr, |
size_t (*get_line) (sb *)) |
{ |
size_t from_len; |
size_t to_len = strlen (to); |
int depth = 1; |
size_t line_start = ptr->len; |
size_t more = get_line (ptr); |
|
if (to_len == 4 && strcasecmp (to, "ENDR") == 0) |
{ |
from = NULL; |
from_len = 0; |
} |
else |
from_len = strlen (from); |
|
while (more) |
{ |
/* Try to find the first pseudo op on the line. */ |
size_t i = line_start; |
bfd_boolean had_colon = FALSE; |
|
/* With normal syntax we can suck what we want till we get |
to the dot. With the alternate, labels have to start in |
the first column, since we can't tell what's a label and |
what's a pseudoop. */ |
|
if (! LABELS_WITHOUT_COLONS) |
{ |
/* Skip leading whitespace. */ |
while (i < ptr->len && ISWHITE (ptr->ptr[i])) |
i++; |
} |
|
for (;;) |
{ |
/* Skip over a label, if any. */ |
if (i >= ptr->len || ! is_name_beginner (ptr->ptr[i])) |
break; |
i++; |
while (i < ptr->len && is_part_of_name (ptr->ptr[i])) |
i++; |
if (i < ptr->len && is_name_ender (ptr->ptr[i])) |
i++; |
/* Skip whitespace. */ |
while (i < ptr->len && ISWHITE (ptr->ptr[i])) |
i++; |
/* Check for the colon. */ |
if (i >= ptr->len || ptr->ptr[i] != ':') |
{ |
/* LABELS_WITHOUT_COLONS doesn't mean we cannot have a |
colon after a label. If we do have a colon on the |
first label then handle more than one label on the |
line, assuming that each label has a colon. */ |
if (LABELS_WITHOUT_COLONS && !had_colon) |
break; |
i = line_start; |
break; |
} |
i++; |
line_start = i; |
had_colon = TRUE; |
} |
|
/* Skip trailing whitespace. */ |
while (i < ptr->len && ISWHITE (ptr->ptr[i])) |
i++; |
|
if (i < ptr->len && (ptr->ptr[i] == '.' |
|| NO_PSEUDO_DOT |
|| macro_mri)) |
{ |
if (! flag_m68k_mri && ptr->ptr[i] == '.') |
i++; |
if (from == NULL |
&& strncasecmp (ptr->ptr + i, "IRPC", from_len = 4) != 0 |
&& strncasecmp (ptr->ptr + i, "IRP", from_len = 3) != 0 |
&& strncasecmp (ptr->ptr + i, "IREPC", from_len = 5) != 0 |
&& strncasecmp (ptr->ptr + i, "IREP", from_len = 4) != 0 |
&& strncasecmp (ptr->ptr + i, "REPT", from_len = 4) != 0 |
&& strncasecmp (ptr->ptr + i, "REP", from_len = 3) != 0) |
from_len = 0; |
if ((from != NULL |
? strncasecmp (ptr->ptr + i, from, from_len) == 0 |
: from_len > 0) |
&& (ptr->len == (i + from_len) |
|| ! (is_part_of_name (ptr->ptr[i + from_len]) |
|| is_name_ender (ptr->ptr[i + from_len])))) |
depth++; |
if (strncasecmp (ptr->ptr + i, to, to_len) == 0 |
&& (ptr->len == (i + to_len) |
|| ! (is_part_of_name (ptr->ptr[i + to_len]) |
|| is_name_ender (ptr->ptr[i + to_len])))) |
{ |
depth--; |
if (depth == 0) |
{ |
/* Reset the string to not include the ending rune. */ |
ptr->len = line_start; |
break; |
} |
} |
} |
|
/* Add the original end-of-line char to the end and keep running. */ |
sb_add_char (ptr, more); |
line_start = ptr->len; |
more = get_line (ptr); |
} |
|
/* Return 1 on success, 0 on unexpected EOF. */ |
return depth == 0; |
} |
|
/* Pick up a token. */ |
|
static size_t |
get_token (size_t idx, sb *in, sb *name) |
{ |
if (idx < in->len |
&& is_name_beginner (in->ptr[idx])) |
{ |
sb_add_char (name, in->ptr[idx++]); |
while (idx < in->len |
&& is_part_of_name (in->ptr[idx])) |
{ |
sb_add_char (name, in->ptr[idx++]); |
} |
if (idx < in->len |
&& is_name_ender (in->ptr[idx])) |
{ |
sb_add_char (name, in->ptr[idx++]); |
} |
} |
/* Ignore trailing &. */ |
if (macro_alternate && idx < in->len && in->ptr[idx] == '&') |
idx++; |
return idx; |
} |
|
/* Pick up a string. */ |
|
static size_t |
getstring (size_t idx, sb *in, sb *acc) |
{ |
while (idx < in->len |
&& (in->ptr[idx] == '"' |
|| (in->ptr[idx] == '<' && (macro_alternate || macro_mri)) |
|| (in->ptr[idx] == '\'' && macro_alternate))) |
{ |
if (in->ptr[idx] == '<') |
{ |
int nest = 0; |
idx++; |
while ((in->ptr[idx] != '>' || nest) |
&& idx < in->len) |
{ |
if (in->ptr[idx] == '!') |
{ |
idx++; |
sb_add_char (acc, in->ptr[idx++]); |
} |
else |
{ |
if (in->ptr[idx] == '>') |
nest--; |
if (in->ptr[idx] == '<') |
nest++; |
sb_add_char (acc, in->ptr[idx++]); |
} |
} |
idx++; |
} |
else if (in->ptr[idx] == '"' || in->ptr[idx] == '\'') |
{ |
char tchar = in->ptr[idx]; |
int escaped = 0; |
|
idx++; |
|
while (idx < in->len) |
{ |
if (in->ptr[idx - 1] == '\\') |
escaped ^= 1; |
else |
escaped = 0; |
|
if (macro_alternate && in->ptr[idx] == '!') |
{ |
idx ++; |
|
sb_add_char (acc, in->ptr[idx]); |
|
idx ++; |
} |
else if (escaped && in->ptr[idx] == tchar) |
{ |
sb_add_char (acc, tchar); |
idx ++; |
} |
else |
{ |
if (in->ptr[idx] == tchar) |
{ |
idx ++; |
|
if (idx >= in->len || in->ptr[idx] != tchar) |
break; |
} |
|
sb_add_char (acc, in->ptr[idx]); |
idx ++; |
} |
} |
} |
} |
|
return idx; |
} |
|
/* Fetch string from the input stream, |
rules: |
'Bxyx<whitespace> -> return 'Bxyza |
%<expr> -> return string of decimal value of <expr> |
"string" -> return string |
(string) -> return (string-including-whitespaces) |
xyx<whitespace> -> return xyz. */ |
|
static size_t |
get_any_string (size_t idx, sb *in, sb *out) |
{ |
sb_reset (out); |
idx = sb_skip_white (idx, in); |
|
if (idx < in->len) |
{ |
if (in->len > idx + 2 && in->ptr[idx + 1] == '\'' && ISBASE (in->ptr[idx])) |
{ |
while (!ISSEP (in->ptr[idx])) |
sb_add_char (out, in->ptr[idx++]); |
} |
else if (in->ptr[idx] == '%' && macro_alternate) |
{ |
offsetT val; |
char buf[20]; |
|
/* Turns the next expression into a string. */ |
/* xgettext: no-c-format */ |
idx = (*macro_expr) (_("% operator needs absolute expression"), |
idx + 1, |
in, |
&val); |
sprintf (buf, "%" BFD_VMA_FMT "d", val); |
sb_add_string (out, buf); |
} |
else if (in->ptr[idx] == '"' |
|| (in->ptr[idx] == '<' && (macro_alternate || macro_mri)) |
|| (macro_alternate && in->ptr[idx] == '\'')) |
{ |
if (macro_alternate && ! macro_strip_at && in->ptr[idx] != '<') |
{ |
/* Keep the quotes. */ |
sb_add_char (out, '"'); |
idx = getstring (idx, in, out); |
sb_add_char (out, '"'); |
} |
else |
{ |
idx = getstring (idx, in, out); |
} |
} |
else |
{ |
char *br_buf = (char *) xmalloc (1); |
char *in_br = br_buf; |
|
*in_br = '\0'; |
while (idx < in->len |
&& (*in_br |
|| (in->ptr[idx] != ' ' |
&& in->ptr[idx] != '\t')) |
&& in->ptr[idx] != ',' |
&& (in->ptr[idx] != '<' |
|| (! macro_alternate && ! macro_mri))) |
{ |
char tchar = in->ptr[idx]; |
|
switch (tchar) |
{ |
case '"': |
case '\'': |
sb_add_char (out, in->ptr[idx++]); |
while (idx < in->len |
&& in->ptr[idx] != tchar) |
sb_add_char (out, in->ptr[idx++]); |
if (idx == in->len) |
{ |
free (br_buf); |
return idx; |
} |
break; |
case '(': |
case '[': |
if (in_br > br_buf) |
--in_br; |
else |
{ |
br_buf = (char *) xmalloc (strlen (in_br) + 2); |
strcpy (br_buf + 1, in_br); |
free (in_br); |
in_br = br_buf; |
} |
*in_br = tchar; |
break; |
case ')': |
if (*in_br == '(') |
++in_br; |
break; |
case ']': |
if (*in_br == '[') |
++in_br; |
break; |
} |
sb_add_char (out, tchar); |
++idx; |
} |
free (br_buf); |
} |
} |
|
return idx; |
} |
|
/* Allocate a new formal. */ |
|
static formal_entry * |
new_formal (void) |
{ |
formal_entry *formal; |
|
formal = (formal_entry *) xmalloc (sizeof (formal_entry)); |
|
sb_new (&formal->name); |
sb_new (&formal->def); |
sb_new (&formal->actual); |
formal->next = NULL; |
formal->type = FORMAL_OPTIONAL; |
return formal; |
} |
|
/* Free a formal. */ |
|
static void |
del_formal (formal_entry *formal) |
{ |
sb_kill (&formal->actual); |
sb_kill (&formal->def); |
sb_kill (&formal->name); |
free (formal); |
} |
|
/* Pick up the formal parameters of a macro definition. */ |
|
static size_t |
do_formals (macro_entry *macro, size_t idx, sb *in) |
{ |
formal_entry **p = ¯o->formals; |
const char *name; |
|
idx = sb_skip_white (idx, in); |
while (idx < in->len) |
{ |
formal_entry *formal = new_formal (); |
size_t cidx; |
|
idx = get_token (idx, in, &formal->name); |
if (formal->name.len == 0) |
{ |
if (macro->formal_count) |
--idx; |
del_formal (formal); /* 'formal' goes out of scope. */ |
break; |
} |
idx = sb_skip_white (idx, in); |
/* This is a formal. */ |
name = sb_terminate (&formal->name); |
if (! macro_mri |
&& idx < in->len |
&& in->ptr[idx] == ':' |
&& (! is_name_beginner (':') |
|| idx + 1 >= in->len |
|| ! is_part_of_name (in->ptr[idx + 1]))) |
{ |
/* Got a qualifier. */ |
sb qual; |
|
sb_new (&qual); |
idx = get_token (sb_skip_white (idx + 1, in), in, &qual); |
sb_terminate (&qual); |
if (qual.len == 0) |
as_bad_where (macro->file, |
macro->line, |
_("Missing parameter qualifier for `%s' in macro `%s'"), |
name, |
macro->name); |
else if (strcmp (qual.ptr, "req") == 0) |
formal->type = FORMAL_REQUIRED; |
else if (strcmp (qual.ptr, "vararg") == 0) |
formal->type = FORMAL_VARARG; |
else |
as_bad_where (macro->file, |
macro->line, |
_("`%s' is not a valid parameter qualifier for `%s' in macro `%s'"), |
qual.ptr, |
name, |
macro->name); |
sb_kill (&qual); |
idx = sb_skip_white (idx, in); |
} |
if (idx < in->len && in->ptr[idx] == '=') |
{ |
/* Got a default. */ |
idx = get_any_string (idx + 1, in, &formal->def); |
idx = sb_skip_white (idx, in); |
if (formal->type == FORMAL_REQUIRED) |
{ |
sb_reset (&formal->def); |
as_warn_where (macro->file, |
macro->line, |
_("Pointless default value for required parameter `%s' in macro `%s'"), |
name, |
macro->name); |
} |
} |
|
/* Add to macro's hash table. */ |
if (! hash_find (macro->formal_hash, name)) |
hash_jam (macro->formal_hash, name, formal); |
else |
as_bad_where (macro->file, |
macro->line, |
_("A parameter named `%s' already exists for macro `%s'"), |
name, |
macro->name); |
|
formal->index = macro->formal_count++; |
*p = formal; |
p = &formal->next; |
if (formal->type == FORMAL_VARARG) |
break; |
cidx = idx; |
idx = sb_skip_comma (idx, in); |
if (idx != cidx && idx >= in->len) |
{ |
idx = cidx; |
break; |
} |
} |
|
if (macro_mri) |
{ |
formal_entry *formal = new_formal (); |
|
/* Add a special NARG formal, which macro_expand will set to the |
number of arguments. */ |
/* The same MRI assemblers which treat '@' characters also use |
the name $NARG. At least until we find an exception. */ |
if (macro_strip_at) |
name = "$NARG"; |
else |
name = "NARG"; |
|
sb_add_string (&formal->name, name); |
|
/* Add to macro's hash table. */ |
if (hash_find (macro->formal_hash, name)) |
as_bad_where (macro->file, |
macro->line, |
_("Reserved word `%s' used as parameter in macro `%s'"), |
name, |
macro->name); |
hash_jam (macro->formal_hash, name, formal); |
|
formal->index = NARG_INDEX; |
*p = formal; |
} |
|
return idx; |
} |
|
/* Free the memory allocated to a macro. */ |
|
static void |
free_macro (macro_entry *macro) |
{ |
formal_entry *formal; |
|
for (formal = macro->formals; formal; ) |
{ |
formal_entry *f; |
|
f = formal; |
formal = formal->next; |
del_formal (f); |
} |
hash_die (macro->formal_hash); |
sb_kill (¯o->sub); |
free (macro); |
} |
|
/* Define a new macro. Returns NULL on success, otherwise returns an |
error message. If NAMEP is not NULL, *NAMEP is set to the name of |
the macro which was defined. */ |
|
const char * |
define_macro (size_t idx, sb *in, sb *label, |
size_t (*get_line) (sb *), |
char *file, unsigned int line, |
const char **namep) |
{ |
macro_entry *macro; |
sb name; |
const char *error = NULL; |
|
macro = (macro_entry *) xmalloc (sizeof (macro_entry)); |
sb_new (¯o->sub); |
sb_new (&name); |
macro->file = file; |
macro->line = line; |
|
macro->formal_count = 0; |
macro->formals = 0; |
macro->formal_hash = hash_new_sized (7); |
|
idx = sb_skip_white (idx, in); |
if (! buffer_and_nest ("MACRO", "ENDM", ¯o->sub, get_line)) |
error = _("unexpected end of file in macro `%s' definition"); |
if (label != NULL && label->len != 0) |
{ |
sb_add_sb (&name, label); |
macro->name = sb_terminate (&name); |
if (idx < in->len && in->ptr[idx] == '(') |
{ |
/* It's the label: MACRO (formals,...) sort */ |
idx = do_formals (macro, idx + 1, in); |
if (idx < in->len && in->ptr[idx] == ')') |
idx = sb_skip_white (idx + 1, in); |
else if (!error) |
error = _("missing `)' after formals in macro definition `%s'"); |
} |
else |
{ |
/* It's the label: MACRO formals,... sort */ |
idx = do_formals (macro, idx, in); |
} |
} |
else |
{ |
size_t cidx; |
|
idx = get_token (idx, in, &name); |
macro->name = sb_terminate (&name); |
if (name.len == 0) |
error = _("Missing macro name"); |
cidx = sb_skip_white (idx, in); |
idx = sb_skip_comma (cidx, in); |
if (idx == cidx || idx < in->len) |
idx = do_formals (macro, idx, in); |
else |
idx = cidx; |
} |
if (!error && idx < in->len) |
error = _("Bad parameter list for macro `%s'"); |
|
/* And stick it in the macro hash table. */ |
for (idx = 0; idx < name.len; idx++) |
name.ptr[idx] = TOLOWER (name.ptr[idx]); |
if (hash_find (macro_hash, macro->name)) |
error = _("Macro `%s' was already defined"); |
if (!error) |
error = hash_jam (macro_hash, macro->name, (void *) macro); |
|
if (namep != NULL) |
*namep = macro->name; |
|
if (!error) |
macro_defined = 1; |
else |
free_macro (macro); |
|
return error; |
} |
|
/* Scan a token, and then skip KIND. */ |
|
static size_t |
get_apost_token (size_t idx, sb *in, sb *name, int kind) |
{ |
idx = get_token (idx, in, name); |
if (idx < in->len |
&& in->ptr[idx] == kind |
&& (! macro_mri || macro_strip_at) |
&& (! macro_strip_at || kind == '@')) |
idx++; |
return idx; |
} |
|
/* Substitute the actual value for a formal parameter. */ |
|
static size_t |
sub_actual (size_t start, sb *in, sb *t, struct hash_control *formal_hash, |
int kind, sb *out, int copyifnotthere) |
{ |
size_t src; |
formal_entry *ptr; |
|
src = get_apost_token (start, in, t, kind); |
/* See if it's in the macro's hash table, unless this is |
macro_strip_at and kind is '@' and the token did not end in '@'. */ |
if (macro_strip_at |
&& kind == '@' |
&& (src == start || in->ptr[src - 1] != '@')) |
ptr = NULL; |
else |
ptr = (formal_entry *) hash_find (formal_hash, sb_terminate (t)); |
if (ptr) |
{ |
if (ptr->actual.len) |
{ |
sb_add_sb (out, &ptr->actual); |
} |
else |
{ |
sb_add_sb (out, &ptr->def); |
} |
} |
else if (kind == '&') |
{ |
/* Doing this permits people to use & in macro bodies. */ |
sb_add_char (out, '&'); |
sb_add_sb (out, t); |
if (src != start && in->ptr[src - 1] == '&') |
sb_add_char (out, '&'); |
} |
else if (copyifnotthere) |
{ |
sb_add_sb (out, t); |
} |
else |
{ |
sb_add_char (out, '\\'); |
sb_add_sb (out, t); |
} |
return src; |
} |
|
/* Expand the body of a macro. */ |
|
static const char * |
macro_expand_body (sb *in, sb *out, formal_entry *formals, |
struct hash_control *formal_hash, const macro_entry *macro) |
{ |
sb t; |
size_t src = 0; |
int inquote = 0, macro_line = 0; |
formal_entry *loclist = NULL; |
const char *err = NULL; |
|
sb_new (&t); |
|
while (src < in->len && !err) |
{ |
if (in->ptr[src] == '&') |
{ |
sb_reset (&t); |
if (macro_mri) |
{ |
if (src + 1 < in->len && in->ptr[src + 1] == '&') |
src = sub_actual (src + 2, in, &t, formal_hash, '\'', out, 1); |
else |
sb_add_char (out, in->ptr[src++]); |
} |
else |
{ |
/* Permit macro parameter substition delineated with |
an '&' prefix and optional '&' suffix. */ |
src = sub_actual (src + 1, in, &t, formal_hash, '&', out, 0); |
} |
} |
else if (in->ptr[src] == '\\') |
{ |
src++; |
if (src < in->len && in->ptr[src] == '(') |
{ |
/* Sub in till the next ')' literally. */ |
src++; |
while (src < in->len && in->ptr[src] != ')') |
{ |
sb_add_char (out, in->ptr[src++]); |
} |
if (src < in->len) |
src++; |
else if (!macro) |
err = _("missing `)'"); |
else |
as_bad_where (macro->file, macro->line + macro_line, _("missing `)'")); |
} |
else if (src < in->len && in->ptr[src] == '@') |
{ |
/* Sub in the macro invocation number. */ |
|
char buffer[10]; |
src++; |
sprintf (buffer, "%d", macro_number); |
sb_add_string (out, buffer); |
} |
else if (src < in->len && in->ptr[src] == '&') |
{ |
/* This is a preprocessor variable name, we don't do them |
here. */ |
sb_add_char (out, '\\'); |
sb_add_char (out, '&'); |
src++; |
} |
else if (macro_mri && src < in->len && ISALNUM (in->ptr[src])) |
{ |
int ind; |
formal_entry *f; |
|
if (ISDIGIT (in->ptr[src])) |
ind = in->ptr[src] - '0'; |
else if (ISUPPER (in->ptr[src])) |
ind = in->ptr[src] - 'A' + 10; |
else |
ind = in->ptr[src] - 'a' + 10; |
++src; |
for (f = formals; f != NULL; f = f->next) |
{ |
if (f->index == ind - 1) |
{ |
if (f->actual.len != 0) |
sb_add_sb (out, &f->actual); |
else |
sb_add_sb (out, &f->def); |
break; |
} |
} |
} |
else |
{ |
sb_reset (&t); |
src = sub_actual (src, in, &t, formal_hash, '\'', out, 0); |
} |
} |
else if ((macro_alternate || macro_mri) |
&& is_name_beginner (in->ptr[src]) |
&& (! inquote |
|| ! macro_strip_at |
|| (src > 0 && in->ptr[src - 1] == '@'))) |
{ |
if (! macro |
|| src + 5 >= in->len |
|| strncasecmp (in->ptr + src, "LOCAL", 5) != 0 |
|| ! ISWHITE (in->ptr[src + 5]) |
/* PR 11507: Skip keyword LOCAL if it is found inside a quoted string. */ |
|| inquote) |
{ |
sb_reset (&t); |
src = sub_actual (src, in, &t, formal_hash, |
(macro_strip_at && inquote) ? '@' : '\'', |
out, 1); |
} |
else |
{ |
src = sb_skip_white (src + 5, in); |
while (in->ptr[src] != '\n') |
{ |
const char *name; |
formal_entry *f = new_formal (); |
|
src = get_token (src, in, &f->name); |
name = sb_terminate (&f->name); |
if (! hash_find (formal_hash, name)) |
{ |
static int loccnt; |
char buf[20]; |
|
f->index = LOCAL_INDEX; |
f->next = loclist; |
loclist = f; |
|
sprintf (buf, IS_ELF ? ".LL%04x" : "LL%04x", ++loccnt); |
sb_add_string (&f->actual, buf); |
|
err = hash_jam (formal_hash, name, f); |
if (err != NULL) |
break; |
} |
else |
{ |
as_bad_where (macro->file, |
macro->line + macro_line, |
_("`%s' was already used as parameter (or another local) name"), |
name); |
del_formal (f); |
} |
|
src = sb_skip_comma (src, in); |
} |
} |
} |
else if (in->ptr[src] == '"' |
|| (macro_mri && in->ptr[src] == '\'')) |
{ |
inquote = !inquote; |
sb_add_char (out, in->ptr[src++]); |
} |
else if (in->ptr[src] == '@' && macro_strip_at) |
{ |
++src; |
if (src < in->len |
&& in->ptr[src] == '@') |
{ |
sb_add_char (out, '@'); |
++src; |
} |
} |
else if (macro_mri |
&& in->ptr[src] == '=' |
&& src + 1 < in->len |
&& in->ptr[src + 1] == '=') |
{ |
formal_entry *ptr; |
|
sb_reset (&t); |
src = get_token (src + 2, in, &t); |
ptr = (formal_entry *) hash_find (formal_hash, sb_terminate (&t)); |
if (ptr == NULL) |
{ |
/* FIXME: We should really return a warning string here, |
but we can't, because the == might be in the MRI |
comment field, and, since the nature of the MRI |
comment field depends upon the exact instruction |
being used, we don't have enough information here to |
figure out whether it is or not. Instead, we leave |
the == in place, which should cause a syntax error if |
it is not in a comment. */ |
sb_add_char (out, '='); |
sb_add_char (out, '='); |
sb_add_sb (out, &t); |
} |
else |
{ |
if (ptr->actual.len) |
{ |
sb_add_string (out, "-1"); |
} |
else |
{ |
sb_add_char (out, '0'); |
} |
} |
} |
else |
{ |
if (in->ptr[src] == '\n') |
++macro_line; |
sb_add_char (out, in->ptr[src++]); |
} |
} |
|
sb_kill (&t); |
|
while (loclist != NULL) |
{ |
formal_entry *f; |
const char *name; |
|
f = loclist->next; |
name = sb_terminate (&loclist->name); |
hash_delete (formal_hash, name, f == NULL); |
del_formal (loclist); |
loclist = f; |
} |
|
return err; |
} |
|
/* Assign values to the formal parameters of a macro, and expand the |
body. */ |
|
static const char * |
macro_expand (size_t idx, sb *in, macro_entry *m, sb *out) |
{ |
sb t; |
formal_entry *ptr; |
formal_entry *f; |
int is_keyword = 0; |
int narg = 0; |
const char *err = NULL; |
|
sb_new (&t); |
|
/* Reset any old value the actuals may have. */ |
for (f = m->formals; f; f = f->next) |
sb_reset (&f->actual); |
f = m->formals; |
while (f != NULL && f->index < 0) |
f = f->next; |
|
if (macro_mri) |
{ |
/* The macro may be called with an optional qualifier, which may |
be referred to in the macro body as \0. */ |
if (idx < in->len && in->ptr[idx] == '.') |
{ |
/* The Microtec assembler ignores this if followed by a white space. |
(Macro invocation with empty extension) */ |
idx++; |
if ( idx < in->len |
&& in->ptr[idx] != ' ' |
&& in->ptr[idx] != '\t') |
{ |
formal_entry *n = new_formal (); |
|
n->index = QUAL_INDEX; |
|
n->next = m->formals; |
m->formals = n; |
|
idx = get_any_string (idx, in, &n->actual); |
} |
} |
} |
|
/* Peel off the actuals and store them away in the hash tables' actuals. */ |
idx = sb_skip_white (idx, in); |
while (idx < in->len) |
{ |
size_t scan; |
|
/* Look and see if it's a positional or keyword arg. */ |
scan = idx; |
while (scan < in->len |
&& !ISSEP (in->ptr[scan]) |
&& !(macro_mri && in->ptr[scan] == '\'') |
&& (!macro_alternate && in->ptr[scan] != '=')) |
scan++; |
if (scan < in->len && !macro_alternate && in->ptr[scan] == '=') |
{ |
is_keyword = 1; |
|
/* It's OK to go from positional to keyword. */ |
|
/* This is a keyword arg, fetch the formal name and |
then the actual stuff. */ |
sb_reset (&t); |
idx = get_token (idx, in, &t); |
if (in->ptr[idx] != '=') |
{ |
err = _("confusion in formal parameters"); |
break; |
} |
|
/* Lookup the formal in the macro's list. */ |
ptr = (formal_entry *) hash_find (m->formal_hash, sb_terminate (&t)); |
if (!ptr) |
{ |
as_bad (_("Parameter named `%s' does not exist for macro `%s'"), |
t.ptr, |
m->name); |
sb_reset (&t); |
idx = get_any_string (idx + 1, in, &t); |
} |
else |
{ |
/* Insert this value into the right place. */ |
if (ptr->actual.len) |
{ |
as_warn (_("Value for parameter `%s' of macro `%s' was already specified"), |
ptr->name.ptr, |
m->name); |
sb_reset (&ptr->actual); |
} |
idx = get_any_string (idx + 1, in, &ptr->actual); |
if (ptr->actual.len > 0) |
++narg; |
} |
} |
else |
{ |
if (is_keyword) |
{ |
err = _("can't mix positional and keyword arguments"); |
break; |
} |
|
if (!f) |
{ |
formal_entry **pf; |
int c; |
|
if (!macro_mri) |
{ |
err = _("too many positional arguments"); |
break; |
} |
|
f = new_formal (); |
|
c = -1; |
for (pf = &m->formals; *pf != NULL; pf = &(*pf)->next) |
if ((*pf)->index >= c) |
c = (*pf)->index + 1; |
if (c == -1) |
c = 0; |
*pf = f; |
f->index = c; |
} |
|
if (f->type != FORMAL_VARARG) |
idx = get_any_string (idx, in, &f->actual); |
else |
{ |
sb_add_buffer (&f->actual, in->ptr + idx, in->len - idx); |
idx = in->len; |
} |
if (f->actual.len > 0) |
++narg; |
do |
{ |
f = f->next; |
} |
while (f != NULL && f->index < 0); |
} |
|
if (! macro_mri) |
idx = sb_skip_comma (idx, in); |
else |
{ |
if (in->ptr[idx] == ',') |
++idx; |
if (ISWHITE (in->ptr[idx])) |
break; |
} |
} |
|
if (! err) |
{ |
for (ptr = m->formals; ptr; ptr = ptr->next) |
{ |
if (ptr->type == FORMAL_REQUIRED && ptr->actual.len == 0) |
as_bad (_("Missing value for required parameter `%s' of macro `%s'"), |
ptr->name.ptr, |
m->name); |
} |
|
if (macro_mri) |
{ |
char buffer[20]; |
|
sb_reset (&t); |
sb_add_string (&t, macro_strip_at ? "$NARG" : "NARG"); |
ptr = (formal_entry *) hash_find (m->formal_hash, sb_terminate (&t)); |
sprintf (buffer, "%d", narg); |
sb_add_string (&ptr->actual, buffer); |
} |
|
err = macro_expand_body (&m->sub, out, m->formals, m->formal_hash, m); |
} |
|
/* Discard any unnamed formal arguments. */ |
if (macro_mri) |
{ |
formal_entry **pf; |
|
pf = &m->formals; |
while (*pf != NULL) |
{ |
if ((*pf)->name.len != 0) |
pf = &(*pf)->next; |
else |
{ |
f = (*pf)->next; |
del_formal (*pf); |
*pf = f; |
} |
} |
} |
|
sb_kill (&t); |
if (!err) |
macro_number++; |
|
return err; |
} |
|
/* Check for a macro. If one is found, put the expansion into |
*EXPAND. Return 1 if a macro is found, 0 otherwise. */ |
|
int |
check_macro (const char *line, sb *expand, |
const char **error, macro_entry **info) |
{ |
const char *s; |
char *copy, *cls; |
macro_entry *macro; |
sb line_sb; |
|
if (! is_name_beginner (*line) |
&& (! macro_mri || *line != '.')) |
return 0; |
|
s = line + 1; |
while (is_part_of_name (*s)) |
++s; |
if (is_name_ender (*s)) |
++s; |
|
copy = (char *) alloca (s - line + 1); |
memcpy (copy, line, s - line); |
copy[s - line] = '\0'; |
for (cls = copy; *cls != '\0'; cls ++) |
*cls = TOLOWER (*cls); |
|
macro = (macro_entry *) hash_find (macro_hash, copy); |
|
if (macro == NULL) |
return 0; |
|
/* Wrap the line up in an sb. */ |
sb_new (&line_sb); |
while (*s != '\0' && *s != '\n' && *s != '\r') |
sb_add_char (&line_sb, *s++); |
|
sb_new (expand); |
*error = macro_expand (0, &line_sb, macro, expand); |
|
sb_kill (&line_sb); |
|
/* Export the macro information if requested. */ |
if (info) |
*info = macro; |
|
return 1; |
} |
|
/* Delete a macro. */ |
|
void |
delete_macro (const char *name) |
{ |
char *copy; |
size_t i, len; |
macro_entry *macro; |
|
len = strlen (name); |
copy = (char *) alloca (len + 1); |
for (i = 0; i < len; ++i) |
copy[i] = TOLOWER (name[i]); |
copy[i] = '\0'; |
|
/* We can only ask hash_delete to free memory if we are deleting |
macros in reverse order to their definition. |
So just clear out the entry. */ |
if ((macro = (macro_entry *) hash_find (macro_hash, copy)) != NULL) |
{ |
hash_jam (macro_hash, copy, NULL); |
free_macro (macro); |
} |
else |
as_warn (_("Attempt to purge non-existant macro `%s'"), copy); |
} |
|
/* Handle the MRI IRP and IRPC pseudo-ops. These are handled as a |
combined macro definition and execution. This returns NULL on |
success, or an error message otherwise. */ |
|
const char * |
expand_irp (int irpc, size_t idx, sb *in, sb *out, size_t (*get_line) (sb *)) |
{ |
sb sub; |
formal_entry f; |
struct hash_control *h; |
const char *err; |
|
idx = sb_skip_white (idx, in); |
|
sb_new (&sub); |
if (! buffer_and_nest (NULL, "ENDR", &sub, get_line)) |
return _("unexpected end of file in irp or irpc"); |
|
sb_new (&f.name); |
sb_new (&f.def); |
sb_new (&f.actual); |
|
idx = get_token (idx, in, &f.name); |
if (f.name.len == 0) |
return _("missing model parameter"); |
|
h = hash_new (); |
err = hash_jam (h, sb_terminate (&f.name), &f); |
if (err != NULL) |
return err; |
|
f.index = 1; |
f.next = NULL; |
f.type = FORMAL_OPTIONAL; |
|
sb_reset (out); |
|
idx = sb_skip_comma (idx, in); |
if (idx >= in->len) |
{ |
/* Expand once with a null string. */ |
err = macro_expand_body (&sub, out, &f, h, 0); |
} |
else |
{ |
bfd_boolean in_quotes = FALSE; |
|
if (irpc && in->ptr[idx] == '"') |
{ |
in_quotes = TRUE; |
++idx; |
} |
|
while (idx < in->len) |
{ |
if (!irpc) |
idx = get_any_string (idx, in, &f.actual); |
else |
{ |
if (in->ptr[idx] == '"') |
{ |
size_t nxt; |
|
if (irpc) |
in_quotes = ! in_quotes; |
|
nxt = sb_skip_white (idx + 1, in); |
if (nxt >= in->len) |
{ |
idx = nxt; |
break; |
} |
} |
sb_reset (&f.actual); |
sb_add_char (&f.actual, in->ptr[idx]); |
++idx; |
} |
|
err = macro_expand_body (&sub, out, &f, h, 0); |
if (err != NULL) |
break; |
if (!irpc) |
idx = sb_skip_comma (idx, in); |
else if (! in_quotes) |
idx = sb_skip_white (idx, in); |
} |
} |
|
hash_die (h); |
sb_kill (&f.actual); |
sb_kill (&f.def); |
sb_kill (&f.name); |
sb_kill (&sub); |
|
return err; |
} |