#ident "@(#)macro.c 1.1 94/10/31 Copyright 1986,1987,1988 Sun Micro" /* * macro.c * * Handle expansion of make macros */ /* * Included files */ #include "defs.h" /* * Defined macros */ /* * typedefs & structs */ /* * Static variables */ static Envvar envvar = NULL; /* * File table of contents */ extern Property setvar_daemon(); extern Name getvar(); extern void setvar_envvar(); extern void expand_value_with_daemon(); extern void expand_value(); extern void expand_macro(); /* * setvar_daemon(name, value, append, daemon) * * Set a macro value, possibly supplying a daemon to be used * when referencing the value. * * Return value: * The property block with the new value * * Parameters: * name Name of the macro to set * value The value to set * append Should we reset or append to the current value? * daemon Special treatment when reading the value * * Static variables used: * envvar A list of environment vars with $ in value * * Global variables used: * debug_level Indicates how much tracing we should do * makefile_type Used to check if we should enforce read only * path_name The Name "PATH", compared against * virtual_root The Name "VIRTUAL_ROOT", compared against * vpath_defined Set if the macro VPATH is set * vpath_name The Name "VPATH", compared against */ Property setvar_daemon(name, value, append, daemon) register Name name; register Name value; Boolean append; Daemon daemon; { register Property macro = maybe_append_prop(name, macro_prop); String_rec destination; char buffer[STRING_BUFFER_LENGTH]; register Chain chain; int length; if ((makefile_type != reading_nothing) && macro->body.macro.read_only) { return macro; } /* Strip spaces from the end of the value */ if (daemon == no_daemon) { length = (value == NULL) || (value->string == NULL) ? 0 : strlen(value->string); if ((length > 0) && isspace(value->string[length-1])) { INIT_STRING_FROM_STACK(destination, buffer); buffer[0] = 0; append_string(value->string, &destination, length); while ((length > 0) && isspace(destination.buffer.start[length-1])) { destination.buffer.start[--length] = 0; } value = GETNAME(destination.buffer.start, FIND_LENGTH); } } if (append) { /* If we are appending we just tack the new value after */ /* the old one with a space in between */ INIT_STRING_FROM_STACK(destination, buffer); buffer[0] = 0; if ((macro != NULL) && (macro->body.macro.value != NULL)) { append_string(macro->body.macro.value->string, &destination, (int) macro->body.macro.value-> hash.length); if (value != NULL) { append_char((int) space_char, &destination); } } if (value != NULL) { append_string(value->string, &destination, (int) value->hash.length); } value = GETNAME(destination.buffer.start, FIND_LENGTH); if (destination.free_after_use) { retmem(destination.buffer.start); } } /* Debugging trace */ if (debug_level > 1) { if (value != NULL) { switch (daemon) { case chain_daemon: (void) printf("%s =", name->string); for (chain = (Chain) value; chain != NULL; chain = chain->next) { (void) printf(" %s", chain->name->string); } (void) printf("\n"); break; case no_daemon: (void) printf("%s= %s\n", name->string, value->string); break; } } else { (void) printf("%s =\n", name->string); } } /* Set the new values in the macro property block */ macro->body.macro.value = value; macro->body.macro.daemon = daemon; /* If the user changes the VIRTUAL_ROOT we need to flush */ /* the vroot package cache */ if (name == path_name) { flush_path_cache(); } if (name == virtual_root) { flush_vroot_cache(); } /* If this sets the VPATH we remember that */ if ((name == vpath_name) && (value != NULL) && (value->hash.length > 0)) { vpath_defined = true; } /* For environment variables we also set the */ /* environment value each time */ if (macro->body.macro.exported) { char *env; if (!reading_environment && (value != NULL) && value->dollar) { Envvar p; for (p = envvar; p != NULL; p = p->next) { if (p->name == name) { p->value = value; goto found_it; } } p = ALLOC(Envvar); p->name = name; p->value = value; p->next = envvar; envvar = p; found_it:; } else { length = 2 + strlen(name->string); if (value != NULL) { length += strlen(value->string); } env = getmem(length); (void) sprintf(env, "%s=%s", name->string, value == NULL ? "" : value->string); (void) putenv(env); } } if (name == target_arch) { Name ha = getvar(host_arch); Name ta = getvar(target_arch); Name vr = getvar(virtual_root); int length; char *new_value; char *old_vr; length = 32 + strlen(ha->string) + strlen(ta->string) + strlen(vr->string); new_value = alloca(length); old_vr = vr->string; if (IS_EQUALN(old_vr, "/usr/arch/", strlen("/usr/arch/"))) { old_vr = strchr(old_vr, (int) colon_char) + 1; } if (ha == ta) { new_value = old_vr; } else { (void) sprintf(new_value, "/usr/arch/%s/%s:%s", ha->string + 1, ta->string + 1, old_vr); } if (new_value[0] != 0) { (void) SETVAR(virtual_root, GETNAME(new_value, FIND_LENGTH), false); } } return macro; } /* * getvar(name) * * Return expanded value of macro. * * Return value: * The expanded value of the macro * * Parameters: * name The name of the macro we want the value for * * Global variables used: */ Name getvar(name) register Name name; { String_rec destination; char buffer[STRING_BUFFER_LENGTH]; register Name result; INIT_STRING_FROM_STACK(destination, buffer); expand_value(maybe_append_prop(name, macro_prop)->body.macro.value, &destination, false); result = GETNAME(destination.buffer.start, FIND_LENGTH); if (destination.free_after_use) { retmem(destination.buffer.start); } return result; } /* * setvar_envvar() * * This function scans the list of environment variables that have * dynamic values and sets them. * * Parameters: * * Static variables used: * envvar A list of environment vars with $ in value */ void setvar_envvar() { Envvar p; int length; char *env; String_rec value; char buffer[STRING_BUFFER_LENGTH]; for (p = envvar; p != NULL; p = p->next) { INIT_STRING_FROM_STACK(value, buffer); expand_value(p->value, &value, false); length = 2 + strlen(p->name->string); length += strlen(value.buffer.start); env = getmem(length); (void) sprintf(env, "%s=%s", p->name->string, value.buffer.start); (void) putenv(env); } } /* * expand_value_with_daemon(macro, destination, cmd) * * Checks for daemons and then maybe calls expand_value(). * * Parameters: * macro The property block with the value to expand * destination Where the result should be deposited * cmd If we are evaluating a command line we * turn \ quoting off * * Global variables used: */ static void expand_value_with_daemon(macro, destination, cmd) register Property macro; register String destination; Boolean cmd; { register Chain chain; switch (macro->body.macro.daemon) { case no_daemon: expand_value(macro->body.macro.value, destination, cmd); return; case chain_daemon: /* If this is a $? value we call the daemon to translate the */ /* list of names to a string */ for (chain = (Chain) macro->body.macro.value; chain != NULL; chain = chain->next) { append_string(chain->name->string, destination, (int) chain->name->hash.length); if (chain->next != NULL) { append_char((int) space_char, destination); } } return; } } /* * expand_value(value, destination, cmd) * * Recursively expands all macros in the string value. * destination is where the expanded value should be appended. * * Parameters: * value The value we are expanding * destination Where to deposit the expansion * cmd If we are evaluating a command line we * turn \ quoting off * * Global variables used: */ void expand_value(value, destination, cmd) Name value; register String destination; Boolean cmd; { Source_rec sourceb; register Source source = &sourceb; register char *source_p; register char *source_end; char *block_start; int quote_seen; if (value == NULL) { /* Make sure to get a string allocated even if it */ /* will be empty */ append_string("", destination, FIND_LENGTH); destination->text.end = destination->text.p; return; } if (!value->dollar) { /* If the value we are expanding does not contain */ /* any $ we dont have to parse it */ append_string(value->string, destination, (int) value->hash.length); destination->text.end = destination->text.p; return; } if (value->being_expanded) { fatal_reader("Loop detected when expanding macro value `%s'", value->string); } value->being_expanded = true; /* Setup the structure we read from */ sourceb.string.text.p = sourceb.string.buffer.start = value->string; sourceb.string.text.end = sourceb.string.buffer.end = sourceb.string.text.p + value->hash.length; sourceb.string.free_after_use = false; sourceb.previous = NULL; sourceb.fd = -1; /* Lift some pointers from the struct to local register variables */ CACHE_SOURCE(0); /* We parse the string in segments */ /* We read chars until we find a $, then we append what we have read so far */ /* (since last $ processing) to the destination. When we find a $ we call */ /* expand_macro() and let it expand that particular $ reference into dest */ block_start = source_p; quote_seen = 0; for (; 1; source_p++) { switch (GET_CHAR()) { case backslash_char: /* Quote $ in macro value */ if (!cmd) { quote_seen = ~quote_seen; } continue; case dollar_char: /* Save the plain string we found since */ /* start of string or previous $ */ if (quote_seen) { append_string(block_start, destination, source_p - block_start - 1); block_start = source_p; break; } append_string(block_start, destination, source_p - block_start); source->string.text.p = ++source_p; UNCACHE_SOURCE(); /* Go expand the macro reference */ expand_macro(source, destination, value->string, cmd); CACHE_SOURCE(1); block_start = source_p + 1; break; case nul_char: /* The string ran out. Get some more */ append_string(block_start, destination, source_p - block_start); GET_NEXT_BLOCK(source); if (source == NULL) { destination->text.end = destination->text.p; value->being_expanded = false; return; } block_start = source_p; source_p--; continue; } quote_seen = 0; } } /* * expand_macro(source, destination, current_string, cmd) * * Should be called with source->string.text.p pointing to * the first char after the $ that starts a macro reference. * source->string.text.p is returned pointing to the first char after * the macro name. * It will read the macro name, expanding any macros in it, * and get the value. The value is then expanded. * destination is a String that is filled in with the expanded macro. * It may be passed in referencing a buffer to expand the macro into. * * Parameters: * source The source block that references the string * to expand * destination Where to put the result * current_string The string we are expanding, for error msg * cmd If we are evaluating a command line we * turn \ quoting off * * Global variables used: * funny Vector of semantic tags for characters * is_conditional Set if a conditional macro is refd * make_word_mentioned Set if the word "MAKE" is mentioned * makefile_type We deliver extra msg when reading makefiles * query The Name "?", compared against * query_mentioned Set if the word "?" is mentioned */ void expand_macro(source, destination, current_string, cmd) register Source source; register String destination; char *current_string; Boolean cmd; { register char *source_p = source->string.text.p; register char *source_end = source->string.text.end; Property macro = NULL; Name name; char *block_start; char *colon; char *eq = NULL; char *percent; char *p; register int closer; register int closer_level = 1; String_rec string; char buffer[STRING_BUFFER_LENGTH]; String_rec extracted; char extracted_string[MAXPATHLEN]; char *left_head; char *left_tail; char *right_tail; int left_head_len; int left_tail_len; char *right_hand[128]; int quote_seen; int i; enum { no_replace, suffix_replace, pattern_replace, sh_replace, } replacement = no_replace; enum { no_extract, dir_extract, file_extract, } extraction = no_extract; static Name make; if (make == NULL) { make = GETNAME("MAKE", FIND_LENGTH); } /* First copy the (macro-expanded) macro name into string */ INIT_STRING_FROM_STACK(string, buffer); recheck_first_char: /* Check the first char of the macro name to figure out what to do */ switch (GET_CHAR()) { case nul_char: GET_NEXT_BLOCK(source); if (source == NULL) { fatal_reader("'$' at end of string `%s'", current_string); } goto recheck_first_char; case parenleft_char: /* Multi char name */ closer = (int) parenright_char; break; case braceleft_char: /* Multi char name */ closer = (int) braceright_char; break; case newline_char: fatal_reader("'$' at end of line"); default: /* Single char macro name. Just suck it up */ append_char(*source_p, &string); source->string.text.p = source_p + 1; goto get_macro_value; } /* Handle multi-char macro names */ block_start = ++source_p; quote_seen = 0; for (; 1; source_p++) { switch (GET_CHAR()) { case nul_char: append_string(block_start, &string, source_p - block_start); GET_NEXT_BLOCK(source); if (source == NULL) { if (current_string != NULL) { fatal_reader("Unmatched `%c' in string `%s'", closer == (int) braceright_char ? (int) braceleft_char : (int) parenleft_char, current_string); } else { fatal_reader("Premature EOF"); } } block_start = source_p; source_p--; continue; case newline_char: fatal_reader("Unmatched `%c' on line", closer == (int) braceright_char ? (int) braceleft_char : (int) parenleft_char); case backslash_char: /* Quote dollar in macro value */ if (!cmd) { quote_seen = ~quote_seen; } continue; case dollar_char: /* Macro names may reference macros. This expands the*/ /* value of such macros into the macro name string */ if (quote_seen) { append_string(block_start, &string, source_p - block_start - 1); block_start = source_p; break; } append_string(block_start, &string, source_p - block_start); source->string.text.p = ++source_p; UNCACHE_SOURCE(); expand_macro(source, &string, current_string, cmd); CACHE_SOURCE(0); block_start = source_p; source_p--; break; case parenleft_char: /* Allow nested pairs of () in the macro name */ if (closer == (int) parenright_char) { closer_level++; } break; case braceleft_char: /* Allow nested pairs of {} in the macro name */ if (closer == (int) braceright_char) { closer_level++; } break; case parenright_char: case braceright_char: /* End of the name. Save the string in the macro name*/ /* string */ if ((*source_p == closer) && (--closer_level <= 0)) { source->string.text.p = source_p + 1; append_string(block_start, &string, source_p - block_start); goto get_macro_value; } break; } quote_seen = 0; } /* We got the macro name. We now inspect it to see if it */ /* specifies any translations of the value */ get_macro_value: name = NULL; /* First check if we have a $(@D) type translation */ if ((char_semantics[string.buffer.start[0]] & (int) special_macro_sem) && (string.text.p - string.buffer.start >= 2) && ((string.buffer.start[1] == 'D') || (string.buffer.start[1] == 'F'))) { switch (string.buffer.start[1]) { case 'D': extraction = dir_extract; break; case 'F': extraction = file_extract; break; default: fatal_reader("Illegal macro reference `%s'", string.buffer.start); } /* Internalize the macro name using the first char only */ name = GETNAME(string.buffer.start, 1); (void) strcpy(string.buffer.start, string.buffer.start+2); } /* Check for other kinds of translations */ if ((colon = strchr(string.buffer.start, (int) colon_char)) != NULL) { /* We have a $(FOO:.c=.o) type translation. Get the */ /* name of the macro proper */ if (name == NULL) { name = GETNAME(string.buffer.start, colon - string.buffer.start); } /* Pickup all the translations */ if ((colon[1] == 's') && (colon[2] == 'h') && (colon[3] == (int) nul_char)){ replacement = sh_replace; } else if ((percent = strchr(colon + 1, (int) percent_char)) == NULL) { while (colon != NULL) { if ((eq = strchr(colon + 1, (int) equal_char)) == NULL) { fatal("= missing from := transformation"); } left_tail_len = eq - colon - 1; left_tail = alloca(left_tail_len + 1); (void) strncpy(left_tail, colon + 1, eq - colon - 1); left_tail[eq - colon - 1] = (int) nul_char; replacement = suffix_replace; if ((colon = strchr(eq + 1, (int) colon_char)) != NULL) { right_tail = alloca(colon - eq); (void) strncpy(right_tail, eq + 1, colon - eq - 1); right_tail[colon - eq - 1] = (int) nul_char; } else { right_tail = alloca(strlen(eq) + 1); (void) strcpy(right_tail, eq + 1); } } } else { if ((eq = strchr(colon + 1, (int) equal_char)) == NULL) { fatal("= missing from := transformation"); } if ((percent = strchr(colon + 1, (int) percent_char)) == NULL) { fatal("%% missing from := transformation"); } if (eq < percent) { fatal("%% missing from pattern of := transformation"); } if (percent > colon+1) { left_head = alloca(percent-colon); (void) strncpy(left_head, colon+1, percent-colon-1); left_head[percent-colon-1] = (int) nul_char; left_head_len = percent-colon-1; } else { left_head = NULL; left_head_len = 0; } if (eq > percent+1) { left_tail = alloca(eq-percent); (void) strncpy(left_tail, percent+1, eq-percent-1); left_tail[eq-percent-1] = (int) nul_char; left_tail_len = eq-percent-1; } else { left_tail = NULL; left_tail_len = 0; } if ((percent = strchr(++eq, (int) percent_char)) == NULL) { right_hand[0] = alloca(strlen(eq)+1); right_hand[1] = NULL; (void) strcpy(right_hand[0], eq); } else { i = 0; do { right_hand[i] = alloca(percent-eq+1); (void) strncpy(right_hand[i], eq, percent-eq); right_hand[i][percent-eq] = (int) nul_char; if (i++ >= VSIZEOF(right_hand)) { fatal("Too many %% in pattern"); } eq = percent + 1; if (eq[0] == (int) nul_char) { right_hand[i] = ""; i++; break; } } while ((percent = strchr(eq, (int) percent_char)) != NULL); if (eq[0] != (int) nul_char) { right_hand[i] = alloca(strlen(eq)+1); (void) strcpy(right_hand[i], eq); i++; } right_hand[i] = NULL; } replacement = pattern_replace; } } if (name == NULL) { /* No translations found. Use the whole string as */ /* the macro name */ name = GETNAME(string.buffer.start, string.text.p - string.buffer.start); } if (string.free_after_use) { retmem(string.buffer.start); } if (name == make) { make_word_mentioned = true; } if (name == query) { query_mentioned = true; } /* Get the macro value */ macro = get_prop(name->prop, macro_prop); if ((macro != NULL) && macro->body.macro.is_conditional) { conditional_macro_used = true; if (makefile_type == reading_makefile) { warning("Possibly conditional macro `%s' referenced when reading makefile", name->string); } } /* Macro name read and parsed. Expand the value. */ if ((macro == NULL) || (macro->body.macro.value == NULL)) { /* If the value is empty we just get out of here */ goto exit; } if (replacement == sh_replace) { /* If we should do a :sh transform we expand the command */ /* and process it */ INIT_STRING_FROM_STACK(string, buffer); /* Expand the value into a local string buffer and run cmd */ expand_value_with_daemon(macro, &string, cmd); sh_command2string(&string, destination); } else if ((replacement != no_replace) || (extraction != no_extract)) { /* If there were any transforms specified in the macro */ /* name we deal with them here */ INIT_STRING_FROM_STACK(string, buffer); /* Expand the value into a local string buffer */ expand_value_with_daemon(macro, &string, cmd); /* Scan the expanded string */ p = string.buffer.start; while (*p != (int) nul_char) { char chr; /* First skip over any white space and append */ /* that to the destination string */ block_start = p; while ((*p != (int) nul_char) && isspace(*p)) { p++; } append_string(block_start, destination, p - block_start); /* Then find the end of the next word */ block_start = p; while ((*p != (int) nul_char) && !isspace(*p)) { p++; } /* If we cant find another word we are done */ if (block_start == p) { break; } /* Then apply the transforms to the word */ INIT_STRING_FROM_STACK(extracted, extracted_string); switch (extraction) { case dir_extract: /* $(@D) type transform. Extract the */ /* path from the word. Deliver "." if */ /* none is found */ if (p != NULL) { chr = *p; *p = (int) nul_char; } eq = strrchr(block_start, (int) slash_char); if (p != NULL) { *p = chr; } if ((eq == NULL) || (eq > p)) { append_string(".", &extracted, 1); } else { append_string(block_start, &extracted, eq - block_start); } break; case file_extract: /* $(@F) type transform. Remove the path */ /* from the word if any */ if (p != NULL) { chr = *p; *p = (int) nul_char; } eq = strrchr(block_start, (int) slash_char); if (p != NULL) { *p = chr; } if ((eq == NULL) || (eq > p)) { append_string(block_start, &extracted, p - block_start); } else { append_string(eq + 1, &extracted, p - eq - 1); } break; case no_extract: append_string(block_start, &extracted, p - block_start); break; } switch (replacement) { case suffix_replace: /* $(FOO:.o=.c) type transform. Maybe */ /* replace the tail of the word */ if (((extracted.text.p - extracted.buffer.start) >= left_tail_len) && IS_EQUALN(extracted.text.p - left_tail_len, left_tail, left_tail_len)) { append_string(extracted.buffer.start, destination, (extracted.text.p - extracted.buffer.start) - left_tail_len); append_string(right_tail, destination, FIND_LENGTH); } else { append_string(extracted.buffer.start, destination, FIND_LENGTH); } break; case pattern_replace: /* $(X:a%b=c%d) type transform */ if (((extracted.text.p - extracted.buffer.start) >= left_head_len+left_tail_len) && IS_EQUALN(left_head, extracted.buffer.start, left_head_len) && IS_EQUALN(left_tail, extracted.text.p - left_tail_len, left_tail_len)) { i = 0; while (right_hand[i] != NULL) { append_string(right_hand[i], destination, FIND_LENGTH); i++; if (right_hand[i] != NULL) { append_string(extracted.buffer. start + left_head_len, destination, (extracted.text.p - extracted.buffer.start)-left_head_len-left_tail_len); } } } else { append_string(extracted.buffer.start, destination, FIND_LENGTH); } break; case no_replace: append_string(extracted.buffer.start, destination, FIND_LENGTH); break; } } if (string.free_after_use) { retmem(string.buffer.start); } } else { /* This is for the case when the macro name did not */ /* specify transforms */ expand_value_with_daemon(macro, destination, cmd); } exit: *destination->text.p = (int) nul_char; destination->text.end = destination->text.p; }