#!/usr/local/bin/pike

// RNTCC, a front-end to WatCOM C/C++ with options similar to GCC
// Written by Fredrik Hubinette.

inherit "lib.pike";

// Verbose is default for now, this can be turned off one this
// frontend has been refined to where it does not require more
// debugging.

int verbose=0;

// Temporary variable
int linking_failed;

// Files to remove upon exit
string *tmpfiles=({});

void exit(int code)
{
  if(getenv("CLEANUP")!="no")
    Array.map(tmpfiles,rm);

  predef::exit(code);
}

string get_ext(string file)
{
  sscanf(file=lower_case(reverse(file)),"%s.",file);
  return reverse(file);
}

int compile(string *sources,
	     string dest,
	     string errorfile,
	     string *cflags)
{
  int ret;
  mixed cmd;
  if(!dest)
  {
    string tmp=reverse(sources[0]);
    sscanf(tmp,"%*s.%s",tmp);
    dest=reverse(tmp)+".o";
  }

  dest=fixpath(dest);

  sources=Array.map(sources,fixpath);
  switch(get_ext(sources[0]))
  {
    case "c":
      cmd=({ "wcc386" }) + cflags + ({"-fr"+errorfile, "-fo"+dest}) + sources;
      break;

    case "cpp":
    case "cc":
      cmd=({ "wpp386" }) + cflags + ({"-fr"+errorfile, "-fo"+dest}) + sources;
      break;

    case "s":
      cmd=({ "wasm", "-fe"+errorfile, "-fo"+dest,})+ sources;

    default:
      werror("Don't know how to compile %s\n",sources[0]);
      exit(1);
  }
      
  if(verbose)
    return do_cmd(cmd);
  else
    return silent_do_cmd(cmd);
}

void check_errorfile(string errorfile)
{
  object f=Stdio.File();
  if(!f->open(errorfile,"r")) return;
	
  string data;
  werror(data=f->read());
  f->close();
  data=replace(data,"\r","");
  rm(errorfile);
  foreach(data/"\n", string line)
    {
      if(!strlen(line)) continue;
      if(search(line,"Warning")!=-1) continue;
      werror("Error in compilation detected. "+line+"\n");
      exit(1);
    }
}

int main(int argc, string *argv)
{
  string target;
  string operation="link";
  string *cflags=({"-d__WIN32__","-d_WIN32"});
  string *ldopts=({"OPTION","STACK=8m"});
  string *libraries=({});
  string *objects=({});
  string *sources=({});
  int debug,optimize;
  string output;
  int share=0;

  mixed *opts=Getopt.find_all_options(argv, aggregate(
    ({"oper_pre",Getopt.NO_ARG, ({"-E"}) }),
    ({"oper_comp",Getopt.NO_ARG, ({"-c"}) }),	
    ({"verbose",Getopt.NO_ARG, ({"-v"}) }),	
    ({"debug",Getopt.MAY_HAVE_ARG, ({"-g"}) }),
    ({"optimize",Getopt.MAY_HAVE_ARG, ({"-O"}) }),
    ({"include",Getopt.HAS_ARG, ({"-I"}) }),
    ({"link",Getopt.HAS_ARG, ({"-l"}) }),
    ({"share",Getopt.MAY_HAVE_ARG, ({"-s"}) }),
    ({"ignore",Getopt.MAY_HAVE_ARG, ({"-t"}) }),
    ({"ignore",Getopt.HAS_ARG, ({"-R","-L","-r"}) }),
    ({"warn",Getopt.MAY_HAVE_ARG, ({"-W"}) }),
    ({"define",Getopt.HAS_ARG, ({"-D"}) }),
    ({"undefine",Getopt.HAS_ARG, ({"-U"})}),
    ({"output",Getopt.HAS_ARG, ({"-o"}) }),
    ({"export",Getopt.HAS_ARG, ({"--export"}) })
    ));
  foreach(opts, mixed *option)
    {
      switch(option[0])
      {
	case "verbose":
	  verbose++;
	  break;

	case "export":
	  ldopts+=({"export",option[1]+"_"});
	  break;

	case "share":
	  share=1;
	  ldopts=({"SYSTEM","nt_dll","initinstance","terminstance"})+ldopts;
	  cflags+=({"-bd"});
	  break;
	  
	case "oper_pre": operation="preprocess"; break;
	case "oper_comp": operation="compile"; break;
	case "debug":
//	  cflags+=({"-d2i","-hw"});
//	  ldopts+=({"DEBUG","WATCOM","ALL"});

	  cflags+=({"-d2","-hd"});
	  ldopts+=({"DEBUG","DWARF"});

//	  cflags+=({"-d2","-hc"});
//	  ldopts+=({"DEBUG","CODEVIEW","OPTION","CVPACK"});
	  debug=1;
	  break;
	  
	case "optimize":
	  if(!option[1]) option[1]=1;
	  switch(optimize=(int)option[1])
	  {
	    case 0: optimize=0; break;
	    case 1: cflags+=({"-ox"}); break;
	    case 2: cflags+=({"-otexan"}); break;
	    case 3..:
	      cflags+=({"-s",
			  "-oe="+(optimize*30),
			  "-ol+",
			  "-oabhikmnrt"});
	      break;
	  }
	  break;
	  
	case "include":
	  // Avoid searching 'local' include dirs.
	  // This is not a very pretty solution.
	  if(sscanf(option[1],"/usr/include/%*s") ||
	     sscanf(option[1],"/usr/local/%*s"))
	    break;
	  
	  cflags+=({"-i"+fixpath(option[1])});
	  break;
	  
	case "link":
	  // -lm and -lc are automatically handled by wlink
	  if(option[1]=="m" || option[1]=="c") break;

	  // We optimiza a little, no need to bring in the same
	  // library many times in a row.
	  if(!sizeof(libraries) || libraries[-1]!=option[1])
	    libraries+=({option[1]});
	  break;
	  
	  case "warn":
	  if(option[1])
	  {
	    // This allows us to pass options to the linker
	    if(sscanf(option[1],"l,%s",string tmp))
	    {
	      // This was done for my convenience, it can be taken
	      // out once smartlink has been fixed to not use absoute
	      // paths for the 'uname' binary.
	      if(sscanf(tmp,"-rpath%*s")) break;

	      ldopts+=({tmp});
	      break;
	    }
	  }

	  // More options should be recognized, options which are not
	  // recognized should generate warning/error messages.
	  switch(option[1])
	  {
	    case "all": cflags+=({"-wx"}); break;
	    default: cflags+=({"-w2"}); break;
	  }
	  break;
	  
	case "define": cflags+=({"-d"+option[1]}); break;
	case "undefine": cflags+=({"-u"+option[1]}); break;
	case "output":
	  output=option[1];
	  break;
      }
    }

  // Scan through the remaining arguments
  argv=Getopt.get_args(argv);
  foreach(argv[1..], string tmp)
  {
    if(tmp[0]=='-')
    {
      werror("Unrecognized option "+tmp+".\n");
      exit(1);
    }

    // Recognize which files need to be compiled
    switch(get_ext(tmp))
    {
      case "o":
      case "a":
      case "obj":
      case "dll":
      case "lib":
	objects+=({tmp});
	break;
	
      default:
	sources+=({tmp});
    }
  }

  if(output) rm(output);

  string errorfile="TMP"+getpid()+".ERR";
  rm(errorfile);
  tmpfiles+=({errorfile});

  // Flags required to make the compiler behave well
  cflags+=({"-bm","-zq","-sg","-5","-fpi87","-fp3"});

  switch(operation)
  {
    default:
    werror("Unknown operation "+operation+".\n");
    exit(1);

    case "compile":
      compile(sources,output,errorfile,cflags);
      break;

    case "preprocess":
    {
      switch(get_ext(sources[0]))
      {
	default:
	case "c":
	  int ret=silent_do_cmd( ({"wcc386","-p","-fr"+errorfile}) + cflags + Array.map(sources, fixpath));
	  break;

	case "cc":
	case "cpp":
	  int ret=silent_do_cmd( ({"wpp386","-p","-fr"+errorfile}) + cflags + Array.map(sources, fixpath));
	  break;
      }
      break;
    }

    case "link":
      int objnum;
      foreach(sources, string source)
	{
	  string obj=sprintf("TMP%dX%d.obj",getpid(),objnum++);
	  compile( ({source}), obj, errorfile, cflags);
	  objects+=({obj});
	  tmpfiles+=({obj});
	  check_errorfile(errorfile);
	}

//      objects+=({"BINMODE.OBJ"});
      string ldfile="TMP"+getpid()+".lk";
      tmpfiles+=({ldfile});
      if(!output) output="a.out";
      rm(ldfile);

      target=output;
      if(!share) target+=".exe";

      string linkopts=("NAME "+target+" " +
		       ldopts*" "+" "+
		       "FILE "+Array.map(objects,fixpath)*","+" "+
		       sprintf("%{LIBRARY %s %}",libraries));
      Stdio.write_file(ldfile,linkopts);
      if(verbose)
	werror("DOING wlink "+linkopts+"\n");

#if 0
      Process.system("cat "+ldfile);
      write("\n");
#endif
      linking_failed=0;

      silent_do_cmd( ({"wlink","@"+ldfile }), lambda(string data)
      {
	if(search(data," W1008:")!=-1)
	  linking_failed++;
      });


      if(linking_failed) exit(1);
  }

  check_errorfile(errorfile);
    
  if(target)
  {
    if(!file_stat(target))
    {
      werror("RNTCC: output file not generated.\n");
      exit(1);
    }
  }

  if(operation == "link" && !share)
  {
    rm(output);
    Stdio.write_file(output,
		     "#!/usr/local/bin/pike\n"
		     "inherit \""+find_lib_location()+"\";\n"
		     "int main(int argc, string *argv) {\n"
		     "argv[0]+=\".exe\";\n"
		     "argv[0]=getenv(\"NTDRIVE\")+fixpath(combine_path(getcwd(),argv[0]));\n"
		     "  int ret=silent_do_cmd(argv);\n"
		     "  exit(ret);\n"
		     "}\n");
    chmod(output,0755);
  }
  exit(0);
}
