/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
*/
/*
 * AtFStk -- Attribute Filesystem Toolkit Library
 *
 * bind_scan.c -- AtFS toolkit library (Version Binding)
 *
 * Author: Andreas Lampen (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: bind_scan.c[7.0] Tue Jun 29 16:15:57 1993 andy@cs.tu-berlin.de frozen $
 */

#include <ctype.h>
#include "atfs.h"
#include "sttk.h"
#include "atfstk.h"
#include "bind.h"

static BindRule *curRule;

/*=========================
 *  bindRuleErrors
 *=========================*/

#define BIND_SCAN_ERROR -1

LOCAL char *bindRuleErrorMsgs[] =
{
  "no error",			/* 0 */
  "invalid predicate name",	/* 1 */
  "unexpected end of rule",	/* 2 */
  "garbage after end of rule",	/* 3 */
  "argument expected",		/* 4 */
  "'(' expected",		/* 5 */
  "')' expected",		/* 6 */
  "',', ';', or '.' expected",	/* 7 */
  "invalid macro citation",	/* 8 */
  "rule name missing",          /* 9 */
  "too many rule arguments",    /* 10 */
  "incomplete argument definition list", /* 11 */
  "",
};

LOCAL char *bindRuleErrorPtr;

LOCAL void reportError ()
{
  char ruleText[16];
  int  i;

  memset (ruleText, 0, 16);

  if (bindRuleErrorPtr) {
    for (i=0; i<12; i++) {
      switch (*bindRuleErrorPtr) {
      case '\n':
	ruleText[i++] = '\\';
	ruleText[i] = 'n';
	break;
      case '\f':
	ruleText[i++] = '\\';
	ruleText[i] = 'f';
	break;
      case '\t':
	ruleText[i++] = '\\';
	ruleText[i] = 't';
	break;
      default:
	ruleText[i] = *bindRuleErrorPtr;
      }
      if (!(*bindRuleErrorPtr++))
	break;
    }
    if (ruleText[11])
      ruleText[12] = ruleText[13] = ruleText[14] = '.';
  }
  
  sprintf (stMessage, "\n%s:%d: ERROR in bind rule -- %s%s%s",
	   curRule->fileName, curRule->srcLine, bindRuleErrorMsgs[curRule->status],
	   ruleText[0] ? " at ->" : "", ruleText[0] ? ruleText : "");
  stLog (stMessage, ST_LOG_MSGERR);
}

LOCAL void bindRuleError (code, pos)
     int code;
     char *pos;
{
  int saveCode = curRule->status;

  curRule->status = code;
  bindRuleErrorPtr = pos;

  if (code && atBindDisplayErrors) reportError ();

  atBindError = TRUE;
  strcpy (atBindErrorMsg, "rule scan error(s)");

  if (saveCode)
    curRule->status = saveCode; /* preserve first error condition */
}

/*=========================
 *  local routines
 *=========================*/

#define skipWhitespace(p) while (isspace (*p)) p++;

#define ALTCHAR  ';'
#define PREDCHAR ','

LOCAL char *moveToNextAlt (ptr)
     char *ptr; 
     /* only called in case of ERROR */
     /* moves forward to next alternative */
{
#define SINGLEQ  01
#define DOUBLEQ  02
  int parenth = 0;

  if (ptr && (*ptr == '\\')) ptr++;

  while (*ptr++) {
    if (*ptr == '\\') { ptr++; continue; }

    /* quotes and parentheses are ignored */
/*
    The code in comments does not work satisfactory
    if (*ptr == '"') quotes ^= DOUBLEQ;
    if (*ptr == '\'') quotes ^= SINGLEQ;
    if (quotes) continue;
  
    if (*ptr == '(') { parenth++; continue; }
    if (*ptr == ')') { parenth--; continue; }
*/
    if ((*ptr == ';') && !parenth) { ptr++; return (ptr); }
  }
  return (NULL);
}

/*==============================
 *  macro handling
 *==============================*/

#define NO_MACRO       0
#define SHAPE_MACRO    1
#define RULE_MACRO     2
#define RULE_PARAMETER 3
#define SHELL_COMMAND  4

LOCAL char *skipMacroCitation (ptr, macroType)
     char *ptr;
     int *macroType;
{
  char *macroPtr = ptr;

  if (!*++macroPtr) {
    *macroType = NO_MACRO;
    return (ptr);
  }

  *macroType = RULE_MACRO;
  if ((*macroPtr == '+') || (*macroPtr == '='))
    return (++macroPtr);
  if (*macroPtr == '_') {
    if (!(*++macroPtr)) {
      *macroType = NO_MACRO;
      return (ptr);
    }
    /* perhaps we should check for RULE_PARAMETER */
    while (*macroPtr) {
      if ((*macroPtr == ',') || (*macroPtr == ')'))
	return (macroPtr);
      else if ((*macroPtr == '$') || isspace (*macroPtr))
	return (++macroPtr);
      else
	macroPtr++;
    }

    bindRuleError (8, macroPtr);
    *macroType = -1;
    return (ptr);
  }

  *macroType = SHAPE_MACRO;
  if ((*macroPtr != '(') && (*macroPtr != '{'))
    return (++macroPtr);
  while (*macroPtr && (*macroPtr != '\n')) {
    if ((*macroPtr == ')') || (*macroPtr == '}'))
      return (++macroPtr);
    else
      macroPtr++;
  }

  bindRuleError (8, macroPtr);
  *macroType = -1;
  return (ptr);
}

/*========================
 *  ruleInit
 *========================*/

static char *nextAlt;
static char *nextPred;

LOCAL int ruleInit (rule)
     BindRule *rule;
     /* do initial parsing if necessary (preprocessing ?).
      * set alternative pointer to first alternative.
      * RETURN: TRUE or FALSE
      */
{
  curRule = rule;
  curRule->status = 0;
  curRule->altList = NULL;

  nextAlt = curRule->body;
  nextPred = NULL;

  if (!curRule->body) {
    bindRuleError (2, (char *)0);
    return (FALSE);
  }

  return (TRUE);
}

/*==========================
 *  predicate list
 *==========================*/

/* predicate names -- ordered alphabetically */

#define PREDCOUNT 29 /* number of valid predicate names */

LOCAL struct {
  char *name;
  int  argCount;
  int  predCode;
} predicates[] =
{
  { "attr",	  2, BIND_PRED_ATTR },
  { "attrex",	  1, BIND_PRED_ATTREX },
  { "attrge",	  2, BIND_PRED_ATTRGE },
  { "attrgt",	  2, BIND_PRED_ATTRGT },
  { "attrle",	  2, BIND_PRED_ATTRLE },
  { "attrlt",	  2, BIND_PRED_ATTRLT },
  { "attrmax",	  1, BIND_PRED_ATTRMAX },
  { "attrmin",	  1, BIND_PRED_ATTRMIN },
  { "attrnot",	  2, BIND_PRED_ATTRNOT },
  { "bindrule",	  1, BIND_PRED_RULE },
  { "condex",	  1, BIND_PRED_CONDEX },
  { "condexpr",	  2, BIND_PRED_CONDEXPR },
  { "condnot",	  1, BIND_PRED_CONDNOT },
  { "conduniq",   1, BIND_PRED_CONDUNIQ },
  { "confirm",	  2, BIND_PRED_CONFIRM },
  { "cut",	  1, BIND_PRED_CUT },
  { "eq",	  2, BIND_PRED_ATTR },
  { "exists",	  1, BIND_PRED_CONDEX },
  { "existsnot",  1, BIND_PRED_CONDNOT },
  { "existsuniq", 1, BIND_PRED_CONDUNIQ },
  { "ge",	  2, BIND_PRED_ATTRGE },
  { "gt",	  2, BIND_PRED_ATTRGT },
  { "hasattr",	  1, BIND_PRED_ATTREX },
  { "le",	  2, BIND_PRED_ATTRLE },
  { "lt",	  2, BIND_PRED_ATTRLT },
  { "max",	  1, BIND_PRED_ATTRMAX },
  { "min",	  1, BIND_PRED_ATTRMIN },
  { "msg",	  1, BIND_PRED_MSG },
  { "ne",	  2, BIND_PRED_ATTRNOT }
};

LOCAL int getPred (str)
     char *str;
{
  int hi = PREDCOUNT-1, lo=0, predNum, res;

  if ((str[0] == '-') && !str[1])
    str = "cut";

  /* get predicate number (binary search) */
  predNum = (hi+lo)/2;
  while (hi >= lo) {
    if ((res = strcmp (str, predicates[predNum].name)) == 0) {
      return (predNum);
    }
    if (res < 0)
      hi = predNum - 1;
    else
      lo = predNum + 1;
    predNum = (hi+lo)/2;
  }
  return (-1);
}

EXPORT char *atBindPredName (pos)
     int pos;
{
  return (predicates[pos].name);
}

EXPORT int atBindPredCode (pos)
     int pos;
{
  return (predicates[pos].predCode);
}

/*=========================
 *  rulePredArgs
 *==========================*/

LOCAL char *rulePredArgs (ptr, pred)
     char *ptr;
     Predicate *pred;
{
  int  singleQuotes = FALSE, doubleQuotes = FALSE;
  int  parenthCount=0, foundEscape, macroType;
  char lastChar, *spacePtr, *auxPtr1, *auxPtr2;

  pred->args[0] = (char *)0;
  pred->args[1] = (char *)0;

  skipWhitespace (ptr);
  if (*ptr != '(')
    {
      bindRuleError (5, ptr);
      return (NULL);
    }
  ptr++;
  skipWhitespace (ptr);

  /* first argument */
  switch (*ptr)
    {
    case '"':
      doubleQuotes = TRUE;
      ptr++;
      break;
    case '\'':
      singleQuotes = TRUE;
      ptr++;
      break;
    case ')': /* empty argument list */
      ptr++;
      switch (predicates[pred->position].argCount) {
      case 0:
	return (ptr);
      case 1:
	pred->args[0] = "";
	return (ptr);
      case 2:
	bindRuleError (4, ptr);
	return (NULL);
      }
      break;
    case ',': /* empty first argument */
      pred->args[0] = "";
      lastChar = *ptr++;
      goto secondArg;
      break;
    }

  if (predicates[pred->position].argCount == 0) {
    bindRuleError (6, ptr);
    return (NULL);
  }

  foundEscape = FALSE;
  spacePtr = NULL;
  pred->args[0] = ptr;
  while (*ptr) {
    if (*ptr == '\\') {
      foundEscape = TRUE;
      ptr++;
      ptr++;
      continue;
    }

    if ((*ptr == '(') && !(doubleQuotes  || singleQuotes))
      parenthCount++;

    if ((*ptr == '`') && !singleQuotes)
      pred->info |= BIND_ARG1_SHELL_COMMAND;

    if ((*ptr == '$') && !singleQuotes) {
      ptr = skipMacroCitation (ptr, &macroType);
      switch (macroType) {
      case SHAPE_MACRO:
	pred->info |= BIND_ARG1_SHAPE_MACRO;
	break;
      case RULE_MACRO:
      case RULE_PARAMETER:
	pred->info |= BIND_ARG1_RULE_MACRO;
	break;
      case NO_MACRO:
	ptr++;
	break;
      default:
	return (NULL);
      }
    }
    else
      ptr++;

    if (isspace (*ptr) && !(doubleQuotes  || singleQuotes)) {
      spacePtr = ptr;
      skipWhitespace (ptr);
    }

    if ((*ptr == ')') && !(doubleQuotes  || singleQuotes)) {
      if (parenthCount)
	parenthCount--;
      else
	break;
    }
    else if ((*ptr == ',') && (!parenthCount || doubleQuotes  || singleQuotes))
      break;

    if (((*ptr == '"') && doubleQuotes) ||
	((*ptr == '\'') && singleQuotes)) {
      *ptr++ = '\0';
      doubleQuotes = FALSE;
      singleQuotes = FALSE;
      skipWhitespace (ptr);
      break;
    }
    spacePtr = NULL;
  }
  if (!*ptr) {
    bindRuleError (2, (char *)0);
    return (FALSE);
  }
  else
    lastChar = *ptr;

  if (spacePtr)
    *spacePtr = '\0';
  else
    *ptr = '\0';
  ptr++;

  /* eliminate escape chars */
  if (foundEscape && (auxPtr1 = auxPtr2 = pred->args[0])) {
    while (*auxPtr1) {
      if (*auxPtr1 == '\\')
	auxPtr1++;
      *auxPtr2++ = *auxPtr1++;
    }
    *auxPtr2 = *auxPtr1;
  }

 secondArg:
  if (lastChar == ',') {
    if (predicates[pred->position].argCount == 1) {
      bindRuleError (6, ptr);
      return (NULL);
    }

    skipWhitespace (ptr);

    /* second argument */
    switch (*ptr)
      {
      case '"':
	doubleQuotes = TRUE;
	ptr++;
	break;
      case '\'':
	singleQuotes = TRUE;
	ptr++;
	break;
      case ')': /* empty second argument */
	pred->args[1] = "";
	ptr++;
	return (ptr);
	break;
      }

    foundEscape = FALSE;
    spacePtr = NULL;
    pred->args[1] = ptr;
    while (*ptr) {
      if (*ptr == '\\') {
	foundEscape = TRUE;
	ptr++;
	ptr++;
	continue;
      }
      
      if ((*ptr == '(') && !(doubleQuotes  || singleQuotes))
	parenthCount++;

      if ((*ptr == '`') && !singleQuotes)
	pred->info |= BIND_ARG2_SHELL_COMMAND;

      if ((*ptr == '$') && !singleQuotes) {
	ptr = skipMacroCitation (ptr, &macroType);
	switch (macroType) {
	case SHAPE_MACRO:
	  pred->info |= BIND_ARG2_SHAPE_MACRO;
	  break;
	case RULE_MACRO:
	case RULE_PARAMETER:
	  pred->info |= BIND_ARG2_RULE_MACRO;
	  break;
	case NO_MACRO:
	  ptr++;
	  break;
	default:
	  return (NULL);
	}
      }
      else
	ptr++;
      
      if (isspace (*ptr) && !(doubleQuotes  || singleQuotes)) {
	spacePtr = ptr;
	skipWhitespace (ptr);
      }
      
      if ((*ptr == ')') && !(doubleQuotes  || singleQuotes)) {
	if (parenthCount)
	  parenthCount--;
	else 
	  break;
      }

      if (((*ptr == '"') && doubleQuotes) ||
	  ((*ptr == '\'') && singleQuotes)) {
	*ptr++ = '\0';
	doubleQuotes = FALSE;
	singleQuotes = FALSE;
	skipWhitespace (ptr);
	break;
      }
      spacePtr = NULL;
    }
    if (!*ptr) {
      bindRuleError (2, (char *)0);
      return (FALSE);
    }
    else
      lastChar = *ptr;

    if (spacePtr)
      *spacePtr = '\0';
    else
      *ptr = '\0';
    ptr++;

    /* eliminate escape chars */
    if (foundEscape && (auxPtr1 = auxPtr2 = pred->args[1])) {
      while (*auxPtr1) {
	if (*auxPtr1 == '\\')
	  auxPtr1++;
	*auxPtr2++ = *auxPtr1++;
      }
      *auxPtr2 = *auxPtr1;
    }
  }
  else {
    if (predicates[pred->position].argCount == 2) {
      bindRuleError (4, ptr);
      return (NULL);
    }
  }

  if (lastChar != ')') {
    bindRuleError (6, ptr);
    return (NULL);
  }
  return (ptr);
}

/*=========================
 *  rulePredicate
 *==========================*/

LOCAL int rulePredicate  (curPred)
     Predicate *curPred;
     /* scan next predicate and promote predicate pointer.
      * RETURN: FALSE on end of predicate, BIND_SCAN_ERROR on error, else TRUE.
      */
{
  char predName[16], *ptr, *newPtr;
  int  i;

  if ((!nextPred) || (!*nextPred))
    return (FALSE);

  ptr = nextPred;
  skipWhitespace (ptr);

  /* skip empty predicates */
  while (*ptr == ',') {
    ptr++;
    skipWhitespace (ptr);
  }

  /* check for end of alternative */
  if (*ptr == ';') {
    nextAlt = ptr+1;
    nextPred = NULL;
    return (FALSE);
  }

  /* check for end of rule */
  if (*ptr == '.') {
    nextPred = NULL;
    nextAlt = NULL;
    ptr++;
    skipWhitespace (ptr);
    if (*ptr) {
      bindRuleError (3, ptr);
      return (BIND_SCAN_ERROR);
    }
    else
      return (FALSE);
  }

  curPred->info = 0;

  i=0;
  while (isalnum (*ptr) || (*ptr == '-')) {
    predName[i++] = *ptr++;
    if (i == 16) {
      bindRuleError (1, ptr);
      nextPred = NULL;
      nextAlt = moveToNextAlt (ptr);
      return (BIND_SCAN_ERROR);
    }
  }
  predName[i] = '\0';

  if ((curPred->position = getPred (predName)) == -1) {
    bindRuleError (1, ptr);
    nextPred = NULL;
    nextAlt = moveToNextAlt (ptr);
    return (BIND_SCAN_ERROR);
  }

  if (!strcmp (predName, "-")) { /* special treatment of cut operator */
    curPred->args[0] = "\0";
    curPred->args[1] = (char *)0;
  }
  else {
    if ((newPtr = rulePredArgs (ptr, curPred)) == (char *)0) {
      nextPred = NULL;
      nextAlt = moveToNextAlt (ptr);
      return (BIND_SCAN_ERROR);
    }
    ptr = newPtr;
  }

  skipWhitespace (ptr);
  switch (*ptr) {
  case ';':
    nextAlt = ptr+1;
    nextPred = NULL;
    return (TRUE);
  case '.':
    nextAlt = NULL;
    nextPred = NULL;
    return (TRUE);
  case ',':
    nextPred = ptr+1;
    return (TRUE);
  default:
    bindRuleError (7, ptr);
    nextPred = NULL;
    nextAlt = moveToNextAlt (ptr);
    return (BIND_SCAN_ERROR);
  }
}

/*=========================
 *  ruleAlternative
 *==========================*/

LOCAL int ruleAlternative (curAlt)
     Alternative *curAlt;
     /* determine pattern of next alternative to be processed.
      * set predicate pointer to first predicate.
      * initialize curAlt with pattern and NULL values.
      * RETURN: FALSE on end of rule BIND_SCAN_ERROR on error, else TRUE.
      */
{
  char *ptr, *auxPtr, *spacePtr = NULL;
  int  foundEscape=FALSE, macroType;

  curAlt->pattern = (char *)0;
  curAlt->predList = (Predicate *)0;
  curAlt->info = 0;
  curAlt->next = (Alternative *)0;

  if ((!nextAlt) || (!*nextAlt))
    return (FALSE);

  ptr = nextAlt;
  skipWhitespace (ptr);

  switch (*ptr) {
  case ',': /* empty pattern */
    nextPred = ptr+1;
    return (TRUE);
  case ';': /* empty alternative */
    nextPred = NULL;
    nextAlt = ptr+1;
    return (TRUE);
  case '.': /* possible end of rule */
    auxPtr = ptr+1;
    skipWhitespace (auxPtr);
    if (!*auxPtr) {
      /* end of rule */
      nextAlt = NULL;
      nextPred = NULL;
      return (TRUE);
    }
    break;
  }

  curAlt->pattern = ptr;
  while (*ptr) {
    if (*ptr == '\\') {
      foundEscape = TRUE;
      ptr++;
      ptr++;
      continue;
    }

    if (*ptr == '`')
      curAlt->info |= BIND_PATTERN_SHELL_COMMAND;

    if (*ptr == '$') {
      ptr = skipMacroCitation (ptr, &macroType);
      switch (macroType) {
      case SHAPE_MACRO:
	curAlt->info |= BIND_PATTERN_SHAPE_MACRO;
	break;
      case RULE_MACRO:
      case RULE_PARAMETER:
	curAlt->info |= BIND_PATTERN_RULE_MACRO;
	break;
      case NO_MACRO:
	ptr++;
	break;
      default:
	nextPred = NULL;
	nextAlt = moveToNextAlt (ptr);
	return (BIND_SCAN_ERROR);
      }
    }
    else
      ptr++;

    if (isspace (*ptr)) {
      spacePtr = ptr;
      skipWhitespace (ptr);
    }
	    
    if ((*ptr == ',') || (*ptr == ';'))
      break;

    if (*ptr == '.') {
      auxPtr = ptr+1;
      skipWhitespace (auxPtr);
      if (!*auxPtr) {
	/* end of rule */
	*ptr = '\0';
	nextAlt = NULL;
	nextPred = NULL;
	goto finish;
      }
    }

    if (*ptr == '(') {
      /* check if rule starts with a predicate instead of a pattern */
      if (spacePtr)
	*spacePtr = '\0';
      else
	*ptr = '\0';
      if (getPred (curAlt->pattern) != -1) {
	/* found regular predicate -- assume no explicit pattern */
	curAlt->pattern = (char *)0;
	if (spacePtr)
	  *spacePtr = ' ';
	else
	  *ptr = '(';
	nextPred = nextAlt;
	return (TRUE);
      }
      else {
	if (spacePtr)
	  *spacePtr = ' ';
	else
	  *ptr = '(';
      }
    }

    if (spacePtr)
      spacePtr = NULL;
  } /* while */

  if (!*ptr) {
    bindRuleError (2, (char *)0);
    nextAlt = NULL;
    nextPred = NULL;
    return (BIND_SCAN_ERROR);
  }

  if (*ptr == ',') 
    nextPred = ptr+1;

  if (*ptr == ';') {
    nextPred = NULL;
    nextAlt = ptr+1;
  }
  if (spacePtr)
    *spacePtr = '\0';
  else
    *ptr = '\0';

 finish:
  
  /* if pattern is empty or just an asterisk */
  if (!*(curAlt->pattern))
    curAlt->pattern = (char *)0;
  else
    if ((curAlt->pattern[0] == '*') && !curAlt->pattern[1])
      curAlt->pattern = (char *)0;

  /* eliminate escape chars */
  if (foundEscape && (auxPtr = ptr = curAlt->pattern)) {
    while (*ptr) {
      if (*ptr == '\\')
	ptr++;
      *auxPtr++ = *ptr++;
    }
    *auxPtr = *ptr;
  }
  
  return (TRUE);
}

/*=========================
 *  atRuleNameScan
 *=========================*/

EXPORT int atRuleNameScan (rule)
     BindRule *rule;
     /* see if rule name contains parameter definitions
      */
{
  char *endPtr, *argPtr = strchr (rule->name, '(');

  rule->argNum = 0;

  if (!rule->name) {
    bindRuleError (9, rule->name);
    return (FALSE);
  }

  if (!argPtr)
    endPtr = &(rule->name[strlen(rule->name)-1]);
  else
    endPtr = argPtr-1;

  if (endPtr < rule->name) {
    bindRuleError (9, rule->name);
    return (FALSE);
  }

  /* remove trailing whitespace */
  while (isspace (*endPtr)) {
    *endPtr = '\0';
    if (endPtr-- == rule->name) {
      bindRuleError (9, rule->name);
      return (FALSE);
    }
  }

  if (!argPtr) /* no arguments */
    return (TRUE);

  *argPtr++ = '\0';

  while (1) {
    while (isspace (*argPtr)) argPtr++;
    rule->argList[rule->argNum++] = argPtr;
    if (rule->argNum == BIND_MAX_RULEARGS) {
      bindRuleError (10, argPtr);
      return (FALSE);
    }
    while (isgraph (*argPtr) && (*argPtr != ',') && (*argPtr != ')')) argPtr++;
    while (isspace (*argPtr)) *argPtr++ = '\0';
    if (!*argPtr) {
      bindRuleError (11, argPtr);
      return (FALSE);
    }
    if (*argPtr == ')') { /* end of argument list */
      *argPtr = '\0';
      return (TRUE);
    }
    *argPtr++ = '\0';
  }

}

/*=========================
 *  atRuleScan
 *=========================*/

#define LISTSEG_SIZE 128

LOCAL Alternative *altList;
LOCAL int         altListIndex = LISTSEG_SIZE;
LOCAL Predicate   *predList;
LOCAL int         predListIndex = LISTSEG_SIZE;

EXPORT int atRuleScan (rule)
     BindRule *rule;
     /* do syntax and plausibility check for version binding rule.
      * write error messages to stderr, if "displayErrors" is set TRUE.
      * else be silent and give up on first error occured.
      * RETURN: 0 or scan error code
      */
{
  Alternative **nextAltPtr;
  Predicate   **nextPredPtr;
  int         retCode = TRUE, resultCode;

  if (ruleInit (rule) == FALSE)
    return (FALSE);

  /* scan rule name */
  if (atRuleNameScan (rule) == FALSE)
    return (FALSE);

  nextAltPtr = &(rule->altList);

  while (1) {
    if (altListIndex == LISTSEG_SIZE) { /* need new Alternative buffers */
      if ((altList = (Alternative *)
	   malloc ((size_t) LISTSEG_SIZE*sizeof(Alternative))) == NULL) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, "not enough memory");
	return (FALSE);
      }
      altListIndex = 0;
    }

    if (!(resultCode = ruleAlternative (&altList[altListIndex]))) {
      /* end of rule */
      *nextAltPtr = (Alternative *)0;
      return (retCode);
    }
    if (resultCode == BIND_SCAN_ERROR) {
      retCode = FALSE;
      continue;
    }

    *nextAltPtr = &altList[altListIndex];
    nextAltPtr = &(altList[altListIndex].next);
    nextPredPtr = &(altList[altListIndex].predList);
    altListIndex++;

    while (1) {
      if (predListIndex == LISTSEG_SIZE) { /* need new Predicate bufs */
	if ((predList = (Predicate *)
	     malloc ((size_t) LISTSEG_SIZE*sizeof(Predicate))) == NULL) {
	  atBindError = TRUE;
	  strcpy (atBindErrorMsg, "not enough memory");
	  return (FALSE);
	}
	predListIndex = 0;
      }

      if (!(resultCode = rulePredicate (&predList[predListIndex]))) {
	/* end of alternative */
	*nextPredPtr = (Predicate *)0;
	break;
      }
      if (resultCode == BIND_SCAN_ERROR) {
	retCode = FALSE;
	continue;
      }
      
      *nextPredPtr = &predList[predListIndex];
      nextPredPtr = &(predList[predListIndex].next);
      predListIndex++;
    }
  }
}

/*=========================
 *  atRuleDisplay
 *=========================*/

EXPORT void atRuleDisplay (rule)
     BindRule *rule;
     /* display selection rule
      */
{
  Alternative *alt = rule->altList;
  Predicate   *pred;
  int         firstAlt = TRUE;

  stLog ("", ST_LOG_MSG);

  if (rule->fileName) {
    sprintf (stMessage, "# from file %s (line %d)", rule->fileName, rule->srcLine);
    stLog (stMessage, ST_LOG_MSG);
  }

  if (rule->status) {
    sprintf (stMessage, "# ERROR -- %s", bindRuleErrorMsgs[rule->status]);
    stLog (stMessage, ST_LOG_MSG);
  }

  if (rule->argNum) {
    int i;
    sprintf (stMessage, "%s (", rule->name);
    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
    for (i=0; i<rule->argNum-1; i++) {
      sprintf (stMessage, "%s, ", rule->argList[i]);
      stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
    }
    sprintf (stMessage, "%s):-", rule->argList[i]);
    stLog (stMessage, ST_LOG_MSG);
  }
  else
    sprintf (stMessage, "%s:-", rule->name);
    stLog (stMessage, ST_LOG_MSG);

  while (alt) {
    if (firstAlt) {
      sprintf (stMessage, "\t%s", alt->pattern ? alt->pattern : "*");
      stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
      fflush (stdout);
      firstAlt = FALSE;
    }
    else {
      sprintf (stMessage, ";\n\t%s", alt->pattern ? alt->pattern : "*");
      stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
      fflush (stdout);
    }

    pred = alt->predList;
    while (pred) {
      sprintf (stMessage, ",\n\t  %s    \t", predicates[pred->position].name);
      stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
      fflush (stdout);
      switch (predicates[pred->position].argCount) {
      case 0:
	stLog ("()", ST_LOG_MSG | ST_LOG_NONL);
	fflush (stdout);
	break;
      case 1:
	sprintf (stMessage, "(%s)", pred->args[0] ? pred->args[0] : "");
	stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	fflush (stdout);
	break;
      case 2:
	sprintf (stMessage, "(%s, %s)", pred->args[0] ? pred->args[0] : "", pred->args[1] ? pred->args[1] : "");
	stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	fflush (stdout);
	break;
      }
      pred = pred->next;
    }
    alt = alt->next;
  }
  
  if (!firstAlt) {
    stLog (".", ST_LOG_MSG);
    fflush (stdout);
  }
}

