//
// IBIS_AMI_tree.c
//
// Implementation of IBIS AMI parameter tree input and output routines
// 
// Copyright (c) 2007. Signal Integrity Software, Inc. (SiSoft)
// All Rights Reserved.
//
// History:
// 26 September 2007, Mike Steinberger: Added support for strings delimited
//                                      by double quotes.
// 8 June 2007, Mike Steinberger: Added support for array-type parameters.
// 22 May 2007, Mike Steinberger: Initial file creation
//
//
// Permission to use, copy, modify, and distribute this software and 
// associated documentation for educational, research and commercial 
// purposes, without fee and without a signed licensing agreement, is hereby
// granted, provided that the above copyright notice, this paragraph and
// the following three paragraphs appear in all copies, modifications, and
// distributions. 
//
// This is part of a sample IBIS Algorithmic Modeling Interface (AMI) model. 
// This code has been provided by SiSoft to help demonstrate and drive 
// adoption of the IBIS AMI modeling standard.  For more information
// on this and other IBIS AMI models, contact Signal Integrity Software, 
// 6 Clock Tower Place, Maynard, MA 01754, (978) 461-0449 or email
// ibis-ami@sisoft.com.
// 
// THIS MATERIAL IS PROVIDED FOR THE RECIPIENT'S USE AT THE RECIPIENT's OWN RISK.
// IN NO EVENT SHALL SISOFT BE LIABLE TO ANY PARTY FOR FOR ANY DIRECT,
// INDIRECT, INCIDENTAL,SPECIAL OR CONSEQUENTIAL DAMAGES, OR ANY OTHER DAMAGES 
// WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, 
// BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) 
// ARISING OUT OF THE USE OR INABILITY TO USE THIS LICENSED MATERIAL, WHETHER 
// OR NOT THE POSSIBILITY OR CAUSE OF SUCH DAMAGES WAS KNOWN TO SISOFT.
// 
// SISOFT SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE. THIS SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
// ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". SISOFT HAS NO OBLIGATION
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
// MODIFICATIONS.

#include "IBIS_AMI_tree.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>

//Tree syntax lexer
//   text_ptr        Incoming character
//   lexer_state     Incoming lexer state
//   parser_state    Parser state (to support string literal values)
//   token_text      Accumulated token
//   string_literal  Flag to indicate when in the middle of a string literal.
//                   Initialize to zero.
//   process_text    Flag to indicate when the text is to be processed
//                   Initialize to zero.
//   Returns updated lexer state.
LexerState AmiLexer( const char   *text_ptr,
                     LexerState   lexer_state,
                     ParserState  parser_state,
                     InLineString *token_text,
                     int          *string_literal,
                     int          *process_text ){
   //lexer
   //   Detects tokens "(", text, ")"
   //   text token is complete when process_text == 1.
   //   text token is a string literal when string_literal == 1.
   if( *string_literal == 1 ) { //quoted string text processing
      if( *text_ptr == '\"' ) {
         *string_literal = 0;
         *process_text = 1;
      }
      else {
         *process_text = 0;
         if( lexer_state == ami_text ) {
            token_text->length++;
         }
         else {
            token_text->start = text_ptr;
            token_text->length   = 1;
         }
         lexer_state = ami_text;
      }
   } //quoted string text processing
   else { //normal text processing
      switch( *text_ptr ) {
         case '(':
            lexer_state = ami_open_paren;
            *process_text = 1;
            break;
         case ')':
            lexer_state = ami_close_paren;
            *process_text = 1;
            break;
         case ' ':
         case '\t':
         case '\r':
         case '\n':
            lexer_state = ami_white_space;
            *process_text = 0;
            break;
         case '\"':
            if( lexer_state == ami_white_space && parser_state == ami_leaf ) {
               *string_literal = 1;
            }
            else {
               printf( "WARNING: Ignoring unexpected quote found at %s\n",
                       text_ptr );
            }
            *process_text = 0;
            break;
         default:
            *process_text = 0;
            if( lexer_state == ami_text ) {
               token_text->length++;
            }
            else {
               token_text->start = text_ptr;
               token_text->length   = 1;
            }
            lexer_state = ami_text;
            //Look ahead to detect end of text token
            switch( *(text_ptr+1) ) {
               case 0:
               case '(':
               case ')':
               case ' ':
               case '\"':
               case '\t':
               case '\r':
               case '\n':
                  *process_text = 1;
                  break;
               default:
                  *process_text = 0;
                  break;
            }
            break;
      } //switch on *text_ptr
   } //normal text processing
   return lexer_state;
}

//Tree syntax parser
//   lexer_state     Incoming lexer state
//   parser_state    Incoming parser state
//   stack_ptr       Branch stack index in and out
//   Returns updated parser state.
ParserState AmiParser( LexerState  lexer_state,
                       ParserState parser_state,
                       int         *process_text,
                       int         *stack_ptr ){
   //parser
   //   States:
   //      start:  Waiting for open paren
   //      branch: Waiting for branch label
   //      leaf:   Waiting for leaf value or start of next branch
   //      end:    Waiting for close paren
   //   State transition diagram:
   //
   //                          |-------|
   //             ")"   ------>| start |<---------
   //         decrement |      |-------|         |
   //           depth   |       |  |             |
   //                   --------   | "("         |
   //                             \|/            |
   //                          |--------|        |
   //                   ------>| branch |   ")"  |
   //                   |      |--------|        |
   //             "("   |          |             |
   //         increment |          | text        |
   //           depth   |         \|/            |
   //                   |       |------|         |
   //                   --------| leaf |         |
   //                           |------|         |
   //                              |             |
   //                              | text incl.  |
   //                             \|/   string   |
   //                   ------->|-----| literal  |
   //            text   |       | end |-----------
   //                   --------|-----|
   //
   if( *process_text != 1 ) { //Do nothing unless token is valid.
      return parser_state;
   }

   switch( lexer_state ) {
      case ami_open_paren:
         switch( parser_state ) {
         case ami_start:
            parser_state = ami_branch;
            break;
         case ami_branch:
            printf(
"WARNING: \'(\' encountered in parameter string when text was expected.\n"
            );
            break;
         case ami_leaf:
            //Increment the stack.
            if( *stack_ptr < MAX_TREE_DEPTH - 1 ) {
               (*stack_ptr)++;
            }
            else {
               printf( "WARNING: Parameter string nested too deeply.\n" );
            }
            parser_state = ami_branch;
            break;
         case ami_end:
            printf(
"WARNING: \'(\' encountered in parameter string when \')\' was expected.\n"
            );
            break;
         } //switch parser_state
         break;
      case ami_text:
         switch( parser_state ) {
            case ami_start:
               printf(
"WARNING: Text encountered in parameter string when \'(\' or \')\' was expected.\n"
               );
               break;
            case ami_branch:
               parser_state = ami_leaf;
               break;
            case ami_leaf:
               parser_state = ami_end;
               break;
            case ami_end:
               break;
         }
         break;
      case ami_white_space:
         break;
      case ami_close_paren:
         switch( parser_state ) {
         case ami_start:
            //Decrement the stack.
            if( *stack_ptr > 0 ) {
               (*stack_ptr)--;
            }
            else {
               printf(
                   "WARNING: Too many close parens in parameter string.\n" );
            }
            parser_state = ami_start;
            break;
         case ami_branch:
            printf(
"WARNING: \')\' encountered in parameter string when text was expected.\n"
            );
            break;
         case ami_leaf:
            printf(
"WARNING: \')\' encountered in parameter string when \'(\' or text was expected.\n"
            );
            break;
         case ami_end:
            parser_state = ami_start;
            break;
         } //switch parser_state
         break;
   } //switch lexer_state
   return parser_state;
}

//Private auxiliary function for ParseTree
int AddToArray( const InLineString text_in,
                ParamType type_in,
                ArrayVal* array_in ) {
   int type_size,
       len,
       cnt = 0;
   void *tmp_array;

   switch( type_in ) {
      case ami_double_array_type:
         type_size = sizeof( double );
         break;
      case ami_long_array_type:
         type_size = sizeof( long );
         break;
      case ami_int_array_type:
         type_size = sizeof( int );
         break;
      case ami_short_array_type:
         type_size = sizeof( short );
         break;
      default:
         assert(0);
   }

   len = array_in->length;
   tmp_array = malloc( (len+1)*type_size );
   if( len ) {
      memcpy( tmp_array, array_in->contents, len*type_size );
      free( array_in->contents );
   }
   array_in->contents = tmp_array;
   array_in->length   = len+1;

   switch( type_in ) {
      case ami_double_array_type:
         cnt = sscanf( text_in.start,"%lf", ((double*)array_in->contents)+len );
         break;
      case ami_long_array_type:
         cnt = sscanf( text_in.start,"%li", ((long*)array_in->contents)+len );
         break;
      case ami_int_array_type:
         cnt = sscanf( text_in.start, "%i", ((int*)array_in->contents)+len );
         break;
      case ami_short_array_type:
         cnt = sscanf( text_in.start,"%hi", ((short*)array_in->contents)+len );
         break;
      default:
         assert(0);
   }

   return cnt;
}

int ParseTree( const char *parameters_in, ParamList param_list_in ) {
   ParamList branch_stack[ MAX_TREE_DEPTH ];
   ParamListItem the_root,
                 *the_leaf;
   int stack_ptr = 0,
       len = 0,
       indx,
       process_text = 0,
       string_literal = 0,
       cnt,
       item_count = 0;
   const char *text_ptr;
   LexerState  lexer_state  = ami_white_space;
   ParserState parser_state = ami_start;
   InLineString token_text = {0,0};

   the_root.name   = param_list_in.name;
   the_root.p_type = ami_list_type;
   the_root.p_val.ptr_val  = (void*)&param_list_in;
   branch_stack[0].name     = "MotherEarth";
   branch_stack[0].length   = 1;
   branch_stack[0].contents = &the_root;

   text_ptr = parameters_in;

   while( *text_ptr ) {
      lexer_state = AmiLexer( text_ptr,
                              lexer_state,
                              parser_state,
                              &token_text,
                              &string_literal,
                              &process_text );
   
      if( process_text == 1 &&
          lexer_state  == ami_text ) { //Process text token.
         switch( parser_state ) {
            case ami_start:
               break;
            case ami_branch:
               the_leaf = 0;
               for( indx = 0; indx < branch_stack[stack_ptr].length; indx++ ) {
                  if( strncmp( token_text.start,
                               branch_stack[stack_ptr].contents[ indx ].name,
                               token_text.length ) == 0 ) {
                     the_leaf = branch_stack[stack_ptr].contents + indx;
                     break;
                  }
               }
               break;
            case ami_leaf:
               if( the_leaf ) {
                  switch( the_leaf->p_type ) {
                     case ami_double_type:
                        cnt = sscanf( token_text.start,
                                      "%lf",
                                      &(the_leaf->p_val.dbl_val) );
                        break;
                     case ami_long_type:
                        cnt = sscanf( token_text.start,
                                      "%li",
                                      &(the_leaf->p_val.long_val) );
                        break;
                     case ami_int_type:
                        cnt = sscanf( token_text.start,
                                      "%i",
                                      &(the_leaf->p_val.int_val) );
                        break;
                     case ami_short_type:
                        cnt = sscanf( token_text.start,
                                      "%hi",
                                      &(the_leaf->p_val.short_val) );
                        break;
                     case ami_char_type:
                        cnt = sscanf( token_text.start,
                                      "%c",
                                      &(the_leaf->p_val.char_val) );
                        break;
                     case ami_double_array_type:
                        cnt = AddToArray( token_text,
                                          ami_double_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_long_array_type:
                        cnt = AddToArray( token_text,
                                          ami_long_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_int_array_type:
                        cnt = AddToArray( token_text,
                                          ami_int_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_short_array_type:
                        cnt = AddToArray( token_text,
                                          ami_short_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_string_type:
                        the_leaf->p_val.string_val =
                        (char*)malloc( (token_text.length+1) * sizeof( char ) );
                        strncpy( the_leaf->p_val.string_val,
                                 token_text.start,
                                 token_text.length );
                        the_leaf->p_val.string_val[ token_text.length ] = 0;
                        cnt = 1;
                        break;
                     case ami_list_type:
                        cnt = 0;
                        break;
                  } //switch the_leaf->p_type
                  item_count += cnt;
                  if( cnt == 0 ) {
                     printf( "WARNING: Could not convert value for %s.\n",
                             the_leaf->name );
                  }
               } //if the_leaf
               break;
            case ami_end:
               if( the_leaf ) {
                  switch( the_leaf->p_type ) {
                     case ami_double_type:
                     case ami_long_type:
                     case ami_int_type:
                     case ami_short_type:
                     case ami_char_type:
                     case ami_string_type:
                     case ami_list_type:
                        cnt = 0;
                        printf(
"WARNING: Extra white space and text encountered in value for %s.\n",
                                the_leaf->name );
                        break;
                     case ami_double_array_type:
                        cnt = AddToArray( token_text,
                                          ami_double_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_long_array_type:
                        cnt = AddToArray( token_text,
                                          ami_long_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_int_array_type:
                        cnt = AddToArray( token_text,
                                          ami_int_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                     case ami_short_array_type:
                        cnt = AddToArray( token_text,
                                          ami_short_array_type,
                                          the_leaf->p_val.array_val );
                        break;
                  } //switch the_leaf->p_type
                  item_count += cnt;
                  if( cnt == 0 ) {
                     printf( "WARNING: Could not convert value for %s.\n",
                             the_leaf->name );
                  }
               } //if the_leaf
               break;
         } //switch parser_state
      } //if process_text

      if( process_text == 1 &&                 //Valid token
          lexer_state  == ami_open_paren &&    //Open paren token
          parser_state == ami_leaf &&          //Transitioning from leaf state
          stack_ptr    <  MAX_TREE_DEPTH-1 ) { //Increment depth
         if( the_leaf && the_leaf->p_type == ami_list_type ) {
            branch_stack[ stack_ptr+1 ] =
                                   *(ParamList*)(the_leaf->p_val.ptr_val);
         }
         else {
            branch_stack[ stack_ptr+1 ].name = "NotMine";
            branch_stack[ stack_ptr+1 ].length = 0;
            branch_stack[ stack_ptr+1 ].contents = 0;
         }
         if( the_leaf && the_leaf->p_type != ami_list_type ) {
            printf(
"WARNING: \'(\' encountered in parameter string when value for %s was expected.\n",
              the_leaf->name );
         }
      }

      parser_state = AmiParser( lexer_state,
                                parser_state,
                                &process_text,
                                &stack_ptr );

      text_ptr++;
   } //while *text_ptr

   return item_count;
}

//Allocate a buffer and concatenate two strings into it.
char* string_cat( const char* str1, const char* str2 ) {
   char *rtn_val;

   rtn_val = (char*)malloc( (strlen(str1) + strlen(str2) + 1)*sizeof( char ) );
   strcpy( rtn_val, str1 );
   strcat( rtn_val, str2 );

   return rtn_val;
}

//Create the string for a value array
char* print_array( const char* name, const ArrayVal values ) {
   char *tmp = 0,
         buf[ MAX_VAL_SIZE ];
   int indx, len;

   if( values.length ) {
      len = MAX_LEAF_SIZE + (values.length - 1)*MAX_VAL_SIZE;
      tmp = (char*)malloc( len*sizeof( char ) );
      memset( tmp, 0, len*sizeof( char ) );
      sprintf( tmp, "(%s ", name );
      switch( values.array_type ) {
         case ami_double_array_type:
            memset( buf, 0, MAX_VAL_SIZE );
            sprintf( buf, "%g", *((double*)values.contents) );
            break;
         case ami_long_array_type:
            memset( buf, 0, MAX_VAL_SIZE );
            sprintf( buf, "%ld", *((long*)values.contents) );
            break;
         case ami_int_array_type:
            memset( buf, 0, MAX_VAL_SIZE );
            sprintf( buf, "%d", *((int*)values.contents) );
            break;
         case ami_short_array_type:
            memset( buf, 0, MAX_VAL_SIZE );
            sprintf( buf, "%hd", *((short*)values.contents) );
            break;
         default:
            assert(0);
      }
      strcat( tmp, buf );
      for( indx = 1; indx < values.length; indx++ ) {
         strcat( tmp, " " );
         switch( values.array_type ) {
            case ami_double_array_type:
               memset( buf, 0, MAX_VAL_SIZE );
               sprintf( buf, "%g", *((double*)values.contents+indx) );
               break;
            case ami_long_array_type:
               memset( buf, 0, MAX_VAL_SIZE );
               sprintf( buf, "%ld", *((long*)values.contents+indx) );
               break;
            case ami_int_array_type:
               memset( buf, 0, MAX_VAL_SIZE );
               sprintf( buf, "%d", *((int*)values.contents+indx) );
               break;
            case ami_short_array_type:
               memset( buf, 0, MAX_VAL_SIZE );
               sprintf( buf, "%hd", *((short*)values.contents+indx) );
               break;
            default:
               assert(0);
         }
         strcat( tmp, buf );
      }
      strcat( tmp, ")" );
   }

   return tmp;
}

//recursive implementation
char* GrowTree( const ParamList list_in ) {
   char *rtn_val,
        *tmp,
        *tmp2,
        buf[ MAX_LEAF_SIZE+1 ];
   int indx;

   rtn_val = (char*)malloc( (strlen( list_in.name )+2)*sizeof( char ) );
   *rtn_val = '(';
   strcpy( rtn_val+1, list_in.name );

   for( indx = 0; indx < list_in.length; indx++ ) {
      switch( list_in.contents[indx].p_type ) {
         case ami_double_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %g)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.dbl_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_long_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %ld)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.long_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_int_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %d)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.int_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_short_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %hd)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.short_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_char_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s %c)",
                     list_in.contents[indx].name,
                     list_in.contents[indx].p_val.char_val );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_double_array_type:
         case ami_long_array_type:
         case ami_int_array_type:
         case ami_short_array_type:
            tmp2 = print_array( list_in.contents[indx].name,
                                *(list_in.contents[indx].p_val.array_val) );
            if( tmp2 ) {
               tmp = string_cat( rtn_val, tmp2 );
               free( tmp2 );
               free( rtn_val );
               rtn_val = tmp;
            }
            break;
         case ami_string_type:
            memset( buf, 0, MAX_LEAF_SIZE+1 );
            sprintf( buf,
                     "(%s \"",
                     list_in.contents[indx].name );
            tmp = string_cat( rtn_val, buf );
            free( rtn_val );
            rtn_val = tmp;
            tmp = string_cat( rtn_val,
                              list_in.contents[indx].p_val.string_val );
            free( rtn_val );
            rtn_val = tmp;
            tmp = string_cat( rtn_val, "\")" );
            free( rtn_val );
            rtn_val = tmp;
            break;
         case ami_list_type:
            tmp2 = GrowTree(*(ParamList*)list_in.contents[indx].p_val.ptr_val);
            tmp = string_cat( rtn_val, tmp2 );
            free( tmp2 );
            free( rtn_val );
            rtn_val = tmp;
            break;
      } //switch contents.p_type
   } //for indx < list_in.length

   tmp = string_cat( rtn_val, ")" );
   free( rtn_val );
   rtn_val = tmp;

   return rtn_val;
}

//Recursive implementation of parameter list destructor
// Assume that names are static strings that should not be destroyed/freed.
void FreeTree( ParamList param_list_in ) {
   int indx;
   for( indx = 0; indx < param_list_in.length; indx++ ) {
      switch( param_list_in.contents[ indx ].p_type ) {
         case ami_double_type:
         case ami_long_type:
         case ami_int_type:
         case ami_short_type:
         case ami_char_type:
            break;
         case ami_double_array_type:
         case ami_long_array_type:
         case ami_int_array_type:
         case ami_short_array_type:
            free( param_list_in.contents[ indx ].p_val.array_val->contents );
            break;
         case ami_string_type:
            free( param_list_in.contents[ indx ].p_val.string_val );
            break;
         case ami_list_type:
            FreeTree(
                    *(ParamList*)param_list_in.contents[ indx ].p_val.ptr_val );
            break;
      } //switch ... p_type
   }
}
