#! /usr/bin/perl -s
#
#  genDebug.pl - generate a debug header file from a standard gl.h
#
#  Note: This file uses quite a number of "here-documents" constructs.
#        All of these statements end with a token that begins with the
#        string "EndOf".  In order for these to work properly, the
#        string must start in the first column.

#-----------------------------------------------------------------------------
#
#  Global Variables and other nicities ...
#

$True = 1;
$False = 0;

%TypeDeclared = ();

%TypeFmt = ( "GLenum",       => "%d",
	     "GLboolean",    => "%d",
	     "GLbitfield",   => "%x",
	     "GLbyte",       => "%d",
	     "GLshort",      => "%d",
	     "GLint",        => "%d",
	     "GLsizei",      => "%d",
	     "GLubyte",      => "%d",
	     "GLushort",     => "%d",
	     "GLuint",       => "%d",
	     "GLfloat",      => "%f",
	     "GLclampf",     => "%f",
	     "GLdouble",     => "%f",
	     "GLclampd",     => "%f",
	     "GLvoid",       => "%x"
	   );

$BeginModeRegEx = "gl(Color[34][u?bdfis]v?|
		      Index[dfis(ub)]v?|
		      Normal3[bdfis]v?|
		      TexCoord[1-4][dfis]v?|
		      EdgeFlagv?|
		      Vertex[2-4][dfis]v?|
		      EvalCoord[12][fd]v?|
		      EvalPoint[12]|
		      ArrayElement|
		      Material[if]v?|
		      CallLists?)";

#-----------------------------------------------------------------------------
#
#  Parse command line arguments
#

$verbose = $v;
$debug = $d;
$multiFile = $m;
$checkBeginModeCalls = $b;

if ( $#ARGV < 0 or $h or $help ) {
  print <<"EndOfHelp";
Usage: $0 [options] <file>
  
  -h, -help : Print this message.
  -b        : Add additional support for calls permitted inside glBegin()/
                glEnd() (i.e. glVertex()) pairs.  By default, calling these
                routines outside glBegin()/glEnd() pairs is undefined, and
                may not generate an error.
  -m        : Add support for multiple files.  This option requires that
                exactly one of the files is compiled with the C preprocessor
                variable "DEFINE_GLOBALS" defined (i.e. cc -DDEFINE_GLOBALS)
                This helps in tracking glBegin() modes.
  -v        : Print parameter values as well as program variable names.

EndOfHelp

  exit;
}


#-----------------------------------------------------------------------------
#
#  main()
#

$firstTime = $True;
$sawTypedef = $False;

LINE: while ( <ARGV> ) {

  if ( /^typedef/ ) {
    $sawTypedef = $True;
    print;
    next LINE;
  }
  
  if ( $firstTime and $sawTypedef ) {
    print "\n";
    if ( $multiFile ) {
      print <<"EndOfDefinition";
#ifdef DEFINE_GLOBALS
GLboolean  __glDebug_InBegin = 0;
#else
extern GLboolean __glDebug_InBegin;
#endif
EndOfDefinition

    } else {
      print "static GLboolean __glDebug_InBegin = 0;\n";
    }

    print <<"EndOfDefinition";

static const char* __glDebug_ErrorString[] = {
  "GL_INVALID_ENUM",
  "GL_INVALID_VALUE",
  "GL_INVALID_OPERATION",
  "GL_STACK_OVERFLOW",
  "GL_STACK_UNDERFLOW",
  "GL_OUT_OF_MEMORY",
  "GL_TABLE_TOO_LARGE",
  "GL_TEXTURE_TOO_LARGE_EXT"					      
};

#define __glDebug_ErrorStringMacro(e) \\
  ( e == GL_TABLE_TOO_LARGE ? __glDebug_ErrorString[6] : \\
    e == GL_TEXTURE_TOO_LARGE_EXT ? __glDebug_ErrorString[7] : \\
    __glDebug_ErrorString[e - GL_INVALID_ENUM] )
    
EndOfDefinition

    $firstTime = $False;
  }

  print unless $debug;
  next LINE unless /^extern/;
  next LINE if /"C"/;
  
  #---------------------------------------------------------------------------
  #
  #  Okay, we've found a line that should look like:
  #
  #    extern $retType $cmdName( $params );
  #
  if ( /^extern \s* (.*?) \s* (gl.*?) \s* \((.*?)\)/x ) {
    $retType = $1;
    $cmdName = $2;
    $paramStr  = $3;
  }
  
  $inBeginCheck = "";
  $skipVarGen = $False;

  #---------------------------------------------------------------------------
  #
  #  Skip redefining glGetError()
  #
  next LINE if $cmdName =~ /glGetError/;

  #---------------------------------------------------------------------------
  #
  #  Handle some special cases.
  #
  foreach ($cmdName) {

    /$BeginModeRegEx/x && do {
      $inBeginCheck = "\\\n  if ( !__glDebug_InBegin )";
      last;
    };
    
    /glBegin/ && do {
      print <<"EndOfMacro";
#define glBegin( mode ) \\
  if ( __glDebug_InBegin ) { \\
    printf( "[%s:%d] glBegin( %s ) called between glBegin()/glEnd() pair\\n", \\
	    __FILE__, __LINE__, #mode ); \\
  } else { \\
    __glDebug_InBegin = GL_TRUE; \\
    glBegin( mode ); \\
  }

EndOfMacro
      next LINE;
    };

    /glEnd/ && do {
      print <<"EndOfMacro";
#define glEnd() \\
  if ( !__glDebug_InBegin ) { \\
    printf( "[%s:%d] glEnd() called outside glBegin()/glEnd() pair\\n", \\
	    __FILE__, __LINE__ ); \\
  } else { \\
    __glDebug_InBegin = GL_FALSE; \\
    glEnd(); \\
  }
	    
EndOfMacro
      next LINE;
    };

    /glGetPointerv/ && do {
      $skipVarGen = $True;
      $prototype = "glGetPointerv( pname, params )";
      if ( $verbose ) {
	$fmt = "glGetPointerv( %s [%d], %s [0x%x] )";
	$args = "#pname, (pname), #params, (params), ";
      } else {
	$fmt = "glGetPointerv( %s, %s )";
	$args = "#pname, #params, ";
      }
      
    };
  }

  #---------------------------------------------------------------------------
  #
  #  Process the return value, if necessary
  #
  $returnValue = $retType ne "void";

  if ( $returnValue and not $DeclaredType{$retType} ) {
    $DeclaredType{$retType} = $False;
  }

  #---------------------------------------------------------------------------
  #
  #  Process function parameters
  #
  if ( not $skipVarGen ) {
    if ( $paramStr eq "void" ) {
      $prototype = "$cmdName()";
      $fmt = $prototype;
      $args = "";
      
    } else {
      @params = split( /,\s*/, $paramStr );
      
      @type = ();
      @var  = ();
      @pointer = ();
      
      foreach ( @params ) {
	$pointer = $False;
	
	/([\w\s]+)\s+(.*)/;
	$type = $1;
	$var  = $2;
	
	if ( $type =~ /^const\s+(.*)/ ) {
	  $type = $1;
	}
	
	if ( $var =~ /^\*/ ) {
	  $pointer = $True;
	  $var = substr( $var, 1 );
	}
	
	push( @type, $type );
	push( @var, $var );
	push( @pointer, $pointer );
      }
      
      $count = $#type;
      
      #
      #  Generate the function prototype
      #
      $prototype = "$cmdName( ";
      for ( $i = 0; $i < $count; ++$i ) {
	$prototype .= "$var[$i], ";
      }
      $prototype .= "$var[$count] )";
      
      #
      #  Generate a printf() like format string
      #
      $fmt = "$cmdName( ";
      if ( $verbose ) {
	for ( $i = 0; $i < $count; ++$i ) {
	  $fmt .= "%s [";
	  if ( $pointer[$i] ) {
	    $fmt .= "0x%x";
	  } else {
	    $fmt .= $TypeFmt{$type[$i]};
	  }
	  $fmt .= "], ";
	}
	if ( $pointer[$count] ) {
	  $fmt .= "%s [0x%x] )";
	} else {
	  $fmt .= "%s [$TypeFmt{$type[$count]}] )";
	}
	
      } else {
	$fmt .= ( "%s, " x $count ) ."%s )";
      }
      
      #
      #  Generate the actual values to be printed
      #
      ++$count;
      $args = "";
      for ( $i = 0; $i < $count; ++$i ) {
	$args .= "#$var[$i], ";
	$args .= "($var[$i]), " if $verbose;
      }
    }
  }

  #---------------------------------------------------------------------------
  #
  #  Generate command macro
  #
  
  if ( not $returnValue ) {
    if ( $checkBeginModeCalls and $inBeginCheck ne "" ) {
      $args = ", " . $args if $args ne "";
      $args = $1 if $args =~ /(.*),\s*$/;
      print <<"EndOfMacro"
#define $prototype \\
  $prototype; $inBeginCheck \\
      printf( "[%s:%d] $fmt called outside of glBegin()/glEnd pair\\n", \\
              __FILE__, __LINE__${args} );

EndOfMacro

    } else {
      print <<"EndOfMacro"
#define $prototype \\
  $prototype; $inBeginCheck \\
  { GLenum _e; \\
    while ( (_e = glGetError()) != GL_NO_ERROR ) \\
      printf( "[%s:%d] $fmt returned an error: %s\\n", \\
              __FILE__, __LINE__, \\
	      ${args}__glDebug_ErrorStringMacro(_e) ); \\
  }

EndOfMacro

    }
    
  } else {
    if ( not $TypeDeclared{$retType} ) {
      $type = $retType;
      if ( $type =~ /^const\s+(.*)\s*\*/ ) {
	$type = $1;
      }
      
      print "static $retType __glDebug_$type;\n";
      $TypeDeclared{$retType} = $type;
    }

    $retType = $TypeDeclared{$retType};

    print <<"EndOfMacro"
#define $prototype \\
  __glDebug_$retType = $prototype;
  { GLenum _e; \\
    while ( (_e = glGetError()) != GL_NO_ERROR ) \\
      printf( "[%s:%d] $fmt returned $TypeFmt{$type} with error: %s\\n", \\
	      __FILE__, __LINE__, \\
	      ${args}__glDebug_$retType, \\
              __glDebug_ErrorStringMacro(_e) ); \\
  }
    
EndOfMacro
  }
}
