#Copyright (c) 1992-2000  Microsoft Corporation
#
#Module Name:
#
#    gnbugcds.pl
#
#Abstract:
#
#    WinDbg Extension Api
#
#Environment:
#
#    User Mode.
#
#Revision History:
# 
#    Kshitix K. Sharma (kksharma)
#        
#
# Parse bugcodes.txt file to generate C-relevant info for bugcheck codes
#
# Expected format of a bugcheck description:
#
# BUGCHECK
# <bugcheck name>         (<numericvalue>)
# optional - <multiline description string about bugcheck>
# optional - PARAMETERS <parameters description as follows>
#               Param [1|2|3|4] - <string>
#                       OR
#               Param [1|2|3|4] 
#                VALUES:
#                   [<value> : <Rest of parameter description>]*
#                END_VALUES <required only in case of nesting>
#DESCRIPTION
#<additional description string>
#

sub emit_file_header;
sub emit_bugcheck_info;
sub emit_param_info;
sub next_line;
sub is_new_bugcheck;
sub is_bugcheck_description;


#
# main
#
$NumLine = 0;
@BugCheckList = {};
@FullParamDesc = {"", "", "", ""};
$ParamValueRec = {
      VALUE      => 0,
      PARAMID    => -1,
      PARAM1DESC => "",
      PARAM2DESC => "",
      PARAM3DESC => "",
      PARAM4DESC => "",
   };
@ParamValueRecList = {};

while ($arg = shift) {
   if ($arg eq "-o") {
      $OutFileName = shift;
   } elsif ($arg eq "-i") {
      $BugCheckTxtFile = shift;
   } else {
      $BugCheckTxtFile = $arg;
   }
}

die "Cannot open file $BugCheckTxtFile\n" if !open(BUGC_FILE, $BugCheckTxtFile);

emit_file_header();

while (is_new_bugcheck() || next_line ) {
   $line = $_ if !is_new_bugcheck();
   if (is_new_bugcheck()) {
      emit_bugcheck_info;
   }
} continue {
   close BUGC_FILE if eof;
}
# emit a list of APIS for quick reference
print OUT_FILE "BUGDESC_APIREFS g_BugDescApiRefs[] = {\n";
for $i(1..$#BugCheckList) {
   printf (OUT_FILE  "    { %30s, &BugCheck%s},\n", $BugCheckList[ $i ], $BugCheckList[ $i ]);
}
print OUT_FILE "};\n";
print OUT_FILE "ULONG g_NumBugDescApiRefs = sizeof(g_BugDescApiRefs) / sizeof(BUGDESC_APIREFS);\n";

#
# Subroutines
#
sub is_new_bugcheck {
   if ($line =~ /^BUGCHECK$/) {
      next_line;
   }
   if ($line =~ /^([A-Z][A-Z_0-9]+)\s*\((.*)\)$/) {
      return 1;
   }
   return 0;
}

sub is_bugcheck_description {
   if ($line =~ /^DESCRIPTION\s*$/) {
      return 1;
   }
   return 0;
}
sub emit_file_header {

   die "Cannot open file $OutFileName\n" if !open(OUT_FILE, ">" . $OutFileName);

   print OUT_FILE "//-------------------------------------".
        "--------------------------------------\n";
   print OUT_FILE "//\n";
   print OUT_FILE "// IMPORTANT:  This file is automatically generated.\n";
   print OUT_FILE "//             Do not edit by hand.\n";
   print OUT_FILE "//\n";
   print OUT_FILE "// Generated from $BugCheckTxtFile " . localtime() . "\n";
   print OUT_FILE "//\n";
   print OUT_FILE "//-------------------------------------".
       "--------------------------------------\n\n";
   print OUT_FILE "#include \"precomp.h\"\n";

   print OUT_FILE "\n\n";
}

sub emit_bugcheck_header {
   print OUT_FILE "//\n";
   print OUT_FILE "// DescriptionRoutine for $_[0] ($_[1])\n";
   print OUT_FILE "//\n";
   print OUT_FILE "void\nBugCheck$_[0] ( \n";
   print OUT_FILE "    PBUGCHECK_ANALYSIS pBugCheck\n";
   print OUT_FILE "    )\n";
   print OUT_FILE "{\n";
   print OUT_FILE "    ULONG Value   = $_[0];\n";
   print OUT_FILE "    PCHAR BugName = \"$_[0]\";\n";
   print OUT_FILE "    PCHAR Description = NULL, ParamDesc[4] = {0};\n\n";
}

sub init_param_array {
   for $i (0..3) {
      if ($FullParamDesc[ $i ] =~ /.+/) { # Non NULL values only
         print OUT_FILE "$_[0]$FullParamDesc[ $i ];\n";
      }
   }

}

sub begin_first_value {
   # $_[0] -> Argument index
   # $_[1] -> Argument value
   print OUT_FILE "$_[2]if (pBugCheck->Args[ $_[0] ] == $_[1]) {\n";
}

sub begin_intermediate_value {
   # $_[0] -> Argument index
   # $_[1] -> Argument value
   # $_[2] -> Indent
   init_param_array($_[2]);
   print OUT_FILE "$_[2]} else if (pBugCheck->Args[ $_[0] ] == $_[1]) {\n";
}

sub begin_intermediate_value_parent {
   # $_[0] -> Argument index
   # $_[1] -> Argument value
   # $_[2] -> Indent
   print OUT_FILE "$_[2]} else if (pBugCheck->Args[ $_[0] ] == $_[1]) {\n";
}

sub end_value {
   # $_[0] -> Indent
   init_param_array($_[0]);
   print OUT_FILE "$_[0]}\n";
}

sub emit_bugcheck_end {
   print OUT_FILE "    pBugCheck->Code            = Value;\n";
   print OUT_FILE "    pBugCheck->szName          = BugName;\n";
   print OUT_FILE "    pBugCheck->szDescription   = Description;\n";
   print OUT_FILE "    pBugCheck->szParamsDesc[0] = ParamDesc[0];\n";
   print OUT_FILE "    pBugCheck->szParamsDesc[1] = ParamDesc[1];\n";
   print OUT_FILE "    pBugCheck->szParamsDesc[2] = ParamDesc[2];\n";
   print OUT_FILE "    pBugCheck->szParamsDesc[3] = ParamDesc[3];\n";
   print OUT_FILE "\n}\n\n";
}

#
# Match the folowing here
# <BugName> (<num>)
# <description>
# <parameters> - done by emit_param_info
# <more description>
#
sub emit_bugcheck_info {
   die "No name Info on line $NumLine\n" if !($BugNameLine = $line) ;
   if (($Name,$Value) = $BugNameLine =~ /^([A-Z][A-Z_0-9]+)\s*\((.*)\)$/) {
      emit_bugcheck_header ( $Name, $Value );      
      push (@BugCheckList, $Name);

      $FullDesc = "    Description = ";
      $DescPrinted = 0;
      while (next_line && (!is_new_bugcheck()) && ($line !~ /^\s*PARAMETERS\s*.*$/)) {
        if ($line !~ /^\s*$/) {
            ($Desc) = $line =~ /^(.*)\s*$/;
            if ($DescPrinted) {
               $FullDesc = $FullDesc . "\n\t";
            }
            ($Desc) =~ s/([\\"])/\\$1/g ;
            $FullDesc = $FullDesc . "\"$Desc\\n\"";
            $DescPrinted++;
        }
      }
      $MoreDesc = 0;
      if ($line =~ /^\s*PARAMETERS\s*.*$/) {
         ($line) = $line =~ /^\s*PARAMETERS\s*(.*)$/;
         if (!$line) {
            next_line;
         }
         $MoreDesc = emit_param_info;
      }
      
      if ($MoreDesc) {
         # BugCheck description string following parameter description
         while (next_line && (!is_new_bugcheck())) {
           if ($line !~ /^\s*$/) {
               ($Desc) = $line =~ /^(.*)\s*$/;
               if ($DescPrinted) {
                  $FullDesc = $FullDesc . "\n\t";
               }
               ($Desc) =~ s/([\\"])/\\$1/g ;
               $FullDesc = $FullDesc . "\"$Desc\\n\"";
               $DescPrinted++;
           }
         }
      }
      if (!$DescPrinted) { $FullDesc = $FullDesc . "\"\"";}
      print OUT_FILE $FullDesc . ";\n";
      emit_bugcheck_end ;
   } else { 
      print "Bad name Info on line $NumLine - $BugNameLine\n"
   }
}


sub emit_param_info {
   @FullParamDesc = {"", "", "", ""};
   $ParamValueDefined = -1;
   $IsFirstValue = 1;
   @ParamValueRecList = {};
   $Level = 0;
   $MovedLevelUp = 0;
   $FullParamDesc[ 0 ] = "";
   $FullParamDesc[ 1 ] = "";
   $FullParamDesc[ 2 ] = "";
   $FullParamDesc[ 3 ] = "";

   while (!is_new_bugcheck() && !is_bugcheck_description() && $line) {
        if ($line !~ /^\s*$/) {
            ($ParamPrefix, $ParamId, $ParamDesc) = $line =~ /^\s*(\w*)\s*([1234])\s*-\s*(.*)$/;
            if ($ParamId && ($ParamPrefix == "" || $ParamPrefix == "Param" || $ParamPrefix == "Arg" ||
                             $ParamPrefix == "Parameter" || $ParamPrefix == "Argument")) {

                ($ParamDesc) =~ s/([\\"])/\\$1/g ;
                $ParamId--; # 0 based index
                $FullParamDesc[ $ParamId ] = "    ParamDesc[ $ParamId ] = \"$ParamDesc\"";   
                $LastParamId = $ParamId;
            
            } elsif ($line =~ /^\s*VALUES\s*:?\s*$/) {

                # emit the parameter description we have so far since the folowing
                # ones would be relevant to specific values only.
                # code path will automatically remember these if they are not defined later
                init_param_array(" " x ($Level * 4));
 
                $ParamValueDefined = $LastParamId;
                $IsFirstValue = 1;
                $ParamValueRec = {
                     VALUE      => 0,
                     PARAMID    => $LastParamId,
                     PARAM1DESC => $FullParamDesc[ 0 ],
                     PARAM2DESC => $FullParamDesc[ 1 ],
                     PARAM3DESC => $FullParamDesc[ 2 ],
                     PARAM4DESC => $FullParamDesc[ 3 ],
                };
                push (@ParamValueRecList, ($ParamValueRec));
                $Level = $#ParamValueRecList;
            
            } elsif ($line =~ /^\s*END_VALUES\s*$/) {

                $Level = $#ParamValueRecList;
                end_value(" " x ($Level * 4));
 
                $IsFirstValue = 0;
                $MovedLevelUp = 1;
                # restore what we had whe we saw VALUES clause
                $LastParamId        = $ParamValueRecList[$Level-1]{PARAMID};
                $FullParamDesc[ 0 ] = $ParamValueRecList[$Level-1]{PARAM1DESC};
                $FullParamDesc[ 1 ] = $ParamValueRecList[$Level-1]{PARAM2DESC};
                $FullParamDesc[ 2 ] = $ParamValueRecList[$Level-1]{PARAM3DESC};
                $FullParamDesc[ 3 ] = $ParamValueRecList[$Level-1]{PARAM4DESC};
                if ($Level) {
                   pop( @ParamValueRecList );
                } else {
                   die "No preivious VALUE clause for END_VALUES on line $NumLine\n";
                }
                $ParamValueDefined  = $LastParamId;
                $Level = $#ParamValueRecList;
                if ($Level == 0) {
                  $ParamValueDefined = -1;
                }
            } elsif (($ParamValueDefined != -1) &&
                     (($Value) = $line =~ /^\s*(0?x?[0-9A-Fa-f]*)\s*:.*$/)) {
                #matched "<Value> : <text>"
                if ($Value !~ /0x.*/) { 
                   # prepend 0x 
                  $Value = "0x" . $Value;
                }
                if ($IsFirstValue) {
                     $IsFirstValue = 0;
                     begin_first_value($ParamValueDefined, $Value, " " x ($Level * 4));
                } elsif ($MovedLevelUp) {
                     $MovedLevelUp = 0;  
                     begin_intermediate_value_parent($ParamValueDefined, $Value, " " x ($Level * 4));
                } else {
                     begin_intermediate_value($ParamValueDefined, $Value, " " x ($Level * 4));
                }  

                $ParamDesc = $FullParamDesc[ $ParamValueDefined ];

                if (($Desc) = $line =~ /^\s*0?x?[0-9A-Fa-f]*\s*:\s*(.*)$/) {
                     ($Desc) =~ s/([\\"])/\\$1/g ;
                     $FullParamDesc[ $ParamValueDefined ] = "    ParamDesc[ $ParamValueDefined ] = \"$Desc\"";   
                } else {
                     $FullParamDesc[ $ParamValueDefined ] = $ParamDesc;
                }
            } else {
                ($line) = $line =~ /^\s*(.*)$/;
                ($line) =~ s/([\\"])/\\$1/g ; #"/# escape chars
                $temp = "\\n\"";
                $FullParamDesc[ $LastParamId ] = $FullParamDesc[ $LastParamId ] . "\n        \"\\n$line\"";
            }
       }
       $line = next_line;
   }
   
   if ($ParamValueDefined != -1) {
      end_value(" " x (4));
   } else {
      init_param_array("");
   }

   $MoreDesc = is_bugcheck_description();

   return $MoreDesc;
}

sub next_line {
   $line = <BUGC_FILE>;
   $NumLine++;
   while ($line =~ /\s*\%.*$/) {
      # Skip commented lines - ones which beging with %
      $line = <BUGC_FILE>;
      $NumLine++;
   }
   return $line;
}