/******************************************************************************/
/*                                                                            */
/*  READ_3DAG - Read specs file for a 3DAG display                            */
/*                                                                            */
/******************************************************************************/

#define STRICT
#include <windows.h>
#include <commctrl.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include "const.h"
#include "classes.h"
#include "funcdefs.h"
#include "lin.rh"
#include "extern.h"


/*
--------------------------------------------------------------------------------

   Local routine counts space needed after macro expansion
   This lets the caller allocate needed memory

--------------------------------------------------------------------------------
*/

static int count_expansion ( int n , char *source )
{
   int i, j, k, extra ;
   char msg[256], name[MAX_NAME_LENGTH+1] ;

// Verify that we never get a right parenthesis without matching left parenthesis
// and same number of left and right parenthesese

   k = 0 ;
   for (i=0 ; i<n ; i++) {
      if (source[i] == '(')
         ++k ;
      else if (source[i] == ')')
         --k ;
      if (k < 0) {
         audit ( "Found ) without matching (" ) ;
         return -1 ;
         }
      }

   if (k != 0) {
      audit ( "Unbalanced parentheses" ) ;
      return -1 ;
      }

   extra = 0 ;
   for (i=0 ; i<n ; i++) {
      if (source[i] == '(') {  // Macro name starts here
         k = 0 ;               // Counts length of name
         while (++i < n) {
            if (source[i] == ')') {   // Found closing paren?
               if (! k) {
                  audit ( "Cannot have an empty name" ) ;
                  return -1 ;
                  }
               name[k] = 0 ;  // Terminate name string
               // Find this macro and add in its length
               for (j=0 ; j<AG3D_params.n_macros ; j++) {
                  if (! strcmp ( name , AG3D_params.macro_name[j] )) { // Found it?
                     extra += AG3D_params.macro_nops[j] ;
                     break ;
                     }
                  } // Searching for this named macro
               if (j == AG3D_params.n_macros) {   // Named macro not present?
                  sprintf_s ( msg, "Macro %s has not been defined", name ) ;
                  audit ( msg ) ;
                  return -1 ;
                  }
               // When we get here we have successfully processed a named macro
               break ;  // Break out of loop that searches for closing paren
               }

            else {  // Continuing to get name
               if (k >= MAX_NAME_LENGTH) {
                  sprintf_s ( msg, "Name cannot exceed %d characters", MAX_NAME_LENGTH ) ;
                  audit ( msg ) ;
                  return -1 ;
                  }
               name[k++] = source[i] ;
               }
            } // While processing macro name, and looking for closing paren
         } // Encountered opening paren for name string
      } // Processing the string
   return n + extra ;
}


/*
--------------------------------------------------------------------------------

   Local routine does macro expansion
   It assumes that the caller allocated needed memory
   We don't have to check for any errors because count_expansion() did that.

--------------------------------------------------------------------------------
*/

static void do_expansion ( int n , char *source , char *dest )
{
   int i, idest, j, k ;
   char name[MAX_NAME_LENGTH+1] ;

   idest = 0 ;   // Will index output string
   for (i=0 ; i<n ; i++) {
      if (source[i] == '(') {  // Macro name starts here
         k = 0 ;               // Counts length of name
         while (++i < n) {
            if (source[i] == ')') {   // Found closing paren?
               assert ( k > 0 ) ;
               name[k] = 0 ;  // Terminate name string
               // Find this macro and add in its length
               for (j=0 ; j<AG3D_params.n_macros ; j++) {
                  if (! strcmp ( name , AG3D_params.macro_name[j] )) { // Found it?
                     for (k=0 ; k<AG3D_params.macro_nops[j] ; k++)
                        dest[idest++] = AG3D_params.macro_string[j][k] ;
                     break ;
                     }
                  } // Searching for this named macro
               assert (j < AG3D_params.n_macros ) ;
               // When we get here we have successfully processed a named macro
               break ;  // Break out of loop that searches for closing paren
               }

            else {  // Continuing to get name
               assert (k < MAX_NAME_LENGTH) ;
               name[k++] = source[i] ;
               }
            } // While processing macro name, and looking for closing paren
         } // Encountered opening paren for name string

      else
         dest[idest++] = source[i] ;  // Just copy the token
      } // if we found ( to signal start of macro name

   dest[idest] = 0 ;  // Terminate string to make printing it later easy
}


/*
--------------------------------------------------------------------------------

   Local routine parses macros, axiom, and productions

--------------------------------------------------------------------------------
*/

static int parse ( int macro_vs_prod, int n , char *source , int *dest )
{
   int i, j, k, in_quotes ;
   char msg[256], msg2[256] ;

// Verify that there are an even number of quotes

   j = 0 ;
   for (i=0 ; i<n ; i++) {
      if (source[i] == '"')
         ++j ;
      }
   if (j % 2) {
      audit ( "Unmatched quote ('\"')" ) ;
      return -1 ;
      }

// Verify that we never get a right bracket without matching left bracket
// and same number of left and right brackets

   k = 0 ;
   for (i=0 ; i<n ; i++) {
      if (source[i] == '[')
         ++k ;
      else if (source[i] == ']')
         --k ;
      if (k < 0) {
         audit ( "Found ] without matching [" ) ;
         return -1 ;
         }
      }

   if (k != 0) {
      audit ( "Unbalanced [] brackets" ) ;
      return -1 ;
      }

// Verify that we never get a right brace without matching left brace
// and same number of left and right braces
// Also verify that between braces {} we have only +, -, |, and S.
// Also verify that if not between braces {} we do not have S.
// (That last check is important because S does special things beyond just moving.)

   k = 0 ;
   in_quotes = 0 ;

   for (i=0 ; i<n ; i++) {
      if (source[i] == '\"')
         ++in_quotes ;
      else if (source[i] == '{')
         ++k ;
      else if (source[i] == '}')
         --k ;
      else if (k > 0) {
         if (source[i] != ' '  &&  source[i] != '+'  &&  source[i] != '-'  &&  source[i] != '|'  &&  source[i] != 'S') {
            audit ( "Inside braces (for filled plane) can only have +, -, |, and S" ) ;
            return -1 ;
            }
         }
      else if (k == 0  &&  in_quotes % 2 == 0) {
         if (source[i] == 'S') {
            audit ( "S cannot appear outside braces (for filled plane)" ) ;
            return -1 ;
            }
         }
      if (k < 0) {
         audit ( "Found } without matching {" ) ;
         return -1 ;
         }
      }

   if (k != 0) {
      audit ( "Unbalanced {} braces" ) ;
      return -1 ;
      }


// Here we go

   j = 0 ;
   for (i=0 ; i<n ; i++) {

      if (source[i] == '+')
         dest[j++] = P_PLUS ;
      else if (source[i] == '-')
         dest[j++] = P_MINUS ;
      else if (source[i] == '[')
         dest[j++] = P_PUSH ;
      else if (source[i] == ']')
         dest[j++] = P_POP ;
      else if (source[i] == '{')
         dest[j++] = P_START_BRACE ;
      else if (source[i] == '}')
         dest[j++] = P_END_BRACE ;
      else if (source[i] == 'V')
         dest[j++] = P_SHRINK ;
      else if (source[i] == 'F')
         dest[j++] = P_FORWARD ;
      else if (source[i] == 'S')
         dest[j++] = P_STEP ;
      else if (source[i] == '&')
         dest[j++] = P_PITCH_DOWN ;
      else if (source[i] == '^')
         dest[j++] = P_PITCH_UP ;
      else if (source[i] == '\\')
         dest[j++] = P_ROLL_LEFT ;
      else if (source[i] == '/')
         dest[j++] = P_ROLL_RIGHT ;
      else if (source[i] == '|')
         dest[j++] = P_TURN_AROUND ;

      else if (source[i] == 'C') {
         dest[j++] = P_COLOR ;
         k = source[++i] - '0' ;  // Which of the 10 possible colors is requested?
         assert ( source[i] >= '0'  &&  source[i] <= '9') ;
         assert ( k >= 0  &&  k <= 9 ) ;
         dest[j++] = k ;
         }

      else if (source[i] == '\"') {
         k = 0 ;
         while (++i < n) {
            if (source[i] == '\"') {   // Found closing quote?
               if (! k) {
                  audit ( "Cannot have an empty name" ) ;
                  return -1 ;
                  }
               msg[k] = 0 ;  // Terminate name string

               if (macro_vs_prod) {  // Process a macro command?
                  // Completed name string.  Error if it's already been defined.
                  for (k=0 ; k<AG3D_params.n_macros ; k++) {
                     if (! strcmp ( msg , AG3D_params.macro_name[k] )) {
                        audit ( "Macro is already defined" ) ;
                        return -1 ;
                        }
                     }
                  if (AG3D_params.n_macros >= MAX_PRODUCTIONS) {
                     audit ( "Too many macros" ) ;
                     return -1 ;
                     }
                  strcpy_s ( AG3D_params.macro_name[k] , msg ) ;
                  ++AG3D_params.n_macros ;
                  // Copy the string to the macro, skipping any leading blanks
                  ++i ;  // Skip the closing quote of the name
                  while (i < n  &&  source[i] == ' ')
                     ++i ;
                  j = 0 ;
                  while (i<n)
                     AG3D_params.macro_string[k][j++] = source[i++] ;
                  AG3D_params.macro_string[k][j] = 0 ;  // We terminate the string here
                  AG3D_params.macro_nops[k] = j ;       // but do not count the NULL terminator
                  }

               else {  // Processing a production command
                  // Completed name string.  See if it's already known; else add it
                  for (k=0 ; k<AG3D_params.n_productions ; k++) {
                     if (! strcmp ( msg , AG3D_params.prod_name[k] ))
                        break ;
                     }
                  if (k == AG3D_params.n_productions) {  // Not yet seen so add it
                     if (AG3D_params.n_productions >= MAX_PRODUCTIONS) {
                        audit ( "Too many productions" ) ;
                        return -1 ;
                        }
                     strcpy_s ( AG3D_params.prod_name[k] , msg ) ;
                     ++AG3D_params.n_productions ;
                     }
                  dest[j++] = P_NAME ;
                  dest[j++] = k ;   // Save index of this name
                  } // Processing a production command

               break ; // Done processing name string
               } // Found closing quote

            else {  // Continuing to get name
               if (k >= MAX_NAME_LENGTH) {
                  sprintf_s ( msg2, "Name cannot exceed %d characters", MAX_NAME_LENGTH ) ;
                  audit ( msg2 ) ;
                  return -1 ;
                  }
               msg[k++] = source[i] ;
               }
            } // While processing string
         } // Encountered opening quote for name string

      else if (source[i] == ' ')
         continue ;
      else if (source[i] == '\t')
         continue ;
      else {
         msg2[0] = source[i] ;
         msg2[1] = 0 ;
         sprintf_s ( msg, "Invalid character: %s", msg2 ) ;
         audit ( msg ) ;
         return -1 ;
         }
      } // For all characters in line
   return j ;
}

/*
--------------------------------------------------------------------------------

   Main routine called externally

--------------------------------------------------------------------------------
*/

int read_3DAG ( char *filename )
{
   int i, j, k, m, n, irand, jrand, ret_val, n_lines, found, *temp_ops ;
   double prob, sum ;
   char msg[2048], *line, *lptr, *temp_char ;
   FILE *fp ;

   audit ( "" ) ;
   audit ( "" ) ;
   audit ( "-------------------------> Beginning new project <-------------------------" ) ;
   audit ( "" ) ;

/*
   Clean up any existing data
*/

   AG3D_params.n_colors = 0 ;

   for (i=0 ; i<AG3D_params.n_macros ; i++) {
      if (AG3D_params.macro_string[i] != NULL) {
         free ( AG3D_params.macro_string[i] ) ;
         AG3D_params.macro_string[i] = NULL ;
         }
      AG3D_params.macro_nops[i] = 0 ;
      }
   AG3D_params.n_macros = 0 ;

   if (AG3D_params.axiom != NULL) {
      free ( AG3D_params.axiom ) ;
      AG3D_params.axiom = NULL ;
      }
   AG3D_params.n_axiom = 0 ;

   if (AG3D_params.erp != NULL) {
      free ( AG3D_params.erp ) ;
      AG3D_params.erp = NULL ;
      }
   AG3D_params.n_erp = 0 ;


   for (i=0 ; i<AG3D_params.n_productions ; i++) {
      for (j=0 ; j<AG3D_params.prod_nrand[i] ; j++) {
         if (AG3D_params.prod_ops[i][j] != NULL) {
            free ( AG3D_params.prod_ops[i][j] ) ;
            AG3D_params.prod_ops[i][j] = NULL ;
            }
         AG3D_params.prod_nops[i][j] = 0 ;
         }
      AG3D_params.prod_nrand[i] = 0 ;
      }

   AG3D_params.n_productions = 0 ;


/*
   Initialize
*/

   fp = NULL ;
   line = NULL ;
   n_lines = 0 ;  // Counts lines read from file
   ret_val = 0;

   if (fopen_s ( &fp , filename , "rt" )) {
      sprintf_s ( msg , "Cannot find file %s", filename ) ;
      audit ( msg ) ;
      return ERROR_CANNOT_OPEN_FILE ;
      }

   sprintf_s ( msg , "Reading 3D AG file %s", filename ) ;
   audit ( "" ) ;
   audit ( msg ) ;

   line = (char *) malloc ( MAX_LINE * sizeof(char) ) ;
   if (line == NULL) {
      audit ( "Insufficient memory" ) ;
      ret_val = ERROR_INSUFFICIENT_MEMORY ;
      goto FINISH ;
      }

/*
   Main loop reads and processes each line
*/

   for (;;) {

      if ((fgets ( line , MAX_LINE , fp ) == NULL) || (strlen ( line ) < 2)) {
         if (ferror ( fp )  ||  ! n_lines) {
            sprintf_s ( msg , "Problem reading file %s", filename ) ;
            audit ( msg ) ;
            return ERROR_CANNOT_READ_FILE ;
            }
         else
            break ;       // Normal end of file
         }

      ++n_lines ;      

/*
   Parse the line
*/

      lptr = line ;
      while (*lptr == ' '  ||  *lptr == '\t')    // Skip leading blanks and tabs
         ++lptr ;

      i = 0 ;
      while (lptr[i]) {  // Convert to upper case and terminate line if return or newline
         if (lptr[i] == '\r'  ||  lptr[i] == '\n') {
            lptr[i] = 0 ;
            break ;
            }
         lptr[i] = (char) toupper ( lptr[i] ) ;
         ++i ;
         }

      //
      // ---------> NROWS <---------
      //

      if (! strncmp ( lptr , "NROWS" , 5 )) {
         lptr += 5 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.nrows = atoi ( lptr ) ;
         sprintf_s ( msg, "Nrows %d", AG3D_params.nrows ) ;
         audit ( msg ) ;
         if (AG3D_params.nrows < 10) {
            audit ( "ERROR... Nrows must be at least 10" ) ;
            ret_val = ERROR_BAD_PARAMETER ;
            goto FINISH ;
            }
         continue ;
         } // Nrows

      //
      // ---------> NCOLS <---------
      //

      if (! strncmp ( lptr , "NCOLS" , 5 )) {
         lptr += 5 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.ncols = atoi ( lptr ) ;
         sprintf_s ( msg, "Ncols %d", AG3D_params.ncols ) ;
         audit ( msg ) ;
         if (AG3D_params.ncols < 10) {
            audit ( "ERROR... Ncols must be at least 10" ) ;
            ret_val = ERROR_BAD_PARAMETER ;
            goto FINISH ;
            }
         continue ;
         } // Ncols

      //
      // ---------> ANGLE <---------
      //

      if (! strncmp ( lptr , "ANGLE" , 5 )) {
         lptr += 5 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.angle = atof ( lptr ) ;
         sprintf_s ( msg, "Angle %.3lf", AG3D_params.angle ) ;
         audit ( msg ) ;
         if (AG3D_params.angle < 0.0  ||  AG3D_params.angle > 90.0) {
            audit ( "ERROR... Angle must be in the range 0-90" ) ;
            ret_val = ERROR_BAD_PARAMETER ;
            goto FINISH ;
            }
         continue ;
         } // Angle

      //
      // ---------> WIDTH <---------
      //

      if (! strncmp ( lptr , "WIDTH" , 5 )) {
         lptr += 5 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.width = atoi ( lptr ) ;
         sprintf_s ( msg, "Width %d", AG3D_params.width ) ;
         audit ( msg ) ;
         if (AG3D_params.width < 1) {
            audit ( "ERROR... Width must be at least 1" ) ;
            ret_val = ERROR_BAD_PARAMETER ;
            goto FINISH ;
            }
         continue ;
         } // Width

      //
      // ---------> DEPTH <---------
      //

      if (! strncmp ( lptr , "DEPTH" , 5 )) {
         lptr += 5 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.depth = atoi ( lptr ) ;
         sprintf_s ( msg, "Depth %d", AG3D_params.depth ) ;
         audit ( msg ) ;
         continue ;
         } // Depth

      //
      // ---------> SHRINK <---------
      //

      if (! strncmp ( lptr , "SHRINK" , 6 )) {
         lptr += 6 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.shrink = atof ( lptr ) ;
         sprintf_s ( msg, "Shrink %.3lf", AG3D_params.shrink ) ;
         audit ( msg ) ;
         continue ;
         } // Shrink

      //
      // ---------> STEP (SIZE) <---------
      //

      if (! strncmp ( lptr , "STEP" , 4 )) {
         lptr += 4 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.stepsize = atof ( lptr ) ;
         sprintf_s ( msg, "Step %.4lf", AG3D_params.stepsize ) ;
         audit ( msg ) ;
         continue ;
         } // Shrink

      //
      // ---------> AZIMUTH <---------
      //

      if (! strncmp ( lptr , "AZIMUTH" , 7 )) {
         lptr += 7 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.azimuth = atof ( lptr ) ;
         sprintf_s ( msg, "Azimuth %.4lf", AG3D_params.azimuth ) ;
         audit ( msg ) ;
         continue ;
         } // Azimuth

      //
      // ---------> ELEVATION <---------
      //

      if (! strncmp ( lptr , "ELEVATION" , 9 )) {
         lptr += 9 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.elevation = atof ( lptr ) ;
         sprintf_s ( msg, "Elevation %.2lf", AG3D_params.elevation ) ;
         audit ( msg ) ;
         continue ;
         } // Elevation

      //
      // ---------> COLOR <---------
      //

      if (! strncmp ( lptr , "COLOR" , 5 )) {
         lptr += 5 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.colors[AG3D_params.n_colors][2] = atoi ( lptr ) ;
         while (isdigit(*lptr))
            ++lptr ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.colors[AG3D_params.n_colors][1] = atoi ( lptr ) ;
         while (isdigit(*lptr))
            ++lptr ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         AG3D_params.colors[AG3D_params.n_colors][0] = atoi ( lptr ) ;
         sprintf_s ( msg, "Color %d  %3d %3d %3d", AG3D_params.n_colors, AG3D_params.colors[AG3D_params.n_colors][2],
                     AG3D_params.colors[AG3D_params.n_colors][1], AG3D_params.colors[AG3D_params.n_colors][0] ) ;
         audit ( msg ) ;
         ++AG3D_params.n_colors ;
         continue ;
         } // Shrink

      //
      // ---------> MACRO <---------
      //

      if (! strncmp ( lptr , "MACRO" , 5 )) {
         audit ( lptr ) ;
         lptr += 5 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         if (*lptr != '\"') {
            audit ( "MACRO must start with the macro name enclosed in quotes (\")" ) ;
            ret_val = ERROR_INVALID_PRODUCTION_OPERATION ;
            goto FINISH ;
            }
         n = (int) strlen ( lptr ) ;
         AG3D_params.macro_string[AG3D_params.n_macros] = (char *) malloc ( n ) ;
         if (AG3D_params.macro_string[AG3D_params.n_macros] == NULL) {
            audit ( "Insufficient memory" ) ;
            ret_val = ERROR_INSUFFICIENT_MEMORY ;
            goto FINISH ;
            }
         j = parse ( 1 , n , lptr , NULL ) ;  // Returns the number of operations
         if (j < 0) {   // Flags error
            ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
            free (AG3D_params.macro_string[AG3D_params.n_macros]) ;
            AG3D_params.macro_string[AG3D_params.n_macros] = NULL ;
            goto FINISH ;
            }
         continue ;
         } // Macro

      //
      // ---------> AXIOM <---------
      //

      if (! strncmp ( lptr , "AXIOM" , 5 )) {
         audit ( lptr ) ;
         lptr += 5 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         m = (int) strlen ( lptr ) ;
         n = count_expansion ( m , lptr ) ;   // This is actually a bit too large, which is fine

         if (AG3D_params.axiom != NULL)
            free ( AG3D_params.axiom ) ;
         temp_char = (char *) malloc ( n + 1 ) ;  // Expansion will also have terminating NULL
         AG3D_params.axiom = (int *) malloc ( n * sizeof(int) ) ;
         if (AG3D_params.axiom == NULL  ||  temp_char == NULL) {
            if (temp_char != NULL)
               free ( temp_char ) ;
            audit ( "Insufficient memory" ) ;
            ret_val = ERROR_INSUFFICIENT_MEMORY ;
            goto FINISH ;
            }

         do_expansion ( (int) strlen ( lptr ) , lptr , temp_char ) ;
         if (n > m) { // After > before?  True only if at least one macro found
            strcpy_s ( msg , "AXIOM after expansion: " ) ;
            strcat_s ( msg , temp_char ) ;
            audit ( msg ) ;
            }

         n = (int) strlen ( temp_char ) ;
         j = parse ( 0 , n , temp_char , AG3D_params.axiom ) ;
         free ( temp_char ) ;
         if (j < 0) {   // Flags error
            ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
            goto FINISH ;
            }
         else
            AG3D_params.n_axiom = j ;

         continue ;
         } // Axiom

      //
      // ---------> ERP (EDGE REPLACEMENT PRODUCTION) <---------
      //

      if (! strncmp ( lptr , "ERP" , 3 )) {
         audit ( lptr ) ;
         lptr += 3 ;
         while (*lptr == ' '  ||  *lptr == '\t')    // Skip blanks and tabs
            ++lptr ;
         m = (int) strlen ( lptr ) ;
         n = count_expansion ( m , lptr ) ;   // This is actually a bit too large, which is fine
         if (AG3D_params.erp != NULL)
            free ( AG3D_params.erp ) ;
         temp_char = (char *) malloc ( n + 1 ) ;  // Expansion will also have terminating NULL
         AG3D_params.erp = (int *) malloc ( n * sizeof(int) ) ;
         if (AG3D_params.erp == NULL  ||  temp_char == NULL) {
            if (temp_char != NULL)
               free ( temp_char ) ;
            audit ( "Insufficient memory" ) ;
            ret_val = ERROR_INSUFFICIENT_MEMORY ;
            goto FINISH ;
            }

         do_expansion ( (int) strlen ( lptr ) , lptr , temp_char ) ;
         if (n > m) { // After > before?  True only if at least one macro found
            strcpy_s ( msg , "ERP after expansion: " ) ;
            strcat_s ( msg , temp_char ) ;
            audit ( msg ) ;
            }

         n = (int) strlen ( temp_char ) ;
         j = parse ( 0 , n , temp_char , AG3D_params.erp ) ;  // Returns the number of operations
         free ( temp_char ) ;
         if (j < 0) {   // Flags error
            ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
            free ( AG3D_params.erp ) ;
            AG3D_params.erp = NULL ;
            goto FINISH ;
            }
         else
            AG3D_params.n_erp = j ;
         continue ;
         } // Production

      //
      // ---------> PRODUCTION <---------
      //

      if (! strncmp ( lptr , "PRODUCTION" , 10 )) {
         audit ( lptr ) ;
         lptr += 10 ;
         while (*lptr == ' '  ||  *lptr == '\t')  // Skip blanks and tabs
            ++lptr ;

         // If the user specified a probability in <>, get it then bypass it
         if (*lptr == '<') {
            ++lptr ;
            prob = atof ( lptr ) ;
            while (isdigit(*lptr) || *lptr==' ' || *lptr=='\t' || *lptr=='.' || *lptr=='>')
               ++lptr ;
            }
         else
            prob = 1.0 ;

         // If this production did not start with probability, it must start with name in quotes
         if (*lptr != '\"') {
            audit ( "ERROR... Production must start with probability in <> or name in quotes" ) ;
            ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
            goto FINISH ;
            }

         m = (int) strlen ( lptr ) ;
         n = count_expansion ( m , lptr ) ;       // To expand macros
         temp_char = (char *) malloc ( n + 1 ) ;  // Expansion will also have terminating NULL
         temp_ops = (int *) malloc ( n * sizeof(int) ) ;
         if (temp_ops == NULL  ||  temp_ops == NULL) {
            if (temp_char != NULL)
               free ( temp_char ) ;
            if (temp_ops != NULL)
               free ( temp_ops ) ;
            audit ( "Insufficient memory" ) ;
            ret_val = ERROR_INSUFFICIENT_MEMORY ;
            goto FINISH ;
            }

         do_expansion ( (int) strlen ( lptr ) , lptr , temp_char ) ; // Do macro expansion
         if (n > m) { // After > before?  True only if at least one macro found
            strcpy_s ( msg , "Production after expansion: " ) ;
            strcat_s ( msg , temp_char ) ;
            audit ( msg ) ;
            }

         n = (int) strlen ( temp_char ) ;
         j = parse ( 0 , n , temp_char , temp_ops ) ;  // Returns the number of operations
         free ( temp_char ) ;
         if (j < 0) {   // Flags error
            ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
            free ( temp_ops ) ;
            temp_ops = NULL ;
            goto FINISH ;
            }
         // The parsed ops in temp_ops incorrectly begins with P_NAME, followed by
         // the index of this just-defined production.  Remove these two things
         // and let the definition be the remainder of temp_ops.
         assert ( temp_ops[0] == P_NAME ) ;
         k = temp_ops[1] ;   // Index of this production
         for (i=2 ; i<j ; i++)   // Compress out these two bad leading items
            temp_ops[i-2] = temp_ops[i] ;
         assert ( k < AG3D_params.n_productions ) ; // k is the index of this production
         m = AG3D_params.prod_nrand[k] ;    // Number of versions of this production so far
         assert ( AG3D_params.prod_nops[k][m] == 0 ) ;
         assert ( AG3D_params.prod_ops[k][m] == NULL ) ;
         AG3D_params.prod_nops[k][m] = j - 2 ;
         AG3D_params.prod_ops[k][m] = temp_ops ;
         AG3D_params.prod_prob[k][m] = prob ;
         AG3D_params.prod_nrand[k] = m + 1 ;
         continue ;
         } // Production

      sprintf_s ( msg, "ERROR... Undefined operation in line %d of file %s", n_lines, filename ) ;
      audit ( msg ) ;
      break ;
      } // Loop that reads and processes lines

/*
   The file lines are all processed, so close the file.
   Print macros
*/

   fclose ( fp ) ;
   fp = NULL ;

   audit ( "" ) ;
   audit ( "" ) ;
   audit ( "File reading is complete.  Parsing productions..." ) ;
   audit ( "" ) ;

   if (AG3D_params.n_macros) {
      audit ( "" ) ;
      audit ( "         Macros" ) ;
      audit ( "---------------" ) ;
      for (i=0 ; i<AG3D_params.n_macros ; i++) {
         sprintf_s ( msg, "%15s %s", AG3D_params.macro_name[i], AG3D_params.macro_string[i] ) ;
         audit ( msg ) ;
         }
      audit ( "" ) ;
      }

/*
   Do some simple error checking
      1) Axiom was defined
      2) No color reference exceeds the defined colors
*/

   if (AG3D_params.n_axiom == 0) {
      audit ( "ERROR... No axiom defined" )  ;
      ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
      goto FINISH ;
      }

   for (k=0 ; k<AG3D_params.n_axiom-1 ; k++) {
      if (AG3D_params.axiom[k] == P_COLOR) {
         if (AG3D_params.axiom[k+1] >= AG3D_params.n_colors) {
            audit ( "ERROR... Illegal color reference in axiom" )  ;
            ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
            goto FINISH ;
            }
         }
      }

   if (AG3D_params.erp != NULL) {
      for (k=0 ; k<AG3D_params.n_erp-1 ; k++) {
         if (AG3D_params.erp[k] == P_COLOR) {
            if (AG3D_params.erp[k+1] >= AG3D_params.n_colors) {
               audit ( "ERROR... Illegal color reference in ERP" )  ;
               ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
               goto FINISH ;
               }
            }
         }
      }

/*
   Make sure that for each production the probabilities of its versions sum to 1
*/

   for (i=0 ; i<AG3D_params.n_productions ; i++) {
      sum = 1.e-30 ;
      for (irand=0 ; irand<AG3D_params.prod_nrand[i] ; irand++)
         sum += AG3D_params.prod_prob[i][irand] ;
      for (irand=0 ; irand<AG3D_params.prod_nrand[i] ; irand++)
         AG3D_params.prod_prob[i][irand] /= sum ;
      }

/*
   Print production names and lengths for the user
   Make sure every production was defined.
   Also check for illegal color reference in productions
*/


   audit ( "" ) ;
   audit ( "     Production  Version  Probability  Length" ) ;
   audit ( "---------------------------------------------" ) ;

   for (i=0 ; i<AG3D_params.n_productions ; i++) {
      if (AG3D_params.prod_nrand[i] == 0) {
         sprintf_s ( msg , "%s was never defined!", AG3D_params.prod_name[i] ) ;
         audit ( msg ) ;
         ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
         goto FINISH ;
         }
      for (irand=0 ; irand<AG3D_params.prod_nrand[i] ; irand++) {
         sprintf_s ( msg, "%15s %5d       %.4lf   %7d", AG3D_params.prod_name[i], irand,
                     AG3D_params.prod_prob[i][irand], AG3D_params.prod_nops[i][irand] ) ;
         for (k=0 ; k<AG3D_params.prod_nops[i][irand]-1 ; k++) {
            if (AG3D_params.prod_ops[i][irand][k] == P_COLOR) {
               if (AG3D_params.prod_ops[i][irand][k+1] >= AG3D_params.n_colors) {
                  strcat_s ( msg , "  Illegal color reference!" )  ;
                  ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
                  }
               }
            }
         audit ( msg ) ;
         }
      }

/*
   Do more error checking: Every production is referenced in another production and/or the axiom
   This is not technically an error, but it indicates a likely user error
*/

   // This first block of code checks all other productions
   // to see if any of them reference production i.

   for (i=0 ; i<AG3D_params.n_productions ; i++) {
      found = 0 ;
      for (j=0 ; j<AG3D_params.n_productions ; j++) {    // Check the other productions
         if (j == i)    // Self-reference doesn't count!
            continue ;
         for (jrand=0 ; jrand<AG3D_params.prod_nrand[j] ; jrand++) { // Must check all versions of production j
            for (k=0 ; k<AG3D_params.prod_nops[j][jrand] ; k++) {
               if (AG3D_params.prod_ops[j][jrand][k] == P_NAME) {    // Do we have a name reference?
                  ++k ;  // Point to index of this name reference
                  if (AG3D_params.prod_ops[j][jrand][k] == i) {  // Found reference to production i?
                     found = 1 ;  // Yay!
                     break ;      // No need to look further in this opstring (k loop)
                     }
                  } // If name reference
               } // For k, checking all ops in production j version jrand
            if (found)  // Did production j version jrand reference i?
               break ; // No need to check other versions of production j (jrand loop)
            } // For jrand, checking all versions of production j
         if (found)  // Did production j (any version) reference i?
            break ; // No need to check other productions (j loop)
         } // For j, checking all other productions j to see if they reference production i

      if (! found) {  // It was never referenced in another production?
         // See if it was referenced in the axiom
         for (k=0 ; k<AG3D_params.n_axiom ; k++) {
            if (AG3D_params.axiom[k] == P_NAME) {
               ++k ;  // Point to index of this name reference
               if (AG3D_params.axiom[k] == i) {  // Reference to production i?
                  found = 1 ;
                  break ;  // Found reference in axiom, so quit checking axiom
                  }
               }
            } // For k, checking all ops in axiom
         } // If not found

      if (! found) {  // Neither did axiom reference production i
         // We get here when production i is not referenced in any regular production or the axiom.
         // See if there is an ERP and it references this production
         if (AG3D_params.erp != NULL) {
            for (k=0 ; k<AG3D_params.n_erp ; k++) {
               if (AG3D_params.erp[k] == P_NAME) {
                  ++k ;  // Point to index of this name reference
                  if (AG3D_params.erp[k] == i) { // Reference to production i?
                     found = 1 ;
                     break ;
                     }
                  }
               } // For k, checking all tokens in erp
            } // If there is an ERP
         } // If not found in production or axiom

      if (! found) {  // Nothing referenced production i
          sprintf_s(msg, "Production %s was never referenced; a likely user error", AG3D_params.prod_name[i]);
         audit ( msg ) ;
         ret_val = AG3D_params.bad_rule = ERROR_INVALID_PRODUCTION_OPERATION ;
         }
      } // For every production i, seeing if it was referenced


/*
   For each production convert individual probabilities to cumulative sum
*/

   for (i=0 ; i<AG3D_params.n_productions ; i++) {
      sum = 0.0 ;
      for (irand=0 ; irand<AG3D_params.prod_nrand[i] ; irand++) {
         sum += AG3D_params.prod_prob[i][irand] ;
         AG3D_params.prod_prob[i][irand] = sum ;
         }
      }

   audit ( "" ) ;
   audit ( "" ) ;
   audit ( "Finished reading and parsing object description.  Now executing..." ) ;
   audit ( "" ) ;
   if (AG3D_params.bad_rule)
      goto FINISH ;

FINISH:
   if (fp != NULL)
      fclose ( fp ) ;
   if (line != NULL)
      free ( line ) ;
   return ret_val ;
}