| /* macro.c - macro support for gas | |
| Copyright (C) 1994-2022 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 htab *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 = htab_create_alloc (16, hash_macro_entry, eq_macro_entry, | |
| NULL, xcalloc, free); | |
| 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; | |
| bool 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++; | |
| size_t len = ptr->len - i; | |
| if (from == NULL) | |
| { | |
| if (len >= 5 && strncasecmp (ptr->ptr + i, "IREPC", 5) == 0) | |
| from_len = 5; | |
| else if (len >= 4 && strncasecmp (ptr->ptr + i, "IREP", 4) == 0) | |
| from_len = 4; | |
| else if (len >= 4 && strncasecmp (ptr->ptr + i, "IRPC", 4) == 0) | |
| from_len = 4; | |
| else if (len >= 4 && strncasecmp (ptr->ptr + i, "REPT", 4) == 0) | |
| from_len = 4; | |
| else if (len >= 3 && strncasecmp (ptr->ptr + i, "IRP", 3) == 0) | |
| from_len = 3; | |
| else if (len >= 3 && strncasecmp (ptr->ptr + i, "REP", 3) == 0) | |
| from_len = 3; | |
| else | |
| from_len = 0; | |
| } | |
| if ((from != NULL | |
| ? (len >= from_len | |
| && strncasecmp (ptr->ptr + i, from, from_len) == 0) | |
| : from_len > 0) | |
| && (len == from_len | |
| || ! (is_part_of_name (ptr->ptr[i + from_len]) | |
| || is_name_ender (ptr->ptr[i + from_len])))) | |
| depth++; | |
| if (len >= to_len | |
| && strncasecmp (ptr->ptr + i, to, to_len) == 0 | |
| && (len == 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; | |
| } | |
| } | |
| /* PR gas/16908 | |
| Apply and discard .linefile directives that appear within | |
| the macro. For long macros, one might want to report the | |
| line number information associated with the lines within | |
| the macro definition, but we would need more infrastructure | |
| to make that happen correctly (e.g. resetting the line | |
| number when expanding the macro), and since for short | |
| macros we clearly prefer reporting the point of expansion | |
| anyway, there's not an obviously better fix here. */ | |
| if (from != NULL && strcasecmp (from, "MACRO") == 0 | |
| && len >= 8 && strncasecmp (ptr->ptr + i, "linefile", 8) == 0) | |
| { | |
| char saved_eol_char = ptr->ptr[ptr->len]; | |
| ptr->ptr[ptr->len] = '\0'; | |
| temp_ilp (ptr->ptr + i + 8); | |
| s_app_line (0); | |
| restore_ilp (); | |
| ptr->ptr[ptr->len] = saved_eol_char; | |
| ptr->len = line_start; | |
| } | |
| } | |
| /* 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 (idx < in->len | |
| && (in->ptr[idx] != '>' || nest)) | |
| { | |
| 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 (idx < in->len && !ISSEP (in->ptr[idx])) | |
| sb_add_char (out, in->ptr[idx++]); | |
| } | |
| else if (in->ptr[idx] == '%' && macro_alternate) | |
| { | |
| offsetT val; | |
| char buf[64]; | |
| /* 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 = XNEWVEC (char, 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 = XNEWVEC (char, 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 = XNEW (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; | |
| formal_hash_entry_t *elt; | |
| 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. */ | |
| elt = formal_entry_alloc (name, formal); | |
| if (htab_insert (macro->formal_hash, elt, 0) != NULL) | |
| { | |
| free (elt); | |
| 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 (); | |
| formal_hash_entry_t *elt; | |
| /* 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. */ | |
| elt = formal_entry_alloc (name, formal); | |
| if (htab_insert (macro->formal_hash, elt, 0) != NULL) | |
| { | |
| free (elt); | |
| as_bad_where (macro->file, macro->line, | |
| _("Reserved word `%s' used as parameter in macro `%s'"), | |
| name, macro->name); | |
| } | |
| 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); | |
| } | |
| htab_delete (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 *), | |
| const char *file, unsigned int line, | |
| const char **namep) | |
| { | |
| macro_entry *macro; | |
| sb name; | |
| const char *error = NULL; | |
| macro = XNEW (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 = htab_create_alloc (7, hash_formal_entry, eq_formal_entry, | |
| NULL, xcalloc, free); | |
| 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 (!error) | |
| { | |
| macro_hash_entry_t *elt = macro_entry_alloc (macro->name, macro); | |
| if (htab_insert (macro_hash, elt, 0) != NULL) | |
| { | |
| free (elt); | |
| error = _("Macro `%s' was already defined"); | |
| } | |
| } | |
| 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 htab *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_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 htab *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 substitution 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[12]; | |
| 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 (); | |
| formal_hash_entry_t *elt; | |
| src = get_token (src, in, &f->name); | |
| name = sb_terminate (&f->name); | |
| elt = formal_entry_alloc (name, f); | |
| if (htab_insert (formal_hash, elt, 0) != NULL) | |
| { | |
| free (elt); | |
| as_bad_where (macro->file, macro->line + macro_line, | |
| _("`%s' was already used as parameter " | |
| "(or another local) name"), name); | |
| del_formal (f); | |
| } | |
| else | |
| { | |
| 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); | |
| } | |
| 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_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); | |
| formal_hash_entry_t needle = { name, NULL }; | |
| htab_remove_elt (formal_hash, &needle); | |
| 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_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_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 = xmemdup0 (line, s - line); | |
| for (cls = copy; *cls != '\0'; cls ++) | |
| *cls = TOLOWER (*cls); | |
| macro = macro_entry_find (macro_hash, copy); | |
| free (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; | |
| void **slot; | |
| macro_hash_entry_t needle; | |
| len = strlen (name); | |
| copy = XNEWVEC (char, len + 1); | |
| for (i = 0; i < len; ++i) | |
| copy[i] = TOLOWER (name[i]); | |
| copy[i] = '\0'; | |
| needle.name = copy; | |
| needle.macro = NULL; | |
| slot = htab_find_slot (macro_hash, &needle, NO_INSERT); | |
| if (slot) | |
| { | |
| free_macro (((macro_hash_entry_t *) *slot)->macro); | |
| htab_clear_slot (macro_hash, slot); | |
| } | |
| else | |
| as_warn (_("Attempt to purge non-existing macro `%s'"), copy); | |
| free (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 htab *h; | |
| const char *err = NULL; | |
| 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 = htab_create_alloc (16, hash_formal_entry, eq_formal_entry, | |
| NULL, xcalloc, free); | |
| htab_insert (h, formal_entry_alloc (sb_terminate (&f.name), &f), 0); | |
| 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 | |
| { | |
| bool 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); | |
| } | |
| } | |
| htab_delete (h); | |
| sb_kill (&f.actual); | |
| sb_kill (&f.def); | |
| sb_kill (&f.name); | |
| sb_kill (&sub); | |
| return err; | |
| } |