/* mp4h -- A macro processor for HTML documents Copyright 2000-2001, Denis Barbier All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is a work based on GNU m4 version 1.4n. Below is the original copyright. */ /* GNU m4 -- A simple macro processor Copyright (C) 1989, 90, 91, 92, 93, 94 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* This file contains the functions, that performs the basic argument parsing and macro expansion. */ #include "mp4h.h" static void expand_macro __P ((symbol *, read_type)); static void expand_unknown_tag __P ((char *, read_type)); static void expand_entity __P ((symbol *, read_type)); static void expand_token __P ((struct obstack *, read_type, token_type, token_data *)); /* Current recursion level in expand_macro (). */ int expansion_level = 0; /* The number of the current call of expand_macro (). */ static int macro_call_id = 0; /* global variable to abort expansion */ static boolean internal_abort = FALSE; /*----------------------------------------------------------------------. | This function read all input, and expands each token, one at a time. | `----------------------------------------------------------------------*/ void expand_input (void) { token_type t; token_data td; while ((t = next_token (&td, READ_NORMAL, FALSE)) != TOKEN_EOF) expand_token ((struct obstack *) NULL, READ_NORMAL, t, &td); } /*------------------------------------------------------------------------. | Expand one token, according to its type. Potential macro names | | (TOKEN_WORD) are looked up in the symbol table, to see if they have a | | macro definition. If they have, they are expanded as macros, otherwise | | the text are just copied to the output. | `------------------------------------------------------------------------*/ static void expand_token (struct obstack *obs, read_type expansion, token_type t, token_data *td) { symbol *sym; char *append_semicolon; char *text = TOKEN_DATA_TEXT (td); switch (t) { /* TOKSW */ case TOKEN_EOF: case TOKEN_MACDEF: case TOKEN_BGROUP: case TOKEN_EGROUP: break; case TOKEN_QUOTED: case TOKEN_SIMPLE: case TOKEN_SPACE: case TOKEN_STRING: if (expansion_level > 0 && t == TOKEN_QUOTED) obstack_1grow (obs, CHAR_LQUOTE); shipout_text (obs, text); if (expansion_level > 0 && t == TOKEN_QUOTED) obstack_1grow (obs, CHAR_RQUOTE); break; case TOKEN_WORD: if (IS_TAG(*text)) text++; else MP4HERROR ((warning_status, 0, _("INTERNAL ERROR: macro has no leading '<' in expand_token ()"))); /* macro names must begin with a letter or an underscore. If another character is found, this string is not a macro. */ if (IS_ALPHA (*text)) { sym = lookup_symbol (text, SYMBOL_LOOKUP); if (sym == NULL || SYMBOL_TYPE (sym) == TOKEN_VOID) { if (exp_flags & EXP_NO_HTMLTAG) shipout_text (obs, TOKEN_DATA_TEXT (td)); else expand_unknown_tag (text, expansion); } else expand_macro (sym, expansion); } else if (*text == '/' && IS_ALPHA (*(text+1))) { sym = lookup_symbol (text+1, SYMBOL_LOOKUP); if (sym != NULL) { MP4HERROR ((warning_status, 0, _("Warning:%s:%d: `<%s>' found without begin tag"), CURRENT_FILE_LINE, text)); } expand_unknown_tag (text, expansion); } else shipout_text (obs, TOKEN_DATA_TEXT (td)); break; case TOKEN_ENTITY: /* entity names must begin with a letter or an underscore. If another character is found, this string is not an entity. */ if (IS_ENTITY(*text)) text++; else MP4HERROR ((warning_status, 0, _("INTERNAL ERROR: entity has no leading '&' in expand_token ()"))); if (IS_ALPHA (*text)) { append_semicolon = NULL; if (LAST_CHAR (text) == ';') { append_semicolon = text + strlen (text) - 1; *append_semicolon = '\0'; } sym = lookup_entity (text, SYMBOL_LOOKUP); if (sym == NULL || SYMBOL_TYPE (sym) == TOKEN_VOID) { if (append_semicolon) *(append_semicolon) = ';'; shipout_text (obs, TOKEN_DATA_TEXT (td)); } else expand_entity (sym, expansion); } else shipout_text (obs, TOKEN_DATA_TEXT (td)); break; default: MP4HERROR ((warning_status, 0, _("INTERNAL ERROR: Bad token type in expand_token ()"))); abort (); } } /*-------------------------------------------------------------------------. | This function parses one argument to a macro call. It skips leading | | whitespace and reads and expands tokens until it finds a space outside | | of any group, or a right angle bracket. It returns a flag indicating | | whether the argument read are the last for the active macro call. The | | argument read are the last for the active macro call. The argument are | | on the obstack OBS, indirectly through expand_token (). | | On exit, this function returns | | 0: close bracket found | | 1: last argument | | 2: other arguments follow | `-------------------------------------------------------------------------*/ static int expand_argument (struct obstack *obs, read_type expansion, token_data *argp, char *last_char_ptr) { char *text; token_type t; token_data td; int group_level = 0; int rc; boolean in_string = FALSE; *last_char_ptr = ' '; TOKEN_DATA_TYPE (argp) = TOKEN_VOID; /* Skip leading white space. */ t = next_token (&td, expansion, FALSE); if (t == TOKEN_SPACE) { if (expansion == READ_ATTR_ASIS || expansion == READ_ATTR_QUOT) { /* We are parsing attributes of a command which will be rescanned, so whitepsaces are preserved. */ obstack_grow (obs, TOKEN_DATA_TEXT (&td), strlen (TOKEN_DATA_TEXT (&td))); } t = next_token (&td, expansion, FALSE); } rc = 0; while (1) { switch (t) { /* TOKSW */ case TOKEN_SPACE: case TOKEN_SIMPLE: text = TOKEN_DATA_TEXT (&td); if ((IS_SPACE(*text) || IS_CLOSE(*text)) && !in_string && (group_level == 0)) { if (t == TOKEN_SPACE && (expansion == READ_ATTR_ASIS || expansion == READ_ATTR_QUOT)) { obstack_grow (obs, TOKEN_DATA_TEXT (&td), strlen (TOKEN_DATA_TEXT (&td))); } /* The argument MUST be finished, whether we want it or not. */ obstack_1grow (obs, '\0'); if (TOKEN_DATA_TYPE (argp) == TOKEN_VOID) { TOKEN_DATA_TYPE (argp) = TOKEN_TEXT; TOKEN_DATA_TEXT (argp) = obstack_finish (obs); } if (IS_SPACE(*TOKEN_DATA_TEXT (&td))) rc = 2; return rc; } expand_token (obs, expansion, t, &td); break; case TOKEN_EOF: internal_abort = TRUE; return FALSE; break; case TOKEN_BGROUP: if (expansion == READ_ATTR_ASIS && group_level == 0) obstack_1grow (obs, CHAR_BGROUP); group_level++; break; case TOKEN_EGROUP: group_level--; if (expansion == READ_ATTR_ASIS && group_level == 0) obstack_1grow (obs, CHAR_EGROUP); break; case TOKEN_QUOTE: in_string = !in_string; obstack_grow (obs, TOKEN_DATA_TEXT (&td), strlen (TOKEN_DATA_TEXT (&td))); break; case TOKEN_QUOTED: case TOKEN_STRING: if (expansion_level > 0 && t == TOKEN_QUOTED) obstack_1grow (obs, CHAR_LQUOTE); obstack_grow (obs, TOKEN_DATA_TEXT (&td), strlen (TOKEN_DATA_TEXT (&td))); if (expansion_level > 0 && t == TOKEN_QUOTED) obstack_1grow (obs, CHAR_RQUOTE); break; case TOKEN_WORD: case TOKEN_ENTITY: expand_token (obs, expansion, t, &td); break; case TOKEN_MACDEF: if (obstack_object_size (obs) == 0) { TOKEN_DATA_TYPE (argp) = TOKEN_FUNC; TOKEN_DATA_FUNC (argp) = TOKEN_DATA_FUNC (&td); TOKEN_DATA_FUNC_TRACED (argp) = TOKEN_DATA_FUNC_TRACED (&td); } break; default: MP4HERROR ((warning_status, 0, _("INTERNAL ERROR: Bad token type in expand_argument ()"))); abort (); } *last_char_ptr = LAST_CHAR (TOKEN_DATA_TEXT (&td)); t = next_token (&td, expansion, in_string); rc = 1; } } /*-------------------------------------------------------------------------. | Collect all the arguments to a call of the macro SYM. The arguments are | | stored on the obstack ARGUMENTS and a table of pointers to the arguments | | on the obstack ARGPTR. | | If there is a slash character before closing bracket, this function | | returns TRUE, otherwise FALSE. | `-------------------------------------------------------------------------*/ static boolean collect_arguments (char *symbol_name, read_type expansion, struct obstack *argptr, struct obstack *arguments) { int ch; token_data td; token_data *tdp; char *last_addr; int more_args; char last_char = ' '; TOKEN_DATA_TYPE (&td) = TOKEN_TEXT; TOKEN_DATA_TEXT (&td) = symbol_name; tdp = (token_data *) obstack_copy (arguments, (voidstar) &td, sizeof (td)); obstack_grow (argptr, (voidstar) &tdp, sizeof (tdp)); TOKEN_DATA_TYPE (&td) = TOKEN_VOID; ch = peek_input (); if (IS_CLOSE (ch)) (void) next_token (&td, READ_BODY, FALSE); else if (IS_SPACE(ch) || IS_TAG(ch) || IS_SLASH(ch)) { do { /* remember last address in use to remove the last argument if it is empty. */ last_addr = argptr->next_free; more_args = expand_argument (arguments, expansion, &td, &last_char); if (internal_abort) { MP4HERROR ((EXIT_FAILURE, 0, _("ERROR:%s:%d: EOF when reading argument of the `%s' tag"), CURRENT_FILE_LINE, symbol_name)); } tdp = (token_data *) obstack_copy (arguments, (voidstar) &td, sizeof (td)); obstack_grow (argptr, (voidstar) &tdp, sizeof (tdp)); } while (more_args == 2); /* If the last argument is empty, it is removed. We need it to remove white spaces before closing brackets. */ if (more_args == 0) argptr->next_free = last_addr; } else { MP4HERROR ((warning_status, 0, _("INTERNAL ERROR: Bad tag expression in `%s'"), CURRENT_FILE_LINE, symbol_name)); } return (IS_SLASH(last_char)); } /*-----------------------------------------------------------------. | Collect the body of a container tag. No expansion is performed, | | but when a macro is found its arguments are collected. This is | | necessary to deal with nested expressions. | `-----------------------------------------------------------------*/ static void collect_body (char *symbol_name, read_type expansion, struct obstack *argptr, struct obstack *bodyptr) { token_type t; token_data td; token_data *tdp; char *text; symbol *newsym; while (1) { t = next_token (&td, expansion, FALSE); text = TOKEN_DATA_TEXT (&td); switch (t) { /* TOKSW */ case TOKEN_EOF: case TOKEN_MACDEF: MP4HERROR ((EXIT_FAILURE, 0, _("ERROR:%s:%d: EOF when reading body of the `%s' tag"), CURRENT_FILE_LINE, symbol_name)); break; case TOKEN_BGROUP: case TOKEN_EGROUP: break; case TOKEN_QUOTE: obstack_1grow (bodyptr, CHAR_QUOTE); break; case TOKEN_QUOTED: case TOKEN_SIMPLE: case TOKEN_SPACE: case TOKEN_STRING: case TOKEN_ENTITY: if (expansion_level > 0 && t == TOKEN_QUOTED) obstack_1grow (bodyptr, CHAR_LQUOTE); shipout_text (bodyptr, text); if (expansion_level > 0 && t == TOKEN_QUOTED) obstack_1grow (bodyptr, CHAR_RQUOTE); break; case TOKEN_WORD: if (IS_TAG(*text)) text++; if (*text == '/') { text++; if (strcasecmp (text, symbol_name) == 0) { /* gobble closing bracket */ do { t = next_token (&td, expansion, FALSE); } while (! IS_CLOSE(*TOKEN_DATA_TEXT (&td))) ; obstack_1grow (bodyptr, '\0'); /* Add body to argptr */ TOKEN_DATA_TYPE (&td) = TOKEN_TEXT; TOKEN_DATA_TEXT (&td) = obstack_finish (bodyptr); tdp = (token_data *) obstack_copy (bodyptr, (voidstar) &td, sizeof (td)); obstack_grow (argptr, (voidstar) &tdp, sizeof (tdp)); return; } else { newsym = lookup_symbol (text, SYMBOL_LOOKUP); if (newsym) { if (!(exp_flags & EXP_NOWARN_NEST)) MP4HERROR ((warning_status, 0, _("Warning:%s:%d: `' found when `' expected"), CURRENT_FILE_LINE, text, symbol_name)); if (exp_flags & EXP_UNM_BREAK) { /* Premature end of body parsing. */ obstack_1grow (bodyptr, '\0'); TOKEN_DATA_TYPE (&td) = TOKEN_TEXT; TOKEN_DATA_TEXT (&td) = obstack_finish (bodyptr); tdp = (token_data *) obstack_copy (bodyptr, (voidstar) &td, sizeof (td)); obstack_grow (argptr, (voidstar) &tdp, sizeof (tdp)); /* Push read input back on the stack */ unget_string (text-2); return; } } obstack_grow (bodyptr, TOKEN_DATA_TEXT (&td), strlen(TOKEN_DATA_TEXT (&td)) ); /* gobble closing bracket */ do { t = next_token (&td, expansion, FALSE); obstack_grow (bodyptr, TOKEN_DATA_TEXT (&td), strlen(TOKEN_DATA_TEXT (&td)) ); } while (! IS_CLOSE(*TOKEN_DATA_TEXT (&td))) ; } } else { newsym = lookup_symbol (text, SYMBOL_LOOKUP); if (newsym == NULL || SYMBOL_TYPE (newsym) == TOKEN_VOID) { if (exp_flags & EXP_NO_HTMLTAG) shipout_text (bodyptr, TOKEN_DATA_TEXT (&td)); else expand_unknown_tag (text, expansion); } else expand_macro (newsym, expansion); } break; default: MP4HERROR ((warning_status, 0, _("INTERNAL ERROR:%d: Bad token type in collect_body ()"), t)); abort (); } } } /*------------------------------------------------------------------------. | The actual call of a macro is handled by call_macro (). call_macro () | | is passed a symbol SYM, whose type is used to call either a builtin | | function, or the user macro expansion function expand_user_macro () | | (lives in builtin.c). There are ARGC arguments to the call, stored in | | the ARGV table. The expansion is left on the obstack EXPANSION. Macro | | tracing is also handled here. | `------------------------------------------------------------------------*/ void call_macro (symbol *sym, struct obstack *obs, int argc, token_data **argv, read_type expansion) { if (SYMBOL_HOOK_BEGIN (sym)) obstack_grow (obs, SYMBOL_HOOK_BEGIN (sym), strlen (SYMBOL_HOOK_BEGIN (sym))); switch (SYMBOL_TYPE (sym)) { case TOKEN_FUNC: (*SYMBOL_FUNC (sym)) (obs, argc, argv, expansion); break; case TOKEN_TEXT: expand_user_macro (obs, sym, argc, argv, expansion); break; default: MP4HERROR ((warning_status, 0, _("INTERNAL ERROR: Bad symbol type in call_macro ()"))); abort (); } if (SYMBOL_HOOK_END (sym)) obstack_grow (obs, SYMBOL_HOOK_END (sym), strlen (SYMBOL_HOOK_END (sym))); } /*-------------------------------------------------------------------------. | The macro expansion is handled by expand_macro (). It parses the | | arguments, using collect_arguments (), and builds a table of pointers to | | the arguments. The arguments themselves are stored on a local obstack. | | Expand_macro () uses call_macro () to do the call of the macro. | | | | Expand_macro () is potentially recursive, since it calls expand_argument | | (), which might call expand_token (), which might call expand_macro (). | `-------------------------------------------------------------------------*/ static void expand_macro (symbol *sym, read_type expansion) { struct obstack arguments, argptr, body; token_data **argv; token_data td; token_data *tdp; int argc, i; struct obstack *obs_expansion; const char *expanded; char *cp; boolean traced, slash; int my_call_id; read_type attr_expansion; expansion_level++; array_current_line[expansion_level] = current_line; if (expansion_level > nesting_limit) MP4HERROR ((EXIT_FAILURE, 0, _("ERROR: Recursion limit of %d exceeded, use -L to change it"), nesting_limit)); macro_call_id++; my_call_id = macro_call_id; traced = (boolean) ((debug_level & DEBUG_TRACE_ALL) || SYMBOL_TRACED (sym)); obstack_init (&arguments); obstack_init (&argptr); obstack_init (&body); if (traced && (debug_level & DEBUG_TRACE_CALL)) trace_prepre (SYMBOL_NAME (sym), my_call_id); if (expansion == READ_ATTR_ASIS || expansion == READ_ATTR_VERB || expansion == READ_BODY) attr_expansion = READ_ATTR_ASIS; else if (! SYMBOL_EXPAND_ARGS (sym)) attr_expansion = READ_ATTR_VERB; else attr_expansion = READ_ATTRIBUTE; slash = collect_arguments (SYMBOL_NAME (sym), attr_expansion, &argptr, &arguments); argc = obstack_object_size (&argptr) / sizeof (token_data *); if (SYMBOL_CONTAINER (sym) && !slash) { collect_body (SYMBOL_NAME (sym), READ_BODY, &argptr, &body); argv = (token_data **) obstack_finish (&argptr); } else { if (SYMBOL_CONTAINER (sym)) { TOKEN_DATA_TYPE (&td) = TOKEN_TEXT; TOKEN_DATA_TEXT (&td) = ""; tdp = (token_data *) obstack_copy (&arguments, (voidstar) &td, sizeof (td)); obstack_grow (&argptr, (voidstar) &tdp, sizeof (tdp)); } argv = (token_data **) obstack_finish (&argptr); if (slash) { cp = TOKEN_DATA_TEXT (argv[argc-1]); if (IS_SLASH(*cp) && *(cp+1) == '\0') { *cp = '\0'; argc--; } else if (IS_SLASH (LAST_CHAR (cp))) { if (IS_SPACE (*(cp+strlen(cp)-2))) *(cp+strlen(cp)-2) = '\0'; else LAST_CHAR (cp) = '\0'; } } else if (!(exp_flags & EXP_NOWARN_SLASH)) MP4HERROR ((warning_status, 0, _("Warning:%s:%d: `<%s>' requires a trailing slash"), CURRENT_FILE_LINE, SYMBOL_NAME (sym))); } if (traced) trace_pre (SYMBOL_NAME (sym), my_call_id, argc, argv); obs_expansion = push_string_init (); if (expansion == READ_NORMAL || expansion == READ_ATTRIBUTE || expansion == READ_ATTR_QUOT) { if (expansion_level > 1) obstack_1grow (obs_expansion, CHAR_BGROUP); call_macro (sym, obs_expansion, argc, argv, expansion); if (expansion_level > 1) obstack_1grow (obs_expansion, CHAR_EGROUP); } else { obstack_1grow (obs_expansion, '<'); shipout_string (obs_expansion, SYMBOL_NAME (sym), 0); for (i = 1; i < argc; i++) { shipout_string (obs_expansion, TOKEN_DATA_TEXT (argv[i]), 0); } if (slash) { obstack_1grow (obs_expansion, CHAR_SLASH); } obstack_1grow (obs_expansion, '>'); if (SYMBOL_CONTAINER (sym) && !slash) { shipout_string (obs_expansion, TOKEN_DATA_TEXT (argv[argc]), 0); obstack_grow (obs_expansion, "'); } } expanded = push_string_finish (expansion); if (traced) trace_post (SYMBOL_NAME (sym), my_call_id, argc, argv, expanded); --expansion_level; obstack_free (&arguments, NULL); obstack_free (&argptr, NULL); obstack_free (&body, NULL); if (expansion_level == 0) clear_tag_attr (); } /*-------------------------------------------------------------------------. | This macro reads attributes without expanding macro. It is useful to | | print unknown tags. | `-------------------------------------------------------------------------*/ static void expand_unknown_tag (char *name, read_type expansion) { struct obstack arguments, argptr, body; token_data **argv; int argc, i; struct obstack *obs_expansion; const char *expanded; char *symbol_name, *cp; read_type attr_expansion; boolean slash, single; expansion_level++; symbol_name = xstrdup (name); obstack_init (&arguments); obstack_init (&argptr); obstack_init (&body); if (expansion == READ_NORMAL || expansion == READ_ATTRIBUTE || expansion == READ_ATTR_QUOT) attr_expansion = READ_ATTR_QUOT; else attr_expansion = READ_ATTR_ASIS; slash = collect_arguments (symbol_name, attr_expansion, &argptr, &arguments); argc = obstack_object_size (&argptr) / sizeof (token_data *); /* This tag is a simple tag if either - tag name begins with a slash (i.e. this is an end tag) - tag name last character is a star - attributes are ended by a trailing slash */ single = slash; if (*(symbol_name) == '/') single = TRUE; if (!single && !(exp_flags & EXP_STAR_COMPLEX)) single = (LAST_CHAR (symbol_name) == '*'); if (!single && !(exp_flags & EXP_DFT_SIMPLE)) collect_body (symbol_name, expansion, &argptr, &body); argv = (token_data **) obstack_finish (&argptr); /* Remove the trailing slash in single tags. */ if (slash) { cp = TOKEN_DATA_TEXT (argv[argc-1]); if (IS_SLASH (*cp) && *(cp+1) == '\0') argc--; else if (IS_SLASH (LAST_CHAR (cp))) { if (IS_SPACE (*(cp+strlen(cp)-2))) *(cp+strlen(cp)-2) = '\0'; else LAST_CHAR (cp) = '\0'; } } if (expansion == READ_ATTR_ASIS || expansion == READ_ATTR_VERB || expansion == READ_BODY) expansion = READ_BODY; obs_expansion = push_string_init (); if (expansion != READ_BODY) obstack_1grow (obs_expansion, CHAR_LQUOTE); obstack_1grow (obs_expansion, '<'); shipout_string (obs_expansion, symbol_name, 0); /* Whitespaces between attributes have been put into attributes text, so there is no need to insert a space between them. */ for (i = 1; i < argc; i++) shipout_string (obs_expansion, TOKEN_DATA_TEXT (argv[i]), 0); if (slash) { if (! (exp_flags & EXP_NOSPACE_BSLASH)) obstack_1grow (obs_expansion, ' '); obstack_1grow (obs_expansion, CHAR_SLASH); } obstack_1grow (obs_expansion, '>'); if (expansion != READ_BODY) obstack_1grow (obs_expansion, CHAR_RQUOTE); if (!single && !(exp_flags & EXP_DFT_SIMPLE)) { shipout_string (obs_expansion, TOKEN_DATA_TEXT (argv[argc]), 0); if (expansion != READ_BODY) obstack_1grow (obs_expansion, CHAR_LQUOTE); obstack_grow (obs_expansion, "'); if (expansion != READ_BODY) obstack_1grow (obs_expansion, CHAR_RQUOTE); } expanded = push_string_finish (expansion); --expansion_level; obstack_free (&arguments, NULL); obstack_free (&argptr, NULL); obstack_free (&body, NULL); xfree ((voidstar) symbol_name); } /*-------------------------------------------------------------------------. | The entity expansion is handled by expand_entity (). Entities are | | treated similarly to macros, they just do not have arguments and the | | symbol text is output directly. | | | | Expand_entity () is not recursive. | `-------------------------------------------------------------------------*/ static void expand_entity (symbol *sym, read_type expansion) { struct obstack *obs_expansion; const char *expanded; boolean traced; int my_call_id; expansion_level++; array_current_line[expansion_level] = current_line; if (expansion_level > nesting_limit) MP4HERROR ((EXIT_FAILURE, 0, _("ERROR: Recursion limit of %d exceeded, use -L to change it"), nesting_limit)); macro_call_id++; my_call_id = macro_call_id; traced = (boolean) ((debug_level & DEBUG_TRACE_ALL) || SYMBOL_TRACED (sym)); if (traced && (debug_level & DEBUG_TRACE_CALL)) trace_prepre (SYMBOL_NAME (sym), my_call_id); obs_expansion = push_string_init (); if (expansion_level > 1) obstack_1grow (obs_expansion, CHAR_BGROUP); shipout_string (obs_expansion, SYMBOL_TEXT (sym), 0); if (expansion_level > 1) obstack_1grow (obs_expansion, CHAR_EGROUP); expanded = push_string_finish (expansion); if (traced) trace_post (SYMBOL_NAME (sym), my_call_id, 0, NULL, expanded); --expansion_level; }