[11/18] argv_ref speedup: support composite arguments
Eric Blake <[email protected]>
| Newsgroups | gmane.comp.gnu.m4.patches |
|---|---|
| Message-ID | <[email protected]> |
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Next in the series. Up till now, every byte of rescanned input has been copied, so the argument collection engine could deal with contiguous text. ~ But with this patch, the argument collection engine has been taught how to create composite tokens, where links in the token chain can come from back-references in the input engine. Basically, the input engine has a new placeholder (CHAR_QUOTE), similar to the placeholder for builtins, which represents a series of rescanned bytes that came from the same quoting rules. Meanwhile, all of the argv accessor methods will flatten text from a composite token on an as-needed basis, rather than wasting effort on flattening it up front when the argument is not used. As a result, the amount of memory usage drops (dramatically on boxed recursion, but even real-life autoconf and unboxed recursion test cases see some benefits). More importantly, with less copying, m4 operates much faster when rescanning back-references. This patch still flattens composite arguments into contiguous text in push_arg (ie. no references to a reference yet), and still handles argument lists one argument at a time, so the speedup is all in a better coefficient and not due to any complexity reduction. 2008-01-22 Eric Blake <[email protected]> Stage 11: full circle for single argument references. ~ * src/m4.h (struct token_chain): Add quote_age member. ~ (struct token_data): Add end member to chain alternate. ~ (make_text_link): New prototype. ~ * src/input.c (CHAR_QUOTE): New macro. ~ (word_start): Pre-allocate. ~ (set_word_regexp): Simplify. ~ (make_text_link): Export, and handle new fields. ~ (next_char, next_char_1): Add parameter. ~ (append_quote_token): New function. ~ (match_input, next_token): Adjust callers to handle quoted input ~ blocks. ~ * src/macro.c (struct macro_arguments): Add wrapper member. ~ (expand_argument): Accept composite blocks from input engine. ~ (expand_macro): Reduce refcounts of composite arguments. ~ (collect_arguments, arg_token, arg_mark, make_argv_ref): Update to ~ use new fields. ~ (arg_type, arg_text, arg_equal, arg_len): Treat composite ~ arguments as text. ~ (push_arg, push_args): Handle composites. - -- Don't work too hard, make some time for fun as well! Eric Blake [email protected] -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.5 (Cygwin) Comment: Public key at home.comcast.net/~ericblake/eblake.gpg Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFHlllN84KuGfSFAYARAn/zAJ4g1+FB+zY+1Wh/N3zyI6RxQBjrKgCffgKm te0swNG/6ja6EH1Y5kxSdoo= =VdmM -----END PGP SIGNATURE----- _______________________________________________ M4-patches mailing list [email protected] http://lists.gnu.org/mailman/listinfo/m4-patches
m4.patch308
(text/plain, 82.7 KB)
From 5307d448bacdf7f588a95f7bc44c520ce80827a6 Mon Sep 17 00:00:00 2001 From: Eric Blake <[email protected]> Date: Mon, 21 Jan 2008 12:04:45 -0700 Subject: [PATCH] Stage 11: full circle for single argument references. Pass quoted strings through to argument collection in a single action, so that an argument can be reused throughout macro recursion if it remains unchanged. Memory impact: noticeable improvement, due to more reuse in argument collection stacks. Speed impact: noticeable improvement, due to less copying. * m4/m4module.h (m4_arg_text): Add parameter. (M4ARG): Adjust. * m4/m4private.h (CHAR_QUOTE): New input engine sentinel. (m4__make_text_link): New prototype. (struct m4_symbol_chain): Add quote_age member. (struct m4_symbol_value): Add end member to chained symbol. (struct m4_macro_args): Add wrapper member. * m4/symtab.c (m4_symbol_value_print): Print composite tokens. (m4_symbol_value_copy, m4_symbol_value_delete): Recognize composite tokens. * m4/input.c (make_text_link): Rename... (m4__make_text_link): ...to this, and export. (m4_push_string_finish): Adjust caller. (make_text_link, m4__push_symbol): Update new field. (file_read, builtin_read, string_read, composite_read, next_char): Add parameter. (m4_skip_line, match_input, consume_syntax): Adjust callers. (append_quote_token): New function. (m4__next_token): Pass quoted strings onto argument collection. (m4_print_token) [DEBUG_INPUT]: Update. * m4/macro.c (expand_argument): Collect composite arguments. (collect_arguments): Update new field. (expand_macro): Reduce ref-count of back-references after use. (arg_mark, m4_arg_symbol, m4_make_argv_ref): Adjust to new member names. (m4_is_arg_text): Also recognize composite symbols as text. (m4_arg_text, m4_arg_len): Merge composite symbols as needed. (m4_arg_equal): Compare composite symbols. (m4_push_arg, m4_push_args): Handle composite symbols. (m4_arg_symbol): Relax assertion. (process_macro): Use single-argument references. * m4/output.c (m4_shipout_string_trunc): Update comment. * tests/macros.at (Rescanning macros): Augment test. Signed-off-by: Eric Blake <[email protected]> --- ChangeLog | 43 ++++++++++ m4/input.c | 236 +++++++++++++++++++++++++++++++++++------------------- m4/m4module.h | 9 +- m4/m4private.h | 17 +++- m4/macro.c | 239 +++++++++++++++++++++++++++++++++++++++++++++++-------- m4/output.c | 3 +- m4/symtab.c | 150 ++++++++++++++++++++++++++--------- tests/macros.at | 20 +++++- 8 files changed, 557 insertions(+), 160 deletions(-) diff --git a/ChangeLog b/ChangeLog index cc00596..782b475 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,46 @@ +2008-01-21 Eric Blake <[email protected]> + + Stage 11: full circle for single argument references. + Pass quoted strings through to argument collection in a single + action, so that an argument can be reused throughout macro + recursion if it remains unchanged. + Memory impact: noticeable improvement, due to more reuse in + argument collection stacks. + Speed impact: noticeable improvement, due to less copying. + * m4/m4module.h (m4_arg_text): Add parameter. + (M4ARG): Adjust. + * m4/m4private.h (CHAR_QUOTE): New input engine sentinel. + (m4__make_text_link): New prototype. + (struct m4_symbol_chain): Add quote_age member. + (struct m4_symbol_value): Add end member to chained symbol. + (struct m4_macro_args): Add wrapper member. + * m4/symtab.c (m4_symbol_value_print): Print composite tokens. + (m4_symbol_value_copy, m4_symbol_value_delete): Recognize + composite tokens. + * m4/input.c (make_text_link): Rename... + (m4__make_text_link): ...to this, and export. + (m4_push_string_finish): Adjust caller. + (make_text_link, m4__push_symbol): Update new field. + (file_read, builtin_read, string_read, composite_read, next_char): + Add parameter. + (m4_skip_line, match_input, consume_syntax): Adjust callers. + (append_quote_token): New function. + (m4__next_token): Pass quoted strings onto argument collection. + (m4_print_token) [DEBUG_INPUT]: Update. + * m4/macro.c (expand_argument): Collect composite arguments. + (collect_arguments): Update new field. + (expand_macro): Reduce ref-count of back-references after use. + (arg_mark, m4_arg_symbol, m4_make_argv_ref): Adjust to new member + names. + (m4_is_arg_text): Also recognize composite symbols as text. + (m4_arg_text, m4_arg_len): Merge composite symbols as needed. + (m4_arg_equal): Compare composite symbols. + (m4_push_arg, m4_push_args): Handle composite symbols. + (m4_arg_symbol): Relax assertion. + (process_macro): Use single-argument references. + * m4/output.c (m4_shipout_string_trunc): Update comment. + * tests/macros.at (Rescanning macros): Augment test. + 2008-01-16 Eric Blake <[email protected]> Stage 10: avoid extra copying of strings and comments. diff --git a/m4/input.c b/m4/input.c index 6dcaac0..0dcb0ae 100644 --- a/m4/input.c +++ b/m4/input.c @@ -93,29 +93,28 @@ between input blocks must update the context accordingly. */ static int file_peek (m4_input_block *); -static int file_read (m4_input_block *, m4 *, bool); +static int file_read (m4_input_block *, m4 *, bool, bool); static void file_unget (m4_input_block *, int); static bool file_clean (m4_input_block *, m4 *, bool); static void file_print (m4_input_block *, m4 *, m4_obstack *); static int builtin_peek (m4_input_block *); -static int builtin_read (m4_input_block *, m4 *, bool); +static int builtin_read (m4_input_block *, m4 *, bool, bool); static void builtin_unget (m4_input_block *, int); static void builtin_print (m4_input_block *, m4 *, m4_obstack *); static int string_peek (m4_input_block *); -static int string_read (m4_input_block *, m4 *, bool); +static int string_read (m4_input_block *, m4 *, bool, bool); static void string_unget (m4_input_block *, int); static void string_print (m4_input_block *, m4 *, m4_obstack *); static int composite_peek (m4_input_block *); -static int composite_read (m4_input_block *, m4 *, bool); +static int composite_read (m4_input_block *, m4 *, bool, bool); static void composite_unget (m4_input_block *, int); static bool composite_clean (m4_input_block *, m4 *, bool); static void composite_print (m4_input_block *, m4 *, m4_obstack *); -static void make_text_link (m4_obstack *, m4_symbol_chain **, - m4_symbol_chain **); static void init_builtin_token (m4 *, m4_symbol_value *); +static void append_quote_token (m4_obstack *, m4_symbol_value *); static bool match_input (m4 *, const char *, bool); -static int next_char (m4 *, bool); +static int next_char (m4 *, bool, bool); static int peek_char (m4 *); static bool pop_input (m4 *, bool); static void unget_input (int); @@ -133,9 +132,10 @@ struct input_funcs int (*peek_func) (m4_input_block *); /* Read input, return an unsigned char, CHAR_BUILTIN if it is a - builtin, or CHAR_RETRY if none available. If SAFE, then do not - alter the current file or line. */ - int (*read_func) (m4_input_block *, m4 *, bool safe); + builtin, or CHAR_RETRY if none available. If ALLOW_QUOTE, then + CHAR_QUOTE may be returned. If SAFE, then do not alter the + current file or line. */ + int (*read_func) (m4_input_block *, m4 *, bool allow_quote, bool safe); /* Unread a single unsigned character or CHAR_BUILTIN, must be the same character previously read by read_func. */ @@ -269,7 +269,8 @@ file_peek (m4_input_block *me) } static int -file_read (m4_input_block *me, m4 *context, bool safe M4_GNUC_UNUSED) +file_read (m4_input_block *me, m4 *context, bool allow_quote M4_GNUC_UNUSED, + bool safe M4_GNUC_UNUSED) { int ch; @@ -397,7 +398,7 @@ builtin_peek (m4_input_block *me) static int builtin_read (m4_input_block *me, m4 *context M4_GNUC_UNUSED, - bool safe M4_GNUC_UNUSED) + bool allow_quote M4_GNUC_UNUSED, bool safe M4_GNUC_UNUSED) { if (me->u.u_b.read) return CHAR_RETRY; @@ -479,7 +480,7 @@ string_peek (m4_input_block *me) static int string_read (m4_input_block *me, m4 *context M4_GNUC_UNUSED, - bool safe M4_GNUC_UNUSED) + bool allow_quote M4_GNUC_UNUSED, bool safe M4_GNUC_UNUSED) { if (!me->u.u_s.len) return CHAR_RETRY; @@ -560,7 +561,7 @@ m4__push_symbol (m4 *context, m4_symbol_value *value, size_t level) next->funcs = &composite_funcs; next->u.u_c.chain = next->u.u_c.end = NULL; } - make_text_link (current_input, &next->u.u_c.chain, &next->u.u_c.end); + m4__make_text_link (current_input, &next->u.u_c.chain, &next->u.u_c.end); chain = (m4_symbol_chain *) obstack_alloc (current_input, sizeof *chain); if (next->u.u_c.end) next->u.u_c.end->next = chain; @@ -568,6 +569,7 @@ m4__push_symbol (m4 *context, m4_symbol_value *value, size_t level) next->u.u_c.chain = chain; next->u.u_c.end = chain; chain->next = NULL; + chain->quote_age = m4_get_symbol_value_quote_age (value); chain->str = m4_get_symbol_value_text (value); chain->len = m4_get_symbol_value_len (value); chain->level = level; @@ -611,7 +613,8 @@ m4_push_string_finish (void) next->u.u_s.len = len; } else - make_text_link (current_input, &next->u.u_c.chain, &next->u.u_c.end); + m4__make_text_link (current_input, &next->u.u_c.chain, + &next->u.u_c.end); next->prev = isp; ret = isp = next; input_change = true; @@ -649,15 +652,19 @@ composite_peek (m4_input_block *me) } static int -composite_read (m4_input_block *me, m4 *context, bool safe) +composite_read (m4_input_block *me, m4 *context, bool allow_quote, bool safe) { m4_symbol_chain *chain = me->u.u_c.chain; while (chain) { + if (allow_quote && chain->quote_age == m4__quote_age (M4SYNTAX)) + return CHAR_QUOTE; if (chain->str) { if (chain->len) { + /* Partial consumption invalidates quote age. */ + chain->quote_age = 0; chain->len--; return to_uchar (*chain->str++); } @@ -668,8 +675,6 @@ composite_read (m4_input_block *me, m4 *context, bool safe) assert (!"implemented yet"); abort (); } - if (safe) - return CHAR_RETRY; if (chain->level < SIZE_MAX) m4__adjust_refcount (context, chain->level, false); me->u.u_c.chain = chain = chain->next; @@ -744,9 +749,9 @@ composite_print (m4_input_block *me, m4 *context, m4_obstack *obs) /* Given an obstack OBS, capture any unfinished text as a link in the chain that starts at *START and ends at *END. START may be NULL if *END is non-NULL. */ -static void -make_text_link (m4_obstack *obs, m4_symbol_chain **start, - m4_symbol_chain **end) +void +m4__make_text_link (m4_obstack *obs, m4_symbol_chain **start, + m4_symbol_chain **end) { m4_symbol_chain *chain; size_t len = obstack_object_size (obs); @@ -762,6 +767,7 @@ make_text_link (m4_obstack *obs, m4_symbol_chain **start, *start = chain; *end = chain; chain->next = NULL; + chain->quote_age = 0; chain->str = str; chain->len = len; chain->level = SIZE_MAX; @@ -905,13 +911,43 @@ init_builtin_token (m4 *context, m4_symbol_value *token) VALUE_MAX_ARGS (token) = block->u.u_b.builtin->max_args; } +/* When a QUOTE token is seen, convert VALUE to a composite (if it is + not one already), consisting of any unfinished text on OBS, as well + as the quoted token from the top of the input stack. Use OBS for + any additional allocations needed to store the token chain. */ +static void +append_quote_token (m4_obstack *obs, m4_symbol_value *value) +{ + m4_symbol_chain *src_chain = isp->u.u_c.chain; + m4_symbol_chain *chain; + assert (isp->funcs == &composite_funcs && obs); + + if (value->type == M4_SYMBOL_VOID) + { + value->type = M4_SYMBOL_COMP; + value->u.u_c.chain = value->u.u_c.end = NULL; + } + assert (value->type == M4_SYMBOL_COMP); + m4__make_text_link (obs, &value->u.u_c.chain, &value->u.u_c.end); + chain = (m4_symbol_chain *) obstack_copy (obs, src_chain, sizeof *chain); + if (value->u.u_c.end) + value->u.u_c.end->next = chain; + else + value->u.u_c.chain = chain; + value->u.u_c.end = chain; + value->u.u_c.end->next = NULL; + isp->u.u_c.chain = src_chain->next; +} + /* Low level input is done a character at a time. The function next_char () is used to read and advance the input to the next - character. If RETRY, then avoid returning CHAR_RETRY by popping - input. */ + character. If ALLOW_QUOTE, and the current input matches the + current quote age, return CHAR_QUOTE and leave consumption of data + for append_quote_token. If RETRY, then avoid returning CHAR_RETRY + by popping input. */ static int -next_char (m4 *context, bool retry) +next_char (m4 *context, bool allow_quote, bool retry) { int ch; @@ -931,7 +967,8 @@ next_char (m4 *context, bool retry) } assert (isp->funcs->read_func); - while ((ch = isp->funcs->read_func (isp, context, !retry)) != CHAR_RETRY + while (((ch = isp->funcs->read_func (isp, context, allow_quote, !retry)) + != CHAR_RETRY) || !retry) { /* if (!IS_IGNORE (ch)) */ @@ -960,7 +997,9 @@ peek_char (m4 *context) assert (block->funcs->peek_func); if ((ch = block->funcs->peek_func (block)) != CHAR_RETRY) { - return /* (IS_IGNORE (ch)) ? next_char (context, true) : */ ch; +/* if (IS_IGNORE (ch)) */ +/* return next_char (context, false, true); */ + return ch; } block = block->prev; @@ -969,7 +1008,7 @@ peek_char (m4 *context) /* The function unget_input () puts back a character on the input stack, using an existing input_block if possible. This is not safe - to call except immediately after next_char(context, false). */ + to call except immediately after next_char(context, allow, false). */ static void unget_input (int ch) { @@ -987,7 +1026,7 @@ m4_skip_line (m4 *context, const char *name) const char *file = m4_get_current_file (context); int line = m4_get_current_line (context); - while ((ch = next_char (context, true)) != CHAR_EOF && ch != '\n') + while ((ch = next_char (context, false, true)) != CHAR_EOF && ch != '\n') ; if (ch == CHAR_EOF) /* current_file changed; use the previous value we cached. */ @@ -1032,14 +1071,14 @@ match_input (m4 *context, const char *s, bool consume) if (s[1] == '\0') { if (consume) - next_char (context, true); + next_char (context, false, true); return true; /* short match */ } - next_char (context, true); + next_char (context, false, true); for (n = 1, t = s++; (ch = peek_char (context)) == to_uchar (*s++); ) { - next_char (context, true); + next_char (context, false, true); n++; if (*s == '\0') /* long match */ { @@ -1071,29 +1110,35 @@ match_input (m4 *context, const char *s, bool consume) /* While the current input character has the given SYNTAX, append it to OBS. Take care not to pop input source unless the next source - would continue the chain. Return true unless the chain ended with + would continue the chain. Return true if the chain ended with CHAR_EOF. */ static bool consume_syntax (m4 *context, m4_obstack *obs, unsigned int syntax) { int ch; + bool allow_quote = m4__safe_quotes (M4SYNTAX); assert (syntax); while (1) { /* It is safe to call next_char without first checking peek_char, except at input source boundaries, which we detect - by CHAR_RETRY. We exploit the fact that CHAR_EOF and - CHAR_MACRO do not satisfy any syntax categories. */ - while ((ch = next_char (context, false)) != CHAR_RETRY + by CHAR_RETRY. We exploit the fact that CHAR_EOF, + CHAR_BUILTIN, and CHAR_QUOTE do not satisfy any syntax + categories. */ + while ((ch = next_char (context, allow_quote, false)) != CHAR_RETRY && m4_has_syntax (M4SYNTAX, ch, syntax)) - obstack_1grow (obs, ch); - if (ch == CHAR_RETRY) + { + assert (ch < CHAR_EOF); + obstack_1grow (obs, ch); + } + if (ch == CHAR_RETRY || ch == CHAR_QUOTE) { ch = peek_char (context); if (m4_has_syntax (M4SYNTAX, ch, syntax)) { + assert (ch < CHAR_EOF); obstack_1grow (obs, ch); - next_char (context, true); + next_char (context, false, true); continue; } return ch == CHAR_EOF; @@ -1141,13 +1186,13 @@ m4_input_exit (void) } -/* Parse and return a single token from the input stream, built in - TOKEN. See m4__token_type for the valid return types, along with a - description of what TOKEN will contain. If LINE is not NULL, set - *LINE to the line number where the token starts. If OBS, expand - safe tokens (strings and comments) directly into OBS rather than in - a temporary staging area. Report errors (unterminated comments or - strings) on behalf of CALLER, if non-NULL. +/* Parse and return a single token from the input stream, constructed + into TOKEN. See m4__token_type for the valid return types, along + with a description of what TOKEN will contain. If LINE is not + NULL, set *LINE to the line number where the token starts. If OBS, + expand safe tokens (strings and comments) directly into OBS rather + than in a temporary staging area. Report errors (unterminated + comments or strings) on behalf of CALLER, if non-NULL. If OBS is NULL or the token expansion is unknown, the token text is collected on the obstack token_stack, which never contains more @@ -1177,7 +1222,6 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, do { obstack_free (&token_stack, token_bottom); - /* Must consume an input character, but not until CHAR_BUILTIN is handled. */ ch = peek_char (context); @@ -1186,28 +1230,29 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, #ifdef DEBUG_INPUT xfprintf (stderr, "next_token -> EOF\n"); #endif - next_char (context, true); + next_char (context, false, true); return M4_TOKEN_EOF; } if (ch == CHAR_BUILTIN) /* BUILTIN TOKEN */ { init_builtin_token (context, token); - next_char (context, true); + next_char (context, false, true); #ifdef DEBUG_INPUT m4_print_token ("next_token", M4_TOKEN_MACDEF, token); #endif return M4_TOKEN_MACDEF; } - next_char (context, true); /* Consume character we already peeked at. */ + /* Consume character we already peeked at. */ + next_char (context, false, true); file = m4_get_current_file (context); *line = m4_get_current_line (context); if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ESCAPE)) { /* ESCAPED WORD */ obstack_1grow (&token_stack, ch); - if ((ch = next_char (context, true)) != CHAR_EOF) + if ((ch = next_char (context, false, true)) < CHAR_EOF) { obstack_1grow (&token_stack, ch); if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ALPHA)) @@ -1234,12 +1279,13 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, quote_level = 1; while (1) { - ch = next_char (context, true); + ch = next_char (context, obs && m4__quote_age (M4SYNTAX), true); if (ch == CHAR_EOF) m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller, _("end of file in string")); - - if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_RQUOTE)) + if (ch == CHAR_QUOTE) + append_quote_token (obs, token); + else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_RQUOTE)) { if (--quote_level == 0) break; @@ -1261,9 +1307,10 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, if (obs) obs_safe = obs; quote_level = 1; + assert (!m4__quote_age (M4SYNTAX)); while (1) { - ch = next_char (context, true); + ch = next_char (context, false, true); if (ch == CHAR_EOF) m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller, _("end of file in string")); @@ -1290,11 +1337,14 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, if (obs && !m4_get_discard_comments_opt (context)) obs_safe = obs; obstack_1grow (obs_safe, ch); - while ((ch = next_char (context, true)) != CHAR_EOF + while ((ch = next_char (context, false, true)) < CHAR_EOF && !m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ECOMM)) obstack_1grow (obs_safe, ch); if (ch != CHAR_EOF) - obstack_1grow (obs_safe, ch); + { + assert (ch < CHAR_EOF); + obstack_1grow (obs_safe, ch); + } else m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller, _("end of file in comment")); @@ -1308,12 +1358,15 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, obs_safe = obs; obstack_grow (obs_safe, context->syntax->bcomm.string, context->syntax->bcomm.length); - while ((ch = next_char (context, true)) != CHAR_EOF + while ((ch = next_char (context, false, true)) < CHAR_EOF && !MATCH (context, ch, context->syntax->ecomm.string, true)) obstack_1grow (obs_safe, ch); if (ch != CHAR_EOF) - obstack_grow (obs_safe, context->syntax->ecomm.string, - context->syntax->ecomm.length); + { + assert (ch < CHAR_EOF); + obstack_grow (obs_safe, context->syntax->ecomm.string, + context->syntax->ecomm.length); + } else m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller, _("end of file in comment")); @@ -1343,6 +1396,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, else if (m4_is_syntax_single_quotes (M4SYNTAX) && m4_is_syntax_single_comments (M4SYNTAX)) { /* EVERYTHING ELSE (SHORT QUOTES AND COMMENTS) */ + assert (ch < CHAR_EOF); obstack_1grow (&token_stack, ch); if (m4_has_syntax (M4SYNTAX, ch, @@ -1374,6 +1428,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, } else /* EVERYTHING ELSE (LONG QUOTES OR COMMENTS) */ { + assert (ch < CHAR_EOF); obstack_1grow (&token_stack, ch); if (m4_has_syntax (M4SYNTAX, ch, @@ -1394,16 +1449,21 @@ m4__next_token (m4 *context, m4_symbol_value *token, int *line, } } while (type == M4_TOKEN_NONE); - if (obs_safe != obs) + if (token->type == M4_SYMBOL_VOID) { - len = obstack_object_size (&token_stack); - obstack_1grow (&token_stack, '\0'); + if (obs_safe != obs) + { + len = obstack_object_size (&token_stack); + obstack_1grow (&token_stack, '\0'); - m4_set_symbol_value_text (token, obstack_finish (&token_stack), len, - m4__quote_age (M4SYNTAX)); + m4_set_symbol_value_text (token, obstack_finish (&token_stack), len, + m4__quote_age (M4SYNTAX)); + } + else + assert (type == M4_TOKEN_STRING); } else - assert (type == M4_TOKEN_STRING); + assert (token->type == M4_SYMBOL_COMP && type == M4_TOKEN_STRING); VALUE_MAX_ARGS (token) = -1; #ifdef DEBUG_INPUT @@ -1440,46 +1500,58 @@ m4__next_token_is_open (m4 *context) int m4_print_token (const char *s, m4__token_type type, m4_symbol_value *token) { - xfprintf (stderr, "%s: ", s ? s : "m4input"); + m4_obstack obs; + size_t len; + + obstack_init (&obs); + if (!s) + s = "m4input"; + obstack_grow (&obs, s, strlen (s)); + obstack_1grow (&obs, ':'); + obstack_1grow (&obs, ' '); switch (type) { /* TOKSW */ case M4_TOKEN_EOF: - xfprintf (stderr, "eof\n"); + obstack_grow (&obs, "eof", strlen ("eof")); + token = NULL; break; case M4_TOKEN_NONE: - xfprintf (stderr, "none\n"); + obstack_grow (&obs, "none", strlen ("none")); + token = NULL; break; case M4_TOKEN_STRING: - xfprintf (stderr, "string\t\"%s\"\n", m4_get_symbol_value_text (token)); + obstack_grow (&obs, "string\t", strlen ("string\t")); break; case M4_TOKEN_SPACE: - xfprintf (stderr, "space\t\"%s\"\n", m4_get_symbol_value_text (token)); + obstack_grow (&obs, "space\t", strlen ("space\t")); break; case M4_TOKEN_WORD: - xfprintf (stderr, "word\t\"%s\"\n", m4_get_symbol_value_text (token)); + obstack_grow (&obs, "word\t", strlen ("word\t")); break; case M4_TOKEN_OPEN: - xfprintf (stderr, "open\t\"%s\"\n", m4_get_symbol_value_text (token)); + obstack_grow (&obs, "open\t", strlen ("open\t")); break; case M4_TOKEN_COMMA: - xfprintf (stderr, "comma\t\"%s\"\n", m4_get_symbol_value_text (token)); + obstack_grow (&obs, "comma\t", strlen ("comma\t")); break; case M4_TOKEN_CLOSE: - xfprintf (stderr, "close\t\"%s\"\n", m4_get_symbol_value_text (token)); + obstack_grow (&obs, "close\t", strlen ("close\t")); break; case M4_TOKEN_SIMPLE: - xfprintf (stderr, "simple\t\"%s\"\n", m4_get_symbol_value_text (token)); + obstack_grow (&obs, "simple\t", strlen ("simple\t")); break; case M4_TOKEN_MACDEF: - { - const m4_builtin *bp; - bp = m4_builtin_find_by_func (NULL, m4_get_symbol_value_func (token)); - assert (bp); - xfprintf (stderr, "builtin\t<%s>{%s}\n", bp->name, - m4_get_module_name (VALUE_MODULE (token))); - } + obstack_grow (&obs, "builtin\t", strlen ("builtin\t")); break; + default: + abort (); } + if (token) + m4_symbol_value_print (token, &obs, true, "\"", "\"", SIZE_MAX, NULL); + obstack_1grow (&obs, '\n'); + len = obstack_object_size (&obs); + fwrite (obstack_finish (&obs), 1, len, stderr); + obstack_free (&obs, NULL); return 0; } #endif /* DEBUG_INPUT */ diff --git a/m4/m4module.h b/m4/m4module.h index 03025af..330a90e 100644 --- a/m4/m4module.h +++ b/m4/m4module.h @@ -1,7 +1,7 @@ /* GNU m4 -- A simple macro processor Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 1999, 2000, 2003, - 2004, 2005, 2006, 2007 Free Software Foundation, Inc. + 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. This file is part of GNU M4. @@ -102,8 +102,9 @@ struct m4_macro m4_module_import (context, STR (M), STR (S), obs) /* Grab the text contents of argument I, or abort if the argument is - not text. Assumes that `m4_macro_args *argv' is in scope. */ -#define M4ARG(i) m4_arg_text (argv, i) + not text. Assumes that `m4 *context' and `m4_macro_args *argv' are + in scope. */ +#define M4ARG(i) m4_arg_text (context, argv, i) extern bool m4_bad_argc (m4 *, int, const char *, unsigned int, unsigned int, bool); @@ -304,7 +305,7 @@ extern unsigned int m4_arg_argc (m4_macro_args *); extern m4_symbol_value *m4_arg_symbol (m4_macro_args *, unsigned int); extern bool m4_is_arg_text (m4_macro_args *, unsigned int); extern bool m4_is_arg_func (m4_macro_args *, unsigned int); -extern const char *m4_arg_text (m4_macro_args *, unsigned int); +extern const char *m4_arg_text (m4 *, m4_macro_args *, unsigned int); extern bool m4_arg_equal (m4_macro_args *, unsigned int, unsigned int); extern bool m4_arg_empty (m4_macro_args *, unsigned int); diff --git a/m4/m4private.h b/m4/m4private.h index 630a9b7..6a08455 100644 --- a/m4/m4private.h +++ b/m4/m4private.h @@ -35,7 +35,7 @@ typedef enum { M4_SYMBOL_TEXT, /* Plain text, u.u_t is valid. */ M4_SYMBOL_FUNC, /* Builtin function, u.func is valid. */ M4_SYMBOL_PLACEHOLDER, /* Placeholder for unknown builtin from -R. */ - M4_SYMBOL_COMP /* Composite symbol, u.chain is valid. */ + M4_SYMBOL_COMP /* Composite symbol, u.u_c.c is valid. */ } m4__symbol_type; #define BIT_TEST(flags, bit) (((flags) & (bit)) == (bit)) @@ -197,6 +197,7 @@ struct m4_symbol struct m4_symbol_chain { m4_symbol_chain *next;/* Pointer to next link of chain. */ + unsigned int quote_age; /* Quote_age of this link of chain, or 0. */ const char *str; /* NUL-terminated string if text, or NULL. */ size_t len; /* Length of str, or 0. */ size_t level; /* Expansion level of content, or SIZE_MAX. */ @@ -230,7 +231,11 @@ struct m4_symbol_value unsigned int quote_age; } u_t; /* Valid when type is TEXT, PLACEHOLDER. */ const m4_builtin * builtin;/* Valid when type is FUNC. */ - m4_symbol_chain * chain; /* Valid when type is COMP. */ + struct + { + m4_symbol_chain * chain; /* First link of the chain. */ + m4_symbol_chain * end; /* Last link of the chain. */ + } u_c; /* Valid when type is COMP. */ } u; }; @@ -248,6 +253,9 @@ struct m4_macro_args bool_bitfield inuse : 1; /* False if all arguments are just text or func, true if this argv refers to another one. */ + bool_bitfield wrapper : 1; + /* False if all arguments belong to this argv, true if some of them + include references to another. */ bool_bitfield has_ref : 1; const char *argv0; /* The macro name being expanded. */ size_t argv0_len; /* Length of argv0. */ @@ -365,7 +373,8 @@ extern void m4__symtab_remove_module_references (m4_symbol_table*, all other characters and sentinels. */ #define CHAR_EOF 256 /* Character return on EOF. */ #define CHAR_BUILTIN 257 /* Character return for BUILTIN token. */ -#define CHAR_RETRY 258 /* Character return for end of input block. */ +#define CHAR_QUOTE 258 /* Character return for quoted string. */ +#define CHAR_RETRY 259 /* Character return for end of input block. */ #define DEF_LQUOTE "`" /* Default left quote delimiter. */ #define DEF_RQUOTE "\'" /* Default right quote delimiter. */ @@ -451,6 +460,8 @@ typedef enum { M4_TOKEN_MACDEF /* Macro's definition (see "defn"), M4_SYMBOL_FUNC. */ } m4__token_type; +extern void m4__make_text_link (m4_obstack *, m4_symbol_chain **, + m4_symbol_chain **); extern bool m4__push_symbol (m4 *, m4_symbol_value *, size_t); extern m4__token_type m4__next_token (m4 *, m4_symbol_value *, int *, m4_obstack *, const char *); diff --git a/m4/macro.c b/m4/macro.c index 9963409..683dd26 100644 --- a/m4/macro.c +++ b/m4/macro.c @@ -334,9 +334,15 @@ expand_argument (m4 *context, m4_obstack *obs, m4_symbol_value *argp, len = obstack_object_size (obs); if (argp->type == M4_SYMBOL_FUNC && !len) return type == M4_TOKEN_COMMA; - obstack_1grow (obs, '\0'); - VALUE_MODULE (argp) = NULL; - m4_set_symbol_value_text (argp, obstack_finish (obs), len, age); + if (argp->type != M4_SYMBOL_COMP) + { + obstack_1grow (obs, '\0'); + VALUE_MODULE (argp) = NULL; + m4_set_symbol_value_text (argp, obstack_finish (obs), len, + age); + } + else + m4__make_text_link (obs, NULL, &argp->u.u_c.end); return type == M4_TOKEN_COMMA; } /* fallthru */ @@ -360,6 +366,20 @@ expand_argument (m4 *context, m4_obstack *obs, m4_symbol_value *argp, case M4_TOKEN_STRING: if (!expand_token (context, obs, type, &token, line, first)) age = 0; + if (token.type == M4_SYMBOL_COMP) + { + if (argp->type != M4_SYMBOL_COMP) + { + argp->type = M4_SYMBOL_COMP; + argp->u.u_c.chain = token.u.u_c.chain; + } + else + { + assert (argp->u.u_c.end); + argp->u.u_c.end->next = token.u.u_c.chain; + } + argp->u.u_c.end = token.u.u_c.end; + } break; case M4_TOKEN_MACDEF: @@ -502,8 +522,23 @@ recursion limit of %zu exceeded, use -L<N> to change it"), if (BIT_TEST (VALUE_FLAGS (value), VALUE_DELETED_BIT)) m4_symbol_value_delete (value); - /* If argv contains references, those refcounts can be reduced now. */ - /* TODO - support references in argv. */ + /* If argv contains references, those refcounts must be reduced now. */ + if (argv->has_ref) + { + m4_symbol_chain *chain; + size_t i; + for (i = 0; i < argv->arraylen; i++) + if (argv->array[i]->type == M4_SYMBOL_COMP) + { + chain = argv->array[i]->u.u_c.chain; + while (chain) + { + if (chain->level < SIZE_MAX) + m4__adjust_refcount (context, chain->level, false); + chain = chain->next; + } + } + } /* We no longer need argv, so reduce the refcount. Additionally, if no other references to argv were created, we can free our portion @@ -550,6 +585,7 @@ collect_arguments (m4 *context, const char *name, size_t len, args.argc = 1; args.inuse = false; + args.wrapper = false; args.has_ref = false; /* Must copy here, since we are consuming tokens, and since symbol table can be changed during argument collection. */ @@ -587,11 +623,14 @@ collect_arguments (m4 *context, const char *name, size_t len, && m4_get_symbol_value_len (tokenp) && m4_get_symbol_value_quote_age (tokenp) != args.quote_age) args.quote_age = 0; + else if (tokenp->type == M4_SYMBOL_COMP) + args.has_ref = true; } while (more_args); } argv = (m4_macro_args *) obstack_finish (argv_stack); argv->argc = args.argc; + argv->has_ref = args.has_ref; if (args.quote_age != m4__quote_age (M4SYNTAX)) argv->quote_age = 0; argv->arraylen = args.arraylen; @@ -674,8 +713,7 @@ process_macro (m4 *context, m4_symbol_value *value, m4_obstack *obs, text = endp; } if (i < argc) - m4_shipout_string (context, obs, M4ARG (i), m4_arg_len (argv, i), - false); + m4_push_arg (context, obs, argv, i); break; case '#': /* number of arguments */ @@ -947,14 +985,14 @@ static void arg_mark (m4_macro_args *argv) { argv->inuse = true; - if (argv->has_ref) + if (argv->wrapper) { /* TODO for now we support only a single-length $@ chain. */ assert (argv->arraylen == 1 && argv->array[0]->type == M4_SYMBOL_COMP - && !argv->array[0]->u.chain->next - && !argv->array[0]->u.chain->str); - argv->array[0]->u.chain->argv->inuse = true; + && !argv->array[0]->u.u_c.chain->next + && !argv->array[0]->u.u_c.chain->str); + argv->array[0]->u.u_c.chain->argv->inuse = true; } } @@ -970,7 +1008,7 @@ m4_arg_symbol (m4_macro_args *argv, unsigned int index) if (argv->argc <= index) return &empty_symbol; - if (!argv->has_ref) + if (!argv->wrapper) return argv->array[index - 1]; /* Must cycle through all array slots until we find index, since wrappers can contain multiple arguments. */ @@ -979,7 +1017,7 @@ m4_arg_symbol (m4_macro_args *argv, unsigned int index) value = argv->array[i]; if (value->type == M4_SYMBOL_COMP) { - m4_symbol_chain *chain = value->u.chain; + m4_symbol_chain *chain = value->u.u_c.chain; /* TODO - for now we support only a single $@ chain. */ assert (!chain->next && !chain->str); if (index < chain->argv->argc - (chain->index - 1)) @@ -994,7 +1032,6 @@ m4_arg_symbol (m4_macro_args *argv, unsigned int index) else if (--index == 0) break; } - assert (value->type != M4_SYMBOL_COMP); return value; } @@ -1003,9 +1040,14 @@ m4_arg_symbol (m4_macro_args *argv, unsigned int index) bool m4_is_arg_text (m4_macro_args *argv, unsigned int index) { + m4_symbol_value *value; if (index == 0 || argv->argc <= index) return true; - return m4_is_symbol_value_text (m4_arg_symbol (argv, index)); + value = m4_arg_symbol (argv, index); + /* Composite tokens are currently sequences of text only. */ + if (m4_is_symbol_value_text (value) || value->type == M4_SYMBOL_COMP) + return true; + return false; } /* Given ARGV, return true if argument INDEX is a builtin function. @@ -1020,37 +1062,125 @@ m4_is_arg_func (m4_macro_args *argv, unsigned int index) /* Given ARGV, return the text at argument INDEX. Abort if the argument is not text. Index 0 is always text, and indices beyond - argc return the empty string. */ + argc return the empty string. The result is always NUL-terminated, + even if it includes embedded NUL characters. */ const char * -m4_arg_text (m4_macro_args *argv, unsigned int index) +m4_arg_text (m4 *context, m4_macro_args *argv, unsigned int index) { m4_symbol_value *value; + m4_symbol_chain *chain; + m4_obstack *obs; if (index == 0) return argv->argv0; if (argv->argc <= index) return ""; value = m4_arg_symbol (argv, index); - return m4_get_symbol_value_text (value); + if (m4_is_symbol_value_text (value)) + return m4_get_symbol_value_text (value); + /* TODO - concatenate argv refs and functions? For now, we assume + all chain elements are text. */ + assert (value->type == M4_SYMBOL_COMP); + chain = value->u.u_c.chain; + obs = m4_arg_scratch (context); + while (chain) + { + assert (chain->str); + obstack_grow (obs, chain->str, chain->len); + chain = chain->next; + } + obstack_1grow (obs, '\0'); + return (char *) obstack_finish (obs); } /* Given ARGV, compare text arguments INDEXA and INDEXB for equality. Both indices must be non-zero. Return true if the arguments contain the same contents; often more efficient than - !strcmp (m4_arg_text (argv, indexa), m4_arg_text (argv, indexb)). */ + !strcmp (m4_arg_text (context, argv, indexa), + m4_arg_text (context, argv, indexb)). */ bool m4_arg_equal (m4_macro_args *argv, unsigned int indexa, unsigned int indexb) { m4_symbol_value *sa = m4_arg_symbol (argv, indexa); m4_symbol_value *sb = m4_arg_symbol (argv, indexb); + m4_symbol_chain tmpa; + m4_symbol_chain tmpb; + m4_symbol_chain *ca = &tmpa; + m4_symbol_chain *cb = &tmpb; + /* Quick tests. */ if (sa == &empty_symbol || sb == &empty_symbol) return sa == sb; + if (m4_is_symbol_value_text (sa) && m4_is_symbol_value_text (sb)) + return (m4_get_symbol_value_len (sa) == m4_get_symbol_value_len (sb) + && memcmp (m4_get_symbol_value_text (sa), + m4_get_symbol_value_text (sb), + m4_get_symbol_value_len (sa)) == 0); + + /* Convert both arguments to chains, if not one already. */ /* TODO - allow builtin tokens in the comparison? */ - assert (m4_is_symbol_value_text (sa) && m4_is_symbol_value_text (sb)); - return (m4_get_symbol_value_len (sa) == m4_get_symbol_value_len (sb) - && strcmp (m4_get_symbol_value_text (sa), - m4_get_symbol_value_text (sb)) == 0); + if (m4_is_symbol_value_text (sa)) + { + tmpa.next = NULL; + tmpa.str = m4_get_symbol_value_text (sa); + tmpa.len = m4_get_symbol_value_len (sa); + } + else + { + assert (sa->type == M4_SYMBOL_COMP); + ca = sa->u.u_c.chain; + } + if (m4_is_symbol_value_text (sb)) + { + tmpb.next = NULL; + tmpb.str = m4_get_symbol_value_text (sb); + tmpb.len = m4_get_symbol_value_len (sb); + } + else + { + assert (sb->type == M4_SYMBOL_COMP); + cb = sb->u.u_c.chain; + } + + /* Compare each link of the chain. */ + while (ca && cb) + { + /* TODO support comparison against $@ refs. */ + assert (ca->str && cb->str); + if (ca->len == cb->len) + { + if (memcmp (ca->str, cb->str, ca->len) != 0) + return false; + ca = ca->next; + cb = cb->next; + } + else if (ca->len < cb->len) + { + if (memcmp (ca->str, cb->str, ca->len) != 0) + return false; + tmpb.next = cb->next; + tmpb.str = cb->str + ca->len; + tmpb.len = cb->len - ca->len; + ca = ca->next; + cb = &tmpb; + } + else + { + assert (cb->len < ca->len); + if (memcmp (ca->str, cb->str, cb->len) != 0) + return false; + tmpa.next = ca->next; + tmpa.str = ca->str + cb->len; + tmpa.len = ca->len - cb->len; + ca = &tmpa; + cb = cb->next; + } + } + + /* If we get this far, the two arguments are equal only if both + chains are exhausted. */ + assert (ca != cb || !ca); + return ca == cb; } /* Given ARGV, return true if argument INDEX is the empty string. @@ -1069,13 +1199,28 @@ size_t m4_arg_len (m4_macro_args *argv, unsigned int index) { m4_symbol_value *value; + m4_symbol_chain *chain; + size_t len; if (index == 0) return argv->argv0_len; if (argv->argc <= index) return 0; value = m4_arg_symbol (argv, index); - return m4_get_symbol_value_len (value); + if (m4_is_symbol_value_text (value)) + return m4_get_symbol_value_len (value); + /* TODO - for now, we assume all chain links are text. */ + assert (value->type == M4_SYMBOL_COMP); + chain = value->u.u_c.chain; + len = 0; + while (chain) + { + assert (chain->str); + len += chain->len; + chain = chain->next; + } + assert (len); + return len; } /* Given ARGV, return the builtin function referenced by argument @@ -1105,11 +1250,11 @@ m4_make_argv_ref (m4 *context, m4_macro_args *argv, const char *argv0, /* When making a reference through a reference, point to the original if possible. */ - if (argv->has_ref) + if (argv->wrapper) { /* TODO for now we support only a single-length $@ chain. */ assert (argv->arraylen == 1 && argv->array[0]->type == M4_SYMBOL_COMP); - chain = argv->array[0]->u.chain; + chain = argv->array[0]->u.u_c.chain; assert (!chain->next && !chain->str); argv = chain->argv; index += chain->index - 1; @@ -1130,10 +1275,12 @@ m4_make_argv_ref (m4 *context, m4_macro_args *argv, const char *argv0, chain = (m4_symbol_chain *) obstack_alloc (obs, sizeof *chain); new_argv->arraylen = 1; new_argv->array[0] = value; + new_argv->wrapper = true; new_argv->has_ref = true; value->type = M4_SYMBOL_COMP; - value->u.chain = chain; + value->u.u_c.chain = value->u.u_c.end = chain; chain->next = NULL; + chain->quote_age = argv->quote_age; chain->str = NULL; chain->len = 0; chain->level = context->expansion_level - 1; @@ -1170,9 +1317,23 @@ m4_push_arg (m4 *context, m4_obstack *obs, m4_macro_args *argv, return; } /* TODO handle builtin tokens? */ - assert (value->type == M4_SYMBOL_TEXT); - if (m4__push_symbol (context, value, context->expansion_level - 1)) - arg_mark (argv); + if (value->type == M4_SYMBOL_TEXT) + { + if (m4__push_symbol (context, value, context->expansion_level - 1)) + arg_mark (argv); + } + else if (value->type == M4_SYMBOL_COMP) + { + /* TODO - really handle composites; for now, just flatten the + composite and push its text. */ + m4_symbol_chain *chain = value->u.u_c.chain; + while (chain) + { + assert (chain->str); + obstack_grow (obs, chain->str, chain->len); + chain = chain->next; + } + } } /* Push series of comma-separated arguments from ARGV, which should @@ -1184,6 +1345,7 @@ m4_push_args (m4 *context, m4_obstack *obs, m4_macro_args *argv, bool skip, bool quote) { m4_symbol_value *value; + m4_symbol_chain *chain; unsigned int i = skip ? 2 : 1; const char *sep = ","; size_t sep_len = 1; @@ -1226,8 +1388,21 @@ m4_push_args (m4 *context, m4_obstack *obs, m4_macro_args *argv, bool skip, else use_sep = true; /* TODO handle builtin tokens? */ - assert (value->type == M4_SYMBOL_TEXT); - inuse |= m4__push_symbol (context, value, context->expansion_level - 1); + if (value->type == M4_SYMBOL_TEXT) + inuse |= m4__push_symbol (context, value, + context->expansion_level - 1); + else + { + /* TODO handle composite text. */ + assert (value->type == M4_SYMBOL_COMP); + chain = value->u.u_c.chain; + while (chain) + { + assert (chain->str); + obstack_grow (obs, chain->str, chain->len); + chain = chain->next; + } + } } if (quote) obstack_grow (obs, rquote, strlen (rquote)); diff --git a/m4/output.c b/m4/output.c index f745efe..dc2194f 100644 --- a/m4/output.c +++ b/m4/output.c @@ -602,7 +602,8 @@ m4_shipout_string (m4 *context, m4_obstack *obs, const char *s, size_t len, current quote characters around S. If LEN is SIZE_MAX, use the string length of S instead. If MAX_LEN, reduce *MAX_LEN by LEN. If LEN is larger than *MAX_LEN, then truncate output and return - true; otherwise return false. */ + true; otherwise return false. CONTEXT may be NULL if QUOTED is + false. */ bool m4_shipout_string_trunc (m4 *context, m4_obstack *obs, const char *s, size_t len, bool quoted, size_t *max_len) diff --git a/m4/symtab.c b/m4/symtab.c index 30a61ed..3ff6f0d 100644 --- a/m4/symtab.c +++ b/m4/symtab.c @@ -1,6 +1,6 @@ /* GNU m4 -- A simple macro processor - Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 2001, 2005, 2006, 2007 - Free Software Foundation, Inc. + Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 2001, 2005, 2006, + 2007, 2008 Free Software Foundation, Inc. This file is part of GNU M4. @@ -326,10 +326,21 @@ m4_symbol_value_delete (m4_symbol_value *value) m4_hash_apply (VALUE_ARG_SIGNATURE (value), arg_destroy_CB, NULL); m4_hash_delete (VALUE_ARG_SIGNATURE (value)); } - if (m4_is_symbol_value_text (value)) - free ((char *) m4_get_symbol_value_text (value)); - else if (m4_is_symbol_value_placeholder (value)) - free ((char *) m4_get_symbol_value_placeholder (value)); + switch (value->type) + { + case M4_SYMBOL_TEXT: + free ((char *) m4_get_symbol_value_text (value)); + break; + case M4_SYMBOL_PLACEHOLDER: + free ((char *) m4_get_symbol_value_placeholder (value)); + break; + case M4_SYMBOL_VOID: + case M4_SYMBOL_FUNC: + break; + default: + assert (!"m4_symbol_value_delete"); + abort (); + } free (value); } } @@ -392,10 +403,21 @@ m4_symbol_value_copy (m4_symbol_value *dest, m4_symbol_value *src) assert (dest); assert (src); - if (m4_is_symbol_value_text (dest)) - free ((char *) m4_get_symbol_value_text (dest)); - else if (m4_is_symbol_value_placeholder (dest)) - free ((char *) m4_get_symbol_value_placeholder (dest)); + switch (dest->type) + { + case M4_SYMBOL_TEXT: + free ((char *) m4_get_symbol_value_text (dest)); + break; + case M4_SYMBOL_PLACEHOLDER: + free ((char *) m4_get_symbol_value_placeholder (dest)); + break; + case M4_SYMBOL_VOID: + case M4_SYMBOL_FUNC: + break; + default: + assert (!"m4_symbol_value_delete"); + abort (); + } if (VALUE_ARG_SIGNATURE (dest)) { @@ -411,19 +433,54 @@ m4_symbol_value_copy (m4_symbol_value *dest, m4_symbol_value *src) /* Caller is supposed to free text token strings, so we have to copy the string not just its address in that case. */ - if (m4_is_symbol_value_text (src)) + switch (src->type) { - size_t len = m4_get_symbol_value_len (src); - unsigned int age = m4_get_symbol_value_quote_age (src); - m4_set_symbol_value_text (dest, - xmemdup (m4_get_symbol_value_text (src), - len + 1), len, age); + case M4_SYMBOL_TEXT: + { + size_t len = m4_get_symbol_value_len (src); + unsigned int age = m4_get_symbol_value_quote_age (src); + m4_set_symbol_value_text (dest, + xmemdup (m4_get_symbol_value_text (src), + len + 1), len, age); + } + break; + case M4_SYMBOL_FUNC: + /* Nothing further to do. */ + break; + case M4_SYMBOL_PLACEHOLDER: + m4_set_symbol_value_placeholder (dest, + xstrdup (m4_get_symbol_value_placeholder + (src))); + break; + case M4_SYMBOL_COMP: + { + m4_symbol_chain *chain = src->u.u_c.chain; + size_t len = 0; + char *str; + char *p; + while (chain) + { + /* TODO for now, only text links are supported. */ + assert (chain->str); + len += chain->len; + chain = chain->next; + } + p = str = xcharalloc (len + 1); + chain = src->u.u_c.chain; + while (chain) + { + memcpy (p, chain->str, chain->len); + p += chain->len; + chain = chain->next; + } + *p = '\0'; + m4_set_symbol_value_text (dest, str, len, 0); + } + break; + default: + assert (!"m4_symbol_value_copy"); + abort (); } - else if (m4_is_symbol_value_placeholder (src)) - m4_set_symbol_value_placeholder (dest, - xstrdup (m4_get_symbol_value_placeholder - (src))); - if (VALUE_ARG_SIGNATURE (src)) VALUE_ARG_SIGNATURE (dest) = m4_hash_dup (VALUE_ARG_SIGNATURE (src), arg_copy_CB); @@ -488,8 +545,9 @@ m4_symbol_value_print (m4_symbol_value *value, m4_obstack *obs, bool quote, size_t len; bool truncated = false; - if (m4_is_symbol_value_text (value)) + switch (value->type) { + case M4_SYMBOL_TEXT: text = m4_get_symbol_value_text (value); len = m4_get_symbol_value_len (value); if (maxlen < len) @@ -497,27 +555,45 @@ m4_symbol_value_print (m4_symbol_value *value, m4_obstack *obs, bool quote, len = maxlen; truncated = true; } - } - else if (m4_is_symbol_value_func (value)) - { - const m4_builtin *bp = m4_get_symbol_value_builtin (value); - text = bp->name; - len = strlen (text); - lquote = "<"; - rquote = ">"; - quote = true; - } - else if (m4_is_symbol_value_placeholder (value)) - { + break; + case M4_SYMBOL_FUNC: + { + const m4_builtin *bp = m4_get_symbol_value_builtin (value); + text = bp->name; + len = strlen (text); + lquote = "<"; + rquote = ">"; + quote = true; + } + break; + case M4_SYMBOL_PLACEHOLDER: text = m4_get_symbol_value_placeholder (value); /* FIXME - is it worth translating "placeholder for "? */ len = strlen (text); lquote = "<placeholder for "; rquote = ">"; quote = true; - } - else - { + break; + case M4_SYMBOL_COMP: + { + m4_symbol_chain *chain = value->u.u_c.chain; + if (quote) + obstack_grow (obs, lquote, strlen (lquote)); + while (chain) + { + /* TODO for now, assume all links are text. */ + assert (chain->str); + if (m4_shipout_string_trunc (NULL, obs, chain->str, chain->len, + false, &maxlen)) + break; + chain = chain->next; + } + if (quote) + obstack_grow (obs, rquote, strlen (rquote)); + assert (!module); + return; + } + default: assert (!"invalid token in symbol_value_print"); abort (); } diff --git a/tests/macros.at b/tests/macros.at index 367d47e..3d74356 100644 --- a/tests/macros.at +++ b/tests/macros.at @@ -1,5 +1,5 @@ # Hand crafted tests for GNU M4. -*- Autotest -*- -# Copyright (C) 2001, 2006, 2007 Free Software Foundation, Inc. +# Copyright (C) 2001, 2006, 2007, 2008 Free Software Foundation, Inc. # This file is part of GNU M4. # @@ -535,6 +535,24 @@ AT_CHECK_M4([in], [0], [[40 ]]) AT_DATA([in], [[define(`echo', `$@')dnl +define(`foo', echo(`01234567890123456789')echo(`98765432109876543210'))dnl +foo +]]) + +AT_CHECK_M4([in], [0], [[0123456789012345678998765432109876543210 +]]) + +AT_DATA([in], [[define(`a', `A')define(`echo', `$@')define(`join', `$1$2')dnl +define(`abcdefghijklmnopqrstuvwxyz', `Z')dnl +join(`a', `bcdefghijklmnopqrstuvwxyz') +join(`a', echo(`bcdefghijklmnopqrstuvwxyz')) +]]) + +AT_CHECK_M4([in], [0], [[Z +Z +]]) + +AT_DATA([in], [[define(`echo', `$@')dnl echo(echo(`01234567890123456789', `01234567890123456789') echo(`98765432109876543210', `98765432109876543210')) len((echo(`01234567890123456789', -- 1.5.3.8 From c2c0a7ddc9f559d66a17184ea8be2c363dd4807c Mon Sep 17 00:00:00 2001 From: Eric Blake <[email protected]> Date: Sat, 27 Oct 2007 05:44:09 -0600 Subject: [PATCH] Stage 11: full circle for single argument references. Pass quoted strings through to argument collection in a single action, so that an argument can be reused throughout macro recursion if it remains unchanged. Memory impact: noticeable improvement, due to more reuse in argument collection stacks. Speed impact: noticeable improvement, due to less copying. * src/m4.h (struct token_chain): Add quote_age member. (struct token_data): Add end member to chain alternate. (make_text_link): New prototype. * src/input.c (CHAR_QUOTE): New macro. (word_start): Pre-allocate. (set_word_regexp): Simplify. (make_text_link): Export, and handle new fields. (next_char, next_char_1): Add parameter. (append_quote_token): New function. (match_input, next_token): Adjust callers to handle quoted input blocks. * src/macro.c (struct macro_arguments): Add wrapper member. (expand_argument): Accept composite blocks from input engine. (expand_macro): Reduce refcounts of composite arguments. (collect_arguments, arg_token, arg_mark, make_argv_ref): Update to use new fields. (arg_type, arg_text, arg_equal, arg_len): Treat composite arguments as text. (push_arg, push_args): Handle composites. (cherry picked from commit b1fef201f5d121e25e5dd61ec8ca3eac41a899ba) Signed-off-by: Eric Blake <[email protected]> --- ChangeLog | 29 ++++++++ src/input.c | 207 +++++++++++++++++++++++++++++++++-------------------- src/m4.h | 25 ++++--- src/macro.c | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 376 insertions(+), 118 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5ad26e3..15549a6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,32 @@ +2008-01-22 Eric Blake <[email protected]> + + Stage 11: full circle for single argument references. + Pass quoted strings through to argument collection in a single + action, so that an argument can be reused throughout macro + recursion if it remains unchanged. + Memory impact: noticeable improvement, due to more reuse in + argument collection stacks. + Speed impact: noticeable improvement, due to less copying. + * src/m4.h (struct token_chain): Add quote_age member. + (struct token_data): Add end member to chain alternate. + (make_text_link): New prototype. + * src/input.c (CHAR_QUOTE): New macro. + (word_start): Pre-allocate. + (set_word_regexp): Simplify. + (make_text_link): Export, and handle new fields. + (next_char, next_char_1): Add parameter. + (append_quote_token): New function. + (match_input, next_token): Adjust callers to handle quoted input + blocks. + * src/macro.c (struct macro_arguments): Add wrapper member. + (expand_argument): Accept composite blocks from input engine. + (expand_macro): Reduce refcounts of composite arguments. + (collect_arguments, arg_token, arg_mark, make_argv_ref): Update to + use new fields. + (arg_type, arg_text, arg_equal, arg_len): Treat composite + arguments as text. + (push_arg, push_args): Handle composites. + 2008-01-17 Eric Blake <[email protected]> Stage 10: avoid extra copying of strings and comments. diff --git a/src/input.c b/src/input.c index bc73c6f..9f25e8f 100644 --- a/src/input.c +++ b/src/input.c @@ -153,6 +153,7 @@ static bool input_change; #define CHAR_EOF 256 /* Character return on EOF. */ #define CHAR_MACRO 257 /* Character return for MACRO token. */ +#define CHAR_QUOTE 258 /* Character return for quoted string. */ /* Quote chars. */ STRING rquote; @@ -167,7 +168,7 @@ STRING ecomm; # define DEFAULT_WORD_REGEXP "[_a-zA-Z][_a-zA-Z0-9]*" /* Table of characters that can start a word. */ -static char *word_start; +static char word_start[256]; /* Current regular expression for detecting words. */ static struct re_pattern_buffer word_regexp; @@ -201,7 +202,7 @@ static const char *token_type_string (token_type); | chain that starts at *START and ends at *END. START may be NULL | | if *END is non-NULL. | `-------------------------------------------------------------------*/ -static void +void make_text_link (struct obstack *obs, token_chain **start, token_chain **end) { token_chain *chain; @@ -218,6 +219,7 @@ make_text_link (struct obstack *obs, token_chain **start, token_chain **end) *start = chain; *end = chain; chain->next = NULL; + chain->quote_age = 0; chain->str = str; chain->len = len; chain->level = -1; @@ -361,6 +363,7 @@ push_token (token_data *token, int level) next->u.u_c.chain = chain; next->u.u_c.end = chain; chain->next = NULL; + chain->quote_age = TOKEN_DATA_QUOTE_AGE (token); chain->str = TOKEN_DATA_TEXT (token); chain->len = TOKEN_DATA_LEN (token); chain->level = level; @@ -563,19 +566,6 @@ pop_wrapup (void) return true; } -/*-------------------------------------------------------------------. -| When a MACRO token is seen, next_token () uses init_macro_token () | -| to retrieve the value of the function pointer and store it in TD. | -`-------------------------------------------------------------------*/ - -static void -init_macro_token (token_data *td) -{ - assert (isp->type == INPUT_MACRO); - TOKEN_DATA_TYPE (td) = TOKEN_FUNC; - TOKEN_DATA_FUNC (td) = isp->u.func; -} - /*--------------------------------------------------------------. | Dump a representation of INPUT to the obstack OBS, for use in | | tracing. | @@ -699,16 +689,19 @@ peek_input (void) | consisting of a newline alone is taken as belonging to the line it | | ends, and the current line number is not incremented until the | | next character is read. 99.9% of all calls will read from a | -| string, so factor that out into a macro for speed. | +| string, so factor that out into a macro for speed. If | +| ALLOW_QUOTE, and the current input matches the current quote age, | +| return CHAR_QUOTE and leave consumption of data for | +| append_quote_token. | `-------------------------------------------------------------------*/ -#define next_char() \ +#define next_char(AQ) \ (isp && isp->type == INPUT_STRING && isp->u.u_s.len && !input_change \ ? (isp->u.u_s.len--, to_uchar (*isp->u.u_s.str++)) \ - : next_char_1 ()) + : next_char_1 (AQ)) static int -next_char_1 (void) +next_char_1 (bool allow_quote) { int ch; token_chain *chain; @@ -765,10 +758,14 @@ next_char_1 (void) chain = isp->u.u_c.chain; while (chain) { + if (allow_quote && chain->quote_age == current_quote_age) + return CHAR_QUOTE; if (chain->str) { if (chain->len) { + /* Partial consumption invalidates quote age. */ + chain->quote_age = 0; chain->len--; return to_uchar (*chain->str++); } @@ -808,7 +805,7 @@ skip_line (const char *name) const char *file = current_file; int line = current_line; - while ((ch = next_char ()) != CHAR_EOF && ch != '\n') + while ((ch = next_char (false)) != CHAR_EOF && ch != '\n') ; if (ch == CHAR_EOF) /* current_file changed to "" if we see CHAR_EOF, use the @@ -825,6 +822,49 @@ skip_line (const char *name) } +/*-------------------------------------------------------------------. +| When a MACRO token is seen, next_token () uses init_macro_token () | +| to retrieve the value of the function pointer and store it in TD. | +`-------------------------------------------------------------------*/ + +static void +init_macro_token (token_data *td) +{ + assert (isp->type == INPUT_MACRO); + TOKEN_DATA_TYPE (td) = TOKEN_FUNC; + TOKEN_DATA_FUNC (td) = isp->u.func; +} + +/*-------------------------------------------------------------------. +| When a QUOTE token is seen, convert TD to a composite (if it is | +| not one already), consisting of any unfinished text on OBS, as | +| well as the quoted token from the top of the input stack. Use OBS | +| for any additional allocations needed to store the token chain. | +`-------------------------------------------------------------------*/ +static void +append_quote_token (struct obstack *obs, token_data *td) +{ + token_chain *src_chain = isp->u.u_c.chain; + token_chain *chain; + assert (isp->type == INPUT_CHAIN && obs && current_quote_age); + + if (TOKEN_DATA_TYPE (td) == TOKEN_VOID) + { + TOKEN_DATA_TYPE (td) = TOKEN_COMP; + td->u.u_c.chain = td->u.u_c.end = NULL; + } + assert (TOKEN_DATA_TYPE (td) == TOKEN_COMP); + make_text_link (obs, &td->u.u_c.chain, &td->u.u_c.end); + chain = (token_chain *) obstack_copy (obs, src_chain, sizeof *chain); + if (td->u.u_c.end) + td->u.u_c.end->next = chain; + else + td->u.u_c.chain = chain; + td->u.u_c.end = chain; + td->u.u_c.end->next = NULL; + isp->u.u_c.chain = src_chain->next; +} + /*------------------------------------------------------------------. | This function is for matching a string against a prefix of the | | input stream. If the string S matches the input and CONSUME is | @@ -848,14 +888,14 @@ match_input (const char *s, bool consume) if (s[1] == '\0') { if (consume) - (void) next_char (); + next_char (false); return true; /* short match */ } - (void) next_char (); + next_char (false); for (n = 1, t = s++; (ch = peek_input ()) == to_uchar (*s++); ) { - (void) next_char (); + next_char (false); n++; if (*s == '\0') /* long match */ { @@ -1016,7 +1056,6 @@ void set_word_regexp (const char *caller, const char *regexp) { int i; - char test[2]; const char *msg; struct re_pattern_buffer new_word_regexp; @@ -1048,15 +1087,10 @@ set_word_regexp (const char *caller, const char *regexp) default_word_regexp = false; set_quote_age (); - if (word_start == NULL) - word_start = (char *) xmalloc (256); - - word_start[0] = '\0'; - test[1] = '\0'; for (i = 1; i < 256; i++) { - test[0] = i; - word_start[i] = re_search (&word_regexp, test, 1, 0, 0, NULL) >= 0; + char test = i; + word_start[i] = re_match (&word_regexp, &test, 1, 0, NULL) > 0; } } @@ -1140,16 +1174,17 @@ safe_quotes (void) /*--------------------------------------------------------------------. -| Parse and return a single token from the input stream. A token | -| can either be TOKEN_EOF, if the input_stack is empty; it can be | -| TOKEN_STRING for a quoted string or comment; TOKEN_WORD for | -| something that is a potential macro name; and TOKEN_SIMPLE for any | -| single character that is not a part of any of the previous types. | -| If LINE is not NULL, set *LINE to the line where the token starts. | -| If OBS is not NULL, expand TOKEN_STRING directly into OBS rather | -| than in token_stack temporary storage area. Report errors | -| (unterminated comments or strings) on behalf of CALLER, if | -| non-NULL. | +| Parse a single token from the input stream, set TD to its | +| contents, and return its type. A token is TOKEN_EOF if the | +| input_stack is empty; TOKEN_STRING for a quoted string or comment; | +| TOKEN_WORD for something that is a potential macro name; and | +| TOKEN_SIMPLE for any single character that is not a part of any of | +| the previous types. If LINE is not NULL, set *LINE to the line | +| where the token starts. If OBS is not NULL, expand TOKEN_STRING | +| directly into OBS rather than in token_stack temporary storage | +| area, and TD could be a TOKEN_COMP instead of the usual | +| TOKEN_TEXT. Report errors (unterminated comments or strings) on | +| behalf of CALLER, if non-NULL. | | | | Next_token () returns the token type, and passes back a pointer to | | the token data through TD. Non-string token text is collected on | @@ -1165,7 +1200,6 @@ next_token (token_data *td, int *line, struct obstack *obs, const char *caller) int quote_level; token_type type; #ifdef ENABLE_CHANGEWORD - int startpos; char *orig_text = NULL; #endif /* ENABLE_CHANGEWORD */ const char *file; @@ -1181,19 +1215,20 @@ next_token (token_data *td, int *line, struct obstack *obs, const char *caller) line = &dummy; /* Can't consume character until after CHAR_MACRO is handled. */ + TOKEN_DATA_TYPE (td) = TOKEN_VOID; ch = peek_input (); if (ch == CHAR_EOF) { #ifdef DEBUG_INPUT xfprintf (stderr, "next_token -> EOF\n"); #endif /* DEBUG_INPUT */ - next_char (); + next_char (false); return TOKEN_EOF; } if (ch == CHAR_MACRO) { init_macro_token (td); - next_char (); + next_char (false); #ifdef DEBUG_INPUT xfprintf (stderr, "next_token -> MACDEF (%s)\n", find_builtin_by_addr (TOKEN_DATA_FUNC (td))->name); @@ -1201,7 +1236,7 @@ next_token (token_data *td, int *line, struct obstack *obs, const char *caller) return TOKEN_MACDEF; } - next_char (); /* Consume character we already peeked at. */ + next_char (false); /* Consume character we already peeked at. */ file = current_file; *line = current_line; if (MATCH (ch, bcomm.string, true)) @@ -1209,11 +1244,14 @@ next_token (token_data *td, int *line, struct obstack *obs, const char *caller) if (obs) obs_td = obs; obstack_grow (obs_td, bcomm.string, bcomm.length); - while ((ch = next_char ()) != CHAR_EOF + while ((ch = next_char (false)) < CHAR_EOF && !MATCH (ch, ecomm.string, true)) obstack_1grow (obs_td, ch); if (ch != CHAR_EOF) - obstack_grow (obs_td, ecomm.string, ecomm.length); + { + assert (ch < CHAR_EOF); + obstack_grow (obs_td, ecomm.string, ecomm.length); + } else /* Current_file changed to "" if we see CHAR_EOF, use the previous value we stored earlier. */ @@ -1225,10 +1263,10 @@ next_token (token_data *td, int *line, struct obstack *obs, const char *caller) else if (default_word_regexp && (isalpha (ch) || ch == '_')) { obstack_1grow (&token_stack, ch); - while ((ch = peek_input ()) != CHAR_EOF && (isalnum (ch) || ch == '_')) + while ((ch = peek_input ()) < CHAR_EOF && (isalnum (ch) || ch == '_')) { obstack_1grow (&token_stack, ch); - (void) next_char (); + next_char (false); } type = TOKEN_WORD; } @@ -1241,20 +1279,17 @@ next_token (token_data *td, int *line, struct obstack *obs, const char *caller) while (1) { ch = peek_input (); - if (ch == CHAR_EOF) + if (ch >= CHAR_EOF) break; obstack_1grow (&token_stack, ch); - startpos = re_search (&word_regexp, - (char *) obstack_base (&token_stack), - obstack_object_size (&token_stack), 0, 0, - ®s); - if (startpos != 0 || - regs.end [0] != obstack_object_size (&token_stack)) + if (re_match (&word_regexp, (char *) obstack_base (&token_stack), + obstack_object_size (&token_stack), 0, ®s) + != obstack_object_size (&token_stack)) { obstack_blank (&token_stack, -1); break; } - next_char (); + next_char (false); } obstack_1grow (&token_stack, '\0'); @@ -1297,14 +1332,16 @@ next_token (token_data *td, int *line, struct obstack *obs, const char *caller) quote_level = 1; while (1) { - ch = next_char (); + ch = next_char (obs != NULL && current_quote_age); if (ch == CHAR_EOF) /* Current_file changed to "" if we see CHAR_EOF, use the previous value we stored earlier. */ m4_error_at_line (EXIT_FAILURE, 0, file, *line, caller, _("end of file in string")); - if (MATCH (ch, rquote.string, true)) + if (ch == CHAR_QUOTE) + append_quote_token (obs, td); + else if (MATCH (ch, rquote.string, true)) { if (--quote_level == 0) break; @@ -1316,35 +1353,49 @@ next_token (token_data *td, int *line, struct obstack *obs, const char *caller) obstack_grow (obs_td, lquote.string, lquote.length); } else - obstack_1grow (obs_td, ch); + { + assert (ch < CHAR_EOF); + obstack_1grow (obs_td, ch); + } } type = TOKEN_STRING; } - TOKEN_DATA_TYPE (td) = TOKEN_TEXT; - TOKEN_DATA_LEN (td) = obstack_object_size (obs_td); - if (obs_td != obs) + if (TOKEN_DATA_TYPE (td) == TOKEN_VOID) { - obstack_1grow (obs_td, '\0'); - TOKEN_DATA_TEXT (td) = (char *) obstack_finish (obs_td); - } - else - TOKEN_DATA_TEXT (td) = NULL; - TOKEN_DATA_QUOTE_AGE (td) = current_quote_age; + TOKEN_DATA_TYPE (td) = TOKEN_TEXT; + TOKEN_DATA_LEN (td) = obstack_object_size (obs_td); + if (obs_td != obs) + { + obstack_1grow (obs_td, '\0'); + TOKEN_DATA_TEXT (td) = (char *) obstack_finish (obs_td); + } + else + TOKEN_DATA_TEXT (td) = NULL; + TOKEN_DATA_QUOTE_AGE (td) = current_quote_age; #ifdef ENABLE_CHANGEWORD - if (orig_text == NULL) - TOKEN_DATA_ORIG_TEXT (td) = TOKEN_DATA_TEXT (td); + if (orig_text == NULL) + TOKEN_DATA_ORIG_TEXT (td) = TOKEN_DATA_TEXT (td); + else + { + TOKEN_DATA_ORIG_TEXT (td) = orig_text; + TOKEN_DATA_LEN (td) = strlen (orig_text); + } +#endif /* ENABLE_CHANGEWORD */ +#ifdef DEBUG_INPUT + xfprintf (stderr, "next_token -> %s (%s), len %zu\n", + token_type_string (type), TOKEN_DATA_TEXT (td), + TOKEN_DATA_LEN (td)); +#endif /* DEBUG_INPUT */ + } else { - TOKEN_DATA_ORIG_TEXT (td) = orig_text; - TOKEN_DATA_LEN (td) = strlen (orig_text); - } -#endif /* ENABLE_CHANGEWORD */ + assert (TOKEN_DATA_TYPE (td) == TOKEN_COMP && type == TOKEN_STRING); #ifdef DEBUG_INPUT - xfprintf (stderr, "next_token -> %s (%s), len %zu\n", - token_type_string (type), TOKEN_DATA_TEXT (td), - TOKEN_DATA_LEN (td)); + xfprintf (stderr, "next_token -> %s <chain>\n", + token_type_string (type)); #endif /* DEBUG_INPUT */ + } return type; } diff --git a/src/m4.h b/src/m4.h index ea3947f..474338b 100644 --- a/src/m4.h +++ b/src/m4.h @@ -271,19 +271,20 @@ enum token_data_type TOKEN_VOID, /* Token still being constructed, u is invalid. */ TOKEN_TEXT, /* Straight text, u.u_t is valid. */ TOKEN_FUNC, /* Builtin function definition, u.func is valid. */ - TOKEN_COMP /* Composite argument, u.chain is valid. */ + TOKEN_COMP /* Composite argument, u.u_c is valid. */ }; /* Composite tokens are built of a linked list of chains. */ struct token_chain { - token_chain *next; /* Pointer to next link of chain. */ - const char *str; /* NUL-terminated string if text, else NULL. */ - size_t len; /* Length of str, else 0. */ - int level; /* Expansion level of link content, or -1. */ - macro_arguments *argv;/* Reference to earlier $@. */ - unsigned int index; /* Argument index within argv. */ - bool flatten; /* True to treat builtins as text. */ + token_chain *next; /* Pointer to next link of chain. */ + unsigned int quote_age; /* Quote_age of this link of chain, or 0. */ + const char *str; /* NUL-terminated string if text, or NULL. */ + size_t len; /* Length of str, else 0. */ + int level; /* Expansion level of link content, or -1. */ + macro_arguments *argv; /* Reference to earlier $@. */ + unsigned int index; /* Argument index within argv. */ + bool flatten; /* True to treat builtins as text. */ }; /* The content of a token or macro argument. */ @@ -319,7 +320,12 @@ struct token_data /* Composite text: a linked list of straight text and $@ placeholders. */ - token_chain *chain; + struct + { + token_chain *chain; /* First link of the chain. */ + token_chain *end; /* Last link of the chain. */ + } + u_c; } u; }; @@ -342,6 +348,7 @@ token_type next_token (token_data *, int *, struct obstack *, const char *); void skip_line (const char *); /* push back input */ +void make_text_link (struct obstack *, token_chain **, token_chain **); void push_file (FILE *, const char *, bool); void push_macro (builtin_func *); struct obstack *push_string_init (void); diff --git a/src/macro.c b/src/macro.c index ef18b8f..62af398 100644 --- a/src/macro.c +++ b/src/macro.c @@ -45,6 +45,9 @@ struct macro_arguments bool_bitfield inuse : 1; /* False if all arguments are just text or func, true if this argv refers to another one. */ + bool_bitfield wrapper : 1; + /* False if all arguments belong to this argv, true if some of them + include references to another. */ bool_bitfield has_ref : 1; const char *argv0; /* The macro name being expanded. */ size_t argv0_len; /* Length of argv0. */ @@ -382,11 +385,16 @@ expand_argument (struct obstack *obs, token_data *argp, const char *caller) return t == TOKEN_COMMA; warn_builtin_concat (caller, TOKEN_DATA_FUNC (argp)); } - obstack_1grow (obs, '\0'); - TOKEN_DATA_TYPE (argp) = TOKEN_TEXT; - TOKEN_DATA_TEXT (argp) = (char *) obstack_finish (obs); - TOKEN_DATA_LEN (argp) = len; - TOKEN_DATA_QUOTE_AGE (argp) = age; + if (TOKEN_DATA_TYPE (argp) != TOKEN_COMP) + { + obstack_1grow (obs, '\0'); + TOKEN_DATA_TYPE (argp) = TOKEN_TEXT; + TOKEN_DATA_TEXT (argp) = (char *) obstack_finish (obs); + TOKEN_DATA_LEN (argp) = len; + TOKEN_DATA_QUOTE_AGE (argp) = age; + } + else + make_text_link (obs, NULL, &argp->u.u_c.end); return t == TOKEN_COMMA; } /* fallthru */ @@ -411,6 +419,23 @@ expand_argument (struct obstack *obs, token_data *argp, const char *caller) case TOKEN_STRING: if (!expand_token (obs, t, &td, line, first)) age = 0; + if (TOKEN_DATA_TYPE (&td) == TOKEN_COMP) + { + if (TOKEN_DATA_TYPE (argp) != TOKEN_COMP) + { + if (TOKEN_DATA_TYPE (argp) == TOKEN_FUNC) + warn_builtin_concat (caller, TOKEN_DATA_FUNC (argp)); + TOKEN_DATA_TYPE (argp) = TOKEN_COMP; + argp->u.u_c.chain = td.u.u_c.chain; + argp->u.u_c.end = td.u.u_c.end; + } + else + { + assert (argp->u.u_c.end); + argp->u.u_c.end->next = td.u.u_c.chain; + argp->u.u_c.end = td.u.u_c.end; + } + } break; case TOKEN_MACDEF: @@ -459,6 +484,7 @@ collect_arguments (symbol *sym, struct obstack *arguments, args.argc = 1; args.inuse = false; + args.wrapper = false; args.has_ref = false; args.argv0 = SYMBOL_NAME (sym); args.argv0_len = strlen (args.argv0); @@ -490,11 +516,14 @@ collect_arguments (symbol *sym, struct obstack *arguments, && TOKEN_DATA_LEN (tdp) > 0 && TOKEN_DATA_QUOTE_AGE (tdp) != args.quote_age) args.quote_age = 0; + else if (TOKEN_DATA_TYPE (tdp) == TOKEN_COMP) + args.has_ref = true; } while (more_args); } argv = (macro_arguments *) obstack_finish (argv_stack); argv->argc = args.argc; + argv->has_ref = args.has_ref; if (args.quote_age != quote_age ()) argv->quote_age = 0; argv->arraylen = args.arraylen; @@ -633,8 +662,23 @@ expand_macro (symbol *sym) if (SYMBOL_DELETED (sym)) free_symbol (sym); - /* If argv contains references, those refcounts can be reduced now. */ - /* TODO - support references in argv. */ + /* If argv contains references, those refcounts must be reduced now. */ + if (argv->has_ref) + { + token_chain *chain; + size_t i; + for (i = 0; i < argv->arraylen; i++) + if (TOKEN_DATA_TYPE (argv->array[i]) == TOKEN_COMP) + { + chain = argv->array[i]->u.u_c.chain; + while (chain) + { + if (chain->level >= 0) + adjust_refcount (chain->level, false); + chain = chain->next; + } + } + } /* We no longer need argv, so reduce the refcount. Additionally, if no other references to argv were created, we can free our portion @@ -698,7 +742,7 @@ arg_token (macro_arguments *argv, unsigned int index) token_data *token; assert (index && index < argv->argc); - if (!argv->has_ref) + if (!argv->wrapper) return argv->array[index - 1]; /* Must cycle through all tokens, until we find index, since a ref may occupy multiple indices. */ @@ -707,7 +751,7 @@ arg_token (macro_arguments *argv, unsigned int index) token = argv->array[i]; if (TOKEN_DATA_TYPE (token) == TOKEN_COMP) { - token_chain *chain = token->u.chain; + token_chain *chain = token->u.u_c.chain; /* TODO - for now we support only a single-length $@ chain. */ assert (!chain->next && !chain->str); if (index < chain->argv->argc - (chain->index - 1)) @@ -731,14 +775,14 @@ static void arg_mark (macro_arguments *argv) { argv->inuse = true; - if (argv->has_ref) + if (argv->wrapper) { /* TODO for now we support only a single-length $@ chain. */ assert (argv->arraylen == 1 && TOKEN_DATA_TYPE (argv->array[0]) == TOKEN_COMP - && !argv->array[0]->u.chain->next - && !argv->array[0]->u.chain->str); - argv->array[0]->u.chain->argv->inuse = true; + && !argv->array[0]->u.u_c.chain->next + && !argv->array[0]->u.u_c.chain->str); + argv->array[0]->u.u_c.chain->argv->inuse = true; } } @@ -761,17 +805,22 @@ arg_type (macro_arguments *argv, unsigned int index) return TOKEN_TEXT; token = arg_token (argv, index); type = TOKEN_DATA_TYPE (token); - assert (type != TOKEN_COMP); + /* Composite tokens are currently sequences of text only. */ + if (type == TOKEN_COMP) + type = TOKEN_TEXT; return type; } /* Given ARGV, return the text at argument INDEX. Abort if the argument is not text. Index 0 is always text, and indices beyond - argc return the empty string. */ + argc return the empty string. The result is always NUL-terminated, + even if it includes embedded NUL characters. */ const char * arg_text (macro_arguments *argv, unsigned int index) { token_data *token; + token_chain *chain; + struct obstack *obs; if (index == 0) return argv->argv0; @@ -783,8 +832,18 @@ arg_text (macro_arguments *argv, unsigned int index) case TOKEN_TEXT: return TOKEN_DATA_TEXT (token); case TOKEN_COMP: - /* TODO - how to concatenate multiple arguments? For now, we expect - only one element in the chain, and arg_token dereferences it. */ + /* TODO - concatenate multiple arguments? For now, we assume + all elements are text. */ + chain = token->u.u_c.chain; + obs = arg_scratch (); + while (chain) + { + assert (chain->str); + obstack_grow (obs, chain->str, chain->len); + chain = chain->next; + } + obstack_1grow (obs, '\0'); + return (char *) obstack_finish (obs); default: break; } @@ -801,14 +860,84 @@ arg_equal (macro_arguments *argv, unsigned int indexa, unsigned int indexb) { token_data *ta = arg_token (argv, indexa); token_data *tb = arg_token (argv, indexb); + token_chain tmpa; + token_chain tmpb; + token_chain *ca = &tmpa; + token_chain *cb = &tmpb; + /* Quick tests. */ if (ta == &empty_token || tb == &empty_token) return ta == tb; + if (TOKEN_DATA_TYPE (ta) == TOKEN_TEXT + && TOKEN_DATA_TYPE (tb) == TOKEN_TEXT) + return (TOKEN_DATA_LEN (ta) == TOKEN_DATA_LEN (tb) + && memcmp (TOKEN_DATA_TEXT (ta), TOKEN_DATA_TEXT (tb), + TOKEN_DATA_LEN (ta)) == 0); + + /* Convert both arguments to chains, if not one already. */ /* TODO - allow builtin tokens in the comparison? */ - assert (TOKEN_DATA_TYPE (ta) == TOKEN_TEXT - && TOKEN_DATA_TYPE (tb) == TOKEN_TEXT); - return (TOKEN_DATA_LEN (ta) == TOKEN_DATA_LEN (tb) - && strcmp (TOKEN_DATA_TEXT (ta), TOKEN_DATA_TEXT (tb)) == 0); + if (TOKEN_DATA_TYPE (ta) == TOKEN_TEXT) + { + tmpa.next = NULL; + tmpa.str = TOKEN_DATA_TEXT (ta); + tmpa.len = TOKEN_DATA_LEN (ta); + } + else + { + assert (TOKEN_DATA_TYPE (ta) == TOKEN_COMP); + ca = ta->u.u_c.chain; + } + if (TOKEN_DATA_TYPE (tb) == TOKEN_TEXT) + { + tmpb.next = NULL; + tmpb.str = TOKEN_DATA_TEXT (tb); + tmpb.len = TOKEN_DATA_LEN (tb); + } + else + { + assert (TOKEN_DATA_TYPE (tb) == TOKEN_COMP); + cb = tb->u.u_c.chain; + } + + /* Compare each link of the chain. */ + while (ca && cb) + { + /* TODO support comparison against $@ refs. */ + assert (ca->str && cb->str); + if (ca->len == cb->len) + { + if (memcmp (ca->str, cb->str, ca->len) != 0) + return false; + ca = ca->next; + cb = cb->next; + } + else if (ca->len < cb->len) + { + if (memcmp (ca->str, cb->str, ca->len) != 0) + return false; + tmpb.next = cb->next; + tmpb.str = cb->str + ca->len; + tmpb.len = cb->len - ca->len; + ca = ca->next; + cb = &tmpb; + } + else + { + assert (ca->len > cb->len); + if (memcmp (ca->str, cb->str, cb->len) != 0) + return false; + tmpa.next = ca->next; + tmpa.str = ca->str + cb->len; + tmpa.len = ca->len - cb->len; + ca = &tmpa; + cb = cb->next; + } + } + + /* If we get this far, the two tokens are equal only if both chains + are exhausted. */ + assert (ca != cb || ca == NULL); + return ca == cb; } /* Given ARGV, return true if argument INDEX is the empty string. @@ -830,6 +959,8 @@ size_t arg_len (macro_arguments *argv, unsigned int index) { token_data *token; + token_chain *chain; + size_t len; if (index == 0) return argv->argv0_len; @@ -842,8 +973,18 @@ arg_len (macro_arguments *argv, unsigned int index) assert ((token == &empty_token) == (TOKEN_DATA_LEN (token) == 0)); return TOKEN_DATA_LEN (token); case TOKEN_COMP: - /* TODO - how to concatenate multiple arguments? For now, we expect - only one element in the chain, and arg_token dereferences it. */ + /* TODO - concatenate multiple arguments? For now, we assume + all elements are text. */ + chain = token->u.u_c.chain; + len = 0; + while (chain) + { + assert (chain->str); + len += chain->len; + chain = chain->next; + } + assert (len); + return len; default: break; } @@ -892,12 +1033,12 @@ make_argv_ref (macro_arguments *argv, const char *argv0, size_t argv0_len, /* When making a reference through a reference, point to the original if possible. */ - if (argv->has_ref) + if (argv->wrapper) { /* TODO - for now we support only a single-length $@ chain. */ assert (argv->arraylen == 1 && TOKEN_DATA_TYPE (argv->array[0]) == TOKEN_COMP); - chain = argv->array[0]->u.chain; + chain = argv->array[0]->u.u_c.chain; assert (!chain->next && !chain->str); argv = chain->argv; index += chain->index - 1; @@ -907,6 +1048,7 @@ make_argv_ref (macro_arguments *argv, const char *argv0, size_t argv0_len, new_argv = (macro_arguments *) obstack_alloc (obs, offsetof (macro_arguments, array)); new_argv->arraylen = 0; + new_argv->wrapper = false; new_argv->has_ref = false; } else @@ -918,10 +1060,12 @@ make_argv_ref (macro_arguments *argv, const char *argv0, size_t argv0_len, chain = (token_chain *) obstack_alloc (obs, sizeof *chain); new_argv->arraylen = 1; new_argv->array[0] = token; + new_argv->wrapper = true; new_argv->has_ref = true; TOKEN_DATA_TYPE (token) = TOKEN_COMP; - token->u.chain = chain; + token->u.u_c.chain = token->u.u_c.end = chain; chain->next = NULL; + chain->quote_age = argv->quote_age; chain->str = NULL; chain->len = 0; chain->level = expansion_level - 1; @@ -955,9 +1099,23 @@ push_arg (struct obstack *obs, macro_arguments *argv, unsigned int index) return; token = arg_token (argv, index); /* TODO handle func tokens? */ - assert (TOKEN_DATA_TYPE (token) == TOKEN_TEXT); - if (push_token (token, expansion_level - 1)) - arg_mark (argv); + if (TOKEN_DATA_TYPE (token) == TOKEN_TEXT) + { + if (push_token (token, expansion_level - 1)) + arg_mark (argv); + } + else if (TOKEN_DATA_TYPE (token) == TOKEN_COMP) + { + /* TODO - concatenate multiple arguments? For now, we assume + all elements are text. */ + token_chain *chain = token->u.u_c.chain; + while (chain) + { + assert (chain->str); + obstack_grow (obs, chain->str, chain->len); + chain = chain->next; + } + } } /* Push series of comma-separated arguments from ARGV, which should @@ -968,6 +1126,7 @@ void push_args (struct obstack *obs, macro_arguments *argv, bool skip, bool quote) { token_data *token; + token_chain *chain; unsigned int i = skip ? 2 : 1; const char *sep = ","; size_t sep_len = 1; @@ -1007,8 +1166,20 @@ push_args (struct obstack *obs, macro_arguments *argv, bool skip, bool quote) else use_sep = true; /* TODO handle func tokens? */ - assert (TOKEN_DATA_TYPE (token) == TOKEN_TEXT); - inuse |= push_token (token, expansion_level - 1); + if (TOKEN_DATA_TYPE (token) == TOKEN_TEXT) + inuse |= push_token (token, expansion_level - 1); + else + { + /* TODO - handle composite text in push_token. */ + assert (TOKEN_DATA_TYPE (token) == TOKEN_COMP); + chain = token->u.u_c.chain; + while (chain) + { + assert (chain->str); + obstack_grow (obs, chain->str, chain->len); + chain = chain->next; + } + } } if (quote) obstack_grow (obs, rquote.string, rquote.length); -- 1.5.3.8