/* Copyright (C) 1996,1997 Robert H”hne, see COPYING.RH for details */
/* This file is part of RHIDE. */
#define Uses_TWindow
#define Uses_TEvent
#define Uses_TStringCollection

#define Uses_TProject
#define Uses_TOptions
#define Uses_TDependency
#define Uses_TDepCollection
#define Uses_TFlagCollection

#include <rhide.h>
#include <rhidehis.h>

#define Uses_TWindowList
#define Uses_TSCollection
#define Uses_ideFunctions
#define Uses_ideCommands
#include <libide.h>

#define Uses_TParamList
#define Uses_TDirList
#define Uses_tvutilFunctions
#include <libtvuti.h>

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/utsname.h>

#if 0
#define A(n) __attribute__ (( regparm(n) ))
#else
#define A(n)
#endif

static char *default_variables[] = {
 "RHIDE_GCC",
 "gcc",

 "RHIDE_AS",
 "gcc",

 "RHIDE_GXX",
 "gcc",

 "RHIDE_GPC",
 "gpc",

 "RHIDE_AR",
 "ar",

 "RHIDE_LD",
 "gcc",

 "RHIDE_G77",
 "g77",

 "RHIDE_NASM",
 "nasm",

 "RHIDE_LD_PASCAL",
 "gpc",

 "RHIDE_ARFLAGS",
 "rcs",

 "RHIDE_TYPED_LIBS.f",
 "m f2c",

#define SAME_LIBS(x,y)\
 "RHIDE_TYPED_LIBS."#x,\
 "$(RHIDE_TYPED_LIBS."#y")"

 SAME_LIBS(for,f),
 SAME_LIBS(F,f),
 SAME_LIBS(fpp,f),

 "RHIDE_TYPED_LIBS.p",
 "m gpc",

 SAME_LIBS(pas,p),

 "RHIDE_TYPED_LIBS_DJGPP.cc",
 "iostr",

 "RHIDE_TYPED_LIBS_Linux.cc",
 "stdc++",

 "RHIDE_TYPED_LIBS.cc",
 "$(RHIDE_TYPED_LIBS_$(RHIDE_OS).cc)",

 SAME_LIBS(cpp,cc),
 SAME_LIBS(cxx,cc),
 SAME_LIBS(C,cc),
 SAME_LIBS(ii,cc),

 "RHIDE_TYPED_LIBS.y",
 "bison",

 "RHIDE_TYPED_LIBS.l",
 "fl",

 "RHIDE_TYPED_LIBS.m",
 "objc",

 "RHIDE_TYPED_LIBS",
 "$(foreach suff,$(sort $(suffix $(PROJECT_ITEMS))),$(RHIDE_TYPED_LIBS$(suff)))",

 "RHIDE_INCLUDES",
 "$(SPECIAL_CFLAGS) $(addprefix -I,$(INCLUDE_DIRS))",

 "RHIDE_LIBDIRS",
 "$(addprefix -L,$(LIB_DIRS))",

 "RHIDE_LIBS",
 "$(addprefix -l,$(LIBS) $(RHIDE_TYPED_LIBS) $(RHIDE_OS_LIBS))",

 "RHIDE_LDFLAGS",
 "$(SPECIAL_LDFLAGS) $(addprefix -Xlinker ,$(LD_EXTRA_FLAGS))",

 "RHIDE_NASM_TARGET_DJGPP",
 "coff",

 "RHIDE_NASM_TARGET_Linux",
 "elf",

 "RHIDE_NASM_TARGET",
 "$(RHIDE_NASM_TARGET_$(RHIDE_OS))",

 "RHIDE_COMPILE_NASM",
 "$(RHIDE_NASM) -f $(RHIDE_NASM_TARGET) $(LOCAL_OPT) -o $(OUTFILE)\
  $(SOURCE_NAME)",

 "RHIDE_COMPILE_FORTRAN",
 "$(RHIDE_G77) $(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS)\
  $(C_WARN_FLAGS) $(C_F_LANG_FLAGS) $(C_EXTRA_FLAGS) $(LOCAL_OPT)\
  -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_FORTRAN_FORCE",
 "$(RHIDE_G77) $(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS)\
  $(C_WARN_FLAGS) $(C_F_LANG_FLAGS) $(C_EXTRA_FLAGS) -x f77 $(LOCAL_OPT)\
  -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_C",
 "$(RHIDE_GCC) $(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS)\
  $(C_WARN_FLAGS) $(C_C_LANG_FLAGS) $(C_EXTRA_FLAGS) $(LOCAL_OPT)\
  $(CPPFLAGS) $(CFLAGS) $(RHIDE_OS_CFLAGS) -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_C_FORCE",
 "$(RHIDE_GCC) $(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS)\
  $(C_WARN_FLAGS) $(C_C_LANG_FLAGS) $(C_EXTRA_FLAGS) -x c $(LOCAL_OPT)\
  $(CPPFLAGS) $(CFLAGS) $(RHIDE_OS_CFLAGS) -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_CC",
 "$(RHIDE_GXX) $(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS)\
  $(C_WARN_FLAGS) $(C_C_LANG_FLAGS) $(C_CXX_LANG_FLAGS) $(C_EXTRA_FLAGS)\
  $(CPPFLAGS) $(CXXFLAGS) $(RHIDE_OS_CXXFLAGS) $(LOCAL_OPT) \
  -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_CC_FORCE",
 "$(RHIDE_GXX) $(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS)\
  $(C_WARN_FLAGS) $(C_C_LANG_FLAGS) $(C_CXX_LANG_FLAGS) $(C_EXTRA_FLAGS)\
  $(CPPFLAGS) $(CXXFLAGS) $(RHIDE_OS_CXXFLAGS) -x c++ $(LOCAL_OPT) \
  -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_ASM",
 "$(RHIDE_AS) $(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS)\
  $(C_WARN_FLAGS) $(C_EXTRA_FLAGS) $(LOCAL_OPT)\
  -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_ASM_FORCE",
 "$(RHIDE_AS) $(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS)\
  $(C_WARN_FLAGS) $(C_EXTRA_FLAGS) -x assembler $(LOCAL_OPT)\
  -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_GPC_FLAGS",
 "$(RHIDE_INCLUDES) $(C_DEBUG_FLAGS) $(C_OPT_FLAGS) $(C_WARN_FLAGS)\
  $(C_P_LANG_FLAGS) $(C_EXTRA_FLAGS)",

 "RHIDE_COMPILE_PASCAL",
 "$(RHIDE_GPC) $(RHIDE_GPC_FLAGS) $(LOCAL_OPT)\
  -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_PASCAL_FORCE",
 "$(RHIDE_GPC) $(RHIDE_GPC_FLAGS) -x pascal $(LOCAL_OPT)\
  -c $(SOURCE_NAME) -o $(OUTFILE)",

 "RHIDE_COMPILE_LINK",
 "$(RHIDE_LD) $(RHIDE_LIBDIRS) $(C_EXTRA_FLAGS) $(RHIDE_LDFLAGS) -o $(OUTFILE)\
  $(LDFLAGS) $(OBJFILES) $(LIBRARIES) $(RHIDE_LIBS)",

 "RHIDE_COMPILE_LINK_PASCAL",
 "$(RHIDE_LD_PASCAL) $(RHIDE_LIBDIRS) $(C_EXTRA_FLAGS) $(RHIDE_LDFLAGS)\
  -o $(OUTFILE) $(LDFLAGS) $(OBJFILES) $(LIBRARIES) $(RHIDE_LIBS)",

 "RHIDE_COMPILE_LINK_PASCAL_AUTOMAKE",
 "$(RHIDE_LD_PASCAL) $(RHIDE_LIBDIRS) $(RHIDE_LDFLAGS)\
  -o $(OUTFILE) --automake=\"$(strip $(RHIDE_GPC_FLAGS))\" $(RHIDE_GPC_FLAGS)\
  $(SOURCE_NAME) $(LDFLAGS) $(LIBRARIES) $(RHIDE_LIBS)",

 "RHIDE_COMPILE_ARCHIVE",
 "$(RHIDE_AR) $(RHIDE_ARFLAGS) $(OUTFILE) $(OBJFILES)",

#define SAME_COMPILE(from,to,ref) \
 "RHIDE_COMPILE."#from"."#to,\
 "$(RHIDE_COMPILE."#from"."#ref")"

 "RHIDE_COMPILE.c.o",
 "$(RHIDE_COMPILE_C)",

 "RHIDE_COMPILE.cc.o",
 "$(RHIDE_COMPILE_CC)",

 "RHIDE_COMPILE.p.o",
 "$(RHIDE_COMPILE_PASCAL)",

 "RHIDE_COMPILE.f.o",
 "$(RHIDE_COMPILE_FORTRAN)",

 "RHIDE_COMPILE.nsm.o",
 "$(RHIDE_COMPILE_NASM)",

 "RHIDE_COMPILE.s.o",
 "$(RHIDE_COMPILE_ASM)",

 "RHIDE_COMPILE.c.s",
 "$(subst -c,-S,$(RHIDE_COMPILE_C))",

 "RHIDE_COMPILE.c.i",
 "$(subst -c,-E,$(RHIDE_COMPILE_C))",

 "RHIDE_COMPILE.i.s",
 "$(RHIDE_COMPILE.c.s)",

 "RHIDE_COMPILE.cc.s",
 "$(subst -c,-S,$(RHIDE_COMPILE_CC))",

 "RHIDE_COMPILE.cc.ii",
 "$(subst -c,-E,$(RHIDE_COMPILE_CC))",

 "RHIDE_COMPILE.ii.s",
 "$(RHIDE_COMPILE.cc.s)",

 SAME_COMPILE(cpp,o,cc),
 SAME_COMPILE(cxx,o,cc),
 SAME_COMPILE(C,o,cc),

 SAME_COMPILE(pas,o,p),

 SAME_COMPILE(for,o,f),
 SAME_COMPILE(F,o,f),
 SAME_COMPILE(fpp,o,f),

 SAME_COMPILE(asm,o,nsm),

 SAME_COMPILE(cpp,s,cc),
 SAME_COMPILE(cxx,s,cc),
 SAME_COMPILE(C,s,cc),

 SAME_COMPILE(cpp,ii,cc),
 SAME_COMPILE(cxx,ii,cc),
 SAME_COMPILE(C,ii,cc),

 "RHIDE_FSDB",
 "fsdb $(OUTFILE) $(addprefix -p ,$(SRC_DIRS)) $(PROG_ARGS)",

 "RHIDE_GDB",
 "gdb $(OUTFILE) $(addprefix -d ,$(SRC_DIRS))",

 "RHIDE_GREP",
 "grep -n $(prompt "__("arguments for GREP")",$(WUC))",

 "RHIDE_GPROF",
 "gprof $(OUTFILE)",

 0,
 0
};

static char **vars;
static int var_count = 0;

void dump_rhide_environment(FILE *f)
{
  int i;
  for (i=0;i<var_count;i++)
  {
    fprintf(f,"%s=%s\n",vars[i*2],vars[i*2+1]);
  }
}


static
void add_variable(char *variable,char *contents)
{
  var_count++;
  vars = (char **)realloc(vars,var_count*2*sizeof(char *));
  string_dup(vars[var_count*2-2],variable);
  string_dup(vars[var_count*2-1],contents);
}

void insert_variable(char *variable,char *contents)
{
  int i;
  for (i=0;i<var_count;i++)
  {
    if (strcmp(vars[i*2],variable) == 0)
    {
      string_free(vars[2*i+1]);
      string_dup(vars[2*i+1],contents);
      return;
    }
  }
  add_variable(variable,contents);
}

static int internal_var_count = 0;
static int internal_var_size = 0;
static char **internal_vars = NULL;

static void add_internal_var(const char *name,const char *value)
{
  if (internal_var_count == internal_var_size)
  {
    internal_var_size += 16;
    internal_vars = (char **)realloc(internal_vars,
                          2*internal_var_size*sizeof(char *));
  }
  internal_vars[internal_var_count*2] = string_dup(name);
  internal_vars[internal_var_count*2+1] = string_dup(value);
  internal_var_count++;
}

static void remove_internal_var(const char *name)
{
  int i;
  for (i=0;i<internal_var_count;i++)
  {
    if (strcmp(internal_vars[i*2],name) == 0)
    {
      string_free(internal_vars[i*2]);
      string_free(internal_vars[i*2+1]);
      if (i<internal_var_count-1)
      {
        memcpy(internal_vars+i*2,internal_vars+i*2+2,
          (internal_var_count-i-1)*sizeof(char *)*2);
      }
      internal_var_count--;
      return;
    }
  }
}

static void set_internal_var(const char *name,const char *value)
{
  int i;
  for (i=0;i<internal_var_count;i++)
  {
    if (strcmp(internal_vars[i*2],name) == 0)
    {
      string_free(internal_vars[i*2+1]);
      internal_vars[i*2+1] = string_dup(value);
      return;
    }
  }
  add_internal_var(name,value);
}

static char *get_internal_var(const char *name)
{
  int i;
  for (i=0;i<internal_var_count;i++)
  {
    if (strcmp(internal_vars[i*2],name) == 0)
      return internal_vars[i*2+1];
  }
  return NULL;
}

static
void put_breakline(FILE *f,int start_len,int max_len,char *s)
{
  int len;
  char *lf = NULL;
  while (1)
  {
    lf = strchr(s,'\n');
    if (lf) *lf = 0;
    len = strlen(s);
    if (len+start_len <= max_len)
    {
      fprintf(f,"%s\n",s);
      if (lf)
      {
        *lf = '\n';
        s = lf+1;
        continue;
      }
      return;
    }
    char *tmp;
    tmp = s + max_len-start_len;
    while (tmp > s && *tmp != ' ') tmp--;
    if (tmp == s)
    {
      fprintf(f,"%s\n",s);
      if (lf)
      {
        *lf = '\n';
        s = lf+1;
        continue;
      }
      return;
    }
    *tmp = 0;
    fprintf(f,"%s\\\n\t",s);
    *tmp = ' ';
    start_len = 8; // tabsize
    s = tmp+1;
  }
}

static
const char * GetVariable(const char *variable,int use_env=1)
{
  int i;
  char *ival = get_internal_var(variable);
  if (ival)
    return ival;
  for (i=0;i<var_count;i++)
  {
    if (strcmp(variable,vars[i*2]) == 0)
      return vars[i*2+1];
  }
  if (!use_env)
    return NULL;
  return getenv(variable);
}

#ifdef __DJGPP__
extern char **environ;
#endif

static __attribute__ ((__constructor__))
void init_variables(void)
{
  char *variable,*contents;
  int i=0;
  while (default_variables[i])
  {
    variable = default_variables[i];
    contents = getenv(variable);
    if (!contents) contents = default_variables[i+1];
    if (!GetVariable(variable,0))
      insert_variable(variable,contents);
    i += 2;
  }
  // Now check the env for any RHIDE_ variable
  for (i=0;environ[i];i++)
  {
    if (strncmp(environ[i],"RHIDE_",6) == 0)
    {
      contents = strchr(environ[i],'=');
      if (!contents) continue;
      contents++;
      char var[256];
      memcpy(var,environ[i],(int)(contents-environ[i])-1);
      var[(int)(contents-environ[i])-1] = 0;
      insert_variable(var,contents);
    }
  }
}

typedef A(1) char *(*string_function)(char *);

typedef struct
{
  char *name;
  int name_len;
  string_function function;
} string_function_rec;

static A(1)
char *string_function_strip(char *);
static A(1)
char *string_function_addsuffix(char *);
static A(1)
char *string_function_addprefix(char *);
static A(1)
char *string_function_notdir(char *);
static A(1)
char *string_function_dir(char *);
static A(1)
char *string_function_subst(char *);
static A(1)
char *string_function_prompt(char *);
static A(1)
char *string_function_suffix(char *);
static A(1)
char *string_function_sort(char *);
static A(1)
char *string_function_foreach(char *);
static A(1)
char *string_function_shell(char *);

static string_function_rec string_function_list[] =
{
#define SF(x) {#x,sizeof(#x)-1,string_function_##x}
  SF(strip),
  SF(addsuffix),
  SF(addprefix),
  SF(notdir),
  SF(dir),
  SF(subst),
  SF(prompt),
  SF(suffix),
  SF(sort),
  SF(foreach),
  SF(shell),
  {NULL,0,NULL}
#undef SF
};

static A(1)
char *find_close_brace(char *open_brace)
{
  int brace_count = 1;
  open_brace++;
  while (brace_count > 0 && *open_brace)
  {
    if (*open_brace == ')') brace_count--;
    else if (*open_brace == '(') brace_count++;
    open_brace++;
  }
  if (brace_count != 0) return NULL;
  return open_brace-1;
}

static A(1)
char *find_next_comma(char *arg)
{
  while (*arg)
  {
    if (*arg == ',') return arg;
    if (*arg == '(')
    {
      arg = find_close_brace(arg+1);
      if (!arg) return NULL;
    }
    arg++;
  }
  return NULL;
}

static
external_token_func external_expand_token;

static A(1)
char *expand_tokens(char *tokens);

static A(1)
char *expand_variable(char *__token)
{
  char *token;

  // At first expand the variable name itself, because it
  // can be a computed variable like $(RHIDE_OS_LIBS_$(RHIDE_OS))
  // which can be computed at runtime depending on the value
  // of $(RHIDE_OS)
  char *end = find_close_brace(__token+2);
  if (end)
  {
    char *_token;
    *end = 0;
    _token = expand_tokens(__token+2);
    *end = ')';
    token = (char *)alloca(strlen(_token)+4);
    strcpy(token,"$(");
    strcat(token,_token);
    strcat(token,")");
    string_free(_token);
  }
  else
  {
    token = (char *)alloca(strlen(__token)+1);
    strcpy(token,__token);
  }
  end = token + strlen(token) - 1;
  char *retval;
  const char *variable;
  *end = 0;
  variable = GetVariable(token+2);
  *end = ')';
  if (!variable) return NULL;
  string_dup(end,variable);
  retval = expand_tokens(end);
  string_free(end);
  return retval;
}

static A(1)
char *check_for_string_function(char *token)
{
  int i=0;
  while (string_function_list[i].name)
  {
    if (strncmp(token+2,string_function_list[i].name,
                string_function_list[i].name_len) == 0)
    {
      char *start,*end,*expanded;
      start = token + 2 + string_function_list[i].name_len + 1;
      end = token + strlen(token)-1;
      *end = 0;
#if 0
/* I cannot expand the argument here, because at least the
   foreach function expects the last argument unexpanded. If
   I would expand it here, it would become probably empty.
   That means, any string function is responsible for expanding
   the arguments */
      expanded = expand_tokens(start);
      *end = ')';
      end = string_function_list[i].function(expanded);
      string_free(expanded);
      return end;
#else
      expanded = string_function_list[i].function(start);
      *end = ')';
      return expanded;
#endif
    }
    i++;
  }
  return NULL;
}

static A(1)
char *expand_token(char *token)
{
  char *retval = check_for_string_function(token);
  if (retval) return retval;
  retval = expand_variable(token);
  if (retval) return retval;
  if (external_expand_token)
    retval = external_expand_token(token,expand_tokens);
  if (retval) return retval;
  return string_dup("");
}

static A(1)
char *expand_tokens(char *tokens)
{
  char *start;
  char *end,c;
  char *_tokens=NULL;
  char *expanded;
  start = tokens;
  while (*start)
  {
    if (start[0] == '$' && start[1] == '(')
    {
      *start = 0;
      string_cat(_tokens,tokens);
      *start = '$';
      end = find_close_brace(start+1);
      if (!end)
      {
        string_cat(_tokens,start);
        return _tokens;
      }
      end++;
      c = *end;
      *end = 0;
      expanded = expand_token(start);
      string_cat(_tokens,expanded);
      string_free(expanded);
      *end = c;
      start = tokens = end;
    }
    else start++;
  }
  string_cat(_tokens,tokens);
  return _tokens;
}

char *expand_spec(const char *spec,external_token_func ext_func)
{
  char *retval;
  char *tokens = string_dup(spec);
  external_token_func old_func = external_expand_token;
  external_expand_token = ext_func;
  retval = expand_tokens(tokens);
  external_expand_token = NULL;
  string_free(tokens);
  external_expand_token = old_func;
  return retval;
}

char *build_command(char *program,char *args)
{
  char *spec;
  char *command;
  string_dup(spec,"$(strip ");
  string_cat(spec,program);
  string_cat(spec," ");
  string_cat(spec,args);
  string_cat(spec,")");
  command = expand_spec(spec,NULL);
  string_free(spec);
  return command;
}

static A(1)
char *string_function_shell(char *_arg)
{
  char *arg = expand_tokens(_arg);
  char *retval = NULL;
  FILE *pipe = popen(arg,"rt");
  if (pipe)
  {
    char buf[1024];
    int count;
    while ((count = fread(buf,1,1023,pipe)))
    {
      char *tmp = buf;
      while (count--)
      {
        if (*tmp == '\n')
          *tmp = ' ';
        tmp++;
      }
      *tmp = 0;
      string_cat(retval,buf);
    }
    pclose(pipe);
  }
  string_free(arg);
  if (!retval)
    retval = string_dup("");
  else
  {
    int len;
    if (retval[len = strlen(retval)-1] == ' ')
      retval[len] = 0;
  }
  return retval;
}

static A(1)
char *string_function_foreach(char *arg)
{
  char *arg1;
  char *arg2;
  char *arg3;
  char *retval = NULL;
  char *tok;
  char *expanded;
  arg3 = find_next_comma(arg);
  if (!arg3)
    return NULL;
  *arg3 = 0;
  arg1 = expand_tokens(arg);
  *arg3 = ',';
  arg3 = find_next_comma(arg2 = (arg3+1));
  if (!arg3)
  {
    string_free(arg1);
    return NULL;
  }
  *arg3 = 0;
  arg2 = expand_tokens(arg2);
  *arg3 = ',';
  arg3++;
  for (tok = strtok(arg2," ");tok;tok=strtok(NULL," "))
  {
    set_internal_var(arg1,tok);
    expanded = expand_tokens(arg3);
    if (expanded && *expanded)
    {
      if (retval)
        string_cat(retval," ");
      string_cat(retval,expanded);
    }
    string_free(expanded);
  }
  remove_internal_var(arg1);
  string_free(arg1);
  string_free(arg2);
  if (!retval)
    retval = string_dup("");
  return retval;
}

static A(1)
char *string_function_prompt(char *arg)
{
  char *init_val;
  char *retval;
  init_val = find_next_comma(arg);
  if (!init_val)
    retval = expand_tokens(arg);
  else
  {
    *init_val = 0;
    retval = expand_tokens(arg);
    *init_val = ',';
    init_val = expand_tokens(init_val+1);
  }
  TParamList *params = new TParamList();
  if (init_val)
    params->insert(init_val);
  if (editParamList(params,_(retval),
      RHIDE_History_prompt) != cmOK)
  {
    destroy(params);
    string_free(retval);
    string_dup(retval,"");
    return retval;
  }
  string_free(retval);
  params->ToString(init_val);
  destroy(params);
  retval = expand_tokens(init_val);
  string_free(init_val);
  return retval;
}

static A(1)
char *string_function_strip(char *arg)
{
  char *retval;
  char *tmp;
  char quote_char = '\'';
  int in_quote = 0;
  retval = expand_tokens(arg);
  tmp = retval;
  while (*tmp && *tmp == ' ') tmp++;
  strcpy(retval,tmp);
  if (!*retval) return retval;
  tmp =retval;
  while (*tmp)
  {
    if (in_quote)
    {
      if (*tmp == quote_char) in_quote = 0;
      tmp++;
      continue;
    }
    if (*tmp == '\'' || *tmp == '"')
    {
      in_quote = 1;
      quote_char = *tmp;
      tmp++;
      continue;
    }
    if (tmp[0] == ' ' && tmp[1] == ' ')
      strcpy(tmp,tmp+1);
    else tmp++;
  }
  tmp = retval + strlen(retval) - 1;
  while (*tmp == ' ') tmp--;
  tmp++;
  *tmp = 0;
  return retval;
}

static A(1)
char *string_function_sort(char *_arg)
{
  char *arg = expand_tokens(_arg);
  char *retval = NULL;
  char *tok;
  TStringCollection coll(15,16);
  int i,count;
  for (tok = strtok(arg," ");tok;tok=strtok(NULL," "))
  {
    ccIndex ind;
    if (coll.search(tok,ind) == False)
    {
      coll.atInsert(ind,string_dup(tok));
    }
  }
  count = coll.getCount();
  for (i=0;i<count;i++)
  {
    if (retval)
      string_cat(retval," ");
    string_cat(retval,(char *)coll.at(i));
  }
  if (!retval)
    retval = string_dup("");
  string_free(arg);
  return retval;
}

static A(1)
char *string_function_suffix(char *_arg)
{
  char *retval=NULL;
  char *arg = expand_tokens(_arg);
  char *tok;
  for (tok = strtok(arg," ");tok;tok=strtok(NULL," "))
  {
    char *dot = strrchr(tok,'.');
    if (!dot)
      continue;
    if (retval)
      string_cat(retval," ");
    string_cat(retval,dot);
  }
  if (!retval)
    string_dup(retval,"");
  string_free(arg);
  return retval;
}

static A(2)
char *string_function_add_pre_or_suffix(char *arg,int prefix)
{
  char *retval=NULL;
  char *tmp;
  char *arg1,*arg2;
  char *tok;
  tmp = find_next_comma(arg);
  if (!tmp) return NULL;
  *tmp = 0;
  arg1 = expand_tokens(arg);
  *tmp = ',';
  tmp++;
  arg2 = expand_tokens(tmp);
  for (tok = strtok(arg2," ");tok;tok = strtok(NULL," "))
  {
    if (retval) string_cat(retval," ");
    if (prefix)
    {
      string_cat(retval,arg1);
      string_cat(retval,tok);
    }
    else
    {
      string_cat(retval,tok);
      string_cat(retval,arg1);
    }
  }
  string_free(arg1);
  string_free(arg2);
  if (!retval) // arg2 may be consist of only ws's or empty
  {
    string_dup(retval,"");
  }
  return retval;
}

static A(1)
char *string_function_addprefix(char *arg)
{
  return string_function_add_pre_or_suffix(arg,1);
}

static A(1)
char *string_function_addsuffix(char *arg)
{
  return string_function_add_pre_or_suffix(arg,0);
}

static A(1)
char *string_function_notdir(char *_arg)
{
  char *retval;
  char *arg = expand_tokens(_arg);
  BaseName(arg,retval);
  string_free(arg);
  return retval;
}

static A(1)
char *string_function_dir(char *_arg)
{
  char *dir,*name,*ext,*arg = expand_tokens(_arg);
  split_fname(arg,dir,name,ext);
  string_free(name);
  string_free(ext);
  string_free(arg);
  return dir;
}

static A(1)
char *string_function_subst(char *arg)
{
  char *arg1,*arg2,*arg3;
  char *retval=NULL;
  char *tmp,*temp;
  int l_arg1;
  tmp = find_next_comma(arg);
  if (!tmp) return NULL;
  arg3 = find_next_comma(tmp+1);
  if (!arg3) return NULL;
  *tmp = 0;
  arg1 = expand_tokens(arg);
  l_arg1 = strlen(arg1);
  *tmp = ',';
  tmp++;
  *arg3 = 0;
  arg2 = expand_tokens(tmp);
  *arg3 = ',';
  tmp = arg3+1;
  arg3 = expand_tokens(tmp);
  temp = arg3;
  while (l_arg1 && (tmp = strstr(temp,arg1)) != NULL)
  {
    char c = *tmp;
    *tmp = 0;
    string_cat(retval,temp);
    string_cat(retval,arg2);
    *tmp = c;
    temp = tmp + l_arg1;
  }
  if (*temp) string_cat(retval,temp);
  string_free(arg1);
  string_free(arg2);
  string_free(arg3);
  return retval;
}

static TDependency *_token_dep;

typedef struct
{
  char *name;
  int name_len;
  char *(*func)();
  char *(*make_func)();
} _rhide_tokens;

#define TF(x) static char *rhide_token_##x()
TF(INCLUDE_DIRS);
TF(LIB_DIRS);
TF(C_DEBUG_FLAGS);
TF(C_OPT_FLAGS);
TF(C_WARN_FLAGS);
TF(C_C_LANG_FLAGS);
TF(C_CXX_LANG_FLAGS);
TF(C_P_LANG_FLAGS);
TF(C_F_LANG_FLAGS);
TF(LIBS);
TF(LD_EXTRA_FLAGS);
TF(C_EXTRA_FLAGS);
TF(LOCAL_OPT);
TF(OBJFILES);
TF(LIBRARIES);
TF(SOURCE_NAME);
TF(OUTFILE);
TF(make_SOURCE_NAME);
TF(make_OUTFILE);
TF(make_LOCAL_OPT);
TF(SPECIAL_CFLAGS);
TF(SPECIAL_LDFLAGS);
TF(PROG_ARGS);
TF(SRC_DIRS);
TF(WUC);
TF(make_WUC);
TF(EDITORS);
TF(make_EDITORS);
TF(RHIDE_OS);
TF(make_RHIDE_OS);
TF(MAIN_TARGET);
TF(PROJECT_ITEMS);

static _rhide_tokens rhide_tokens[] =
{
#define SF(x,y) {"$("#x")",sizeof("$("#x")")-1,rhide_token_##x,y}
  SF(INCLUDE_DIRS,NULL),
  SF(LIB_DIRS,NULL),
  SF(C_DEBUG_FLAGS,NULL),
  SF(C_OPT_FLAGS,NULL),
  SF(C_WARN_FLAGS,NULL),
  SF(C_C_LANG_FLAGS,NULL),
  SF(C_CXX_LANG_FLAGS,NULL),
  SF(C_P_LANG_FLAGS,NULL),
  SF(C_F_LANG_FLAGS,NULL),
  SF(LIBS,NULL),
  SF(LD_EXTRA_FLAGS,NULL),
  SF(C_EXTRA_FLAGS,NULL),
  SF(LOCAL_OPT,rhide_token_make_LOCAL_OPT),
  SF(OBJFILES,NULL),
  SF(LIBRARIES,NULL),
  SF(SOURCE_NAME,rhide_token_make_SOURCE_NAME),
  SF(OUTFILE,rhide_token_make_OUTFILE),
  SF(SPECIAL_CFLAGS,NULL),
  SF(SPECIAL_LDFLAGS,NULL),
  SF(PROG_ARGS,NULL),
  SF(SRC_DIRS,NULL),
  SF(WUC,rhide_token_make_WUC),
  SF(EDITORS,rhide_token_make_EDITORS),
  SF(RHIDE_OS,rhide_token_make_RHIDE_OS),
  SF(MAIN_TARGET,NULL),
  SF(PROJECT_ITEMS,NULL),
  {NULL,0,NULL,NULL}
#undef SF
};

TF(WUC)
{
  char *tmp;
  return (tmp = WUC()) ? tmp : string_dup("");
}

TF(make_WUC)
{
  return NULL;
}

TF(PROJECT_ITEMS)
{
  char *retval = NULL;
  int i,count = Project.dependencies->getCount();
  for (i=0;i<count;i++)
  {
    if (retval)
      string_cat(retval," ");
    string_cat(retval,FName(((TDependency *)Project.dependencies->at(i))->source_name));
  }
  if (!retval)
    retval = string_dup("");
  return retval;
}

TF(MAIN_TARGET)
{
  return string_dup(FName(Project.dest_name));
}

TF(make_RHIDE_OS)
{
return string_dup("$(RHIDE_OS_)
ifeq ($(strip $(RHIDE_OS)),)
ifneq ($(strip $(DJDIR)),)
RHIDE_OS_:=DJGPP
else
RHIDE_OS_:=$(shell uname)
endif
endif
");
}

TF(RHIDE_OS)
{
  if (getenv("DJDIR"))
    return string_dup("DJGPP");
  struct utsname u;
  uname(&u);
  return string_dup(u.sysname);
}

TF(make_EDITORS)
{
  return NULL;
}

TF(EDITORS)
{
  char *retval = NULL;
  if (windows)
  {
    int i,count = windows->getCount();
    for (i=0;i<count;i++)
    {
      TEvent event;
      DeskTopWindow *w = (DeskTopWindow *)windows->at(i);
      event.what = evBroadcast;
      event.message.command = cmEditorAnswer;
      w->window->handleEvent(event);
      if (event.what == evNothing)
      {
        if (retval) string_cat(retval," ");
        string_cat(retval,w->full_name);
      }
    }
  }
  return retval;
}

TF(make_LOCAL_OPT)
{
  return string_dup("$(subst ___~~~___, ,$(subst $(notdir $<)___,,"
             "$(filter $(notdir $<)___%,$(LOCAL_OPTIONS))))\n");
}

TF(make_SOURCE_NAME)
{
  return string_dup("$<");
}

TF(make_OUTFILE)
{
  return string_dup("$@");
}

static A(1)
char *_dirs(TDirList *list)
{
  char *retval;
  list->ToString(retval," ");
  return retval;
}

TF(SRC_DIRS)
{
  return _dirs(Options.SrcDirs);
}

TF(INCLUDE_DIRS)
{
  return _dirs(Options.include_path);
}

TF(LIB_DIRS)
{
  return _dirs(Options.library_path);
}

static A(1)
char *_flags(TFlagCollection *flags)
{
  char *retval;
  flags->ToString(retval," ");
  return retval;
}

TF(C_DEBUG_FLAGS)
{
  return _flags(Options.debug_flags);
}

TF(C_OPT_FLAGS)
{
  return _flags(Options.opt_flags);
}

TF(C_WARN_FLAGS)
{
  return _flags(Options.warn_flags);
}

TF(C_C_LANG_FLAGS)
{
  return _flags(Options.c_flags);
}

TF(C_CXX_LANG_FLAGS)
{
  return _flags(Options.cxx_flags);
}

TF(C_P_LANG_FLAGS)
{
  return _flags(Options.pascal_flags);
}

TF(C_F_LANG_FLAGS)
{
  return _flags(Options.fortran_flags);
}

static A(1)
char *_params(TParamList *params)
{
  char *retval;
  params->ToString(retval);
  return retval;
}

TF(PROG_ARGS)
{
  return _params(Options.ProgArgs);
}

TF(LD_EXTRA_FLAGS)
{
  return _params(Options.link_opt);
}

TF(SPECIAL_LDFLAGS)
{
  char *retval = NULL;
  if (NoStdLib)
  {
    string_dup(retval,"-nostdlib");
  }
  if (ForProfile)
  {
    if (retval) string_cat(retval," ");
    string_cat(retval,"-pg");
  }
  return retval;
}

TF(SPECIAL_CFLAGS)
{
  char *retval = NULL;
  if (NoStdInc)
  {
    if (retval) string_cat(retval," ");
    string_cat(retval,"-nostdinc");
  }
  return retval;
}

TF(C_EXTRA_FLAGS)
{
  return _params(Options.comp_opt);
}

TF(LOCAL_OPT)
{
  return _params(_token_dep->local_options);
}

TF(SOURCE_NAME)
{
  char *retval;
  FindFile(FName(_token_dep->source_name),retval);
  AbsToRelPath(project_directory,retval);
  return retval;
}

TF(OUTFILE)
{
  char *retval;
  FindFile(FName(_token_dep->dest_name),retval);
  AbsToRelPath(project_directory,retval);
  return retval;
}

TF(LIBRARIES)
{
  char *retval = NULL;
  char *tmp;
  int i,count=0;
  if (_token_dep->dependencies)
    count = _token_dep->dependencies->getCount();
  for (i=0;i<count;i++)
  {
    TDependency *dep = (TDependency *)_token_dep->dependencies->at(i);
    if (dep->dest_file_type != FILE_LIBRARY
        && dep->dest_file_type != FILE_PROJECT) continue;
    if (dep->exclude_from_link) continue;
    if (dep->dest_file_type == FILE_LIBRARY)
    {
      FindFile(FName(dep->dest_name),tmp);
      AbsToRelPath(project_directory,tmp);
      if (retval) string_cat(retval," ");
      string_cat(retval,tmp);
      string_free(tmp);
    }
    else
    {
      string_dup(tmp,FName(dep->dest_name));
      AbsToRelPath(project_directory,tmp);
      if (retval) string_cat(retval," ");
      string_cat(retval,tmp);
      string_free(tmp);
    }
  }
  return retval;
}

TF(OBJFILES)
{
  char *retval=NULL,*tmp;
  int i,count=0;
  if (_token_dep->dependencies)
    count = _token_dep->dependencies->getCount();
  for (i=0;i<count;i++)
  {
    TDependency *dep = (TDependency *)_token_dep->dependencies->at(i);
    if (dep->dest_file_type != FILE_OBJECT &&
        dep->dest_file_type != FILE_UNKNOWN) // unknown files also
      continue;
    if (dep->exclude_from_link) continue;
    FindFile(FName(dep->dest_name),tmp);
    AbsToRelPath(project_directory,tmp);
    if (retval) string_cat(retval," ");
    string_cat(retval,tmp);
    string_free(tmp);
  }
  return retval;
}

TF(LIBS)
{
  char *retval;
  AddLibraries(retval);
  return retval;
}

static char *_handle_rhide_token(const char *token,token_func expand_tokens)
{
  int i=0;
  char *retval;
  while (rhide_tokens[i].name)
  {
    if (strncmp(token,rhide_tokens[i].name,rhide_tokens[i].name_len) == 0)
    {
      char *_token;
      _token = rhide_tokens[i].func();
      if (!_token) string_dup(_token,"");
      if (!expand_tokens) return _token;
      retval = expand_tokens(_token);
      string_free(_token);
      return retval;
    }
    i++;
  }
  return NULL;
}

static void WriteRule(FILE *f,char *from,char *to)
{
  TDependency *dep;
  dep = new TDependency();
  InitFName(dep->source_name,from);
  InitFName(dep->dest_name,to);
  dep->source_file_type = get_file_type(from);
  dep->dest_file_type = get_file_type(to);
  dep->compile_id = how_to_compile(dep->source_file_type,
                                   dep->dest_file_type);
  Boolean is_user;
  char *spec = GetCompilerSpec(dep,is_user);
  destroy(dep);
  if (!spec) return;
  fprintf(f,"%c%s: %c%s\n\t%s\n",'%',to,'%',from,spec);
  string_free(spec);
}

static void WriteRules(FILE *f)
{
#define WR(from,to) WriteRule(f,"."#from,"."#to)
  WR(c,o);
  WR(i,o);
  WR(cc,o);
  WR(cpp,o);
  WR(cxx,o);
  WR(C,o);
  WR(ii,o);
  WR(s,o);
  WR(S,o);
  WR(c,s);
  WR(i,s);
  WR(cc,s);
  WR(cpp,s);
  WR(cxx,s);
  WR(C,s);
  WR(pas,o);
  WR(p,o);
  WR(pas,s);
  WR(p,s);
  WR(m,o);
  WR(m,s);
  WR(f,o);
  WR(for,o);
  WR(F,o);
  WR(fpp,o);
  WR(asm,o);
  WR(nsm,o);
#undef WR
}

void WriteSpecData(FILE *f)
{
  int i;
  for (i=0;i<var_count;i++)
  {
    fprintf(f,"%s=",vars[i*2]);
    put_breakline(f,strlen(vars[i*2]),75,vars[i*2+1]);
  }
  i=0;
  _token_dep = project;
  while (rhide_tokens[i].name)
  {
    char *token;
    char *name;
    if (rhide_tokens[i].make_func) token = rhide_tokens[i].make_func();
    else token = rhide_tokens[i].func();
    string_dup(name,rhide_tokens[i].name+2);
    name[strlen(name)-1] = 0;
    fprintf(f,"%s=",name);
    put_breakline(f,strlen(name)+1,75,token?token:"");
    string_free(token);
    string_free(name);
    i++;
  }
  WriteRules(f);
}

static char *ExpandSpec(const char *spec)
{
  return expand_spec(spec,_handle_rhide_token);
}

char *BuildCompiler(TDependency *dep,const char *spec)
{
  _token_dep = dep;
  return ExpandSpec(spec);
}

char *expand_spec(const char *spec)
{
  return BuildCompiler(project,spec);
}

/*
  search in the environment for a compile spec, for compiling
  files with specified suffixes taken from 'from' and 'to'.
  The name of the variable should

  RHIDE_COMPILE.extfrom.extto

  Example: define the variable
  RHIDE_COMPILE.cp.o
  to tell RHIDE, how to compile files with suffix '.cp' to files
  with suffix '.o'

  If the variable was found, the returned string is malloced, otherwise
  NULL is returned.
*/

static char *GetUserCompileSpec(const char *from,const char *to)
{
  char *var;
  string_dup(var,"RHIDE_COMPILE");
  if (!from)
    string_cat(var,".");
  else
  {
    char *dir,*name,*ext;
    split_fname(from,dir,name,ext);
    string_cat(var,ext);
    string_free(dir);
    string_free(name);
    string_free(ext);
  }
  if (!to)
    string_cat(var,".");
  else
  {
    char *dir,*name,*ext;
    split_fname(to,dir,name,ext);
    string_cat(var,ext);
    string_free(dir);
    string_free(name);
    string_free(ext);
  }
  if (!GetVariable(var))
  {
    string_free(var);
    return NULL;
  }
  char *_var;
  string_dup(_var,"$(");
  string_cat(_var,var);
  string_cat(_var,")");
  string_free(var);
  return _var;
}

char *GetCompilerSpec(TDependency *dep,Boolean & is_user)
{
  is_user = False;
  char *user_spec = NULL;
  switch (dep->compiler_type)
  {
    case COMPILER_NASM:
      return string_dup("$(RHIDE_COMPILE_NASM)");
    case COMPILER_C:
      return string_dup("$(RHIDE_COMPILE_C_FORCE)");
    case COMPILER_CC:
      return strdup("$(RHIDE_COMPILE_CC_FORCE)");
    case COMPILER_ASM:
      return string_dup("$(RHIDE_COMPILE_ASM_FORCE)");
    case COMPILER_GPC:
      return string_dup("$(RHIDE_COMPILE_PASCAL_FORCE)");
    case COMPILER_FORTRAN:
      return string_dup("$(RHIDE_COMPILE_FORTRAN_FORCE)");
    case COMPILER_NONE:
      return NULL;
    case COMPILER_AUTO:
      user_spec = GetUserCompileSpec(FName(dep->source_name),FName(dep->dest_name));
      if (user_spec)
      {
        if (dep->compile_id == COMPILE_UNKNOWN)
          is_user = True;
        return user_spec;
      }
    case COMPILER_USER:
      switch (dep->compile_id)
      {
#       define C(x) case x: return string_dup("$(RHIDE_"#x")");
        C(COMPILE_C);
        C(COMPILE_CC);
        C(COMPILE_PASCAL);
        C(COMPILE_ARCHIVE);
        C(COMPILE_ASM);
        C(COMPILE_OBJC);
        C(COMPILE_LINK_PASCAL_AUTOMAKE);
        C(COMPILE_FORTRAN);
        C(COMPILE_NASM);
#       undef C
        default:
          break;
      }
      if (dep->compile_id == COMPILE_LINK)
      {
        int i,count = 0;
        if (dep->dependencies) count = dep->dependencies->getCount();
        for (i=0;i<count;i++)
        {
          if (((TDependency *)dep->dependencies->at(i))->source_file_type
              == FILE_PASCAL_SOURCE)
          {
            return string_dup("$(RHIDE_COMPILE_LINK_PASCAL)");
          }
        }
        return string_dup("$(RHIDE_COMPILE_LINK)");
      }
      if (dep->compile_id == COMPILE_USER)
      {
        is_user = True;
        return string_dup(dep->compiler);
      }
      return GetUserCompileSpec(FName(dep->source_name),FName(dep->dest_name));
      break;
  }
  return NULL;
}

#ifdef TEST

TProject *project;
char *WUC() { return NULL; }
void AddLibraries(char *&) {}
char *project_directory = NULL;

int main()
{
  int i = 0;
  while (default_variables[i])
  {
    fprintf(stdout,"%s=%s\n",default_variables[i],default_variables[i+1]);
    i += 2;
  }
}

#endif

