/*
 * ddepcheck.d
 * Version 1.0.2
 * Last modified 22nd of January 2005
 *
 * Dependency walker for D source files.
 * Copyright 2003-2005 Lars Ivar Igesund <larsivar'at'igesund.net>
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation. It is provided "as is" without express 
 * or implied warranty.
 */

/*
 * TODO:
 * - Audit code, some of it is hairy as hell.
 * - Exchange ctype with utype when it shows up
 * - Document better, especially code
 * - Long term; With multiple input files, cache import results.
 */

import std.conv;
import std.ctype;
import std.file;
import std.path;
import std.stream;
import std.string;
import std.c.stdio;
import std.stdio;

alias std.ctype.isdigit isdigit;

version (Win32) {
import std.c.windows.windows;
}

bit helpcalled = false;
bit versioncalled = false;
bit write = false;
bit makesyntax = false;
bit comment = false;
bit runtime = false;
bit checkprivate = false;
bit fulldebug = false;
bit disableversion = false;
bit disabledebug = false;
int curopt = 1;
int numpaths = 0;
int allpaths = 10;
int depthlimit = -1;
int depth = 0;
int numdeps = 0;
int alldeps = 10;
int firstfile = 1;
char [][] depmods;
char [][] depfiles;
char [][] depdepths;
char [][] paths;
char [][] argslist;
char [][] dbgidentsstr;
char [][] veridentsstr;
int [][] privatearr;
int dbgidentsnum;
int veridentsnum;

/*!
 * Static constructor.
 * Adds version identifier strings to the list used by ddepcheck based on what
 * is defined by the compiler.
 */

static this()
{
version (DigitalMars) {
  veridentsstr ~= "DigitalMars";
}
version (X86) {
  veridentsstr ~= "X86";
}
version (AMD64) {
  veridentsstr ~= "AMD64";
}
version (Windows) {
  veridentsstr ~= "Windows";
}
version (Win32) {
  veridentsstr ~= "Win32";
}
version (Win64) {
  veridentsstr ~= "Win64";
}
version (linux) {
  veridentsstr ~= "linux";
}
version (LittleEndian) {
  veridentsstr ~= "LittleEndian";
}
version (BigEndian) {
  veridentsstr ~= "BigEndian";
}
version (D_InlineAsm) {
  veridentsstr ~= "D_InlineAsm";
}
}

/*
 * Function called when the "help" option is specified.
 */

void optHelp()
{
  optVersion();
  if (!helpcalled) {
    writefln();
    writefln("Syntax: ddepcheck [options] [src ...]");
    writefln(" -I[path]               Add a path to be searched.");
    writefln(" -h/--help              Prints this help table."); 
    writefln(" -d=[num]/--depth=[num] Limit the depth searched");
    writefln("                          for dependencies.");
    writefln(" -m/--make-syntax       Print the dependencies using the make");
    writefln("                          syntax. \"objectfile : src deps\"");
    writefln(" -V/--version           Prints the version.");
    writefln(" -w/--writetofile       Prints the dependencies to the file");
    writefln("                          'depfile' instead of to the console."); 
    writefln(" -l/--checkruntimelib   Tries to add runtimelib dependencies.");
    writefln("                          Note that the path must be added ");
    writefln("                          explicitly.");
    writefln(" --checkprivate         Ignore private if it affects the import");
    writefln(" -debug=[ident/num]     Uses the same switch as the compiler");
    writefln("                          to remove/include debug codeblocks.");
    writefln(" -version=[ident/num]   Uses the same switch as the compiler");
    writefln("                          to remove/include version codeblocks.");
    writefln(" -disableVersion        Don't check for version statements.");
    writefln("                          (All imports are checked.)");
    writefln(" -disableDebug          Don't check for debug statements.");
    writefln("                          (All imports are checked.)");
  }
  helpcalled = true;
}

/*
 * Function called when the "checkruntimelib" option is specified.
 */

void optCheckRuntime()
{
  runtime = true;
}

/*!
 * Function called when the "checkprivate" option is specified.
 */

void optCheckPrivate()
{
  checkprivate = true;
}

/*!
 * Function called if the "version" option is used.
 */

void optVersion()
{
  if (!versioncalled) {
    writefln("ddepcheck"); 
    writefln("Dependency walker for D source files.");
    writefln("Version 1.0.2 Copyright Lars Ivar Igesund 2003 - 2005");
  }
  versioncalled = true;
}

/*!
 * Function called if the "writetofile" option is used.
 */

void optWrite()
{
  write = true;
}

/*!
 * Function called if the "depth" option is used to decide the maximum
 * depth for recursion.
 */

void optDepth(int arg, bit doubledash)
{
  if (doubledash) {
    depthlimit = cast(int)atoi(argslist[arg][8..argslist[arg].length]);
  }
  else {
    depthlimit = cast(int)atoi(argslist[arg][3..argslist[arg].length]);
  }
}

/*!
 * Function called for all the "-I" import options used.
 */

void optImport(int arg)
{
  if (argslist[arg].length == 2) {
    return;
  }
  addPath(argslist[arg][2..argslist[arg].length]);
}

/*!
 * Function called for all the "-debug" options used.
 */

void optDebug(int arg)
{
  if (argslist[arg].length == 6) {
    fulldebug = true;
  }
  else {
    char [] ident = argslist[arg][7..argslist[arg].length];
    bit num = isNumber(ident);
    if (num) {
      dbgidentsnum = toInt(ident);
    }
    else {
      dbgidentsstr ~= ident;
    }
  }
}

/*!
 * Function called for all the "-version" options used.
 */

void optVersion(int arg)
{
  if (argslist[arg].length == 2) {
    return;
  }
  char [] ident = argslist[arg][9..argslist[arg].length];
  bit num = isNumber(ident);
  if (num) {
    veridentsnum = toInt(ident);
  }
  else {
    veridentsstr ~= ident;
  }
}

/*!
 * Function called when the "-m"/"--make-syntax" option is used.
 */

void optMakeSyntax()
{
  makesyntax = true;
}

/*!
 * Function called when the "-disableVersion" option is used.
 */

void optDisableVersion()
{
  disableversion = true;
}

/*!
 * Function called when the "-disableDebug" option is used.
 */

void optDisableDebug()
{
  disabledebug = true;
}

/*!
 * Function that checks if an argument is an option.
 */

bit checkOption(int arg)
{
  switch (argslist[arg]) {
  case "--version":
  case "-V":
    optVersion();
    return true;
    break;
  case "--help":
  case "-h":
    optHelp();
    return true;
    break;
  case "--writetofile":
  case "-w":
    optWrite();
    return true;
    break;
  case "--make-syntax":
  case "-m":
    optMakeSyntax();
    return true;
    break;
  case "--checkruntimelib":
  case "-l":
    optCheckRuntime();
    return true;
    break;
  case "--checkprivate":
  case "-p":
    optCheckPrivate();
    return true;
    break;
  case "--disableVersion":
    optDisableVersion();
    return true;
    break;
  case "--disableDebug":
    optDisableDebug();
    return true;
    break;
  default:
    break;
  }
  if (argslist[arg].length > 3 && 
      cmp(argslist[arg][0..3], "-d=") == 0) {
    optDepth(arg, false);
    return true;
  }
  else if (argslist[arg].length > 8 && 
           cmp(argslist[arg][0..8], "--depth=") == 0) {
    optDepth(arg, true);
    return true;
  }
  if (argslist[arg].length > 2 && 
      cmp(argslist[arg][0..2], "-I") == 0) {
    optImport(arg);
    return true;
  }
  if (argslist[arg].length > 5 && 
      cmp(argslist[arg][0..6], "-debug") == 0) {
    optDebug(arg);
    return true;
  }
  if (argslist[arg].length > 8 && 
      cmp(argslist[arg][0..8], "-version") == 0) {
    optVersion(arg);
    return true;
  }
  return false;
}

/*!
 * Function that starts the dependency walking for base source files.
 */

void depBase(int arg)
{
  addNewBaseFile(argslist[arg]);
  depWalk(argslist[arg], "", true);
}

/*!
 * Function that do the main dependency walking recursively. If it find
 * files that has been walked before, it skips it to avoid infinite
 * cyclic dependencies.
 */

void depWalk(char [] file,char [] mod, bit base)
{
  debug writefln("Filename: ", file);

  char [] filepath = file;
  char [] src;
  int pathnum = 0;
  int [] importpos;
  importpos.length = 0;
  int nextimport = -1;
  char [][] verspecstrings;
  char [][] dbgspecstrings;

  while (pathnum < numpaths && !fileExist(filepath)) {
    filepath = std.path.join(paths[pathnum], file);
    pathnum++;
  }

  try {
    src = (new File(filepath)).toString();
  }
  catch (Error e) {
    return;
  }

  if (!base) {
    if (!addDep(filepath, mod, depth)) {
      return;
    }
  }

  stripComment(src);


  bit useVersion(int pos) 
  {
    int i = pos + 7;
    int j;
    while (src[i] != '(') {
      i++;
    }
    j = i + 1;
    while (src[j] != ')') {
      j++;
    }
    char [] ident = src[i + 1..j];
    if (ident.length == 0) {
      return true;
    }
    bit num = true;
    foreach (char c; ident) {
      if (!isdigit(c)) {
        num = false;
        break;
      }
    }
    if (num) {
      int identnum = toInt(ident);
      if (veridentsnum >= identnum) {
        return true;
      }
    }
    else {
      foreach (char [] id; veridentsstr) {
        if (id == ident) {
          return true;
        }
        else if (id == "none") {
          return false;
        }
      }
    }
    return false;
  }

  void search(int a, int z) 
  {
    int dbgfound = -1;
    int verfound = -1;
    int impfound = -1;
    int privfound = -1;
  
    int dbgend = -1;
    int verend = -1;
    int i = a;

    bit moreimports = false;
    bit keepprivate = false;

    bit useDebug(int pos) 
    {
      int i = pos + 5;
      int j;
      if (i >= src.length) {
        return false;
      }
      while (i < (src.length - 1) && isspace(src[i])) {
        i++;
      }
      if (src[i] == '(') {
        j = i + 1;
        while (j < (src.length - 1) && src[j] != ')') {
          j++;
        }
        char [] ident = src[i + 1..j];
        bit num = true;
        foreach (char c; ident) {
          if (!isdigit(c)) {
            num = false;
            break;
          }
        }
        if (num) {
          int identnum = toInt(ident);
          if (dbgidentsnum <= identnum) {
            return true;
          }
        }
        else {
          foreach (char [] id; dbgidentsstr ~ dbgspecstrings) {
            if (id == ident) {
              return true;
            }
          }
        }
        if (fulldebug) {
          return false;
        }
      }
      else {
        if (fulldebug) {
          return true;
        }
      }
      return false;
    }

    bit isKeyword(int startpos, int length)
    {
      if (startpos < 0) {
        debug writefln("- isKeyword startpos < 0.");
        return false;
      }
      if (startpos + length >= src.length) {
        return false;
      }
      debug writefln("- Checking keyword ", src[startpos..(startpos + length)]);
      if (startpos == 0 || isspace(src[startpos - 1]) || 
          src[startpos] == ';') {
        debug writefln("- isKeyword: inside first nested if.");
        if (((startpos + length) == src.length) || 
             isspace(src[startpos + length]) ||
             (src[startpos..startpos + length] == "debug" && 
              src[startpos + length] == ':')) {
          return true;
        }
      }
      return false;
    }

    int findBlockEnd(int pos) 
    {
      int levels = 0;
      debug writefln("- Finding block end after pos ", pos);

      int j = pos;
      if (pos >= src.length) {
        return 0;
      }
      while (j < (src.length - 1) && isspace(src[j])) {
        j++;
      }

      int findEnd()
      {
        while (isspace(src[j])) {
          j++;
        }     
        if (src[j] == '{') {
          levels++;
          while (levels) {
            j++;
            if (src[j] == '{') {
              levels++;
            }
            else if (src[j] == '}') {
              levels--;
            }
          }
          return j;
        }
        else {
          return 0;
        }
      }
      
      int retval = -1;
      if (src[j] != '(') {
        retval = findEnd();
        debug writefln("- Found end at pos ", retval);
        return retval;
      }
      else {
        j++;
        while (src[j] != ')') {
          j++;
        }
        j++;
      }

      retval = findEnd();
      debug writefln("- Found end at pos ", retval);
      return retval;
    }

    int findAttrEnd(int pos, bit priv = false)
    {
      int j = pos;
      if (pos >= src.length) {
        return 0;
      }
      while (j < (src.length - 1) && isspace(src[j])) {
        j++;
      }
      if (src[j] == ':') {
        while (j < (src.length - 1) && src[j] != '}') {
          j++;
        }
        int pub = find(src[pos..j], "public");
        int prot = find(src[pos..j], "protected");
        int best = -1;
        if (isKeyword(pub, 6)) {
          best = pub;
        }
        if (prot > -1 && prot < pub) {
          if (isKeyword(prot, 9)) {
            best = prot;
          }
        }
        if (best > -1) {
          return best;
        }
        else {
          return j;
        }
      }
      else {
        return 0;
      }
    }

    int findDebugAndVersionEnd(int pos, bit ver, bit other) 
    {
      int findSpecEnd(int pos)
      {
        if (other) {
          return 0;
        }

        int j = pos;
        if (pos >= src.length) {
          return 0;
        }
        while (j < (src.length - 1) && isspace(src[j])) {
          j++;
        }
        if (src[j] == '=') {
          int startidx = -1;
          j++;
          while (j < (src.length - 1) && src[j] != ';') {
            if (startidx == -1 && isalnum(src[j])) {
              startidx = j;
            }
            j++;
          }
          char [] ident = src[startidx..j];
          if (ident.length > 0) {
            bit num = isNumber(ident);
            if (ver) {
              if (num) {
                try {
                  veridentsnum = toInt(ident);
                }
                catch (Exception e) {
                  // Catching top exception here due to strange naming 
                  // conventions in Phobos. larsivi 20040724
                  return j;
                }
              }
              else {
                verspecstrings ~= ident;
              }
            }
            else {
              if (num) {
                try {
                  dbgidentsnum = toInt(ident);
                }
                catch (Exception e) {
                  // Catching top exception here due to strange naming 
                  // conventions in Phobos. larsivi 20040724
                  return j;
                }
              }
              else {
                dbgspecstrings ~= ident;
              }
            }
          } 
          return j;
        }
        return 0; 
      }

      int findStmntEnd(int pos)
      {
        int j = pos;
        if (pos >= src.length) {
          return 0;
        }
        while (j < (src.length - 1) && src[j] != ';') {
          j++;
        }
        return j;
      }

      int i = pos;
      int res = 0;

      if (i >= src.length) {
        return -1;
      }
    
      if (other) {
        i += 4;
      }
      else if (ver) {
        i += 7;
      }
      else {
        i += 5;
      }
   
      if (cast(bit)(res = findSpecEnd(i))) {
        return res;
      }
      else if (cast(bit)(res = findBlockEnd(i))) {
        return res;
      }
      else if (cast(bit)(res = findAttrEnd(i))) {
        return res;
      }
      else if (cast(bit)(res = findStmntEnd(i))) {
        return res;
      }
      else {
        return -1;
      }
    }

    int findElse(int pos, bit ver, inout bit elseif)
    {
      int i = pos;
      while (isspace(src[i++])) { }
      if (src[i..i + 4] == "else") {
        i += 4;
        while (isspace(src[i++])) { }
        if (ver) {
          if (src[i..i + 7] == "version") {
            elseif = true;            
          }
        }
        else {
          if (src[i..i + 5] == "debug") {
            elseif = true;
          }
        }
        return findDebugAndVersionEnd(i, ver, true);
      }
      else {
        return -1;
      }
    }

    int checkDebugAndVersion(int pos, bit ver) 
    {
      int i = pos + (ver ? 7 : 5);
    
      int findElseifStart(int pos)
      {
        int i = pos;
        while (isspace(src[i++])) { }
        if (src[i..i + 4] == "else") {
          i += 4;
          while (isspace(src[i++])) { }
        }
        else {
          return -1;
        }
        return i;
      }

      debug {
        if (ver) {
          writefln("- Checking if version identifier at ", pos, " is set.");
        }
        else {
          writefln("- Checking if debug identifier at ", pos, " is set.");
        }
      }
    
      int elseend = -1;
      int end = findDebugAndVersionEnd(pos, ver, false);
      int found = -1;
      bit elseif = false;
      
      if (end == -1) {
        debug writefln("- No end found.");
        return i;
      }

      elseend = findElse(end, ver, elseif);
      debug writefln("- Value of elsend is ", elseend);
      debug {
        if (elseif) {
          writefln("- Value of elseif is true");
        }
      }

      if (ver ? useVersion(pos) : useDebug(pos)) {
        debug writefln("- Using primary debug/version part.");
        search(i, end);
        while (elseif) {
          elseend = findElse(elseend, ver, elseif);
        }
        if (elseend > -1) {
          return elseend + 1;
        }
        else {
          debug writefln("- Ending checkDebugAndVersion with value ", end + 1);
          return end + 1;
        }
      }
      else if (elseend > -1) {
        while (elseif) {
          int elseifstart = findElseifStart(end);
          debug writefln("- Found elseif start at pos ", elseifstart);
          if (ver ? useVersion(elseifstart) : useDebug(elseifstart)) {
            search(elseifstart, elseend);
            while (elseif) {
              elseend = findElse(elseend, ver, elseif);
            }
            return elseend + 1;
          }
          end = elseifstart;
          elseend = findElse(elseend, ver, elseif);
        }
        search(end + 1, elseend);
        return elseend + 1;
      }

      debug writefln("- Version identifier not set.");
      
      return i + 1;
    }

    int handleDebug(int pos) 
    {
      return checkDebugAndVersion(pos, false);
    }

    int handleVersion(int pos)
    {
      return checkDebugAndVersion(pos, true);
    }

    int handleImport(int pos) 
    {
      bit checkPrivate(int pos)
      {
        foreach (int [] positions; privatearr) {
          if (pos > positions[0] && pos < positions[1]) {
            return true;
          }
        }
        return false;
      }
    
      debug writefln("- Handling import at position ", pos);
      int endpos;
      int i = pos + 8;
      while (true) {
        if (src[i] == ',') { // several imports following an 'import' keyword
          moreimports = true;
          break;
        }
        else if (src[i] == ';') { // last import in a list
          moreimports = false;
          break;
        }
        i++;
        if (i == src.length) {
          return i;
        }
      }
      endpos = i;
      if (!base && !checkprivate) {
        if (pos > 7) {
          if (keepprivate) {
            debug writefln("- Ending due to private import");
            return endpos + 1;
          }
          else if (checkPrivate(pos)) {
            debug writefln("- Ending due to private import");
            return endpos + 1;
          }
          i = pos - 1;
          while (isspace(src[i]) && i > 7) {
            i--;
          }
          if (i >= 7 && src[i - 6..i + 1] == "private") {
            keepprivate = moreimports ? true : false;
            debug writefln("- Ending due to private import");
            return endpos + 1;
          }
        }
      }
      char [] mod = strip(src[pos + 6..endpos]);
      debug writefln("- Handling import ", mod);
      if (!checkIfModule(mod)) {
        return endpos + 1;
      }
      if (!runtime) {
        if (checkPhobos(mod)) {
          return endpos + 1;
        }
      }
      char [] fp = createFilePath(mod);
      depth++;
      if (depthlimit == -1 || depth <= depthlimit) {  
        depWalk(fp, mod, false);
      }
      depth--;
      debug writefln("- Ending handling import ", mod, " at pos ", endpos);
      return endpos + 1;
    }

    int delegate(int) handler;
    int best;

    do {
      handler = null;
      best = z + 1;
      uint belowz = z;

      if (!checkprivate) {
        int privstart = -1;
        do {
          int privend = 0;
          debug writefln("- Searching for private from ", i, " to ", z);
          privfound = find(src[i..z], "private");
          if (privfound > -1) {
            int privstart = privfound + i;
            if (isKeyword(privstart, 7)) {
              privend = findBlockEnd(privstart);
              if (!privend) {
                privend = findAttrEnd(privstart, true);
              }
              if (privend) {
                privatearr[privatearr.length][0] = privstart;
                privatearr[privatearr.length][1] = privend;
              }
            }
          }
        } while (privstart > -1);
      }

      debug writefln("- Searching for import from ", i, " to ", z); 
      impfound = find(src[i..z], "import");
      if (impfound > -1) {
        impfound += i;
        belowz = impfound;
      }
      if (!disabledebug) {
        debug writefln("- Searching for debug from ", i, " to ", belowz);
        dbgfound = find(src[i..belowz], "debug");
        if (dbgfound > -1) {
          dbgfound += i;
          belowz = dbgfound < belowz ? dbgfound : belowz;
        }
      }
      if (!disableversion) {
        debug writefln("- Searching for version from ", i, " to ", belowz);
        verfound = find(src[i..belowz], "version");
        if (verfound > -1) {
          verfound += i;
        }
      }

      if (impfound > -1) {
        debug writefln("- Value of impfound is ", impfound, " and i is ", i);
        if (isKeyword(impfound, 6)) {
          best = impfound;
          debug writefln("- Import found at pos ", best);
          handler = &handleImport;
        }
      }
      if (!disabledebug) {
        if (dbgfound > -1 && (dbgfound) < best) {
          if (isKeyword(dbgfound, 5)) {
            best = dbgfound;
            debug writefln("- Debug found at pos ", best);
            handler = &handleDebug;
          }
        }
      }
      if (!disableversion) {
        if (verfound > -1 && (verfound) < best) {
          if (isKeyword(verfound, 7)) {
            best = verfound;
            debug writefln("- Version found at pos ", best);
            handler = &handleVersion;
          }
        }
      }
      if (!(handler is null)) {
        i = handler(best);
        while (moreimports) {
          i = handler(i);
        }
      }
      else {
        break;
      }
    } while (i < z);
  }

  int pos = 0;
  int found = -1;

  search(0, src.length);

  dbgidentsstr ~= dbgspecstrings;
  veridentsstr ~= verspecstrings;
}

/*!
 * Function that adds a dependency to the list when it is verified.
 * A path and the depth where it was found is also added. If the
 * dependency has been found before, the new depth is added to the
 * same entry.
 */

bit addDep(char [] filepath, char [] mod, int depth)
{
  for (int i = 0; i < numdeps; i++) {
    if (depmods[i] == mod) {
      depdepths[i] ~= ",";
      depdepths[i] ~= toString(depth);
      return false;
    }
  }

  numdeps++;
  if (numdeps > alldeps) {
    alldeps *= 2;
    depmods.length = alldeps;
    depfiles.length = alldeps;
    depdepths.length = alldeps;
  }
  depmods[numdeps - 1] = mod;
  depfiles[numdeps - 1] = filepath;
  depdepths[numdeps - 1] = toString(depth);

  return true;
}

/*!
 * Function that adds the filename of the current checked file to
 * the printout.
 */

void addNewBaseFile(char [] file)
{
  numdeps += 3;
  if (numdeps > alldeps) {
    alldeps *= 2;
    depmods.length = alldeps;
    depfiles.length = alldeps;
    depdepths.length = alldeps;
  }
  depmods[numdeps-3] = "#";
  depmods[numdeps-2] = file;
  depmods[numdeps-1] = "##";
  depfiles[numdeps-3] = depfiles[numdeps-2] = depfiles[numdeps-1] = "";
  depdepths[numdeps-3] = depdepths[numdeps-2] = depdepths[numdeps-1] = "";
}

/*!
 * Function that takes a module name and creates a real path.
 */
 
char [] createFilePath(char [] mod)
{
  char [] tempmod;
  tempmod = replace(mod, ".", sep);
  tempmod ~= ".d";
  return tempmod;
}

/*!
 * Checks if the found dependency is a part of phobos in which case
 * it is ignored.
 */

bit checkPhobos(char [] mod)
{
  if (mod == "Object") {
    return true;
  }
  else if (mod.length < 4) {
    return false;
  }
  else if (cmp(mod[0..4], "std.") == 0) {
    return true;
  }
  else {
    return false;
  }
}

/*!
 * Checks if the found module really can be a module since the parsing
 * is a bit hackish.
 */

bit checkIfModule(char [] mod)
{
  if (!isalpha(mod[0])) {
    return false;
  }
  foreach (char c; mod[1..mod.length]) {
    if (!(isalnum(c) || c == '.')) {
      return false;
    }
  }
  return true;
}

/*!
 * Adds a path given with "-I" to the path list.
 */

void addPath(char [] path)
{
  numpaths++;
  if (numpaths > allpaths) {
    allpaths *= 2;
    paths.length = allpaths;
  }
  paths[numpaths - 1] = path;
}

/*!
 * Checks if a file exist.
 */

bit fileExist(char [] file)
{
version (Win32) {
  return (GetFileAttributesA(toStringz(file)) != 0xFFFFFFFF);
}
else {
  // FIXME: I guess there is a better way to do this. larsivi 18042004
  bit result = true;
  try {
    read(file);
  }
  catch (FileException e) {
    result = false;
  }
  return result;
}
}

/*!
 * Checks if the string is a number. Returns true if it is.
 */

bit isNumber(char [] ident)
{
  foreach (char c; ident) {
    if (!isdigit(c)) {
      return false;
    }
  }
  return true;
}


/*!
 * Convert a substring in src to whitespace.
 */

void toWhiteSpace(inout char [] src, int start, int end)
in {
  assert (end > start);
  assert (start >= 0);
  assert (end <= src.length);
}
body {
  src[start..end] = ' ';
}

/*!
 * Strip off the comments.
 */

void stripComment(inout char[] src)
{
  int len = src.length;
  int doubleslash = -1;
  int star = -1;
  int plus = 0;
  int plusstart = -1;
  
  for (int i = 0; i < len; i++) {
    if (doubleslash > -1) {
      if (src[i] == '\\') {
        if (src[i + 1] == 'n') {
          toWhiteSpace(src, doubleslash, i + 2);
          i++;
          doubleslash = -1;
          continue;
        }
      }
      continue;
    }
    else if (star > -1) {
      if (src[i] == '*') {
        if (src[i + 1] == '/') {
          toWhiteSpace(src, star, i + 2);
          i++;
          star = -1;
          continue;
        }
      }
      continue;
    }
    else if (plus) {
      if (src[i] == '+') {
        if (src[i + 1] == '/') {
          i++;
          plus--;
          if (!plus) {
            toWhiteSpace(src, plusstart, i + 1);
            plusstart = -1;
          }
          continue;
        }
      }
    }

    if (src[i] == '/') {
      i++;
      if (src[i] == '/') {
        doubleslash = i - 1;
      }
      else if (src[i] == '*') {
        star = i - 1;
      }
      else if (src[i] == '+') {
        if (!plus) {
          plusstart = i - 1;
        }
        plus++;
      }
    }
  }
}

/*!
 * Writes dependencies to file.
 */

void printToFile()
{
  File depfile = new File("depfile", FileMode.Out);
  for (int i = 0; i < numdeps; i++) {
    depfile.printf(depmods[i] ~ " " ~ depfiles[i] ~ " " ~ depdepths[i] ~ "\n");
  }
  depfile.close();
}

/*!
 * Prints dependencies to stdout.
 */

void printToStdout()
{
  if (makesyntax) {
    char [] objsuf = "";
    version (Win32) {
      objsuf = ".obj";
    }
    version (linux) {
      objsuf = ".o";
    }
    writef(replace(argslist[firstfile], ".d", objsuf), " : ");
    writef(argslist[firstfile]);
    if (numdeps > 3) {
      writef(" ");
      for (int i = 3; i < numdeps; i++) {
        writef(depfiles[i], " ");
      }
    }
  }
  else {
    for (int i = 0; i < numdeps; i++) {
      writefln(depmods[i], " ", depfiles[i], " ", depdepths[i]);
    }
  }
}

int main(char[][] args)
{
  if (args.length == 1) {
    optHelp();
    return 0;
  }
  
  depmods.length = 10;
  depfiles.length = 10;
  depdepths.length = 10;
  paths.length = 10;

  argslist = args;
  while (curopt < args.length && checkOption(curopt)) {
    curopt++;
  }

  if (curopt < args.length) {
    firstfile = curopt;
    for (int i = curopt; i < args.length; i++) {
      // FIXME: Handle multiple input files better
      // larsivi 20040715
      depBase(i);
    }
  }

  if (write) {
    printToFile();
  }
  else {
    printToStdout();
  }

  return 0;
}
