diff --git a/m4/macro.c b/m4/macro.c index 01f5f0f..fd8a4db 100644 --- a/m4/macro.c +++ b/m4/macro.c @@ -125,9 +125,19 @@ static bool expand_token (m4 *, m4_obstack *, m4__token_type, m4_symbol_value *, int, bool); static bool expand_argument (m4 *, m4_obstack *, m4_symbol_value *, const m4_call_info *); -static void process_macro (m4 *, m4_symbol_value *, m4_obstack *, int, +static bool process_macro (m4 *, m4_symbol_value *, m4_obstack *, int, m4_macro_args *); +static const char *expand_parameter (m4 *, m4_obstack *, const char*, + const char*, m4_macro_args *); +static const char *collect_parameter_arguments (m4 *, const char*, const char*, + size_t, m4_macro_args*, + m4_macro_args**); +static const char *expand_parameter_argument (m4*, const char*, const char *, + m4_macro_args *, m4_obstack *, + m4_symbol_value *); +static void m4_parameter_call(m4*, m4_obstack*, m4_macro_args*, m4_macro_args*); + static unsigned int trace_pre (m4 *, m4_macro_args *); static void trace_post (m4 *, unsigned int, const m4_call_info *); static unsigned int trace_header (m4 *, const m4_call_info *); @@ -153,6 +163,7 @@ static int debug_macro_level; #define PRINT_REFCOUNT_INCREASE 2 /* Any increase to refcount. */ #define PRINT_REFCOUNT_DECREASE 4 /* Any decrease to refcount. */ +static m4_symbol_value * arg_symbol (m4_macro_args *, size_t, size_t*, bool); /* This function reads all input, and expands each token, one at a time. */ @@ -517,6 +528,7 @@ recursion limit of %zu exceeded, use -L to change it"), expansion = m4_push_string_init (context, info.file, info.line); m4_macro_call (context, value, expansion, argv); m4_push_string_finish (); + stack = &context->arg_stacks[level]; /* Cleanup. */ argv->info = NULL; @@ -691,136 +703,612 @@ m4_macro_call (m4 *context, m4_symbol_value *value, m4_obstack *expansion, will be placed, as an unfinished object. SYMBOL points to the macro definition, giving the expansion text. ARGC and ARGV are the arguments, as usual. */ -static void +static const char* +syntax_dollar (m4 *context, const char *text, const char *end) +{ + do + if (m4_has_syntax (M4SYNTAX, *text, M4_SYNTAX_DOLLAR)) + return text; + while (++text < end); + return NULL; +} +static const char* +memchr_dollar (m4 *context, const char *text, const char *end) +{ + return (const char*) memchr(text, M4SYNTAX->dollar, end - text); +} + +static bool process_macro (m4 *context, m4_symbol_value *value, m4_obstack *obs, int argc, m4_macro_args *argv) { const char *text = m4_get_symbol_value_text (value); - size_t len = m4_get_symbol_value_len (value); - const char *end = text + len; - int i; - while (1) + const char *end = text + m4_get_symbol_value_len (value); + const char *dollar; + const char *(*find)(m4*, const char *, const char *) = syntax_dollar; + bool found_dollar = false; + + if (text >= end) + return found_dollar; + /* parameter expansion cannot modify syntax, so only check once. */ + if (m4_is_syntax_single_dollar (M4SYNTAX)) + find = memchr_dollar; + while( (dollar = find (context, text, end)) ) { - const char *dollar; - if (m4_is_syntax_single_dollar (M4SYNTAX)) - dollar = (char *) memchr (text, M4SYNTAX->dollar, len); + found_dollar = true; + obstack_grow (obs, text, dollar - text); + text = expand_parameter(context, obs, dollar, end, argv); + } + obstack_grow (obs, text, end - text); + return found_dollar; +} + +static const char* +expand_parameter (m4 *context, m4_obstack *obs, + const char* text, const char* end, m4_macro_args *macro_argv) +{ + int macro_argc = macro_argv->argc; + void *args_base; /* Base of stack->args on entry. */ + void *args_scratch; /* Base of scratch space for m4_macro_call. */ + void *argv_base; /* Base of stack->argv on entry. */ + m4_macro_args *argv = NULL; /* Arguments to the called macro. */ + size_t level; /* Expansion level of this parameter. */ + m4__macro_arg_stacks *stack; /* Storage for this parameter. */ + int n; + size_t len = end - text; + + if (text >= end) + return end; + if (len == 1) + { + obstack_1grow (obs, *text); + return end; + } + switch(*++text) /* Single character parameters */ + { + /* POSIX single character parameters */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = *text - '0'; + if (n >= macro_argc) + return ++text; + assert (macro_argv->info); + if (n == 0) + m4_shipout_string (context, obs, macro_argv->info->name, + macro_argv->info->name_len, false); + else + m4__symbol_value_print (context, arg_symbol(macro_argv, n, NULL, false), + obs, NULL, true, NULL, NULL, false); + return ++text; + case '#': /* number of arguments */ + m4_shipout_int (obs, macro_argc - 1); + return ++text; + case '*': /* all arguments */ + case '@': /* ... same, but quoted */ + m4_push_args (context, obs, macro_argv, false, *text == '@'); + return ++text; + default: + if (!m4_has_syntax (M4SYNTAX, *text, M4_SYNTAX_LBRACE)) + { + assert(m4_has_syntax (M4SYNTAX, *(text-1), M4_SYNTAX_DOLLAR)); + obstack_1grow (obs, *(text-1)); /* non-significant DOLLAR */ + return text; /* "$$$ foo $$$" does not need escaping */ + } + /* Extended ${...} parameter syntax */ + len = end - text; + if (len == 1) + m4_error (context, EXIT_FAILURE, 0, macro_argv->info, + _("unterminated parameter")); + /* remove ${} from stream, "X${}X" is somewhat like "X`'X" */ + if (m4_has_syntax (M4SYNTAX, *(text+1), M4_SYNTAX_RBRACE)) + return text + 2; + /* escape "${SH_VAR}" as "${ SH_VAR}", ${ VAR} is invalid sh syntax */ + if (m4_has_syntax (M4SYNTAX, *(text+1), M4_SYNTAX_SPACE)) + { + obstack_1grow (obs, *(text-1)); /* use M4_SYNTAX_DOLLAR just seen */ + obstack_1grow (obs, *text); /* use M4_SYNTAX_LBRACE just seen */ + return text + 2; + } + if (len == 2) + m4_error (context, EXIT_FAILURE, 0, macro_argv->info, + _("unterminated parameter")); + /* escape `$' `{' `,' `}' */ + if (m4_has_syntax (M4SYNTAX, *(text+2), M4_SYNTAX_RBRACE)) + { + const char *c; + if (m4_has_syntax (M4SYNTAX, *(text+1), + M4_SYNTAX_DOLLAR | + M4_SYNTAX_COMMA)) + c = text + 1; /* ${$} => `$', ${,} => `,' */ + else if (m4_has_syntax (M4SYNTAX, *(text+1), M4_SYNTAX_OPEN)) + c = text; /* ${(} => `{' */ + else if (m4_has_syntax (M4SYNTAX, *(text+1), M4_SYNTAX_CLOSE)) + c = text + 2; /* ${)} => `}' */ + else + break; /* exit switch, and use following process */ + obstack_1grow (obs, *c); /* use the M4_SYNTAX_* char just seen */ + return text + 3; + } + } + /* *text == '{' */ + assert('{' == *text); + /* Obstack preparation. */ + level = context->expansion_level; + if (context->stacks_count <= level) + { + size_t count = context->stacks_count; + context->arg_stacks + = (m4__macro_arg_stacks *) x2nrealloc (context->arg_stacks, + &context->stacks_count, + sizeof *context->arg_stacks); + memset (&context->arg_stacks[count], 0, + sizeof *context->arg_stacks * (context->stacks_count - count)); + } + stack = &context->arg_stacks[level]; + if (!stack->args) + { + assert (!stack->refcount); + stack->args = (m4_obstack *) xmalloc (sizeof *stack->args); + stack->argv = (m4_obstack *) xmalloc (sizeof *stack->argv); + obstack_init (stack->args); + obstack_init (stack->argv); + stack->args_base = obstack_finish (stack->args); + stack->argv_base = obstack_finish (stack->argv); + } + assert (obstack_object_size (stack->args) == 0 + && obstack_object_size (stack->argv) == 0); + args_base = obstack_finish (stack->args); + argv_base = obstack_finish (stack->argv); + m4__adjust_refcount (context, level, true); + stack->argcount++; + + if (m4_get_nesting_limit_opt (context) < ++context->expansion_level) + m4_error (context, EXIT_FAILURE, 0, NULL, _("\ +recursion limit of %zu exceeded, use -L to change it"), + m4_get_nesting_limit_opt (context)); + + text = collect_parameter_arguments (context, text, end, level, macro_argv, + &argv); + /* Since collect_parameter_arguments can invalidate stack by reallocating + context->arg_stacks during a recursive expand_parameter call, we must + reset it here. */ + stack = &context->arg_stacks[level]; + args_scratch = obstack_finish (stack->args); + + /* The actual parameter function call. */ + m4_parameter_call (context, obs, macro_argv, argv); + //stack = &context->arg_stacks[level]; + + /* Cleanup. */ + argv->info = NULL; + --context->expansion_level; + + /* We no longer need argv, so reduce the refcount. Additionally, if + no other references to argv were created, we can free our portion + of the obstack, although we must leave earlier content alone. A + refcount of 0 implies that adjust_refcount already freed the + entire stack. */ + m4__arg_adjust_refcount (context, argv, false); + if (stack->refcount) + { + if (argv->inuse) + { + obstack_free (stack->args, args_scratch); + if (debug_macro_level & PRINT_ARGCOUNT_CHANGES) + xfprintf (stderr, "m4debug: -%zu- `%s' in use, level=%zu, " + "refcount=%zu, argcount=%zu\n", argv->info->call_id, + argv->info->name, level, stack->refcount, + stack->argcount); + } else { - dollar = text; - while (dollar != end) - { - if (m4_has_syntax (M4SYNTAX, *dollar, M4_SYNTAX_DOLLAR)) - break; - dollar++; - } - if (dollar == end) - dollar = NULL; + obstack_free (stack->args, args_base); + obstack_free (stack->argv, argv_base); + stack->argcount--; } - if (!dollar) + } + return text; +} + +static void +m4_parameter_call (m4 *context, m4_obstack *obs, m4_macro_args *macro_argv, + m4_macro_args *argv) +{ + int macro_argc = macro_argv->argc - 1; + int argc = argv->argc - 1; + const char *func; + char *endp; + long optind = 0, start = 1, stop = -1, step = 1; + const m4_string_pair *quotes = NULL; + size_t level; + m4_symbol_value *value = arg_symbol (argv, 1, &level, false); + func = m4_get_symbol_value_text(value); + m4_symbol_value sep = {0}; + + assert (argv->info); + sep.type = M4_SYMBOL_TEXT; + switch (*func) + { + //case '/': /* regex */ + //case '=': /* math eval */ + //case '?': /* match statement */ + //case '%': /* list generator */ + case '@': /* ${@, ...} => ${*, ...} but with quoting */ + quotes = m4_get_syntax_quotes (M4SYNTAX); + /* fall through */ + case '*': /* ${*} => ${*,1,-1,1} => ${1,-1,1} */ + optind = 1; /* skip '@' or '*' */ + if (*(func + 1) != '\0') + break; + /* Fall through */ + case '-': case '+': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* start */ + if (optind < argc) { - obstack_grow (obs, text, len); - return; + value = arg_symbol (argv, optind + 1, NULL, false); + start = strtol (m4_get_symbol_value_text (value), &endp, 0); + if (*endp != '\0') + m4_error (context, 0, 0, argv->info, _("invalid number: %s"), + m4_get_symbol_value_text (value)); + stop = start; /* explicit start defaults stop to 1 item */ } - obstack_grow (obs, text, dollar - text); - len -= dollar - text; - text = dollar; - if (len == 1) + else + start = 1; + if (start < 0) { - obstack_1grow (obs, *dollar); - return; - } - len--; - switch (*++text) - { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - /* FIXME - multidigit arguments should convert over to ${10} - syntax instead of $10; see - http://lists.gnu.org/archive/html/m4-discuss/2006-08/msg00028.html - for more discussion. */ - if (m4_get_posixly_correct_opt (context) - || !isdigit (to_uchar (text[1]))) - { - i = *text++ - '0'; - len--; - } + if (0 != macro_argc + start + 1) + start = macro_argc + start + 1; else - { - char *endp; - i = (int) strtol (text, &endp, 10); - len -= endp - text; - text = endp; - } - if (i < argc) - m4_push_arg (context, obs, argv, i); - break; - - case '#': /* number of arguments */ - m4_shipout_int (obs, argc - 1); - text++; - len--; - break; - - case '*': /* all arguments */ - case '@': /* ... same, but quoted */ - m4_push_args (context, obs, argv, false, *text == '@'); - text++; - len--; - break; + return; + } + if (start > macro_argc) + return; + /* stop */ + if (optind + 1 < argc) + { + value = arg_symbol (argv, optind + 2, NULL, false); + stop = strtol (m4_get_symbol_value_text (value), &endp, 0); + if (*endp != '\0') + m4_error (context, 0, 0, argv->info, _("invalid number: %s"), + m4_get_symbol_value_text (value)); + } + if (stop < 0) + { + if (macro_argc + stop + 1 > 0) + stop = macro_argc + stop + 1; + else + return; + } + if (stop > macro_argc) + stop = macro_argc; + if (0 != stop && stop < start) + return; + /* step */ + if (optind + 2 < argc) + { + value = arg_symbol (argv, optind + 3, NULL, false); + step = strtol (m4_get_symbol_value_text (value), &endp, 0); + if (*endp != '\0') + m4_error (context, 0, 0, argv->info, _("invalid number: %s"), + m4_get_symbol_value_text (value)); + } + else + step = 1; + if (step < 0) + { + long tmp = start; + start = stop; + stop = tmp; + } + /* Joining Separator */ + if (optind + 3 < argc) + { + value = arg_symbol (argv, optind + 4, NULL, false); + m4_set_symbol_value_text (&sep, + m4_get_symbol_value_text(value), + m4_get_symbol_value_len (value), + m4__quote_age (M4SYNTAX)); + } + else + m4_set_symbol_value_text (&sep, ",", strlen(","), + m4__quote_age (M4SYNTAX)); + /* return $0 if start, stop, or step are 0 */ + if (0 == start || 0 == stop || 0 == step) /* ${0,0,0} => $0 */ + { + m4_shipout_string (context, obs, argv->info->name, + argv->info->name_len, quotes != NULL); + return; + } + /* return slice */ + m4__symbol_value_print (context, arg_symbol (macro_argv, start, + NULL, false), + obs, quotes, true, NULL, NULL, false); + start += step; + while ( (step > 0 && start <= stop) || (step < 0 && start >= stop) ) + { + m4__symbol_value_print (context, &sep, obs, + false, true, NULL, NULL, false); + m4__symbol_value_print (context, arg_symbol (macro_argv, start, + NULL, false), + obs, quotes, true, NULL, NULL, false); + start += step; + } + return; + case '#': + if (*(func + 1) != '\0') + break; + switch (argc - 1) + { + case 0: /* ${#} => $# */ + m4_shipout_int (obs, macro_argc); + return; + case 1: /* ${#, Blind Macro} Note: ${#, X} => ${#, 0, 0, ${0}, X} */ + if (macro_argc == 0) + m4_shipout_string (context, obs, argv->info->name, + argv->info->name_len, false); + else + m4__symbol_value_print (context, arg_symbol(argv, 2, NULL, false), + obs, NULL, true, NULL, NULL, false); + return; default: - if (m4_get_posixly_correct_opt (context) - || !VALUE_ARG_SIGNATURE (value)) + while (optind < argc - 1) { - obstack_1grow (obs, *dollar); - } - else - { - size_t len1 = 0; - const char *endp; - char *key; - - for (endp = ++text; - len1 < len && m4_has_syntax (M4SYNTAX, *endp, - (M4_SYNTAX_OTHER - | M4_SYNTAX_ALPHA - | M4_SYNTAX_NUM)); - ++endp) + bool keep_going = false; + switch (argc - 1 - optind) { - ++len1; - } - key = xstrndup (text, len1); - - if (*endp) - { - struct m4_symbol_arg **arg - = (struct m4_symbol_arg **) - m4_hash_lookup (VALUE_ARG_SIGNATURE (value), key); - - if (arg) + case 1: /* ${#, ..., DEFAULT} */ + start = 0; + stop = -1; + optind -= 2; /* adjust for arg_symbol below */ + break; + case 2: /* ${#, ... , N, ALT_TEXT} */ + value = arg_symbol (argv, optind + 2, NULL, false); + start = strtol (m4_get_symbol_value_text (value), &endp, 0); + if (*endp != '\0') + m4_error (context, 0, 0, argv->info, _("invalid number: %s"), + m4_get_symbol_value_text (value)); + if (start >= 0) /* ${#,3,X} => ${#,3,3,X} */ + stop = start; + else /* ${#,-2,X} => ${#,2,-1,X} */ { - i = SYMBOL_ARG_INDEX (*arg); - assert (i < argc); - m4_shipout_string (context, obs, M4ARG (i), M4ARGLEN (i), - false); + start = -start; + stop = -1; } + optind -= 1; /* adjust for arg_symbol below */ + break; + default: /* ${#, ... , START, STOP, ALT_TEXT, ...} */ + value = arg_symbol (argv, optind + 2, NULL, false); + start = strtol (m4_get_symbol_value_text (value), &endp, 0); + if (*endp != '\0') + m4_error (context, 0, 0, argv->info, _("invalid number: %s"), + m4_get_symbol_value_text (value)); + value = arg_symbol (argv, optind + 3, NULL, false); + stop = strtol (m4_get_symbol_value_text (value), &endp, 0); + if (*endp != '\0') + m4_error (context, 0, 0, argv->info, _("invalid number: %s"), + m4_get_symbol_value_text (value)); + } + + if (stop < 0) /* ${#,3,-1,X} => if $# >= 3 then X */ + stop = macro_argc; + if (start < 0) /* treat test as a case: without a break; */ + { + start = -start; + keep_going = true; + } + if (macro_argc >= start && macro_argc <= stop) + { + m4__symbol_value_print (context, + arg_symbol(argv, optind + 4, NULL, + false), + obs, NULL, true, NULL, NULL, false); + if (!keep_going) + return; + } + optind += 3; + } + } + break; + case '$': /* recursive parameter expansion */ + { + m4_obstack expand; + + obstack_init (&expand); + value = (m4_symbol_value *) obstack_alloc (&expand, + sizeof(m4_symbol_value)); + value->type = M4_SYMBOL_TEXT; + + for (optind = 1 ; optind < argc ; ++optind) + m4__symbol_value_print (context, + arg_symbol (argv, optind + 1, NULL, false), + &expand, NULL, true, NULL, NULL, false); + do + if (value->type != M4_SYMBOL_COMP) + { + size_t len = obstack_object_size (&expand); + VALUE_MODULE (value) = NULL; + if (len) + { + obstack_1grow (&expand, '\0'); + m4_set_symbol_value_text (value, obstack_finish (&expand), + len, m4__quote_age (M4SYNTAX)); } else - { - m4_error (context, 0, 0, argv->info, - _("unterminated parameter reference: %s"), key); - } - - len -= endp - text; - text = endp; - - free (key); + m4_set_symbol_value_text (value, "", len, 0); } - break; - } + else + { + m4__make_text_link (&expand, NULL, &value->u.u_c.end); + if (value->u.u_c.chain == value->u.u_c.end + && value->u.u_c.chain->type == M4__CHAIN_FUNC) + { + value->type = M4_SYMBOL_FUNC; + value->u.builtin = value->u.u_c.chain->u.builtin; + } + } + while (process_macro (context, value, &expand, macro_argc, macro_argv)); + m4__symbol_value_print (context, value, obs, + NULL, true, NULL, NULL, false); + obstack_free (&expand, NULL); + return; + } + default: + m4_error (context, 0, 0, argv->info, _("invalid parameter: %s"), func); } } +static const char* +collect_parameter_arguments (m4 *context, const char *text, const char *end, + size_t level, m4_macro_args *macro_argv, + m4_macro_args **argv) +{ + m4__macro_arg_stacks *stack = &context->arg_stacks[level]; + m4_symbol_value *tokenp; + m4_macro_args args = {0}; + + if (NULL == argv) + return end; + args.argc = 1; // parameters do not have a macro name + //args.inuse = false; + //args.wrapper = false; + //args.has_ref = false; + //args.flatten = false; + //args.has_func = false; + args.quote_age = m4__quote_age (M4SYNTAX); + args.info = macro_argv->info; + args.level = context->expansion_level - 1; + //args.arraylen = 0; + obstack_grow (stack->argv, &args, offsetof (m4_macro_args, array)); + + /* Gobble brace, then collect arguments. */ + ++text; + do + { + tokenp = (m4_symbol_value *) obstack_alloc (stack->args, sizeof *tokenp); + text = expand_parameter_argument(context, text, end, macro_argv, + stack->args, tokenp); + stack = &context->arg_stacks[level]; + if ((m4_is_symbol_value_text (tokenp) + && !m4_get_symbol_value_len (tokenp)) + || (args.flatten && m4_is_symbol_value_func (tokenp))) + { + obstack_free (stack->args, tokenp); + tokenp = &empty_symbol; + } + obstack_ptr_grow (stack->argv, tokenp); + args.arraylen++; + args.argc++; + switch (tokenp->type) + { + case M4_SYMBOL_TEXT: + /* Be conservative - any change in quoting while + collecting arguments, or any unsafe argument, will + require a rescan if $@ is reused. */ + if (m4_get_symbol_value_len (tokenp) + && m4_get_symbol_value_quote_age (tokenp) != args.quote_age) + args.quote_age = 0; + break; + case M4_SYMBOL_FUNC: + args.has_func = true; + break; + case M4_SYMBOL_COMP: + args.has_ref = true; + if (tokenp->u.u_c.wrapper) + { + assert (tokenp->u.u_c.chain->type == M4__CHAIN_ARGV + && !tokenp->u.u_c.chain->next); + args.argc += (tokenp->u.u_c.chain->u.u_a.argv->argc + - tokenp->u.u_c.chain->u.u_a.index + - tokenp->u.u_c.chain->u.u_a.skip_last - 1); + args.wrapper = true; + } + if (tokenp->u.u_c.has_func) + args.has_func = true; + break; + default: + assert (!"expand_argument"); + abort (); + } + } + while (!m4_has_syntax (M4SYNTAX, *(text++), M4_SYNTAX_RBRACE)); + + *argv = (m4_macro_args *) obstack_finish (stack->argv); + (*argv)->argc = args.argc; + (*argv)->wrapper = args.wrapper; + (*argv)->has_ref = args.has_ref; + (*argv)->has_func = args.has_func; + if (args.quote_age != m4__quote_age (M4SYNTAX)) + (*argv)->quote_age = 0; + (*argv)->arraylen = args.arraylen; + + return text; +} + +/* This function parses one argument to a brace expantion. It expects the + first left brace or the separating comma to have been read by + the caller. It skips leading whitespace, then reads but does not expand + tokens, until it finds a comma or a right brace at the same + level. It returns a flag indicating whether the + argument read is the last for the active brace call. The arguments + are built on the obstack OBS, indirectly through expand_token (). + Report errors on behalf of CALLER. */ +static const char* +expand_parameter_argument (m4 *context, const char *text, const char *end, + m4_macro_args *macro_argv, m4_obstack *obs, + m4_symbol_value *argp) +{ + size_t len; + unsigned int age = m4__quote_age (M4SYNTAX); + + memset (argp, '\0', sizeof *argp); + VALUE_MAX_ARGS (argp) = -1; + + /* Skip leading white space. */ + while (text < end && m4_has_syntax (M4SYNTAX, *text, M4_SYNTAX_SPACE)) + ++text; + + while (text < end) + if (m4_has_syntax (M4SYNTAX, *text, M4_SYNTAX_DOLLAR)) + text = expand_parameter(context, obs, text, end, macro_argv); + else if (m4_has_syntax (M4SYNTAX, *text, M4_SYNTAX_COMMA | M4_SYNTAX_RBRACE)) + { + assert (argp->type != M4_SYMBOL_FUNC); + if (argp->type != M4_SYMBOL_COMP) + { + len = obstack_object_size (obs); + VALUE_MODULE (argp) = NULL; + if (len) + { + obstack_1grow (obs, '\0'); + m4_set_symbol_value_text (argp, obstack_finish (obs), + len, age); + } + else + m4_set_symbol_value_text (argp, "", len, 0); + } + else + { + m4__make_text_link (obs, NULL, &argp->u.u_c.end); + if (argp->u.u_c.chain == argp->u.u_c.end + && argp->u.u_c.chain->type == M4__CHAIN_FUNC) + { + const m4__builtin *func = argp->u.u_c.chain->u.builtin; + argp->type = M4_SYMBOL_FUNC; + argp->u.builtin = func; + } + } + return text; + } + else + obstack_1grow (obs, *(text++)); + + m4_error (context, EXIT_FAILURE, 0, macro_argv->info, + _("unterminated parameter")); + return end; +} /* The next portion of this file contains the functions for macro git diff --histogram ./m4/macro.c >../0001-extended-brace-parameters.patc