# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is IDL pre-processor.
#
# The Initial Developer of the Original Code is
# Alexander J. Vincent <ajvincent@gmail.com>.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

use strict;
use warnings;

my %defines;

my $documentation = <<"Documentation"

idl-preprocess.pl -DFOO -UBAR --srcdir=path/to/directory/containing.idl.in

preprocessor.defines structure:
-DFOO -UBAR

-DFOO means #ifdef FOO will pass, #ifndef FOO will fail
-UBAR means #ifdef BAR will fail, #ifndef BAR will pass

Supported preprocessing (.idl.in files)
#ifdef
#ifndef
#endif
#switchdef
#casedef
#casebreak
#defaultcase
#endswitchdef

Example (IDL):

#switchdef
#casedef WF2_PATTERN
[scriptable, uuid(99639d86-39d6-46d7-aa0d-4ba4c3d72a00)]
#casebreak
#default
[scriptable, uuid(5a2e9790-3b72-4512-b3bf-203cec7b3379)]
#casebreak
#endswitchdef
interface nsIDOMWF2ValidityState : nsISupports
{
#ifdef WF2_PATTERN
/**
 * True if the owner control's value failed to match against its pattern attribute.
 */
  readonly attribute boolean         patternMismatch;

#endif
  /**
   * True if the owner control required a value and did not have it.
   */
  readonly attribute boolean         valueMissing;

  /**
   * True if the owner control meets all constraints on it.
   */
  readonly attribute boolean         valid;
};
Documentation
;

my $log = 0;
my $level = 0;

# Diagnostic logging: called at the beginning of a function, logs the function
# name and parameters, and increments the indent level.
sub enterLog($@)
{
  if ($log)
  {
    my ($f, @parms) = @_;
    $level++;
    my $format = "%" . $level * 2 . "s Enter %s";
    printf ($format, " ", $f);
    while (@parms)
    {
      print " ";
      my $parm = shift (@parms);
      print ($parm);
    }
    print "\n\n";
  }
}

# Diagnostic logging: called in the middle of functions, logs the data at the 
# current indent level.
sub printLog($)
{
  if ($log)
  {
    my ($arg) = @_;
    my $format = "%" . $level * 2 . "s %s\n";
    printf ($format, " ", $arg);
  }
}

# Diagnostic logging: called at the end of functions, decrements the indent.
sub exitLog
{
  if ($log)
  {
    my ($f, @parms) = @_;
    my $format = "%" . $level * 2 . "s Exit %s";
    printf ($format, " ", $f);
    while (@parms)
    {
      print " ";
      my $parm = shift (@parms);
      print ($parm);
    }
    print "\n\n";
    $level--;
  }
}

sub checkPwd($)
{
  my ($pwd) = @_;
  my $nowPwd = `pwd`;

  if ($pwd ne $nowPwd)
  {
    die("checkPwd failed!\n begin: $pwd\n close: $nowPwd\n");
  }
}

sub processParameters()
{
  enterLog("processParameters", @_);
  my $pwd = `pwd`;
  my $fileDefines = "";
  my $diff = "";
  my $diffFilename = "";
  my $srcdir = "";

  while (@ARGV)
  {
    my $param = shift (@ARGV);
    if ($param =~ /--srcdir=/)
    {
      if ($srcdir ne "")
      {
        die("srcdir can only be defined once!");
      }
      $srcdir = substr($param, 9);
      next;
    }

    if ($param eq "--help")
    {
      print $documentation;
      exit;
    }

    if ($param =~ /^-[DU]/)
    {
      $fileDefines .= " $param";
    }
  }

  my @arrayDefines = split(/ /, $fileDefines);
  for (my $i = 1; $i <= $#arrayDefines; $i++)
  {
    my $define = substr($arrayDefines[$i], 2);
    if ($arrayDefines[$i] =~ /^-D/)
    {
      if ($define =~ /=1$/)
      {
        $defines{substr($define, 0, -2)} = 1;
        next;
      }

      if ($define =~ /=0$/)
      {
        $defines{substr($define, 0, -2)} = 0;
        next;
      }

      if ($define =~ /=/)
      {
        next;
      }

      $defines{$define} = 1;
      next;
    }

    if ($arrayDefines[$i] =~ /^-U/)
    {
      $defines{$define} = 0;
      next;
    }

    die("Bad parameter " . $arrayDefines[$i] . "\n");
  }

  exitLog("processParameters", @_);
  return ($srcdir);
}

sub updatePrintLine($@)
{
  my ($newLine, @stack) = @_;
  chomp($newLine);
  enterLog("updatePrintLine", @_);
  my $ok = 0;
  my $lastLine = $stack[($#stack)];

  if (($newLine =~ /^#ifdef /) ||
      ($newLine =~ /^#ifndef /))
  {
    $stack[$#stack + 1] = $newLine;
    $ok = 1;
  }

  if ($newLine eq "#switchdef")
  {
    $stack[$#stack + 1] = $newLine . " 1";
    $ok = 1;
  }

  if ((($newLine eq "#defaultcase") ||
       ($newLine =~ /^#casedef /)) &&
      ($lastLine =~ /^#switchdef /))
  {
    $stack[$#stack + 1] = $newLine;
    $ok = 1;
  }

  if (($newLine eq "#endif") &&
      (($lastLine =~ /^#ifdef /) ||
       ($lastLine =~ /^#ifndef /))) {
    $#stack --;
    $ok = 1;
  }

  if (($newLine eq "#casebreak") &&
      (($lastLine =~ /^#casedef /) ||
       ($lastLine eq "#defaultcase")))
  {
    $#stack --;
    $ok = 1;
  }

  if (($newLine eq "#endswitchdef") &&
      ($lastLine =~ /^#switchdef /))
  {
    $#stack --;
    $ok = 1;
  }

  if (!$ok)
  {
    die("Stack corruption in preprocessor!\n$#stack\n$lastLine\n$newLine");
  }

  my $retval = 1;

  for (my $i = 1; $i <= $#stack; $i++)
  {
    my $line = $stack[$i];
    if (!$ok)
    {
      die("Stack corruption in preprocessor!\n$i\n$line;");
    }
    $ok = 0;

    my $mustBeDefined;
    my $variableName;
    if ($line =~ /^#ifdef /)
    {
      $ok = 1;
      $mustBeDefined = 1;
      $variableName = substr($line, 7);

      $retval = ($mustBeDefined == $defines{$variableName});
      if (!$retval)
      {
        last;
      }
      next;
    }

    if ($line =~ /^#ifndef /)
    {
      $ok = 1;
      $mustBeDefined = 0;
      $variableName = substr($line, 8);

      $retval = ($mustBeDefined == $defines{$variableName});
      if (!$retval)
      {
        last;
      }
      next;
    }

    if ($line =~ /^#switchdef /)
    {
      $ok = 1;
      $retval = 0;
      # We go to next instead of break because there may be a casedef after us in the stack.
      next;
    }

    if ($line =~ /^#casedef /)
    {
      $lastLine = $stack[$i - 1];
      if ($lastLine eq "#switchdef 0")
      {
        $ok = 1;
        $retval = 0;
        last;
      }

      if ($lastLine eq "#switchdef 1")
      {
        $ok = 1;

        $variableName = substr($line, 9);
        $retval = (1 == $defines{$variableName});
        if ($retval)
        {
          $stack[$i - 1] = "#switchdef 0";
        }
        next;
      }

      die("casedef must be child of switchdef!");
    }

    if ($line eq "#defaultcase")
    {
      $lastLine = $stack[$i - 1];
      if ($lastLine eq "#switchdef 0")
      {
        $retval = 0;
        $ok = 1;
        last;
      }
      if ($lastLine eq "#switchdef 1")
      {
        $retval = 1;
        $ok = 1;
        $stack[$i - 1] = "#switchdef 0";
        next;
      }

      die("defaultcase must be child of switchdef!");
    }
  }

  exitLog("updatePrintLine", @_);
  return ($retval, @stack);
}


sub preprocessIDL($)
{
  my ($includeFile) = @_;
  enterLog("preprocessIDL", @_);

  my @stack = [];
  
  open(IDL_IN, "<$includeFile") || die("Can't open file $includeFile!");
  my $outFile = substr($includeFile, 0, -3);
  open(IDL, ">$outFile") || die("Can't open file $includeFile!");
  my $contents = "";

  my $printLine = 1;

  while (my $line = <IDL_IN>)
  {
    if ($line =~ /^[^#]/)
    {
      if ($printLine)
      {
        print IDL $line;
      }
      next;
    }

    if ($line =~ /^#include /)
    {
      if ($printLine)
      {
        print IDL $line;
      }
      next;
    }

    ($printLine, @stack) = updatePrintLine($line, @stack);
    next; # We never print #foo lines, except for #include.
  }
  close(IDL);
  close(IDL_IN);

  sleep(1);

  exitLog("preprocessIDL", @_);
}

sub main()
{
  enterLog("main", @_);
  my ($srcdir) = processParameters();

  my @DIR_FILES = split(/\n/, `ls $srcdir`);
  foreach my $i (@DIR_FILES)
  {
    chomp($i);
    if ($i =~ /\.idl\.in$/)
    {
      preprocessIDL("$srcdir/$i");
    }
  }
  exitLog("main", @_);
}
main();
