/* $Id: pattypes.c,v 1.13 2009/02/12 13:14:49 kiesling Exp $ */

/*
  This file is part of Ctalk.
  Copyright © 2005-2009  Robert Kiesling, ctalk@ctalklang.org.
  Permission is granted to copy this software provided that this copyright
  notice is included in all source code modules.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Lesser 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., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "lex.h"
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "typeof.h"
#include "rtinfo.h"

/*
 *  Complete expression is within arrayopen.. arrayclose pair.
 */
int is_simple_subscript (MESSAGE_STACK messages, int expr_start_idx,
			 int stack_start_idx, int stack_end_idx) {
  int prev_tok_idx, next_tok_idx, expr_end_idx;
  char *exprbuf;
  
  if ((prev_tok_idx = __ctalkPrevLangMsg (messages, expr_start_idx, 
				      stack_start_idx)) == ERROR)
    return FALSE;

  if (M_TOK(messages[prev_tok_idx]) != ARRAYOPEN) return FALSE;

  exprbuf = collect_expression (messages, expr_start_idx,
				&expr_end_idx, stack_start_idx,
				stack_end_idx);
  free (exprbuf);
  
  if ((next_tok_idx = __ctalkNextLangMsg (messages, expr_end_idx, 
				      stack_end_idx)) == ERROR)
    return FALSE;

  if (M_TOK(messages[next_tok_idx]) != ARRAYCLOSE) return FALSE;

  return TRUE;
}

int is_fmt_arg (MESSAGE_STACK messages, int expr_start_idx,
		int stack_start_idx, int stack_end_idx) {
  return (is_char_ptr_fmt_arg (messages,expr_start_idx,
			       stack_start_idx, stack_end_idx) ||
	  is_char_fmt_arg (messages,expr_start_idx,
			   stack_start_idx, stack_end_idx) ||
	  is_double_fmt_arg (messages,expr_start_idx,
			   stack_start_idx, stack_end_idx) ||
	  is_int_fmt_arg (messages,expr_start_idx,
			  stack_start_idx, stack_end_idx));
}

int is_char_ptr_fmt_arg (MESSAGE_STACK messages, int expr_start_idx,
			 int stack_start_idx, int stack_end_idx) {
  int i, n_th_arg;
  int paren_lvl, block_lvl, array_lvl, start_paren_lvl, start_array_lvl;
  MESSAGE *m;
  int fmt_str_length;
  char *fmt_char_ptr;
  enum {
    ptr_fmt_arg_null,
    ptr_fmt_arg_expr,
    ptr_fmt_arg_separator,
    ptr_fmt_arg_literal
  } state;

  paren_lvl = block_lvl = array_lvl = n_th_arg = 0;
  start_paren_lvl = start_array_lvl = -1;
  for (i = expr_start_idx, state = ptr_fmt_arg_null; i <= stack_start_idx; i++) {

    m = messages[i];
    if (M_ISSPACE(m)) continue;

    switch (M_TOK(m))
      {
      case LABEL:
	if ((state == ptr_fmt_arg_null) || (state == ptr_fmt_arg_separator))
	  state = ptr_fmt_arg_expr;
	break; 
      case ARGSEPARATOR:
	if (start_paren_lvl != -1) {
	  if ((paren_lvl == start_paren_lvl) && !block_lvl && !array_lvl) {
	    state = ptr_fmt_arg_separator;
	    --n_th_arg;
	  }
	} else {
	  if (!paren_lvl && !block_lvl && !array_lvl) {
	    state = ptr_fmt_arg_separator;
	    --n_th_arg;
	  }
	}
	break;
      case LITERAL:
 	if (state == ptr_fmt_arg_separator) {
 	  if ((fmt_str_length = is_printf_fmt (M_NAME(m))) != 0) {
	    int __n;
	    /*
	     *  n_th_arg is -1 for the first argument, -2 for the 
	     *  second, -3 for the third, ....
	     */
  	    if ((n_th_arg == -1) && is_char_ptr_fmt(M_NAME(m))) {
  	      return TRUE;
 	    } else {
	      fmt_char_ptr = index (M_NAME(m), '%');
 	      for (__n = -2; __n >= n_th_arg; __n--) {
		if ((fmt_char_ptr = index (fmt_char_ptr + 1, '%')) == NULL)
		  return FALSE;
		if (is_char_ptr_fmt (fmt_char_ptr) && (__n == n_th_arg)) {
		  return TRUE;
		} else {
		  if (__n == n_th_arg) {
		    return FALSE;
		  }
		}
	      }
	    }
	  }
 	} else {
	  state = ptr_fmt_arg_expr;
	}
	break;
      case OPENPAREN:
 	++paren_lvl;
 	if (state == ptr_fmt_arg_null) {
 	  start_paren_lvl = paren_lvl;
	  state = ptr_fmt_arg_expr;
	} else {
	  if (start_paren_lvl != -1) {
	    if ((paren_lvl < start_paren_lvl) && (state == ptr_fmt_arg_expr)) {
	      return FALSE;
	    }
	  } else {
	    int __prev_idx;
	    if ((__prev_idx = __ctalkPrevLangMsg (messages, i, stack_start_idx)) 
		!= ERROR) {
	      /*
	       *  Function call syntax.
	       */
	      if (M_TOK(messages[__prev_idx]) == LABEL)
		return FALSE;
	    }
	  }
	}
	break;
      case CLOSEPAREN:
 	--paren_lvl;
	if (state == ptr_fmt_arg_separator)
	  state = ptr_fmt_arg_expr;
	break;
      case OPENBLOCK:
 	++block_lvl;
	break;
      case CLOSEBLOCK:
 	--block_lvl;
	break;
      case ARRAYOPEN:
 	++array_lvl;
 	if (state == ptr_fmt_arg_null)
 	  start_array_lvl = array_lvl;
	break;
      case ARRAYCLOSE:
 	--array_lvl;
	break;
      default:
	break;
      } /* switch (M_TOK(m)) */
  }
  return TRUE;
}

int is_double_fmt_arg (MESSAGE_STACK messages, int expr_start_idx,
			 int stack_start_idx, int stack_end_idx) {
  int i, n_th_arg;
  int paren_lvl, block_lvl, array_lvl, start_paren_lvl, start_array_lvl;
  MESSAGE *m;
  int fmt_str_length;
  char *fmt_char_ptr;
  enum {
    ptr_fmt_arg_null,
    ptr_fmt_arg_expr,
    ptr_fmt_arg_separator,
    ptr_fmt_arg_literal
  } state;

  paren_lvl = block_lvl = array_lvl = n_th_arg = 0;
  start_paren_lvl = start_array_lvl = -1;
  for (i = expr_start_idx, state = ptr_fmt_arg_null; i <= stack_start_idx; i++) {

    m = messages[i];
    if (M_ISSPACE(m)) continue;

    switch (M_TOK(m))
      {
      case LABEL:
	if ((state == ptr_fmt_arg_null) || (state == ptr_fmt_arg_separator))
	  state = ptr_fmt_arg_expr;
	break; 
      case ARGSEPARATOR:
	if (start_paren_lvl != -1) {
	  if ((paren_lvl == start_paren_lvl) && !block_lvl && !array_lvl) {
	    state = ptr_fmt_arg_separator;
	    --n_th_arg;
	  }
	} else {
	  if (!paren_lvl && !block_lvl && !array_lvl) {
	    state = ptr_fmt_arg_separator;
	    --n_th_arg;
	  }
	}
	break;
      case LITERAL:
 	if (state == ptr_fmt_arg_separator) {
 	  if ((fmt_str_length = is_printf_fmt (M_NAME(m))) != 0) {
	    int __n;
	    /*
	     *  n_th_arg is -1 for the first argument, -2 for the 
	     *  second, -3 for the third, ....
	     */
  	    if ((n_th_arg == -1) && is_double_fmt(M_NAME(m))) {
  	      return TRUE;
 	    } else {
	      fmt_char_ptr = index (M_NAME(m), '%');
 	      for (__n = -2; __n >= n_th_arg; __n--) {
		if ((fmt_char_ptr = index (fmt_char_ptr + 1, '%')) == NULL)
		  return FALSE;
		if (is_double_fmt (fmt_char_ptr) && (__n == n_th_arg)) {
		  return TRUE;
		} else {
		  if (__n == n_th_arg) {
		    return FALSE;
		  }
		}
	      }
	    }
	  }
 	} else {
	  state = ptr_fmt_arg_expr;
	}
	break;
      case OPENPAREN:
 	++paren_lvl;
 	if (state == ptr_fmt_arg_null) {
 	  start_paren_lvl = paren_lvl;
	  state = ptr_fmt_arg_expr;
	} else {
	  if (start_paren_lvl != -1) {
	    if ((paren_lvl < start_paren_lvl) && (state == ptr_fmt_arg_expr)) {
	      return FALSE;
	    }
	  } else {
	    int __prev_idx;
	    if ((__prev_idx = __ctalkPrevLangMsg (messages, i, stack_start_idx)) 
		!= ERROR) {
	      /*
	       *  Function call syntax.
	       */
	      if (M_TOK(messages[__prev_idx]) == LABEL)
		return FALSE;
	    }
	  }
	}
	break;
      case CLOSEPAREN:
 	--paren_lvl;
	if (state == ptr_fmt_arg_separator)
	  state = ptr_fmt_arg_expr;
	break;
      case OPENBLOCK:
 	++block_lvl;
	break;
      case CLOSEBLOCK:
 	--block_lvl;
	break;
      case ARRAYOPEN:
 	++array_lvl;
 	if (state == ptr_fmt_arg_null)
 	  start_array_lvl = array_lvl;
	break;
      case ARRAYCLOSE:
 	--array_lvl;
	break;
      default:
	break;
      } /* switch (M_TOK(m)) */
  }
  return TRUE;
}

int is_char_fmt_arg (MESSAGE_STACK messages, int expr_start_idx,
		     int stack_start_idx, int stack_end_idx) {
  int i, n_th_arg;
  int paren_lvl, block_lvl, array_lvl, start_paren_lvl, start_array_lvl;
  MESSAGE *m;
  int fmt_str_length;
  char *fmt_char_ptr;
  enum {
    ptr_fmt_arg_null,
    ptr_fmt_arg_expr,
    ptr_fmt_arg_separator,
    ptr_fmt_arg_literal
  } state;

  paren_lvl = block_lvl = array_lvl = n_th_arg = 0;
  start_paren_lvl = start_array_lvl = -1;
  for (i = expr_start_idx, state = ptr_fmt_arg_null; i <= stack_start_idx; i++) {

    m = messages[i];
    if (M_ISSPACE(m)) continue;

    switch (M_TOK(m))
      {
      case LABEL:
	if ((state == ptr_fmt_arg_null) || (state == ptr_fmt_arg_separator))
	  state = ptr_fmt_arg_expr;
	break; 
      case ARGSEPARATOR:
	if (start_paren_lvl != -1) {
	  if ((paren_lvl == start_paren_lvl) && !block_lvl && !array_lvl) {
	    state = ptr_fmt_arg_separator;
	    --n_th_arg;
	  }
	} else {
	  if (!paren_lvl && !block_lvl && !array_lvl) {
	    state = ptr_fmt_arg_separator;
	    --n_th_arg;
	  }
	}
	break;
      case LITERAL:
 	if (state == ptr_fmt_arg_separator) {
 	  if ((fmt_str_length = is_printf_fmt (M_NAME(m))) != 0) {
	    int __n;
	    /*
	     *  n_th_arg is -1 for the first argument, -2 for the 
	     *  second, -3 for the third, ....
	     */
  	    if ((n_th_arg == -1) && is_char_fmt(M_NAME(m))) {
  	      return TRUE;
 	    } else {
	      fmt_char_ptr = index (M_NAME(m), '%');
 	      for (__n = -2; __n >= n_th_arg; __n--) {
		if ((fmt_char_ptr = index (fmt_char_ptr + 1, '%')) == NULL)
		  return FALSE;
		if (is_char_fmt (fmt_char_ptr) && (__n == n_th_arg)) {
		  return TRUE;
		} else {
		  if (__n == n_th_arg) {
		    return FALSE;
		  }
		}
	      }
	    }
	  }
 	} else {
	  state = ptr_fmt_arg_expr;
	}
	break;
      case OPENPAREN:
 	++paren_lvl;
 	if (state == ptr_fmt_arg_null) {
 	  start_paren_lvl = paren_lvl;
	  state = ptr_fmt_arg_expr;
	} else {
	  if (start_paren_lvl != -1) {
	    if ((paren_lvl < start_paren_lvl) && (state == ptr_fmt_arg_expr)) {
	      return FALSE;
	    }
	  } else {
	    int __prev_idx;
	    if ((__prev_idx = __ctalkPrevLangMsg (messages, i, stack_start_idx)) 
		!= ERROR) {
	      /*
	       *  Function call syntax.
	       */
	      if (M_TOK(messages[__prev_idx]) == LABEL)
		return FALSE;
	    }
	  }
	}
	break;
      case CLOSEPAREN:
 	--paren_lvl;
	if (state == ptr_fmt_arg_separator)
	  state = ptr_fmt_arg_expr;
	break;
      case OPENBLOCK:
 	++block_lvl;
	break;
      case CLOSEBLOCK:
 	--block_lvl;
	break;
      case ARRAYOPEN:
 	++array_lvl;
 	if (state == ptr_fmt_arg_null)
 	  start_array_lvl = array_lvl;
	break;
      case ARRAYCLOSE:
 	--array_lvl;
	break;
      default:
	break;
      } /* switch (M_TOK(m)) */
  }
  return TRUE;
}

int is_int_fmt_arg (MESSAGE_STACK messages, int expr_start_idx,
		     int stack_start_idx, int stack_end_idx) {
  int i, n_th_arg;
  int paren_lvl, block_lvl, array_lvl, start_paren_lvl, start_array_lvl;
  MESSAGE *m;
  int fmt_str_length;
  char *fmt_char_ptr;
  enum {
    int_fmt_arg_null,
    int_fmt_arg_expr,
    int_fmt_arg_separator,
    int_fmt_arg_literal
  } state;

  paren_lvl = block_lvl = array_lvl = n_th_arg = 0;
  start_paren_lvl = start_array_lvl = -1;
  for (i = expr_start_idx, state = int_fmt_arg_null; i <= stack_start_idx; i++) {

    m = messages[i];
    if (M_ISSPACE(m)) continue;

    switch (M_TOK(m))
      {
      case LABEL:
	if ((state == int_fmt_arg_null) || (state == int_fmt_arg_separator))
	  state = int_fmt_arg_expr;
	break; 
      case ARGSEPARATOR:
	if (start_paren_lvl != -1) {
	  if ((paren_lvl == start_paren_lvl) && !block_lvl && !array_lvl) {
	    state = int_fmt_arg_separator;
	    --n_th_arg;
	  }
	} else {
	  if (!paren_lvl && !block_lvl && !array_lvl) {
	    state = int_fmt_arg_separator;
	    --n_th_arg;
	  }
	}
	break;
      case LITERAL:
 	if (state == int_fmt_arg_separator) {
 	  if ((fmt_str_length = is_printf_fmt (M_NAME(m))) != 0) {
	    int __n;
	    /*
	     *  n_th_arg is -1 for the first argument, -2 for the 
	     *  second, -3 for the third, ....
	     */
  	    if ((n_th_arg == -1) && is_int_fmt(M_NAME(m))) {
  	      return TRUE;
 	    } else {
	      fmt_char_ptr = index (M_NAME(m), '%');
 	      for (__n = -2; __n >= n_th_arg; __n--) {
		if ((fmt_char_ptr = index (fmt_char_ptr + 1, '%')) == NULL)
		  return FALSE;
		if (is_int_fmt (fmt_char_ptr) && (__n == n_th_arg)) {
		  return TRUE;
		} else {
		  if (__n == n_th_arg) {
		    return FALSE;
		  }
		}
	      }
	    }
	  }
 	} else {
	  state = int_fmt_arg_expr;
	}
	break;
      case OPENPAREN:
 	++paren_lvl;
 	if (state == int_fmt_arg_null) {
 	  start_paren_lvl = paren_lvl;
	  state = int_fmt_arg_expr;
	} else {
	  if (start_paren_lvl != -1) {
	    if ((paren_lvl < start_paren_lvl) && (state == int_fmt_arg_expr)) {
	      return FALSE;
	    }
	  } else {
	    int __prev_idx;
	    if ((__prev_idx = __ctalkPrevLangMsg (messages, i, stack_start_idx)) 
		!= ERROR) {
	      /*
	       *  Function call syntax.
	       */
	      if (M_TOK(messages[__prev_idx]) == LABEL)
		return FALSE;
	    }
	  }
	}
	break;
      case CLOSEPAREN:
 	--paren_lvl;
	if (state == int_fmt_arg_separator)
	  state = int_fmt_arg_expr;
	break;
      case OPENBLOCK:
 	++block_lvl;
	break;
      case CLOSEBLOCK:
 	--block_lvl;
	break;
      case ARRAYOPEN:
 	++array_lvl;
 	if (state == int_fmt_arg_null)
 	  start_array_lvl = array_lvl;
	break;
      case ARRAYCLOSE:
 	--array_lvl;
	break;
      default:
	break;
      } /* switch (M_TOK(m)) */
  }
  return TRUE;
}

int is_char_ptr_fmt (char *s) {
  int i;
  char buf[MAXMSG];
  enum {
    char_ptr_fmt_arg_null,
    char_ptr_fmt_arg_percent,
    char_ptr_fmt_arg_precision,
    char_ptr_fmt_arg_length_mod,
    char_ptr_fmt_fmt_char
  } state;

  strcpy (buf, s);
  if (*buf == '"')
    TRIM_LITERAL(buf);

  for (i = 0, state = char_ptr_fmt_arg_null; s[i]; i++) {

    switch (buf[i])
      {
      case '%':
	if (state == char_ptr_fmt_arg_null)
	  state = char_ptr_fmt_arg_percent;
	else
	  return FALSE;
	break;
      case '.':
	if (state == char_ptr_fmt_arg_percent)
	  state = char_ptr_fmt_arg_precision;
	else
	  return FALSE;
	break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
	if (state != char_ptr_fmt_arg_precision)
	  return FALSE;
	break;
      case 'l':
	if ((state == char_ptr_fmt_arg_percent) ||
	    (state == char_ptr_fmt_arg_precision))
	  state = char_ptr_fmt_arg_length_mod;
	else
	  return FALSE;
	break;
      case 's':
      case 'S':
	if ((state == char_ptr_fmt_arg_percent) ||
	    (state == char_ptr_fmt_arg_precision) ||
	    (state == char_ptr_fmt_arg_length_mod))
	  return TRUE;
	else
	  return FALSE;
	break;
      }
  }
  return FALSE;
}

int is_char_fmt (char *s) {
  int i;
  char buf[MAXMSG];
  enum {
    char_fmt_arg_null,
    char_fmt_arg_percent,
    char_fmt_arg_precision,
    char_fmt_arg_length_mod,
    char_fmt_fmt_char
  } state;

  strcpy (buf, s);
  if (*buf == '"')
    TRIM_LITERAL(buf);

  for (i = 0, state = char_fmt_arg_null; s[i]; i++) {

    switch (buf[i])
      {
      case '%':
	if (state == char_fmt_arg_null)
	  state = char_fmt_arg_percent;
	else
	  return FALSE;
	break;
      case '.':
	if (state == char_fmt_arg_percent)
	  state = char_fmt_arg_precision;
	else
	  return FALSE;
	break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
	if (state != char_fmt_arg_precision)
	  return FALSE;
	break;
      case 'l':
	if ((state == char_fmt_arg_percent) ||
	    (state == char_fmt_arg_precision))
	  state = char_fmt_arg_length_mod;
	else
	  return FALSE;
	break;
      case 'c':
	if (state == char_fmt_arg_percent)
	  return TRUE;
	break;
      }
  }
  return FALSE;
}

int is_int_fmt (char *s) {
  int i;
  char buf[MAXMSG];
  enum {
    int_fmt_arg_null,
    int_fmt_arg_percent,
    int_fmt_arg_precision,
    int_fmt_arg_length_mod,
    int_fmt_fmt_char
  } state;

  strcpy (buf, s);
  if (*buf == '"')
    TRIM_LITERAL(buf);

  for (i = 0, state = int_fmt_arg_null; s[i]; i++) {

    switch (buf[i])
      {
      case '%':
	if (state == int_fmt_arg_null)
	  state = int_fmt_arg_percent;
	else
	  return FALSE;
	break;
      case '.':
	if (state == int_fmt_arg_percent)
	  state = int_fmt_arg_precision;
	else
	  return FALSE;
	break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
	if (state != int_fmt_arg_precision)
	  return FALSE;
	break;
      case 'l':
	if ((state == int_fmt_arg_percent) ||
	    (state == int_fmt_arg_precision))
	  state = int_fmt_arg_length_mod;
	else
	  return FALSE;
	break;
      case 'i':
      case 'd':
      case 'o':
      case 'u':
      case 'x':
      case 'X':
	if (state == int_fmt_arg_percent)
	  return TRUE;
	break;
      }
  }
  return FALSE;
}

int is_printf_fmt_msg (MESSAGE_STACK messages, int fmt_tok_idx,
		       int stack_start_idx, int stack_end_idx) {
  return is_printf_fmt (M_NAME(messages[fmt_tok_idx]));
}

int is_function_call (MESSAGE_STACK messages, int fn_label_idx,
		      int stack_start_idx, int stack_end_idx) {
  int open_paren_idx, close_paren_idx;
  if ((open_paren_idx = __ctalkNextLangMsg (messages, fn_label_idx,
					    stack_end_idx)) == ERROR)
    return FALSE;
  if (M_TOK(messages[open_paren_idx]) != OPENPAREN)
    return FALSE;
  if ((close_paren_idx = __ctalkMatchParen (messages, 
					    open_paren_idx,
					    stack_end_idx)) == ERROR)
    return FALSE;
  return TRUE;
}

int is_struct_or_union_expr (MESSAGE_STACK messages, 
			     int expr_start_label_idx,
			     int stack_start_idx,
			     int stack_end_idx) {
  int next_tok_idx;
  if ((next_tok_idx = __ctalkNextLangMsg(messages,
					 expr_start_label_idx,
					 stack_end_idx)) != ERROR) {
    if ((M_TOK(messages[next_tok_idx]) == PERIOD) ||
	(M_TOK(messages[next_tok_idx]) == DEREF)) {
      return TRUE;
    }
  }
  return FALSE;
}

/*
 *  Returns the index of the argument, or -1;
 *
 *  Args except for printf format args.
 */
int obj_expr_is_arg (MESSAGE_STACK messages, int expr_start_idx,
		     int stack_start_idx, int stack_end_idx,
		     int *fn_label_idx) {
  int arg_idx = -1;
  int i;
  int n_parens = 0;

  for (i = expr_start_idx; i <= stack_start_idx; i++) {
    switch (M_TOK(messages[i]))
      {
      case ARGSEPARATOR:
	++arg_idx;
	break;
      case OPENPAREN:
	--n_parens;
	if (n_parens < 0) {
	  ++arg_idx;
	  *fn_label_idx = __ctalkPrevLangMsg (messages, i, stack_start_idx);
	  return arg_idx;
	}
	break;
      case CLOSEPAREN:
	++n_parens;
	break;
      case SEMICOLON:
	return ERROR;
	break;
      case OPENBLOCK:
	return ERROR;
	break;
      case LITERAL:
	if (is_printf_fmt (M_NAME(messages[i])))
	  return ERROR;
	break;
      }
  }
  return ERROR;
}

int is_double_fmt (char *s) {
  int i;
  char buf[MAXMSG];
  enum {
    double_fmt_arg_null,
    double_fmt_arg_percent,
    double_fmt_arg_precision,
    double_fmt_arg_length_mod,
    double_fmt_fmt_char
  } state;

  strcpy (buf, s);
  if (*buf == '"')
    TRIM_LITERAL(buf);

  for (i = 0, state = double_fmt_arg_null; s[i]; i++) {

    switch (buf[i])
      {
      case '%':
	if (state == double_fmt_arg_null)
	  state = double_fmt_arg_percent;
	else
	  return FALSE;
	break;
      case '.':
	if (state == double_fmt_arg_percent)
	  state = double_fmt_arg_precision;
	else
	  return FALSE;
	break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
	if (state != double_fmt_arg_precision)
	  return FALSE;
	break;
      case 'l':
	if ((state == double_fmt_arg_percent) ||
	    (state == double_fmt_arg_precision))
	  state = double_fmt_arg_length_mod;
	else
	  return FALSE;
	break;
      case 'e':
      case 'E':
      case 'f':
      case 'F':
      case 'g':
      case 'G':
	if (state == double_fmt_arg_percent)
	  return TRUE;
	break;
      }
  }
  return FALSE;
}
