Hello Everyone, I am attaching a rough draft of a patch to add ${1} curly brace support to m4. I was reading the archives and someone gave me an idea for an extended syntax. It is my understanding that POSIX lets us extent the ${} syntax. NOTE: in this email I use `TEXT' to denote m4 syntax examples. So with that being said heres what this patch does:
1) add an expand_parameter function that A) supports the old `$0', `$1', ..., `$9', `$#', `$*', and `$@' syntax B) `$10' is now parsed as `$1'`0' C) if `$' is not as above or followed by `{' just print `$' so that `$$$ Save Money Now! $$$' is unmodified by parameter expansion D) remove empty `${}' and allow it to break apart parameter expansions: `X${}X' somewhat like `X`'X' E) add 1 level escape sequence `${ ' so that `${ HOME}' prints `${HOME}'. The idea is that this is a fast escape for shell variables. Most shells do not allow a space to start ${}, so it should be uncommon anyways. F) add escape syntax: `${$}' => `$', `${(}' => `{', `${,}' => `,', and `${)}' => `}' these use the m4_has_syntax function and should work if the syntax is changed around via `changesyntax()' H) Otherwise call collect_parameter_arguments, and m4_parameter_call 2) add collect_parameter_arguments which is analogous to collect_arguments: A) `{' does not nest within `${}' like `(' does in `macro(())' B) the start and end quotes have no special meaning in ${}, so the above escapes must be used C) parameters are separated by unescaped `,' as in `${1,3}' and end at the first unescaped `}' D) whitespace after `,' is ignored like macro arguments: `${1, 2}' => `${1,2}' E) parameter expansion does occur while gathering arguments so that `${${#}}' expands as the last macro argument's value. 3) add m4_parameter_call to handle the extended parameters A) parameter expansion has no access to macro values, and no macro expansion occurs while parameters evaluate B) parameter expansion is only evaluated once and is non-recursive unlike macro expansion. See `${$, TEXT}' below. C) `${@}' is equivalent to `$@' but allows for an extended syntax: i) `${@}' => `${@, 1, -1, 1, ${,}}' ii) the second argument is used as the start of a slice iii) the third argument is the end of the slice, and defaults to the start value if a start is given, -1 otherwise iv) the fourth argument is the slice step and defaults to 1. v) the fifth argument is used as the value separator like "join" in other languages. Default: `,' vi) if either the slice start or end is negative it is relative to the and of the macro argument list. (e.g. -1 is the last macro argument) vii) a negative slice start or stop will never evaluate as 0 viii) if either slice start or stop are `0' then the parameter expands as the macro name just as in `$0' ix) if the slice step is given then only every step'th argument after start is included, possibly including stop x) `${@, 2, 7, 3}' => `${@, 2}${@,5}' ; `${@, 2, 8, 3}' => `${@,2}${@,5}${@,8}' xi) if the slice step is negative then the macro arguments are reversed before the slice start/stop is applied xii) `${@, 2, 7, -3}' => `${@,7}${@,4}' xiii) all macro arguments expanded by `${@}' are quoted just as in `$@' D) `${*}' => `${*, 1, -1, -, ${,}}' => `${1, -1}' i) `${*}' is basically like `${@}' only the expanded macro arguments are not quoted just as in `$*' ii) currently `${*}' is exactly like `${1, -1}', as such either could be changed to provide a different functionality. E) `${0}' is the macro name i) if the start or stop of a slice is 0 then the macro name is returned. ii) `${0}' => `${0, 0}' => `${@, 2, 0}' => `${*, 0, 4}' => `$0' iii) I could see the case for `${0, 2}' => `$0(${2, -1})' as a commonly used expression but I think it may be too ugly iv) we should then maybe also allow `${3,0}' => `$0(${1,3})' for symmetry? this is even more ugly. F) `${1}' => `$1'; `${10}' => m4 1.X `$10' i) currently `${1, 2, 1, ${,}}' => `${*, 1, 2}' . Maybe `${START, STOP, STEP, ...}' could accept other parameters G) `${#}' => `$#' i) `${#}' returns the number of macro arguments ii) if more arguments are passed to `${#}' it behaves as a fast, efficient shortcut for `ifelse' with `$#' iii) `${#, TEXT}' is used as a blind macro shorthand. If no args are passed to the macro then this evals to `$0' otherwise `TEXT' iv) `$(#, BLIND MACRO BODY)' => `${#, 0, 0, ${0}, BLIND MACRO BODY}' v) `${#, N, TEXT}' evaluate to `TEXT' iff $# == N vi) `${#, -N, TEXT}' evaluate to `TEXT' if $# >= N, somewhat ugly, but a very common use case vii) `${#, START, STOP, TEXT,...}' evaluate to `TEXT', if $# >= START && $# <= STOP, either START or STOP may be 0 viii) if STOP is negative then it is set to $# so that `${#, 2, -1, TEXT}' works as expected. setting -STOP to anything other than $# would always fail. ix) if START is negative then START = -START, but continue to evaluate other clauses even with this one is true, like a C "case:" without a "break;" x) `${#, -2, 4, TWO-FOUR, 3, -1, _THREE-OR-MORE, DEFAULT}' => `DEFAULT' if $# == 0 || $# == 1 xi) `${#, -2, 4, TWO-FOUR, 3, -1, _THREE-OR-MORE, DEFAULT}' => `TWO-FOUR' if $# == 2 xii) `${#, -2, 4, TWO-FOUR, 3, -1, _THREE-OR-MORE, DEFAULT}' => `TWO-FOUR_THREE-OR-MORE' if $# == 3 || $# == 4 xiii) `${#, -2, 4, TWO-FOUR, 3, -1, _THREE-OR-MORE, DEFAULT}' => `_THREE-OR-MORE' if $# >= 5 xiv) this -START syntax is ugly but leads to shorter macros. just like "case:" without "break" leads to shorted code, but is equally ugly. I'm open to ideas on what to do with -START and/or -STOP xv) when ${#} has 3 or more arguments, they are parsed in groups of 3, with the final 3 parsed as ${#, ..., START, STOP, IF-TRUE}, final 2 as ${#, ..., N, TEXT} as above, or final 1 argument as ${#,...,DEFAULT} H) `${$, TEXT}' recursively reevaluate TEXT until no more ${} references exist. i) this allows macros such as `define(`X', `${#, ${$, ${1}}}')' to be defined which would allow arbitrary parameter expansions for things like advanced quoting ii) there is currently a bug where this will expand forever so long as a `$' remains in the text. I) Possible `${/, regex, replacement}' to do string manipulation i) this is not implemented yet, I want to get feedback first ii) remember that no macro expansion occurs during parameter expansion, so macros can't be used iii) theoretically this could handle most string manipulation (limited by CS theory, regex engin, etc.) iv) other string functions should have their own function v) again: parameter expansion happens at a special time and this could lead to macros that are many time more efficient. J) Possible `${?, STRING, pattern-1, if-1, pattern-2, if-2, ..., default}' i) this is not implemented yet, I want to get feedback first ii) this would be like the shell's "case" statement iii) this would be even faster than `ifelse' K) Possible `${=, 2 + 2}' i) this is not implemented yet, I want to get feedback first ii) this would be like `eval' but available as parameter expansion time. L) Possible `${%, list comprehension, pattern, stuff, format, ...}' i) this is not implemented yet, I want to get feedback first ii) this would allow for the generation of text for things like loops, arrays, etc iii) this would work something like the shell's "{1..4}" brace expansion mechanism. I'm not sure if this is too far removed from the m4 ethos, or if parts should be changed. The code should probably be reviewed to make sure that I am using the internal structures properly. Any constructive feedback is welcome. The attached patch is based off of the current git head. The code does need work, but its at a point where its ideas come through. I'd like to get this in m4 2.0 if everyone things this is worth doing. Thank you, Lester Scofield
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<N> 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<N> 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
_______________________________________________ M4-patches mailing list M4-patches@gnu.org https://lists.gnu.org/mailman/listinfo/m4-patches