#import "Eval.h"

extern int unused ;

@implementation Eval(DotH)

- buildDotH:(char *) textBuf length:(int)textLen ; 
{ // Build anObjC interface from the (not necessarily
  // null-terminated) text in textBuf of length textLen.
  // Normally, this file will have a .h extension, but
  // this is not checked for.
  // If there is a #pragma .hfile somePath, save the .h
  // into somePath, otherwise pop up a save panel to get
  // the path.
  //
  // Every line of the form #pragma .h ``whatever'' has
  // ``whatever'' copied into the resulting .h.
  //  
  // Essentially, this method is just a state machine with each
  // state specified by a combiation of boolean variables
  // such as "inComment", "inMethodDef", and so on.  The
  // variable inImp (inImplementation) is more complex, as
  // it is fairly hard to pick out the parts of an @implementation
  // that belong in the header file.  inImp is set to 0 when
  // we are not scanning an @implementation ...., otherwise it takes
  // on an integer value specifying one of these states:
  //   (where W = required whitespace (may include comments)
  //          w = optional whitespace (may include comments)
  //          C = required, sequence of nonwhite chars
  //          N = anything other than a ")"
  //          X = anything other that a "(" or a ":"
  //          :,(,) ... exact matches
  //  state:         123 456
  //  @implementationWCw:wCwC
  //  state:         123 7
  //  @implementationWCw(N)
  //  state:         123
  //   @implementationWCWX
  NXStream *inStream =  NXOpenMemory(textBuf, textLen, NX_READONLY) ;
  NXStream *dotHStream =  NXOpenMemory(NULL, 0, NX_READWRITE) ;
  char buf[1024], *memBuf ;
  char *dotHFileName = NULL ;
  int i, len, depthKnt = 0, statementStart = 0 ;
  BOOL newline ; // do I really care?
  BOOL inComment = NO, inSlashComment = NO, inString = NO,
       inStatement = NO, inMethodSig = NO, semiColon = NO,
       inIvar = NO, eqInStatement = NO, output = YES ;
  int inImp = 0 ; 
  do
  { newline = EvalGets(inStream,buf) ;
    len = strlen(buf) ;
    if(buf[0] == '#' && !inComment) 
    { // found # in column 1: now look for a pragma
      i = 1 ;
      while(buf[i] && isspace(buf[i]))
        i++ ; // span space
      if(!strncmp(&buf[i],"pragma ",7))
      { // pragma found...now look for .h
        i += 7 ;
        while(buf[i] && isspace(buf[i]))
          i++ ; // span space
        if(!strncmp(&buf[i],".h ",3))
        { // .h found: write #pragma .h to outstream
          i += 3 ;
          while(buf[i] && isspace(buf[i]))
            i++ ; // span space
          NXWrite(dotHStream,&buf[i],len - i) ;
          NXPutc(dotHStream,'\n') ;
          while(newline && buf[len-1] == '\\') 
          { // while there is a continuation line...
            newline = EvalGets(inStream,buf) ;
            len = strlen(buf) ;
            NXWrite(dotHStream,buf,len) ;
            NXPutc(dotHStream,'\n') ;
          }
        }
        else if(!strncmp(&buf[i],".hfile ",7))
        { // .hfile found: grab the dotHFileName which should follow...
          int j ;
          i += 7 ;
          while((i < len) && (buf[i] == '\t' || buf[i] == ' '))
            i++ ; // span white space
          if(buf[i] == '"')
            i++ ; // if there is a quote, skip over it
          j = i ;
          while(buf[j] && !isspace(buf[j]) && buf[j] != '"') 
            j++ ;         
          dotHFileName = alloca(j - i + 1) ;
          strncpy(dotHFileName,&buf[i],j-i) ;
          dotHFileName[j-i] = '\0' ;
        }
      }
    }

    else
    { inString = NO ; // ansi C forbids newline in string
      inSlashComment = NO ; 
      for(i = 0 ; i < len ; i++)
      { switch(buf[i])
        { case '"': if(output && !inComment)
                    { if(inString)
                      { if( ((i > 0) && (buf[i-1] != '\\') ) ||
                            ((i > 1) && (buf[i-1] == '\\') && (buf[i-2] != '\\') ))
                          inString = NO ;
                      }
                      else if(i == 0) 
                         inString = YES ;
                      else if( !( 
                         (i > 0) &&
                         (len > 2) && 
                         (buf[i - 1] == '\'') && 
                         (buf[i+1] = '\'')
                         )) // i.e. if not a character constant, '"'
                          inString = YES ;
                    }
                    break ;
          case '/': if(!inString)
                    { if(!inSlashComment && inComment && (i > 0) && (buf[i - 1] == '*'))
                        inComment = NO ; // end of a slash-star,star-slash comment
                      else if(!inSlashComment && !inComment && (i < (len - 1)) &&
                               (buf[i+1] == '*'))
                         inComment = YES ; // start of a slash-star,star-slash comment
                      else if(!inSlashComment && !inComment && (i < (len -1)) 
                          && (buf[i + 1] == '/'))
                           inSlashComment = YES ;
                    }
                    break ;
          case '{': if(output && !inSlashComment && !inComment && !inString)
                    { depthKnt++ ;
                      if(inImp)
                      { inImp = 0 ;
                        inIvar = YES ;
                      }
                      else if(inMethodSig)
                      { inMethodSig = NO ;
                        if(!semiColon)
                        { // we must add a semicolon
                          NXPutc(dotHStream,';') ;
                          NXPutc(dotHStream,'\n') ;
                          NXSeek(dotHStream, 0, NX_FROMEND) ;
                        }
                      }
                      else if(inStatement && (depthKnt == 1) && !eqInStatement)
                      { // This may be a function def.  Our algorithm to
                        // recognize a function declarator is any statement that 
                        // terminates with an un-embedded '{' , and which
                        // does not begin with struct,union, or typedef, and which
                        // does not contain an =  character (other than inside a comment)
                        // This algortithm will not recognize K&R style function declarators.
                        NXStream *tmpStream = NXOpenMemory(NULL, 0, NX_READWRITE) ;
                        int currentPnt = NXTell(inStream) ;
                        int statementLen = currentPnt - 1 - len + i - statementStart ;
                        // copy statement to tmpStream
                        NXSeek(inStream,statementStart,NX_FROMSTART) ;
                        for(i = 0 ; i < statementLen ; i++)
                          NXPutc(tmpStream,NXGetc(inStream)) ;
                        // reset inStream
                        NXSeek(inStream,currentPnt,NX_FROMSTART) ;
                        // we must add a semicolon
                        NXPutc(tmpStream,' ') ;
                        NXPutc(tmpStream,'\0') ;
                        // now see if we have a function def...it might also
                        // be a struct, union, or typedef...
                        NXGetMemoryBuffer(tmpStream,&memBuf,&unused,&unused) ;
                        if(strncmp(memBuf,"struct ",7) &&
                           strncmp(memBuf,"union ",6)  &&
                           strncmp(memBuf,"typedef ",8))
                        { // we conclude that this is a function def
                          NXWrite(dotHStream,memBuf,strlen(memBuf)) ;
                          NXWrite(dotHStream," ;\n",3) ;
                        }
                        NXCloseMemory(tmpStream,NX_FREEBUFFER) ;
                        inStatement = eqInStatement = NO ;                        
                      }
                    }                   
                    break ;
          case '}': if(output && !inSlashComment && !inComment && !inString)
                    { depthKnt-- ;
                      if(depthKnt < 0)
                        depthKnt = 0 ;
                      if(inIvar && (depthKnt == 0))
                      { NXWrite(dotHStream,"}\n\n",3) ;
                        inIvar = 0 ;
                        inStatement = NO ;
                      }
                    }
                    break ;
          case '=': if(output && inStatement && !inComment && !inSlashComment)
                      eqInStatement = YES ;
                    break ;
          case '@': if(!inSlashComment && !inComment && !inString)
                    { if(output && !strncmp(&buf[i+1],"implementation ",14))
                      { inImp = 1 ;
                        i += 15 ; // we point to blank after implementation
                        NXWrite(dotHStream,"@interface",10) ;
                      } 
                      else if(!strncmp(&buf[i+1],"end",3) && 
                            (buf[i+4] == '\0' || isspace(buf[i+4])))
                      { inImp = 0 ;
                        if(output)
                          NXWrite(dotHStream,"@end",4) ;
                        output = YES ;
                        i += 4 ;
                      }
                      else if((!strncmp(&buf[i+1],"interface ",10)) || 
                        (!strncmp(&buf[i+1],"protocol ",9)))
                      { // Skip @protocol or @interface sections that show
                        // up in the implementation files. Setting output to
                        // NO turns off all output (except preprocessor stuff)
                        // until @end is seen.
                        output = NO ;
                      }
                    }
                    break ;
          case '+': 
          case '-': if(output && (depthKnt == 0) &&
                      !inSlashComment && !inComment && !inString && !inStatement)
                    { inMethodSig = YES ;
                      inImp = 0 ;
                      semiColon = NO ;
                    }
                    break ;
          case ';': if(output && !inSlashComment && !inComment && !inString)
                    { inStatement = eqInStatement = NO ; // end of statement
                      inImp = 0 ;
                      if(inMethodSig) // note that medthod signature has semicolon at end
                        semiColon = YES ;
                    }
                    break ;
          case  ' ':
          case '\t':
                    /* whitespace !*/
                    if(output && !inSlashComment && !inComment && !inString)
                    { switch(inImp) // run our state machine for @implementation
                      { case 2: inImp = 3 ; break ; 
                        case 5: inImp = 6 ; break ;
                        default:            break ; // otherwise no state transitions
                      }
                    }
                    break ;
          default:  if(output && !inSlashComment && !inComment && !inString && !inMethodSig)
                    { switch(inImp)
                      { case 0: if(!inStatement)
                                { inStatement = YES ;
                                  statementStart = NXTell(inStream) - len + i - 1 ;
                                }
                                break ; 
                        case 1: inImp = 2 ; break ;
                        case 2:
                        case 3: switch(buf[i])
                                { case ':': inImp = 4 ; break ;
                                  case '(': inImp = 7 ; break ;
                                  default : if(inImp == 3)
                                            { inImp = 0 ;
                                              inStatement = YES ;
                                              statementStart = NXTell(inStream) - len + i - 1 ;
                                            }
                                            break ;
                                }
                                break ;
                        case 4: inImp = 5 ; break ;
                        case 6: inImp = 0 ; 
                                inStatement = YES ;
                                statementStart = NXTell(inStream) - len + i - 1 ;
                                break ;
                        case 7: if(buf[i] == ')')
                                { NXWrite(dotHStream,")\n",2) ;
                                  inImp = 0 ;
                                }
                                break ;
                      }
                      break ;
                   }
               
        }
        if(output && (inMethodSig || inImp || inIvar))
          NXPutc(dotHStream,buf[i]) ;
      }
      if(output && ((inMethodSig || inImp || inIvar) && newline))
      { switch(inImp) // run our state machine for @implementation
        { case 2: inImp = 3 ; break ; 
          case 5: inImp = 6 ; break ;
          default:            break ; // otherwise no state transitions
        }
        NXPutc(dotHStream,'\n') ;
      }
    }
  } while(!NXAtEOS(inStream)) ;
  NXPutc(dotHStream,'\n') ;
  NXCloseMemory(inStream,NX_SAVEBUFFER) ;

  if(!dotHFileName)
  { id savePanel = [SavePanel new] ;
    if([savePanel runModalForDirectory: NULL file: NULL])
      dotHFileName =  (char *) [savePanel filename] ;
  }
  if(dotHFileName)
  { if(NXSaveToFile(dotHStream, dotHFileName) == -1)
      NXRunAlertPanel("Eval","Couldn't create .h file: %s",NULL,NULL,NULL,dotHFileName) ;
  }    
  NXCloseMemory(dotHStream,NX_FREEBUFFER) ;
  return self ;
}

- buildDotH:(id) pboard userData: (const char *) uBuf error: (char **) msg ;
{ // build .h file from the selection
  char *text ;
  const char *const *types ;
  int i, textLen ;
  BOOL found = NO ;
  // fetch the data from pasteboard
  types = [pboard types] ;
  for(i = 0 ; types[i]; i++)
  { if(!strcmp(types[i], NXAsciiPboardType))
    { found = YES ;
      break ;
    }
  }
  if(!found) // no ascii found...
    return self ;
  [pboard readType:NXAsciiPboardType data:&text length:&textLen];
  if(!text || !textLen) // nothing there...
    return self ;
  else
  { [self buildDotH: text length: textLen] ;
    [pboard  deallocatePasteboardData:text length:textLen];
  }
  return self ;
}


@end
