Home | History | Annotate | Line # | Download | only in src
      1 /* xgettext awk backend.
      2    Copyright (C) 2002-2003, 2005-2006 Free Software Foundation, Inc.
      3 
      4    This file was written by Bruno Haible <haible (at) clisp.cons.org>, 2002.
      5 
      6    This program is free software; you can redistribute it and/or modify
      7    it under the terms of the GNU General Public License as published by
      8    the Free Software Foundation; either version 2, or (at your option)
      9    any later version.
     10 
     11    This program is distributed in the hope that it will be useful,
     12    but WITHOUT ANY WARRANTY; without even the implied warranty of
     13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14    GNU General Public License for more details.
     15 
     16    You should have received a copy of the GNU General Public License
     17    along with this program; if not, write to the Free Software Foundation,
     18    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
     19 
     20 #ifdef HAVE_CONFIG_H
     21 # include "config.h"
     22 #endif
     23 
     24 #include <errno.h>
     25 #include <stdbool.h>
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <string.h>
     29 
     30 #include "message.h"
     31 #include "xgettext.h"
     32 #include "x-awk.h"
     33 #include "error.h"
     34 #include "error-progname.h"
     35 #include "xalloc.h"
     36 #include "exit.h"
     37 #include "gettext.h"
     38 
     39 #define _(s) gettext(s)
     40 
     41 
     42 /* The awk syntax is defined in the gawk manual page and documentation.
     43    See also gawk/awkgram.y.  */
     44 
     45 
     46 /* ====================== Keyword set customization.  ====================== */
     47 
     48 /* If true extract all strings.  */
     49 static bool extract_all = false;
     50 
     51 static hash_table keywords;
     52 static bool default_keywords = true;
     53 
     54 
     55 void
     56 x_awk_extract_all ()
     57 {
     58   extract_all = true;
     59 }
     60 
     61 
     62 void
     63 x_awk_keyword (const char *name)
     64 {
     65   if (name == NULL)
     66     default_keywords = false;
     67   else
     68     {
     69       const char *end;
     70       struct callshape shape;
     71       const char *colon;
     72 
     73       if (keywords.table == NULL)
     74 	hash_init (&keywords, 100);
     75 
     76       split_keywordspec (name, &end, &shape);
     77 
     78       /* The characters between name and end should form a valid C identifier.
     79 	 A colon means an invalid parse in split_keywordspec().  */
     80       colon = strchr (name, ':');
     81       if (colon == NULL || colon >= end)
     82 	insert_keyword_callshape (&keywords, name, end - name, &shape);
     83     }
     84 }
     85 
     86 /* Finish initializing the keywords hash table.
     87    Called after argument processing, before each file is processed.  */
     88 static void
     89 init_keywords ()
     90 {
     91   if (default_keywords)
     92     {
     93       /* When adding new keywords here, also update the documentation in
     94 	 xgettext.texi!  */
     95       x_awk_keyword ("dcgettext");
     96       x_awk_keyword ("dcngettext:1,2");
     97       default_keywords = false;
     98     }
     99 }
    100 
    101 void
    102 init_flag_table_awk ()
    103 {
    104   xgettext_record_flag ("dcgettext:1:pass-awk-format");
    105   xgettext_record_flag ("dcngettext:1:pass-awk-format");
    106   xgettext_record_flag ("dcngettext:2:pass-awk-format");
    107   xgettext_record_flag ("printf:1:awk-format");
    108 }
    109 
    110 
    111 /* ======================== Reading of characters.  ======================== */
    112 
    113 /* Real filename, used in error messages about the input file.  */
    114 static const char *real_file_name;
    115 
    116 /* Logical filename and line number, used to label the extracted messages.  */
    117 static char *logical_file_name;
    118 static int line_number;
    119 
    120 /* The input file stream.  */
    121 static FILE *fp;
    122 
    123 /* These are for tracking whether comments count as immediately before
    124    keyword.  */
    125 static int last_comment_line;
    126 static int last_non_comment_line;
    127 
    128 
    129 /* 1. line_number handling.  */
    130 
    131 static int
    132 phase1_getc ()
    133 {
    134   int c = getc (fp);
    135 
    136   if (c == EOF)
    137     {
    138       if (ferror (fp))
    139 	error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
    140 	       real_file_name);
    141       return EOF;
    142     }
    143 
    144   if (c == '\n')
    145     line_number++;
    146 
    147   return c;
    148 }
    149 
    150 /* Supports only one pushback character.  */
    151 static void
    152 phase1_ungetc (int c)
    153 {
    154   if (c != EOF)
    155     {
    156       if (c == '\n')
    157 	--line_number;
    158 
    159       ungetc (c, fp);
    160     }
    161 }
    162 
    163 
    164 /* 2. Replace each comment that is not inside a string literal or regular
    165    expression with a newline character.  We need to remember the comment
    166    for later, because it may be attached to a keyword string.  */
    167 
    168 static int
    169 phase2_getc ()
    170 {
    171   static char *buffer;
    172   static size_t bufmax;
    173   size_t buflen;
    174   int lineno;
    175   int c;
    176 
    177   c = phase1_getc ();
    178   if (c == '#')
    179     {
    180       buflen = 0;
    181       lineno = line_number;
    182       for (;;)
    183 	{
    184 	  c = phase1_getc ();
    185 	  if (c == '\n' || c == EOF)
    186 	    break;
    187 	  /* We skip all leading white space, but not EOLs.  */
    188 	  if (!(buflen == 0 && (c == ' ' || c == '\t')))
    189 	    {
    190 	      if (buflen >= bufmax)
    191 		{
    192 		  bufmax = 2 * bufmax + 10;
    193 		  buffer = xrealloc (buffer, bufmax);
    194 		}
    195 	      buffer[buflen++] = c;
    196 	    }
    197 	}
    198       if (buflen >= bufmax)
    199 	{
    200 	  bufmax = 2 * bufmax + 10;
    201 	  buffer = xrealloc (buffer, bufmax);
    202 	}
    203       buffer[buflen] = '\0';
    204       savable_comment_add (buffer);
    205       last_comment_line = lineno;
    206     }
    207   return c;
    208 }
    209 
    210 /* Supports only one pushback character.  */
    211 static void
    212 phase2_ungetc (int c)
    213 {
    214   if (c != EOF)
    215     phase1_ungetc (c);
    216 }
    217 
    218 
    219 /* ========================== Reading of tokens.  ========================== */
    220 
    221 
    222 enum token_type_ty
    223 {
    224   token_type_eof,
    225   token_type_lparen,		/* ( */
    226   token_type_rparen,		/* ) */
    227   token_type_comma,		/* , */
    228   token_type_string,		/* "abc" */
    229   token_type_i18nstring,	/* _"abc" */
    230   token_type_symbol,		/* symbol, number */
    231   token_type_semicolon,		/* ; */
    232   token_type_other		/* regexp, misc. operator */
    233 };
    234 typedef enum token_type_ty token_type_ty;
    235 
    236 typedef struct token_ty token_ty;
    237 struct token_ty
    238 {
    239   token_type_ty type;
    240   char *string;		/* for token_type_{symbol,string,i18nstring} */
    241   int line_number;
    242 };
    243 
    244 
    245 /* 7. Replace escape sequences within character strings with their
    246    single character equivalents.  */
    247 
    248 #define P7_QUOTES (1000 + '"')
    249 
    250 static int
    251 phase7_getc ()
    252 {
    253   int c;
    254 
    255   for (;;)
    256     {
    257       /* Use phase 1, because phase 2 elides comments.  */
    258       c = phase1_getc ();
    259 
    260       if (c == EOF || c == '\n')
    261 	break;
    262       if (c == '"')
    263 	return P7_QUOTES;
    264       if (c != '\\')
    265 	return c;
    266       c = phase1_getc ();
    267       if (c == EOF)
    268 	break;
    269       if (c != '\n')
    270 	switch (c)
    271 	  {
    272 	  case 'a':
    273 	    return '\a';
    274 	  case 'b':
    275 	    return '\b';
    276 	  case 'f':
    277 	    return '\f';
    278 	  case 'n':
    279 	    return '\n';
    280 	  case 'r':
    281 	    return '\r';
    282 	  case 't':
    283 	    return '\t';
    284 	  case 'v':
    285 	    return '\v';
    286 	  case '0': case '1': case '2': case '3': case '4':
    287 	  case '5': case '6': case '7':
    288 	    {
    289 	      int n = c - '0';
    290 
    291 	      c = phase1_getc ();
    292 	      if (c != EOF)
    293 		{
    294 		  if (c >= '0' && c <= '7')
    295 		    {
    296 		      n = (n << 3) + (c - '0');
    297 		      c = phase1_getc ();
    298 		      if (c != EOF)
    299 			{
    300 			  if (c >= '0' && c <= '7')
    301 			    n = (n << 3) + (c - '0');
    302 			  else
    303 			    phase1_ungetc (c);
    304 			}
    305 		    }
    306 		  else
    307 		    phase1_ungetc (c);
    308 		}
    309 	      return (unsigned char) n;
    310 	    }
    311 	  case 'x':
    312 	    {
    313 	      int n = 0;
    314 
    315 	      for (;;)
    316 		{
    317 		  c = phase1_getc ();
    318 		  if (c == EOF)
    319 		    break;
    320 		  else if (c >= '0' && c <= '9')
    321 		    n = (n << 4) + (c - '0');
    322 		  else if (c >= 'A' && c <= 'F')
    323 		    n = (n << 4) + (c - 'A' + 10);
    324 		  else if (c >= 'a' && c <= 'f')
    325 		    n = (n << 4) + (c - 'a' + 10);
    326 		  else
    327 		    {
    328 		      phase1_ungetc (c);
    329 		      break;
    330 		    }
    331 		}
    332 	      return (unsigned char) n;
    333 	    }
    334 	  default:
    335 	    return c;
    336 	  }
    337     }
    338 
    339   phase1_ungetc (c);
    340   error_with_progname = false;
    341   error (0, 0, _("%s:%d: warning: unterminated string"), logical_file_name,
    342 	 line_number);
    343   error_with_progname = true;
    344   return P7_QUOTES;
    345 }
    346 
    347 
    348 /* Free the memory pointed to by a 'struct token_ty'.  */
    349 static inline void
    350 free_token (token_ty *tp)
    351 {
    352   switch (tp->type)
    353     {
    354     case token_type_string:
    355     case token_type_i18nstring:
    356     case token_type_symbol:
    357       free (tp->string);
    358       break;
    359     default:
    360       break;
    361     }
    362 }
    363 
    364 
    365 /* Combine characters into tokens.  Discard whitespace.  */
    366 
    367 /* There is an ambiguity about '/': It can start a division operator ('/' or
    368    '/=') or it can start a regular expression.  The distinction is important
    369    because inside regular expressions, '#' and '"' lose its special meanings.
    370    If you look at the awk grammar, you see that the operator is only allowed
    371    right after a 'variable' or 'simp_exp' nonterminal, and these nonterminals
    372    can only end in the NAME, LENGTH, YSTRING, YNUMBER, ')', ']' terminals.
    373    So we prefer the division operator interpretation only right after
    374    symbol, string, number, ')', ']', with whitespace but no newline allowed
    375    in between.  */
    376 static bool prefer_division_over_regexp;
    377 
    378 static void
    379 x_awk_lex (token_ty *tp)
    380 {
    381   static char *buffer;
    382   static int bufmax;
    383   int bufpos;
    384   int c;
    385 
    386   for (;;)
    387     {
    388       tp->line_number = line_number;
    389       c = phase2_getc ();
    390 
    391       switch (c)
    392 	{
    393 	case EOF:
    394 	  tp->type = token_type_eof;
    395 	  return;
    396 
    397 	case '\n':
    398 	  if (last_non_comment_line > last_comment_line)
    399 	    savable_comment_reset ();
    400 	  /* Newline is not allowed inside expressions.  It usually
    401 	     introduces a fresh statement.
    402 	     FIXME: Newlines after any of ',' '{' '?' ':' '||' '&&' 'do' 'else'
    403 	     does *not* introduce a fresh statement.  */
    404 	  prefer_division_over_regexp = false;
    405 	  /* FALLTHROUGH */
    406 	case '\t':
    407 	case ' ':
    408 	  /* Ignore whitespace and comments.  */
    409 	  continue;
    410 
    411 	case '\\':
    412 	  /* Backslash ought to be immediately followed by a newline.  */
    413 	  continue;
    414 	}
    415 
    416       last_non_comment_line = tp->line_number;
    417 
    418       switch (c)
    419 	{
    420 	case '.':
    421 	  {
    422 	    int c2 = phase2_getc ();
    423 	    phase2_ungetc (c2);
    424 	    if (!(c2 >= '0' && c2 <= '9'))
    425 	      {
    426 
    427 		tp->type = token_type_other;
    428 		prefer_division_over_regexp = false;
    429 		return;
    430 	      }
    431 	  }
    432 	  /* FALLTHROUGH */
    433 	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
    434 	case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
    435 	case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
    436 	case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
    437 	case 'Y': case 'Z':
    438 	case '_':
    439 	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
    440 	case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
    441 	case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
    442 	case 's': case 't': case 'u': case 'v': case 'w': case 'x':
    443 	case 'y': case 'z':
    444 	case '0': case '1': case '2': case '3': case '4':
    445 	case '5': case '6': case '7': case '8': case '9':
    446 	  /* Symbol, or part of a number.  */
    447 	  bufpos = 0;
    448 	  for (;;)
    449 	    {
    450 	      if (bufpos >= bufmax)
    451 		{
    452 		  bufmax = 2 * bufmax + 10;
    453 		  buffer = xrealloc (buffer, bufmax);
    454 		}
    455 	      buffer[bufpos++] = c;
    456 	      c = phase2_getc ();
    457 	      switch (c)
    458 		{
    459 		case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
    460 		case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
    461 		case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
    462 		case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
    463 		case 'Y': case 'Z':
    464 		case '_':
    465 		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
    466 		case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
    467 		case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
    468 		case 's': case 't': case 'u': case 'v': case 'w': case 'x':
    469 		case 'y': case 'z':
    470 		case '0': case '1': case '2': case '3': case '4':
    471 		case '5': case '6': case '7': case '8': case '9':
    472 		  continue;
    473 		default:
    474 		  if (bufpos == 1 && buffer[0] == '_' && c == '"')
    475 		    {
    476 		      tp->type = token_type_i18nstring;
    477 		      goto case_string;
    478 		    }
    479 		  phase2_ungetc (c);
    480 		  break;
    481 		}
    482 	      break;
    483 	    }
    484 	  if (bufpos >= bufmax)
    485 	    {
    486 	      bufmax = 2 * bufmax + 10;
    487 	      buffer = xrealloc (buffer, bufmax);
    488 	    }
    489 	  buffer[bufpos] = '\0';
    490 	  tp->string = xstrdup (buffer);
    491 	  tp->type = token_type_symbol;
    492 	  /* Most identifiers can be variable names; after them we must
    493 	     interpret '/' as division operator.  But for awk's builtin
    494 	     keywords we have three cases:
    495 	     (a) Must interpret '/' as division operator. "length".
    496 	     (b) Must interpret '/' as start of a regular expression.
    497 		 "do", "exit", "print", "printf", "return".
    498 	     (c) '/' after this keyword in invalid anyway. All others.
    499 	     I used the following script for the distinction.
    500 		for k in $awk_keywords; do
    501 		  echo; echo $k; awk "function foo () { $k / 10 }" < /dev/null
    502 		done
    503 	   */
    504 	  if (strcmp (buffer, "do") == 0
    505 	      || strcmp (buffer, "exit") == 0
    506 	      || strcmp (buffer, "print") == 0
    507 	      || strcmp (buffer, "printf") == 0
    508 	      || strcmp (buffer, "return") == 0)
    509 	    prefer_division_over_regexp = false;
    510 	  else
    511 	    prefer_division_over_regexp = true;
    512 	  return;
    513 
    514 	case '"':
    515 	  tp->type = token_type_string;
    516 	case_string:
    517 	  bufpos = 0;
    518 	  for (;;)
    519 	    {
    520 	      c = phase7_getc ();
    521 	      if (c == EOF || c == P7_QUOTES)
    522 		break;
    523 	      if (bufpos >= bufmax)
    524 		{
    525 		  bufmax = 2 * bufmax + 10;
    526 		  buffer = xrealloc (buffer, bufmax);
    527 		}
    528 	      buffer[bufpos++] = c;
    529 	    }
    530 	  if (bufpos >= bufmax)
    531 	    {
    532 	      bufmax = 2 * bufmax + 10;
    533 	      buffer = xrealloc (buffer, bufmax);
    534 	    }
    535 	  buffer[bufpos] = '\0';
    536 	  tp->string = xstrdup (buffer);
    537 	  prefer_division_over_regexp = true;
    538 	  return;
    539 
    540 	case '(':
    541 	  tp->type = token_type_lparen;
    542 	  prefer_division_over_regexp = false;
    543 	  return;
    544 
    545 	case ')':
    546 	  tp->type = token_type_rparen;
    547 	  prefer_division_over_regexp = true;
    548 	  return;
    549 
    550 	case ',':
    551 	  tp->type = token_type_comma;
    552 	  prefer_division_over_regexp = false;
    553 	  return;
    554 
    555 	case ';':
    556 	  tp->type = token_type_semicolon;
    557 	  prefer_division_over_regexp = false;
    558 	  return;
    559 
    560 	case ']':
    561 	  tp->type = token_type_other;
    562 	  prefer_division_over_regexp = true;
    563 	  return;
    564 
    565 	case '/':
    566 	  if (!prefer_division_over_regexp)
    567 	    {
    568 	      /* Regular expression.
    569 	         Counting brackets is non-trivial. [[] is balanced, and so is
    570 	         [\]]. Also, /[/]/ is balanced and ends at the third slash.
    571 	         Do not count [ or ] if either one is preceded by a \.
    572 	         A '[' should be counted if
    573 	          a) it is the first one so far (brackets == 0), or
    574 	          b) it is the '[' in '[:'.
    575 	         A ']' should be counted if not preceded by a \.
    576 	         According to POSIX, []] is how you put a ] into a set.
    577 	         Try to handle that too.
    578 	       */
    579 	      int brackets = 0;
    580 	      bool pos0 = true;		/* true at start of regexp */
    581 	      bool pos1_open = false;	/* true after [ at start of regexp */
    582 	      bool pos2_open_not = false; /* true after [^ at start of regexp */
    583 
    584 	      for (;;)
    585 		{
    586 		  c = phase1_getc ();
    587 
    588 		  if (c == EOF || c == '\n')
    589 		    {
    590 		      phase1_ungetc (c);
    591 		      error_with_progname = false;
    592 		      error (0, 0, _("%s:%d: warning: unterminated regular expression"),
    593 			     logical_file_name, line_number);
    594 		      error_with_progname = true;
    595 		      break;
    596 		    }
    597 		  else if (c == '[')
    598 		    {
    599 		      if (brackets == 0)
    600 			brackets++;
    601 		      else
    602 			{
    603 			  c = phase1_getc ();
    604 			  if (c == ':')
    605 			    brackets++;
    606 			  phase1_ungetc (c);
    607 			}
    608 		      if (pos0)
    609 			{
    610 			  pos0 = false;
    611 			  pos1_open = true;
    612 			  continue;
    613 			}
    614 		    }
    615 		  else if (c == ']')
    616 		    {
    617 		      if (!(pos1_open || pos2_open_not))
    618 			brackets--;
    619 		    }
    620 		  else if (c == '^')
    621 		    {
    622 		      if (pos1_open)
    623 			{
    624 			  pos1_open = false;
    625 			  pos2_open_not = true;
    626 			  continue;
    627 			}
    628 		    }
    629 		  else if (c == '\\')
    630 		    {
    631 		      c = phase1_getc ();
    632 		      /* Backslash-newline is valid and ignored.  */
    633 		    }
    634 		  else if (c == '/')
    635 		    {
    636 		      if (brackets <= 0)
    637 			break;
    638 		    }
    639 
    640 		  pos0 = false;
    641 		  pos1_open = false;
    642 		  pos2_open_not = false;
    643 		}
    644 
    645 	      tp->type = token_type_other;
    646 	      prefer_division_over_regexp = false;
    647 	      return;
    648 	    }
    649 	  /* FALLTHROUGH */
    650 
    651 	default:
    652 	  /* We could carefully recognize each of the 2 and 3 character
    653 	     operators, but it is not necessary, as we only need to recognize
    654 	     gettext invocations.  Don't bother.  */
    655 	  tp->type = token_type_other;
    656 	  prefer_division_over_regexp = false;
    657 	  return;
    658 	}
    659     }
    660 }
    661 
    662 
    663 /* ========================= Extracting strings.  ========================== */
    664 
    665 
    666 /* Context lookup table.  */
    667 static flag_context_list_table_ty *flag_context_list_table;
    668 
    669 
    670 /* The file is broken into tokens.  Scan the token stream, looking for
    671    a keyword, followed by a left paren, followed by a string.  When we
    672    see this sequence, we have something to remember.  We assume we are
    673    looking at a valid C or C++ program, and leave the complaints about
    674    the grammar to the compiler.
    675 
    676      Normal handling: Look for
    677        keyword ( ... msgid ... )
    678      Plural handling: Look for
    679        keyword ( ... msgid ... msgid_plural ... )
    680 
    681    We use recursion because the arguments before msgid or between msgid
    682    and msgid_plural can contain subexpressions of the same form.  */
    683 
    684 
    685 /* Extract messages until the next balanced closing parenthesis.
    686    Extracted messages are added to MLP.
    687    Return true upon eof, false upon closing parenthesis.  */
    688 static bool
    689 extract_parenthesized (message_list_ty *mlp,
    690 		       flag_context_ty outer_context,
    691 		       flag_context_list_iterator_ty context_iter,
    692 		       struct arglist_parser *argparser)
    693 {
    694   /* Current argument number.  */
    695   int arg = 1;
    696   /* 0 when no keyword has been seen.  1 right after a keyword is seen.  */
    697   int state;
    698   /* Parameters of the keyword just seen.  Defined only in state 1.  */
    699   const struct callshapes *next_shapes = NULL;
    700   /* Whether to implicitly assume the next tokens are arguments even without
    701      a '('.  */
    702   bool next_is_argument = false;
    703   /* Context iterator that will be used if the next token is a '('.  */
    704   flag_context_list_iterator_ty next_context_iter =
    705     passthrough_context_list_iterator;
    706   /* Current context.  */
    707   flag_context_ty inner_context =
    708     inherited_context (outer_context,
    709 		       flag_context_list_iterator_advance (&context_iter));
    710 
    711   /* Start state is 0.  */
    712   state = 0;
    713 
    714   for (;;)
    715     {
    716       token_ty token;
    717 
    718       x_awk_lex (&token);
    719 
    720       if (next_is_argument && token.type != token_type_lparen)
    721 	{
    722 	  /* An argument list starts, even though there is no '('.  */
    723 	  context_iter = next_context_iter;
    724 	  outer_context = inner_context;
    725 	  inner_context =
    726 	    inherited_context (outer_context,
    727 			       flag_context_list_iterator_advance (
    728 				 &context_iter));
    729 	}
    730 
    731       switch (token.type)
    732 	{
    733 	case token_type_symbol:
    734 	  {
    735 	    void *keyword_value;
    736 
    737 	    if (hash_find_entry (&keywords, token.string, strlen (token.string),
    738 				 &keyword_value)
    739 		== 0)
    740 	      {
    741 		next_shapes = (const struct callshapes *) keyword_value;
    742 		state = 1;
    743 	      }
    744 	    else
    745 	      state = 0;
    746 	  }
    747 	  next_is_argument =
    748 	    (strcmp (token.string, "print") == 0
    749 	     || strcmp (token.string, "printf") == 0);
    750 	  next_context_iter =
    751 	    flag_context_list_iterator (
    752 	      flag_context_list_table_lookup (
    753 		flag_context_list_table,
    754 		token.string, strlen (token.string)));
    755 	  free (token.string);
    756 	  continue;
    757 
    758 	case token_type_lparen:
    759 	  if (extract_parenthesized (mlp, inner_context, next_context_iter,
    760 				     arglist_parser_alloc (mlp,
    761 							   state ? next_shapes : NULL)))
    762 	    {
    763 	      arglist_parser_done (argparser, arg);
    764 	      return true;
    765 	    }
    766 	  next_is_argument = false;
    767 	  next_context_iter = null_context_list_iterator;
    768 	  state = 0;
    769 	  continue;
    770 
    771 	case token_type_rparen:
    772 	  arglist_parser_done (argparser, arg);
    773 	  return false;
    774 
    775 	case token_type_comma:
    776 	  arg++;
    777 	  inner_context =
    778 	    inherited_context (outer_context,
    779 			       flag_context_list_iterator_advance (
    780 				 &context_iter));
    781 	  next_is_argument = false;
    782 	  next_context_iter = passthrough_context_list_iterator;
    783 	  state = 0;
    784 	  continue;
    785 
    786 	case token_type_string:
    787 	  {
    788 	    lex_pos_ty pos;
    789 	    pos.file_name = logical_file_name;
    790 	    pos.line_number = token.line_number;
    791 
    792 	    if (extract_all)
    793 	      remember_a_message (mlp, NULL, token.string, inner_context, &pos,
    794 				  savable_comment);
    795 	    else
    796 	      arglist_parser_remember (argparser, arg, token.string,
    797 				       inner_context,
    798 				       pos.file_name, pos.line_number,
    799 				       savable_comment);
    800 	  }
    801 	  next_is_argument = false;
    802 	  next_context_iter = null_context_list_iterator;
    803 	  state = 0;
    804 	  continue;
    805 
    806 	case token_type_i18nstring:
    807 	  {
    808 	    lex_pos_ty pos;
    809 	    pos.file_name = logical_file_name;
    810 	    pos.line_number = token.line_number;
    811 
    812 	    remember_a_message (mlp, NULL, token.string, inner_context, &pos,
    813 				savable_comment);
    814 	  }
    815 	  next_is_argument = false;
    816 	  next_context_iter = null_context_list_iterator;
    817 	  state = 0;
    818 	  continue;
    819 
    820 	case token_type_semicolon:
    821 	  /* An argument list ends, and a new statement begins.  */
    822 	  /* FIXME: Should handle newline that acts as statement separator
    823 	     in the same way.  */
    824 	  /* FIXME: Instead of resetting outer_context here, it may be better
    825 	     to recurse in the next_is_argument handling above, waiting for
    826 	     the next semicolon or other statement terminator.  */
    827 	  outer_context = null_context;
    828 	  context_iter = null_context_list_iterator;
    829 	  next_is_argument = false;
    830 	  next_context_iter = passthrough_context_list_iterator;
    831 	  inner_context =
    832 	    inherited_context (outer_context,
    833 			       flag_context_list_iterator_advance (
    834 				 &context_iter));
    835 	  state = 0;
    836 	  continue;
    837 
    838 	case token_type_eof:
    839 	  arglist_parser_done (argparser, arg);
    840 	  return true;
    841 
    842 	case token_type_other:
    843 	  next_is_argument = false;
    844 	  next_context_iter = null_context_list_iterator;
    845 	  state = 0;
    846 	  continue;
    847 
    848 	default:
    849 	  abort ();
    850 	}
    851     }
    852 }
    853 
    854 
    855 void
    856 extract_awk (FILE *f,
    857 	     const char *real_filename, const char *logical_filename,
    858 	     flag_context_list_table_ty *flag_table,
    859 	     msgdomain_list_ty *mdlp)
    860 {
    861   message_list_ty *mlp = mdlp->item[0]->messages;
    862 
    863   fp = f;
    864   real_file_name = real_filename;
    865   logical_file_name = xstrdup (logical_filename);
    866   line_number = 1;
    867 
    868   last_comment_line = -1;
    869   last_non_comment_line = -1;
    870 
    871   prefer_division_over_regexp = false;
    872 
    873   flag_context_list_table = flag_table;
    874 
    875   init_keywords ();
    876 
    877   /* Eat tokens until eof is seen.  When extract_parenthesized returns
    878      due to an unbalanced closing parenthesis, just restart it.  */
    879   while (!extract_parenthesized (mlp, null_context, null_context_list_iterator,
    880 				 arglist_parser_alloc (mlp, NULL)))
    881     ;
    882 
    883   fp = NULL;
    884   real_file_name = NULL;
    885   logical_file_name = NULL;
    886   line_number = 0;
    887 }
    888