#!/usr/local/bin/perl

# What args were we called with?
if ((@ARGV < 1) || (@ARGV > 2)) {
 print "Usage: msqlpp infile [outfile]\n";
}

# Open the file
open(DCFILE, $ARGV[0]) || die "Cannot open $ARGV[0]: $!\n";

# Either write to arg 2, or stdout
if ($ARGV[1] ne "") {
 open(MCFILE, "> $ARGV[1]") || die "Cannot open $ARGV[1]: $!\n";
 select(MCFILE);
}

# Read the file!
while (<DCFILE>) 
{
 # check for a line ending in a close paren with no semicolon to see
 # if we're in the prototypes for a procedure
 if (/\)\s*$/) {
  $in_procedure_declaration = 1;
 }

 # ok, have we gotten to a procedure yet? (check for an { )
 # if so, pop another depth level on the stack
 # also, if we're in a procedure, we aren't in the declaration anymore
 if (/[\s\t ]*{[\s\t ]*/) {
  $procedure_depth = $procedure_depth + 1;
  $in_procedure_declaration = 0;
 }

 # check for } to see if we're leaving a procedure
 if (/[\s\t ]*}[\s\t ]*/) {
  $procedure_depth = $procedure_depth - 1;
 }
 
 # check for a SQL declare section
 # if so, check for it being global
 # else, reset the local variable array
 if (/^\s*EXEC SQL BEGIN DECLARE SECTION/) {
#  $_ = "#ifdef INGRES\n" . $_ . "#endif\n";
  $sql_declare_section = 1;
  if (($in_procedure_declaration == 0) && ($procedure_depth == 0)) {
   $global_sql_declare_section = 1;
  } else {
# For whatever reason, this doesn't work, so I'm leaving it commented out
#   if ($in_procedure_declaration == 0) {
#    %localvar = ();
#   }
  }
 }

 # check for end of a SQL declare section
 if (/^\s*EXEC SQL END DECLARE SECTION/) {
#  $_ = "#ifdef INGRES\n" . $_ . "#endif\n";
  $global_sql_declare_section = 0;
  $sql_declare_section = 0;
 }

 # if we're in a SQL declare section, build a symbol table
 if ($sql_declare_section == 1) {
  print;
  $lastline = $_;

  # strip away things we don't use
  s/^\s*extern//g;
  s/^\s*static//g;
  s/^\s*short//g;

  # don't deal with EXEC SQL lines
  if (/^\s*EXEC\s+/)
   {}

  # don't deal with empty lines
  elsif (/^\s*$/)
   {}

  # do deal with ints
  elsif (/^\s*int\s+/) 
   {
    s/\s*int\s*//g;
    s/\;\s*$//g;
    s/\s*//g;
    @elements=split(/,/,$_);
    $number_of_elements = @elements;
    foreach $element_number (1..$number_of_elements) {
     $elements[$element_number-1] =~ s/=\s*\S+//g;
     if ($global_sql_declare_section == 1) {
      $globalvar{$elements[$element_number-1]} = "%d";
     } else {
      $localvar{$elements[$element_number-1]} = "%d";
     }
    }
   }
  
  # do deal with doubles
  elsif (/^\s*double\s+/)
   {
    s/\s*double\s*//g;
    s/\;\s*$//g;
    s/\s*//g;
    @elements=split(/,/,$_);
    $number_of_elements = @elements;
    foreach $element_number (1..$number_of_elements) {
     $elements[$element_number-1] =~ s/=\s*\S+//g;
     if ($global_sql_declare_section == 1) {
      $globalvar{$elements[$element_number-1]} = "%d";
     } else {
      $localvar{$elements[$element_number-1]} = "%d";
     }
    }
   }

  # do deal with chars
  elsif (/^\s*char\s+/)
   {
    s/\s*char\s*//g;
    s/\;\s*$//g;
    s/\s*//g;
    @elements=split(/,/,$_);
    $number_of_elements = @elements;
    foreach $element_number (1..$number_of_elements) {
     $elements[$element_number-1] =~ s/=\s*\S+//g;
     $elements[$element_number-1] =~ s/^\*//g;
     $elements[$element_number-1] =~ s/\[\S*\]\s*$//g;
     if ($global_sql_declare_section == 1) {
      $globalvar{$elements[$element_number-1]} = "'%s'";
     } else {
      $localvar{$elements[$element_number-1]} = "'%s'";
     }
    }
   }

  # deal with everything else
  else
   {
     print STDERR "msqlpp: in file $ARGV[0] can't deal with SQL variable delcaration $_";    
   }

 # end of parser for SQL variables (closes on line below)
 } else {
 # here begins the general stuff that happens whenever we *aren't in an
 # SQL delcaration section

  # change ifsql's to ifdefs
  s/^#(\s*)ifsql/#$1ifdef/;

  # perform edits on embedded SQL lines only
  if (/^\s*EXEC SQL/) {

   # strip off comments 
   s#/\*[\S\t ]*\*/[\S\t ]*$##;
  
   # do we need to pull in the next line?
   # if so, do so and clean it up. the entire thing should be there,
   # ending with a semicolon
   while (!/;\s*$/) {
    chop;
    $_ .= " " . <DCFILE>;
   }
   s/[ \t]+/ /g;

   # There is no concept of REPEATED in mSQL. mSQL is *always* fast
   s/ REPEATED//g;

   # do funny stuff to deal with "now"
   s/'now'/:'now'/g;   
   
   # CHAR() conversion means nothing
   s/(\s+)CHAR\([ \t]*([:\w])[ \t]*\)/$1$2/g;

   # replace connect statements with mSQL style
   if (/^\s*EXEC SQL CONNECT/) {
    # if they use a dynamic SQL variable for DB name, deal
    s/EXEC SQL CONNECT :(\S+)/\tmsqlConnect((char *)NULL);\n\tif ((ingres_errno = msqlSelectDB(msql_sock, $1)) < 0) { msqlerr(); }\n/g;
    # otherwise, just pass the args on to msqlSelectDB
    s/EXEC SQL CONNECT (\S+)/\tmsqlConnect((char *)NULL);\n\tif ((ingres_errno = msqlSelectDB(msql_sock, $1)) < 0) { msqlerr(); }\n/g;
   }

   # replace update statement with msql style
   if (/^\s*EXEC SQL UPDATE/) {

    # strip out unneeded spaces
    s/(\S) (,)/$1$2/g;

    # reset the list of SQL variables used
    @sql_variables_used = ();
    
    # recurse over statement until it's clean
    do {
     $previous_pass = $_;
     if (/[ \t=\(]:([\w']+)/) {
      $variable = $1;
      if ($variable eq "'now'") {
       s/([ \t=\(]):'now'/$1%d/;
       $sql_variables_used[$#sql_variables_used+1] = "time((time_t *)0)";
      }      
      elsif ($localvar{$variable}) {
       s/([ \t=\(]):\w+/$1$localvar{$variable}/;
       $sql_variables_used[$#sql_variables_used+1] = $variable;
      }
      elsif ($globalvar{$variable}) {
       s/([ \t=\(]):\w+/$1$globalvar{$variable}/;
       $sql_variables_used[$#sql_variables_used+1] = $variable;
      }
      else {
       print STDERR "msqlpp: $ARGV[0] WARNING: SQL variable $variable not declared!\n";
      }
     }
    } until $previous_pass eq $_;

    if ($#sql_variables_used < 0) {
     s/EXEC SQL UPDATE ([\t\S ]+);\s*$/sprintf(mstmt_buf, "UPDATE $1");\n\tif ((ingres_errno = msqlQuery(msql_sock, mstmt_buf) < 0) { msqlerr(); }\n/g;
    } else {
     $sql_variables = join(', ',@sql_variables_used);
     s/EXEC SQL UPDATE ([\t\S ]+);\s*$/sprintf(mstmt_buf, "UPDATE $1", $sql_variables);\n\tif ((ingres_errno = msqlQuery(msql_sock, mstmt_buf) < 0) { msqlerr(); }\n/g;
    }
    
   }

   # replace delete statement with msql style
   if (/^\s*EXEC SQL DELETE/) {

    # strip out unneeded spaces
    s/(\S) (,)/$1$2/g;

    # reset the list of SQL variables used
    @sql_variables_used = ();
    
    # recurse over statement until it's clean
    do {
     $previous_pass = $_;
     if (/[ \t=\(]:(['\w]+)/) {
      $variable = $1;
      if ($variable eq "'now'") {
       s/([ \t=\(]):'now'/$1%d/;
       $sql_variables_used[$#sql_variables_used+1] = "time((time_t *)0)";
      }      
      elsif ($localvar{$variable}) {
       s/([ \t=\(]):\w+/$1$localvar{$variable}/;
       $sql_variables_used[$#sql_variables_used+1] = $variable;
      }
      elsif ($globalvar{$variable}) {
       s/([ \t=\(]):\w+/$1$globalvar{$variable}/;
       $sql_variables_used[$#sql_variables_used+1] = $variable;
      }
      else {
       print STDERR "msqlpp: $ARGV[0] WARNING: SQL variable $variable not declared!\n";
      }
     }
    } until $previous_pass eq $_;

    if ($#sql_variables_used < 0) {
     s/EXEC SQL DELETE ([\t\S ]+);\s*$/sprintf(mstmt_buf, "DELETE $1");\n\tif ((ingres_errno = msqlQuery(msql_sock, mstmt_buf) < 0) { msqlerr(); }\n/g;
    } else {
     $sql_variables = join(', ',@sql_variables_used);
     s/EXEC SQL DELETE ([\t\S ]+);\s*$/sprintf(mstmt_buf, "DELETE $1", $sql_variables);\n\tif ((ingres_errno = msqlQuery(msql_sock, mstmt_buf) < 0) { msqlerr(); }\n/g;
    }
    
   }

   # replace insert statement with msql style
   if (/^\s*EXEC SQL INSERT/) {

    # strip out unneeded spaces
    s/(\S) (,)/$1$2/g;

    # reset the list of SQL variables used
    @sql_variables_used = ();
    
    # recurse over statement until it's clean
    do {
     $previous_pass = $_;
     if (/[ \t=\(]:(['\w]+)/) {
      $variable = $1;
      if ($variable eq "'now'") {
       s/([ \t=\(]):'now'/$1%d/;
       $sql_variables_used[$#sql_variables_used+1] = "time((time_t *)0)";
      }      
      elsif ($localvar{$variable}) {
       s/([ \t=\(]):\w+/$1$localvar{$variable}/;
       $sql_variables_used[$#sql_variables_used+1] = $variable;
      }
      elsif ($globalvar{$variable}) {
       s/([ \t=\(]):\w+/$1$globalvar{$variable}/;
       $sql_variables_used[$#sql_variables_used+1] = $variable;
      }
      else {
       print STDERR "msqlpp: $ARGV[0] WARNING: SQL variable $variable not declared!\n";
      }
     }
    } until $previous_pass eq $_;

    if ($#sql_variables_used < 0) {
     s/EXEC SQL INSERT ([\t\S ]+);\s*$/sprintf(mstmt_buf, "INSERT $1");\n\tif ((ingres_errno = msqlQuery(msql_sock, mstmt_buf) < 0) { msqlerr(); }\n/g;
    } else {
     $sql_variables = join(', ',@sql_variables_used);
     s/EXEC SQL INSERT ([\t\S ]+);\s*$/sprintf(mstmt_buf, "INSERT $1", $sql_variables);\n\tif ((ingres_errno = msqlQuery(msql_sock, mstmt_buf) < 0) { msqlerr(); }\n/g;
    }
    
   }

   # replace select statement with mSQL style
   if (/^\s*EXEC SQL SELECT/) {
    # conversions we don't do yet:
    # UPPERCASE(char) causes us to ustrcpy(char, char, sizeof(char))    
    #  except that won't work. needs to be dealt with.
    # LEFT(foo,count) returns a null terminated string of length count
    # SIZE(foo) returns size of column. should use msqlFetchField and msqlListFields
    # 'now' becomes time((time_t *)0)
    # COUNT()is number of occurances # of rows returned?
    # SUM() is probably the sum of all the results we got

    # strip out unneeded spaces
    s/(\S) (,)/$1$2/g;

    # reset INTO's arg list
    @into_fields_used = ();

    $done = 0;
    # pull out INTO clause
    do {
     if (/INTO[ \t]+([:\w,]+)/) {
      $variable = $1;
      s/(INTO[ \t]+)[:\w,]+/$1/;
      if ($variable =~ /,$/) {
       $variable =~ s/,$//;
      } else {
       $done = 1;
      }
      $into_fields_used[$#into_fields_used+1] = $variable;
     }
    } until ($done == 1);
    s/ INTO//;

    # reset the list of SQL variables used
    @sql_variables_used = ();
    
    # recurse over statement until it's clean
    do {
     $previous_pass = $_;
     if (/[ \t=\(]:(['\w]+)/) {
      $variable = $1;
      if ($variable eq "'now'") {
       s/([ \t=\(]):'now'/$1%d/;
       $sql_variables_used[$#sql_variables_used+1] = "time((time_t *)0)";
      }      
      elsif ($localvar{$variable}) {
       s/([ \t=\(]):\w+/$1$localvar{$variable}/;
       $sql_variables_used[$#sql_variables_used+1] = $variable;
      }
      elsif ($globalvar{$variable}) {
       s/([ \t=\(]):\w+/$1$globalvar{$variable}/;
       $sql_variables_used[$#sql_variables_used+1] = $variable;
      }
      else {
       print STDERR "msqlpp: $ARGV[0] WARNING: SQL variable $variable not declared!\n";
      }
     }
    } until $previous_pass eq $_;

    if ($#sql_variables_used < 0) {
     s/EXEC SQL SELECT ([\t\S ]+);\s*$/sprintf(mstmt_buf, "SELECT $1");\n\tif ((ingres_errno = msqlQuery(msql_sock, mstmt_buf) < 0) { msqlerr(); }\n\tmsql_result = msqlStoreResult();\n\tmsql_row = msqlFetchRow(msql_result);\n/g;
    } else {
     $sql_variables = join(', ',@sql_variables_used);
     s/EXEC SQL SELECT ([\t\S ]+);\s*$/sprintf(mstmt_buf, "SELECT $1", $sql_variables);\n\tif ((ingres_errno = msqlQuery(msql_sock, mstmt_buf) < 0) { msqlerr(); }\n\tmsql_result = msqlStoreResult();\n\tmsql_row = msqlFetchRow(msql_result);\n/g;
    }
    $count = 0;
    foreach $field (@into_fields_used) {
     $field =~ s/^[ ]*://;
     if (($globalvar{$field} eq "%d") || ($localvar{$field} eq "%d")) {
      s/$/\n\t$field = atoi(msql_row[$count]);/;
     } elsif (($globalvar{$field} eq "'%s'") || ($localvar{$field} eq "'%s'")) {
      s/$/\n\tsprintf($field, "%s", msql_row[$count]);/;
     } else {
      print STDERR "msqlpp: $ARGV[0] WARNING: variable $field is of an unknown type!\n";
      s/$/\n\tCOPY msql_row[$count] into $field/;
     }
     $count++;
    }
    s/$/\n\tmsqlFreeResult(msql_result);/;
   } # end of code to deal with SELECT

  }

  print;
 }

}
