* Reworked function.c to use separate functions instead of a huge case stmt.
authorPaul Smith <psmith@gnu.org>
Fri, 26 Mar 1999 04:04:42 +0000 (04:04 +0000)
committerPaul Smith <psmith@gnu.org>
Fri, 26 Mar 1999 04:04:42 +0000 (04:04 +0000)
* Added new functions $(error ...) and $(warning ...) and documented same.
* In windows/dos shells, only treat \ as an escape for special chars.

ChangeLog
NEWS
function.c
job.c
make.texinfo

index b0d344d..2d23dac 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,31 @@
+1999-03-22  Paul D. Smith  <psmith@gnu.org>
+
+       * make.texinfo (Functions): Add a new section documenting the new
+       $(error ...) and $(warning ...) functions.  Also updated copyright
+       dates.
+       * NEWS: Updated for the new functions.
+       * function.c (func_error): Implement the new $(error ...) and
+       $(warning ...) functions.
+       (function_table): Insert new functions into the table.
+       (func_firstword): Don't call find_next_token() with argv[0]
+       itself, since that function modifies the pointer.
+       * function.c: Cleanups and slight changes to the new method of
+       calling functions.
+
+1999-03-20  Han-Wen Nienhuys  <hanwen@cs.uu.nl>
+
+       * function.c: Rewrite to use one C function per make function,
+       instead of a huge switch statement.  Also allows some cleanup of
+       multi-architecture issues.
+
+1999-03-19  Eli Zaretskii  <eliz@is.elta.co.il>
+1999-03-19  Rob Tulloh  <rob_tulloh@dev.tivoli.com>
+
+       * job.c (construct_command_argv_internal): Don't treat _all_
+       backslashes as escapes, only those which really escape a special
+       character.  This allows most normal "\" directory separators to be
+       treated normally.
+
 1999-03-05  Paul D. Smith  <psmith@gnu.org>
 
        * configure.in: Check for a system strdup().
@@ -17,7 +45,7 @@
 1999-03-04  Paul D. Smith  <psmith@gnu.org>
 
        * amiga.c, amiga.h, ar.c, arscan.c, commands.c, commands.h,
-       * default.c, dep.h, dir.c, expand.c, file.c, filedef.h, functions.c,
+       * default.c, dep.h, dir.c, expand.c, file.c, filedef.h, function.c,
        * implicit.c, job.c, job.h, main.c, make.h, misc.c, read.c, remake.c
        * remote-cstms.c, remote-stub.c, rule.h, variable.c, variable.h,
        * vpath.c, Makefile.ami, NMakefile.template, build.template,
diff --git a/NEWS b/NEWS
index fcff5f2..c7044b6 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,6 @@
 GNU make NEWS                                               -*-indented-text-*-
   History of user-visible changes.
-  22 Feb 1999
+  22 Mar 1999
 
 Copyright (C) 1992,93,94,95,96,97,98,1999 Free Software Foundation, Inc.
 See the end for copying conditions.
@@ -12,6 +12,12 @@ Please send GNU make bug reports to bug-make@gnu.org.
 \f
 Version 3.78
 
+* Two new functions, $(error ...) and $(warning ...) are provided.  The
+  former will cause make to fail and exit immediately upon expansion of
+  the function, with the text provided as the error message.  The latter
+  causes the text provided to be printed as a warning message, but make
+  proceeds normally.
+
 * Make defines a new variable, .LIBPATTERNS.  This variable controls how
   the link library dependency expansion (dependencies like ``-lfoo'') is
   performed.
index def3863..2183cd8 100644 (file)
@@ -1,4 +1,4 @@
-/* Variable function expansion for GNU Make.
+/* Builtin function expansion for GNU Make.
 Copyright (C) 1988,89,91,92,93,94,95,96,97 Free Software Foundation, Inc.
 This file is part of GNU Make.
 
@@ -27,13 +27,19 @@ Boston, MA 02111-1307, USA.  */
 #ifdef _AMIGA
 #include "amiga.h"
 #endif
-#ifdef WINDOWS32
-#include <windows.h>
-#include <io.h>
-#include "sub_proc.h"
-#endif
 
-static char *string_glob PARAMS ((char *line));
+
+struct function_table_entry
+  {
+    const char *name;
+    int len;
+    int required_arguments;
+    int expand_all_arguments;
+    char *(*func_ptr) PARAMS((char *output, char **argv, const char*funcname));
+  };
+
+static struct function_table_entry function_table[];
+
 \f
 /* Store into VARIABLE_BUFFER at O the result of scanning TEXT and replacing
    each occurrence of SUBST with REPLACE. TEXT is null-terminated.  SLEN is
@@ -214,75 +220,29 @@ patsubst_expand (o, text, pattern, replace, pattern_percent, replace_percent)
   return o;
 }
 \f
-/* Handle variable-expansion-time functions such as $(dir foo/bar) ==> foo/  */
 
-/* These enumeration constants distinguish the
-   various expansion-time built-in functions.  */
-
-enum function
-  {
-    function_subst,
-    function_addsuffix,
-    function_addprefix,
-    function_dir,
-    function_notdir,
-    function_suffix,
-    function_basename,
-    function_wildcard,
-    function_firstword,
-    function_word,
-    function_words,
-    function_wordlist,
-    function_findstring,
-    function_strip,
-    function_join,
-    function_patsubst,
-    function_filter,
-    function_filter_out,
-    function_foreach,
-    function_sort,
-    function_origin,
-    function_shell,
-    function_invalid
-  };
+/* Look up a function by name.
+   The table is currently small enough that it's not really worthwhile to use
+   a fancier lookup algorithm.  If it gets larger, maybe...
+*/
 
-/* Greater than the length of any function name.  */
-#define MAXFUNCTIONLEN 11
+static const struct function_table_entry *
+lookup_function (table, s)
+     const struct function_table_entry *table;
+     const char *s;
+{
+  int len = strlen(s);
 
-/* The function names and lengths of names, for looking them up.  */
+  for (; table->name != NULL; ++table)
+    if (table->len <= len
+        && (isblank (s[table->len]) || s[table->len] == '\0')
+        && strneq (s, table->name, table->len))
+      return table;
 
-static struct
-  {
-    char *name;
-    unsigned int len;
-    enum function function;
-  } function_table[] =
-  {
-    { "subst", 5, function_subst },
-    { "addsuffix", 9, function_addsuffix },
-    { "addprefix", 9, function_addprefix },
-    { "dir", 3, function_dir },
-    { "notdir", 6, function_notdir },
-    { "suffix", 6, function_suffix },
-    { "basename", 8, function_basename },
-    { "wildcard", 8, function_wildcard },
-    { "firstword", 9, function_firstword },
-    { "word", 4, function_word },
-    { "words", 5, function_words },
-    { "wordlist", 8, function_wordlist },
-    { "findstring", 10, function_findstring },
-    { "strip", 5, function_strip },
-    { "join", 4, function_join },
-    { "patsubst", 8, function_patsubst },
-    { "filter", 6, function_filter },
-    { "filter-out", 10, function_filter_out },
-    { "foreach", 7, function_foreach },
-    { "sort", 4, function_sort },
-    { "origin", 6, function_origin },
-    { "shell", 5, function_shell },
-    { 0, 0, function_invalid }
-  };
+  return NULL;
+}
 \f
+
 /* Return 1 if PATTERN matches STR, 0 if not.  */
 
 int
@@ -294,9 +254,9 @@ pattern_matches (pattern, percent, str)
   if (percent == 0)
     {
       unsigned int len = strlen (pattern) + 1;
-      char *new = (char *) alloca (len);
-      bcopy (pattern, new, len);
-      pattern = new;
+      char *new_chars = (char *) alloca (len);
+      bcopy (pattern, new_chars, len);
+      pattern = new_chars;
       percent = find_percent (pattern);
       if (percent == 0)
        return streq (pattern, str);
@@ -312,1288 +272,1626 @@ pattern_matches (pattern, percent, str)
   return !strcmp (percent + 1, str + (strlength - sfxlen));
 }
 \f
-int shell_function_pid = 0, shell_function_completed;
 
-/* Perform the function specified by FUNCTION on the text at TEXT.
-   END is points to the end of the argument text (exclusive).
-   The output is written into VARIABLE_BUFFER starting at O.  */
+/* Find the next comma or ENDPAREN (counting nested STARTPAREN and
+   ENDPARENtheses), starting at PTR before END.  Return a pointer to
+   next character.
 
-/* Note this absorbs a semicolon and is safe to use in conditionals.  */
-#define BADARGS(func)  do {                                                   \
-    fatal (reading_file, "insufficient arguments to function `%s'", func);    \
-  } while (0)
+   If no next argument is found, return NULL.
+*/
 
 static char *
-expand_function (o, function, text, end)
-     char *o;
-     enum function function;
-     char *text;
-     char *end;
+find_next_argument (startparen, endparen, ptr, end)
+     char startparen;
+     char endparen;
+     const char *ptr;
+     const char *end;
 {
-  char *p, *p2, *p3;
-  unsigned int i, j, len;
-  int doneany = 0;
-  int count;
-  char endparen = *end, startparen = *end == ')' ? '(' : '{';
+  int count = 0;
 
-  switch (function)
-    {
-    default:
-      abort ();
-      break;
+  for (; ptr < end; ++ptr)
+    if (*ptr == startparen)
+      ++count;
 
-#ifndef VMS /* not supported for vms yet */
-    case function_shell:
+    else if (*ptr == endparen)
       {
-       char* batch_filename = NULL;
-#ifdef WINDOWS32
-       SECURITY_ATTRIBUTES saAttr;
-       HANDLE hIn;
-       HANDLE hErr;
-       HANDLE hChildOutRd;
-       HANDLE hChildOutWr;
-       HANDLE hProcess;
-#endif
-#ifdef __MSDOS__
-       FILE *fpipe;
-#endif
-       char **argv;
-       char *error_prefix;
-#ifndef _AMIGA
-       char **envp;
-       int pipedes[2];
-       int pid;
-#endif
-
-       /* Expand the command line.  */
-       text = expand_argument (text, end);
-
-#ifndef __MSDOS__
-       /* Construct the argument list.  */
-       argv = construct_command_argv (text,
-                                      (char **) NULL, (struct file *) 0, &batch_filename);
-       if (argv == 0)
-         break;
-#endif
-
-#ifndef _AMIGA
-       /* Using a target environment for `shell' loses in cases like:
-               export var = $(shell echo foobie)
-          because target_environment hits a loop trying to expand $(var)
-          to put it in the environment.  This is even more confusing when
-          var was not explicitly exported, but just appeared in the
-          calling environment.  */
-#if 1
-       envp = environ;
-#else
-       /* Construct the environment.  */
-       envp = target_environment ((struct file *) 0);
-#endif
-#endif  /* Not Amiga.  */
-
-       /* For error messages.  */
-       if (reading_file != 0)
-         {
-           error_prefix = (char *) alloca (strlen(reading_file->filenm)+100);
-           sprintf (error_prefix,
-                    "%s:%lu: ", reading_file->filenm, reading_file->lineno);
-         }
-       else
-         error_prefix = "";
+       --count;
+       if (count < 0)
+         return NULL;
+      }
 
-#ifndef _AMIGA
-# ifdef WINDOWS32
-       saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
-       saAttr.bInheritHandle = TRUE;
-       saAttr.lpSecurityDescriptor = NULL;
-
-       if (DuplicateHandle(GetCurrentProcess(),
-                           GetStdHandle(STD_INPUT_HANDLE),
-                           GetCurrentProcess(),
-                           &hIn,
-                           0,
-                           TRUE,
-                           DUPLICATE_SAME_ACCESS) == FALSE) {
-         fatal (NILF, "create_child_process: DuplicateHandle(In) failed (e=%d)\n",
-               GetLastError());
-       }
-       if (DuplicateHandle(GetCurrentProcess(),
-                           GetStdHandle(STD_ERROR_HANDLE),
-                           GetCurrentProcess(),
-                           &hErr,
-                           0,
-                           TRUE,
-                           DUPLICATE_SAME_ACCESS) == FALSE) {
-         fatal (NILF, "create_child_process: DuplicateHandle(Err) failed (e=%d)\n",
-               GetLastError());
-       }
+    else if (*ptr == ',' && !count)
+      return (char *)ptr;
 
-       if (!CreatePipe(&hChildOutRd, &hChildOutWr, &saAttr, 0))
-         fatal (NILF, "CreatePipe() failed (e=%d)\n", GetLastError());
+  /* We didn't find anything.  */
+  return NULL;
+}
+\f
 
-       hProcess = process_init_fd(hIn, hChildOutWr, hErr);
+static char *
+expand_builtin_function (o, argc, argv, entry_p)
+     char *o;
+     int argc;
+     char **argv;
+     struct function_table_entry *entry_p;
+{
+  if (argc < entry_p->required_arguments && entry_p->required_arguments >= 0)
+    fatal (reading_file,
+           "Insufficient number of arguments (%d) to function `%s'",
+           argc, entry_p->name);
 
-       if (!hProcess)
-         fatal (NILF, "expand_function: process_init_fd() failed\n");
-       else
-         process_register(hProcess);
+  if (!entry_p->func_ptr)
+    fatal (reading_file, "Unimplemented on this platform: function `%s'",
+           entry_p->name);
 
-       /* make sure that CreateProcess() has Path it needs */
-       sync_Path_environment();
+  return entry_p->func_ptr (o, argv, entry_p->name);
+}
 
-       if (!process_begin(hProcess, argv, envp, argv[0], NULL))
-               pid = (int) hProcess;
-       else
-               fatal (NILF, "expand_function: unable to launch process (e=%d)\n",
-                     process_last_err(hProcess));
+/* Check for a function invocation in *STRINGP.  *STRINGP points at the
+   opening ( or { and is not null-terminated.  If a function invocation
+   is found, expand it into the buffer at *OP, updating *OP, incrementing
+   *STRINGP past the reference and returning nonzero.  If not, return zero.  */
 
-       /* set up to read data from child */
-       pipedes[0] = _open_osfhandle((long) hChildOutRd, O_RDONLY);
+int
+handle_function (op, stringp)
+     char **op;
+     char **stringp;
+{
+  const struct function_table_entry *entry_p;
+  char openparen = (*stringp)[0];
+  char closeparen = openparen == '(' ? ')' : '}';
+  char *beg = *stringp + 1;
+  char *endref;
+  int count = 0;
+  char *argbeg;
+  register char *p;
+  char **argv, **argvp;
+  int nargs;
 
-       /* this will be closed almost right away */
-       pipedes[1] = _open_osfhandle((long) hChildOutWr, O_APPEND);
-# else /* WINDOWS32 */
-#  ifdef __MSDOS__
-       {
-         /* MSDOS can't fork, but it has `popen'.
-            (Bwt, why isn't `popen' used in all the versions?) */
-         struct variable *sh = lookup_variable ("SHELL", 5);
-         int e;
-         extern int dos_command_running, dos_status;
-
-         /* Make sure not to bother processing an empty line.  */
-         while (isblank (*text))
-           ++text;
-         if (*text == '\0')
-           break;
+  entry_p = lookup_function (function_table, beg);
 
-         if (sh)
-           {
-             char buf[PATH_MAX + 7];
-             /* This makes sure $SHELL value is used by $(shell), even
-                though the target environment is not passed to it.  */
-             sprintf (buf, "SHELL=%s", sh->value);
-             putenv (buf);
-           }
+  if (!entry_p)
+    return 0;
 
-         e = errno;
-         errno = 0;
-         dos_command_running = 1;
-         dos_status = 0;
-         fpipe = popen (text, "rt");
-         dos_command_running = 0;
-         if (!fpipe || dos_status)
-           {
-             pipedes[0] = -1;
-             pid = -1;
-             if (dos_status)
-               errno = EINTR;
-             else if (errno == 0)
-               errno = ENOMEM;
-             shell_function_completed = -1;
-           }
-         else
-           {
-             pipedes[0] = fileno (fpipe);
-             pid = 42;
-             errno = e;
-             shell_function_completed = 1;
-           }
-       }
-       if (pipedes[0] < 0)
-#  else /* ! __MSDOS__ */
-       if (pipe (pipedes) < 0)
-#  endif /* __MSDOS__ */
-         {
-           perror_with_name (error_prefix, "pipe");
-           break;
-         }
+  /* We have found a call to a builtin function.  Find the end of the
+     arguments, and do the function.  */
 
-#  ifndef  __MSDOS__
-       pid = vfork ();
-       if (pid < 0)
-         perror_with_name (error_prefix, "fork");
-       else if (pid == 0)
-         child_execute_job (0, pipedes[1], argv, envp);
-       else
-#  endif /* ! __MSDOS__ */
-# endif /* WINDOWS32 */
-         {
-           /* We are the parent.  */
-
-           char *buffer;
-           unsigned int maxlen;
-           int cc;
-#if 0
-           for (i = 0; envp[i] != 0; ++i)
-             free (envp[i]);
-           free ((char *) envp);
-#endif
+  endref = beg + entry_p->len;
 
-           /* Record the PID for reap_children.  */
-           shell_function_pid = pid;
-#ifndef  __MSDOS__
-           shell_function_completed = 0;
+  /* Space after function name isn't part of the args.  */
+  p = next_token (endref);
+  argbeg = p;
 
-           /* Free the storage only the child needed.  */
-           free (argv[0]);
-           free ((char *) argv);
+  /* Find the end of the function invocation, counting nested use of
+     whichever kind of parens we use.  Since we're looking, count commas
+     to get a rough estimate of how many arguments we might have.  The
+     count might be high, but it'll never be low.  */
 
-           /* Close the write side of the pipe.  */
-           (void) close (pipedes[1]);
-#endif
+  for (nargs=1; *p != '\0'; ++p)
+    if (*p == ',')
+      ++nargs;
+    else if (*p == openparen)
+      ++count;
+    else if (*p == closeparen && --count < 0)
+      break;
 
-           /* Set up and read from the pipe.  */
-
-           maxlen = 200;
-           buffer = (char *) xmalloc (maxlen + 1);
-
-           /* Read from the pipe until it gets EOF.  */
-           i = 0;
-           do
-             {
-               if (i == maxlen)
-                 {
-                   maxlen += 512;
-                   buffer = (char *) xrealloc (buffer, maxlen + 1);
-                 }
-
-               errno = 0;
-               cc = read (pipedes[0], &buffer[i], maxlen - i);
-               if (cc > 0)
-                 i += cc;
-             }
-#ifdef EINTR
-           while (cc > 0 || errno == EINTR);
-#else
-           while (cc > 0);
-#endif
+  if (count >= 0)
+    fatal (reading_file,
+          "unterminated call to function `%s': missing `%c'",
+          entry_p->name, closeparen);
 
-           /* Close the read side of the pipe.  */
-#ifdef  __MSDOS__
-           if (fpipe)
-             (void) pclose (fpipe);
-#else
-           (void) close (pipedes[0]);
-#endif
+  /* Get some memory to store the arg pointers.  */
+  argvp = argv = (char **) alloca (sizeof(char *) * (nargs + 2));
 
-           /* Loop until child_handler sets shell_function_completed
-              to the status of our child shell.  */
-           while (shell_function_completed == 0)
-             reap_children (1, 0);
-
-            if (batch_filename) {
-              if (debug_flag)
-                printf("Cleaning up temporary batch file %s\n", batch_filename);
-              remove(batch_filename);
-              free(batch_filename);
-            }
-           shell_function_pid = 0;
-
-           /* The child_handler function will set shell_function_completed
-              to 1 when the child dies normally, or to -1 if it
-              dies with status 127, which is most likely an exec fail.  */
-
-           if (shell_function_completed == -1)
-             {
-               /* This most likely means that the execvp failed,
-                  so we should just write out the error message
-                  that came in over the pipe from the child.  */
-               fputs (buffer, stderr);
-               fflush (stderr);
-             }
-           else
-             {
-               /* The child finished normally.  Replace all
-                  newlines in its output with spaces, and put
-                  that in the variable output buffer.  */
-               if (i > 0)
-                 {
-                   if (buffer[i - 1] == '\n')
-                     {
-                       if (i > 1 && buffer[i - 2] == '\r')
-                         --i;
-                       buffer[--i] = '\0';
-                     }
-                   else
-                     buffer[i] = '\0';
-
-                   p = buffer;
-                   for (p2=p; *p != '\0'; ++p)
-                     {
-                       if (p[0] == '\r' && p[1] == '\n')
-                         continue;
-                       if (*p == '\n')
-                         *p2++ = ' ';
-                       else
-                         *p2++ = *p;
-                     }
-                   *p2 = '\0';
-                   o = variable_buffer_output (o, buffer, i);
-                 }
-             }
-
-           free (buffer);
-         }
-#else   /* Amiga */
-        {
-          /* Amiga can't fork nor spawn, but I can start a program with
-             redirection of my choice.  However, this means that we
-             don't have an opportunity to reopen stdout to trap it.  Thus,
-             we save our own stdout onto a new descriptor and dup a temp
-             file's descriptor onto our stdout temporarily.  After we
-             spawn the shell program, we dup our own stdout back to the
-             stdout descriptor.  The buffer reading is the same as above,
-             except that we're now reading from a file.  */
-#include <dos/dos.h>
-#include <proto/dos.h>
+  /* Chop the string into arguments, then store the end pointer and a nul.  */
+  *argvp = argbeg;
+  nargs = 1;
+  while (1)
+    {
+      char *next = find_next_argument (openparen, closeparen, *argvp, p);
 
-          BPTR child_stdout;
-          char tmp_output[FILENAME_MAX];
-          unsigned int maxlen = 200;
-          int cc;
-          char * buffer, * ptr;
-          char ** aptr;
-          int len = 0;
-
-          strcpy (tmp_output, "t:MakeshXXXXXXXX");
-          mktemp (tmp_output);
-          child_stdout = Open (tmp_output, MODE_NEWFILE);
-
-          for (aptr=argv; *aptr; aptr++)
-            {
-              len += strlen (*aptr) + 1;
-            }
-
-          buffer = xmalloc (len + 1);
-          ptr = buffer;
-
-          for (aptr=argv; *aptr; aptr++)
-            {
-              strcpy (ptr, *aptr);
-              ptr += strlen (ptr) + 1;
-              *ptr ++ = ' ';
-              *ptr = 0;
-            }
-
-          ptr[-1] = '\n';
-
-          Execute (buffer, NULL, child_stdout);
-          free (buffer);
-
-          Close (child_stdout);
-
-          child_stdout = Open (tmp_output, MODE_OLDFILE);
-
-          buffer = xmalloc (maxlen);
-          i = 0;
-          do
-            {
-              if (i == maxlen)
-                {
-                  maxlen += 512;
-                  buffer = (char *) xrealloc (buffer, maxlen + 1);
-                }
-
-              cc = Read (child_stdout, &buffer[i], maxlen - i);
-              if (cc > 0)
-                i += cc;
-            } while (cc > 0);
-
-          Close (child_stdout);
-          DeleteFile (tmp_output);
-
-          if (i > 0)
-            {
-              if (buffer[i - 1] == '\n')
-                buffer[--i] = '\0';
-              else
-                buffer[i] = '\0';
-              p = buffer;
-              while ((p = index (p, '\n')) != 0)
-                *p++ = ' ';
-              o = variable_buffer_output (o, buffer, i);
-            }
-          free (buffer);
-        }
-#endif  /* Not Amiga.  */
-
-       free (text);
+      if (!next)
        break;
-      }
-#endif /* !VMS */
 
-    case function_origin:
-      /* Expand the argument.  */
-      text = expand_argument (text, end);
+      *(++argvp) = next+1;
+      ++nargs;
+    }
 
-      {
-       register struct variable *v = lookup_variable (text, strlen (text));
-       if (v == 0)
-         o = variable_buffer_output (o, "undefined", 9);
-       else
-         switch (v->origin)
-           {
-           default:
-           case o_invalid:
-             abort ();
-             break;
-           case o_default:
-             o = variable_buffer_output (o, "default", 7);
-             break;
-           case o_env:
-             o = variable_buffer_output (o, "environment", 11);
-             break;
-           case o_file:
-             o = variable_buffer_output (o, "file", 4);
-             break;
-           case o_env_override:
-             o = variable_buffer_output (o, "environment override", 20);
-             break;
-           case o_command:
-             o = variable_buffer_output (o, "command line", 12);
-             break;
-           case o_override:
-             o = variable_buffer_output (o, "override", 8);
-             break;
-           case o_automatic:
-             o = variable_buffer_output (o, "automatic", 9);
-             break;
-           }
-      }
+  *(++argvp) = p+1;
+  *(++argvp) = 0;
 
-      free (text);
-      break;
+  /* If we should expand, do it.  */
+  if (entry_p->expand_all_arguments)
+    {
+      for (argvp=argv; argvp[1] != 0; ++argvp)
+       *argvp = expand_argument (*argvp, argvp[1]-1);
 
-    case function_sort:
-      /* Expand the argument.  */
-      text = expand_argument (text, end);
+      /* end pointer doesn't make sense for expanded stuff.  */
+      *argvp = 0;
+    }
 
-      {
-       char **words = (char **) xmalloc (10 * sizeof (char *));
-       unsigned int nwords = 10;
-       register unsigned int wordi = 0;
-       char *t;
-
-       /* Chop TEXT into words and put them in WORDS.  */
-       t = text;
-       while ((p = find_next_token (&t, &len)) != 0)
-         {
-           if (wordi >= nwords - 1)
-             {
-               nwords *= 2;
-               words = (char **) xrealloc ((char *) words,
-                                           nwords * sizeof (char *));
-             }
-           words[wordi++] = savestring (p, len);
-         }
+  /* Finally!  Run the function...  */
+  *op = expand_builtin_function (*op, nargs, argv, entry_p);
 
-       if (wordi > 0)
-         {
-           /* Now sort the list of words.  */
-           qsort ((char *) words, wordi, sizeof (char *), alpha_compare);
-
-           /* Now write the sorted list.  */
-           for (i = 0; i < wordi; ++i)
-             {
-               len = strlen (words[i]);
-               if (i == wordi - 1 || strlen (words[i + 1]) != len
-                   || strcmp (words[i], words[i + 1]))
-                 {
-                   o = variable_buffer_output (o, words[i], len);
-                   o = variable_buffer_output (o, " ", 1);
-                 }
-               free (words[i]);
-             }
-           /* Kill the last space.  */
-           --o;
-         }
+  /* If we allocated memory for the expanded args, free it again.  */
+  if (entry_p->expand_all_arguments)
+    for (argvp=argv; *argvp != 0; ++argvp)
+      free (*argvp);
 
-       free ((char *) words);
-      }
+  *stringp = p;
 
-      free (text);
-      break;
+  return 1;
+}
+\f
 
-    case function_foreach:
-      {
-       /* Get three comma-separated arguments but
-          expand only the first two.  */
-       char *var, *list;
-       register struct variable *v;
+/* Glob-expand LINE.  The returned pointer is
+   only good until the next call to string_glob.  */
 
-       count = 0;
-       for (p = text; p < end; ++p)
-         {
-           if (*p == startparen)
-             ++count;
-           else if (*p == endparen)
-             --count;
-           else if (*p == ',' && count <= 0)
-             break;
-         }
-       if (p == end)
-         BADARGS ("foreach");
-       var = expand_argument (text, p);
+static char *
+string_glob (line)
+     char *line;
+{
+  static char *result = 0;
+  static unsigned int length;
+  register struct nameseq *chain;
+  register unsigned int idx;
 
-       p2 = p + 1;
-       count = 0;
-       for (p = p2; p < end; ++p)
-         {
-           if (*p == startparen)
-             ++count;
-           else if (*p == endparen)
-             --count;
-           else if (*p == ',' && count <= 0)
-             break;
-         }
-       if (p == end)
-         BADARGS ("foreach");
-       list = expand_argument (p2, p);
+  chain = multi_glob (parse_file_seq
+                     (&line, '\0', sizeof (struct nameseq),
+                      /* We do not want parse_file_seq to strip `./'s.
+                         That would break examples like:
+                         $(patsubst ./%.c,obj/%.o,$(wildcard ./*.c)).  */
 
-       ++p;
-       text = savestring (p, end - p);
-
-       push_new_variable_scope ();
-       v = define_variable (var, strlen (var), "", o_automatic, 0);
-        free (v->value);
-        v->value = 0;
-       p3 = list;
-       while ((p = find_next_token (&p3, &len)) != 0)
-         {
-           char *result;
-           char save = p[len];
-           p[len] = '\0';
-           v->value = p;
-           result = allocated_variable_expand (text);
-           p[len] = save;
-
-           o = variable_buffer_output (o, result, strlen (result));
-           o = variable_buffer_output (o, " ", 1);
-           doneany = 1;
-           free (result);
-         }
-       if (doneany)
-         /* Kill the last space.  */
-         --o;
+                      /* Yep, and the preceding comment triggers a GCC
+                          warning.  Nothing wrong though.  */
+                      0),
+                     sizeof (struct nameseq));
 
-       v->value = 0;
-       pop_variable_scope ();
+  if (result == 0)
+    {
+      length = 100;
+      result = (char *) xmalloc (100);
+    }
 
-       free (var);
-       free (list);
-       free (text);
-      }
-      break;
+  idx = 0;
+  while (chain != 0)
+    {
+      register char *name = chain->name;
+      unsigned int len = strlen (name);
 
-    case function_filter:
-    case function_filter_out:
-      {
-       struct a_word
-         {
-           struct a_word *next;
-           char *str;
-           int matched;
-         } *words, *wordtail, *wp;
-
-       /* Get two comma-separated arguments and expand each one.  */
-       count = 0;
-       for (p = text; p < end; ++p)
-         {
-           if (*p == startparen)
-             ++count;
-           else if (*p == endparen)
-             --count;
-           else if (*p == ',' && count <= 0)
-             break;
-         }
-       if (p == end)
-         BADARGS (function == function_filter ? "filter" : "filter-out");
-       p2 = expand_argument (text, p);
+      struct nameseq *next = chain->next;
+      free ((char *) chain);
+      chain = next;
 
-       text = expand_argument (p + 1, end);
+      /* multi_glob will pass names without globbing metacharacters
+        through as is, but we want only files that actually exist.  */
+      if (file_exists_p (name))
+       {
+         if (idx + len + 1 > length)
+           {
+             length += (len + 1) * 2;
+             result = (char *) xrealloc (result, length);
+           }
+         bcopy (name, &result[idx], len);
+         idx += len;
+         result[idx++] = ' ';
+       }
 
-       /* Chop TEXT up into words and then run each pattern through.  */
-       words = wordtail = 0;
-       p3 = text;
-       while ((p = find_next_token (&p3, &len)) != 0)
-         {
-           struct a_word *w = (struct a_word *)alloca(sizeof(struct a_word));
-           if (words == 0)
-             words = w;
-           else
-             wordtail->next = w;
-           wordtail = w;
-
-           if (*p3 != '\0')
-             ++p3;
-           p[len] = '\0';
-           w->str = p;
-           w->matched = 0;
-         }
+      free (name);
+    }
 
-       if (words != 0)
-         {
-           wordtail->next = 0;
-
-           /* Run each pattern through the words, killing words.  */
-           p3 = p2;
-           while ((p = find_next_token (&p3, &len)) != 0)
-             {
-               char *percent;
-               char save = p[len];
-               p[len] = '\0';
-
-               percent = find_percent (p);
-               for (wp = words; wp != 0; wp = wp->next)
-                 wp->matched |= (percent == 0 ? streq (p, wp->str)
-                                 : pattern_matches (p, percent, wp->str));
-
-               p[len] = save;
-             }
-
-           /* Output the words that matched (or didn't, for filter-out).  */
-           for (wp = words; wp != 0; wp = wp->next)
-             if (function == function_filter ? wp->matched : !wp->matched)
-               {
-                 o = variable_buffer_output (o, wp->str, strlen (wp->str));
-                 o = variable_buffer_output (o, " ", 1);
-                 doneany = 1;
-               }
-           if (doneany)
-             /* Kill the last space.  */
-             --o;
-         }
+  /* Kill the last space and terminate the string.  */
+  if (idx == 0)
+    result[0] = '\0';
+  else
+    result[idx - 1] = '\0';
 
-       free (p2);
-       free (text);
-      }
-      break;
+  return result;
+}
+\f
+/*
+  Builtin functions
+ */
 
-    case function_patsubst:
-      /* Get three comma-separated arguments and expand each one.  */
-      count = 0;
-      for (p = text; p < end; ++p)
-       {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
-       }
-      if (p == end)
-       BADARGS ("patsubst");
+static char *
+func_patsubst (o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  o = patsubst_expand (o, argv[2], argv[0], argv[1], (char *) 0, (char *) 0);
+  return o;
+}
 
-      p2 = p;
-      count = 0;
-      for (++p; p < end; ++p)
-       {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
-       }
-      if (p == end)
-       BADARGS ("patsubst");
 
-      text = expand_argument (text, p2);
-      p3 = expand_argument (p2 + 1, p);
-      p2 = expand_argument (p + 1, end);
+static char *
+func_join(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  int doneany = 0;
+
+  /* Write each word of the first argument directly followed
+     by the corresponding word of the second argument.
+     If the two arguments have a different number of words,
+     the excess words are just output separated by blanks.  */
+  register char *tp;
+  register char *pp;
+  char *list1_iterator = argv[0];
+  char *list2_iterator = argv[1];
+  do
+    {
+      unsigned int len1, len2;
 
-      o = patsubst_expand (o, p2, text, p3, (char *) 0, (char *) 0);
+      tp = find_next_token (&list1_iterator, &len1);
+      if (tp != 0)
+       o = variable_buffer_output (o, tp, len1);
 
-      free (text);
-      free (p3);
-      free (p2);
-      break;
+      pp = find_next_token (&list2_iterator, &len2);
+      if (pp != 0)
+       o = variable_buffer_output (o, pp, len2);
 
-    case function_join:
-      /* Get two comma-separated arguments and expand each one.  */
-      count = 0;
-      for (p = text; p < end; ++p)
+      if (tp != 0 || pp != 0)
        {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
+         o = variable_buffer_output (o, " ", 1);
+         doneany = 1;
        }
-      if (p == end)
-       BADARGS ("join");
-      text = expand_argument (text, p);
+    }
+  while (tp != 0 || pp != 0);
+  if (doneany)
+    /* Kill the last blank.  */
+    --o;
+
+  return o;
+}
 
-      p = expand_argument (p + 1, end);
 
+static char *
+func_origin(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  /* Expand the argument.  */
+  register struct variable *v = lookup_variable (argv[0], strlen (argv[0]));
+  if (v == 0)
+    o = variable_buffer_output (o, "undefined", 9);
+  else
+    switch (v->origin)
       {
-       /* Write each word of the first argument directly followed
-          by the corresponding word of the second argument.
-          If the two arguments have a different number of words,
-          the excess words are just output separated by blanks.  */
-       register char *tp, *pp;
-       p2 = text;
-       p3 = p;
-       do
-         {
-           unsigned int tlen, plen;
+      default:
+      case o_invalid:
+       abort ();
+       break;
+      case o_default:
+       o = variable_buffer_output (o, "default", 7);
+       break;
+      case o_env:
+       o = variable_buffer_output (o, "environment", 11);
+       break;
+      case o_file:
+       o = variable_buffer_output (o, "file", 4);
+       break;
+      case o_env_override:
+       o = variable_buffer_output (o, "environment override", 20);
+       break;
+      case o_command:
+       o = variable_buffer_output (o, "command line", 12);
+       break;
+      case o_override:
+       o = variable_buffer_output (o, "override", 8);
+       break;
+      case o_automatic:
+       o = variable_buffer_output (o, "automatic", 9);
+       break;
+      }
 
-           tp = find_next_token (&p2, &tlen);
-           if (tp != 0)
-             o = variable_buffer_output (o, tp, tlen);
+  return o;
+}
 
-           pp = find_next_token (&p3, &plen);
-           if (pp != 0)
-             o = variable_buffer_output (o, pp, plen);
+#ifdef VMS
+#define IS_PATHSEP(c) ((c) == ']')
+#else
+#ifdef __MSDOS__
+#define IS_PATHSEP(c) ((c) == '/' || (c) == '\\')
+#else
+#define IS_PATHSEP(c) ((c) == '/')
+#endif
+#endif
 
-           if (tp != 0 || pp != 0)
-             {
-               o = variable_buffer_output (o, " ", 1);
-               doneany = 1;
-             }
-         }
-       while (tp != 0 || pp != 0);
-       if (doneany)
-         /* Kill the last blank.  */
-         --o;
-      }
 
-      free (text);
-      free (p);
-      break;
+static char *
+func_notdir_suffix(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  /* Expand the argument.  */
+  char *list_iterator = argv[0];
+  char *p2 =0;
+  int doneany =0;
+  int len=0;
+
+  int is_suffix = streq(funcname, "suffix");
+  int is_notdir = !is_suffix;
+  while ((p2 = find_next_token (&list_iterator, &len)) != 0)
+    {
+      char *p = p2 + len;
 
-    case function_strip:
-      /* Expand the argument.  */
-      text = expand_argument (text, end);
 
-      p2 = text;
-      while (*p2 != '\0')
+      while (p >= p2 && (!is_suffix || *p != '.'))
        {
-         while (isspace(*p2))
-           ++p2;
-         p = p2;
-         for (i=0; *p2 != '\0' && !isspace(*p2); ++p2, ++i)
-           {}
-         if (!i)
+         if (IS_PATHSEP (*p))
            break;
-         o = variable_buffer_output (o, p, i);
+         --p;
+       }
+
+      if (p >= p2)
+       {
+         if (is_notdir)
+           ++p;
+         else if (*p != '.')
+           continue;
+         o = variable_buffer_output (o, p, len - (p - p2));
+       }
+#if defined(WINDOWS32) || defined(__MSDOS__)
+      /* Handle the case of "d:foo/bar".  */
+      else if (streq(funcname, "notdir") && p2[0] && p2[1] == ':')
+       {
+         p = p2 + 2;
+         o = variable_buffer_output (o, p, len - (p - p2));
+       }
+#endif
+      else if (is_notdir)
+       o = variable_buffer_output (o, p2, len);
+
+      if (is_notdir || p >= p2)
+       {
          o = variable_buffer_output (o, " ", 1);
          doneany = 1;
        }
-      if (doneany)
-       /* Kill the last space.  */
+    }
+  if (doneany)
+    /* Kill last space.  */
+    --o;
+
+
+  return o;
+
+}
+
+
+static char *
+func_basename_dir(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  /* Expand the argument.  */
+  char *p3 = argv[0];
+  char *p2=0;
+  int doneany=0;
+  int len=0;
+  char *p=0;
+  int is_basename= streq(funcname, "basename");
+  int is_dir= !is_basename;
+
+  while ((p2 = find_next_token (&p3, &len)) != 0)
+       {
+         p = p2 + len;
+         while (p >= p2 && (!is_basename  || *p != '.'))
+           {
+             if (IS_PATHSEP(*p))
+               break;
+                   --p;
+           }
+
+         if (p >= p2 && (is_dir))
+           o = variable_buffer_output (o, p2, ++p - p2);
+         else if (p >= p2 && (*p == '.'))
+           o = variable_buffer_output (o, p2, p - p2);
+#if defined(WINDOWS32) || defined(__MSDOS__)
+       /* Handle the "d:foobar" case */
+         else if (p2[0] && p2[1] == ':' && is_dir)
+           o = variable_buffer_output (o, p2, 2);
+#endif
+         else if (is_dir)
+#ifdef VMS
+           o = variable_buffer_output (o, "[]", 2);
+#else
+#ifndef _AMIGA
+           o = variable_buffer_output (o, "./", 2);
+#else
+           ; /* Just a nop...  */
+#endif /* AMIGA */
+#endif /* !VMS */
+         else
+           /* The entire name is the basename.  */
+           o = variable_buffer_output (o, p2, len);
+
+         o = variable_buffer_output (o, " ", 1);
+         doneany = 1;
+       }
+      if (doneany)
+       /* Kill last space.  */
        --o;
 
-      free (text);
+
+ return o;
+}
+
+static char *
+func_addsuffix_addprefix(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  int fixlen = strlen (argv[0]);
+  char *list_iterator = argv[1];
+  int is_addprefix = streq (funcname, "addprefix");
+  int is_addsuffix = !is_addprefix;
+
+  int doneany =0;
+  char *p=0;
+  int len =0;
+
+  while ((p = find_next_token (&list_iterator, &len)) != 0)
+    {
+      if (is_addprefix)
+       o = variable_buffer_output (o, argv[0], fixlen);
+      o = variable_buffer_output (o, p, len);
+      if (is_addsuffix)
+       o = variable_buffer_output (o, argv[0], fixlen);
+      o = variable_buffer_output (o, " ", 1);
+      doneany = 1;
+    }
+
+  if (doneany)
+    /* Kill last space.  */
+    --o;
+
+  return o;
+}
+
+static char *
+func_subst(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  o = subst_expand (o, argv[2], argv[0], argv[1], strlen (argv[0]),
+                   strlen (argv[1]), 0, 0);
+
+  return o;
+}
+
+
+static char *
+func_firstword(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  int i=0;
+  char *words = argv[0];
+  char *p = find_next_token (&words, &i);
+
+  if (p != 0)
+    o = variable_buffer_output (o, p, i);
+
+  return o;
+}
+
+
+static char *
+func_words(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  int i = 0;
+  char *word_iterator = argv[0];
+  char buf[20];
+
+  while (find_next_token (&word_iterator, (unsigned int *) 0) != 0)
+    ++i;
+
+  sprintf (buf, "%d", i);
+  o = variable_buffer_output (o, buf, strlen (buf));
+
+
+  return o;
+}
+
+char *
+strip_whitespace (begpp, endpp)
+     char **begpp;
+     char **endpp;
+{
+  while (isspace (**begpp) && *begpp <= *endpp)
+    (*begpp) ++;
+  while (isspace (**endpp) && *endpp >= *begpp)
+    (*endpp) --;
+  return *begpp;
+}
+
+int
+is_numeric (p)
+     char *p;
+{
+  char *end = p + strlen (p) -1;
+  char *beg = p;
+  strip_whitespace (&p, &end);
+  while (p <= end)
+    {
+      if (!isdigit (*p))
+       return 0;
+
+      p++;
+    }
+
+  return (end - beg >= 0);
+}
+
+void
+check_numeric (s, message)
+     char *s;
+     char *message;
+{
+  if (!is_numeric (s))
+    fatal (reading_file, message);
+}
+
+
+
+static char *
+func_word(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  char *end_p=0;
+  int i=0;
+  char *p=0;
+
+  /* Check the first argument.  */
+  check_numeric (argv[0], "non-numeric first argument to `word' function");
+  i =  atoi (argv[0]);
+
+  if (i == 0)
+    fatal (reading_file, "the `word' function takes a positive index argument");
+
+
+  end_p = argv[1];
+  while ((p = find_next_token (&end_p, 0)) != 0)
+    if (--i == 0)
       break;
 
-    case function_wildcard:
-      text = expand_argument (text, end);
+  if (i == 0)
+    o = variable_buffer_output (o, p, end_p - p);
+
+  return o;
+}
+
+static char *
+func_wordlist (o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  int i=0;
+  int j=0;
+
+  /* Check the first argument.  */
+  check_numeric (argv[0],
+                "non-numeric first argument to `wordlist' function");
+  i =atoi(argv[0]);
+  check_numeric (argv[1],
+                "non-numeric second argument to `wordlist' function");
+
+  j = atoi(argv[1]);
+
+
+  {
+    char *p;
+    char *end_p = argv[2];
+
+    int start = (i < j) ? i : j;
+    int count = j -i ;
+    if (count < 0)
+      count = - count;
+    count ++;
+
+
+
+    while (((p = find_next_token (&end_p, 0)) != 0) && --start)
+      {}
+    if (p)
+      {
+       while (--count && (find_next_token (&end_p, 0) != 0))
+         {}
+       o = variable_buffer_output (o, p, end_p - p);
+      }
+  }
+  return o;
+}
+
+static char*
+func_findstring(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  /* Find the first occurrence of the first string in the second.  */
+  int i = strlen (argv[0]);
+  if (sindex (argv[1], 0, argv[0], i) != 0)
+    o = variable_buffer_output (o, argv[0], i);
+
+  return o;
+}
+
+static char *
+func_foreach (o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  /* expand only the first two.  */
+  char *varname = expand_argument (argv[0], argv[1] -1);
+  char *list = expand_argument (argv[1], argv[2] -1);
+  char *body = savestring (argv[2], argv[3] - argv[2] -1 );
+
+  int len =0;
+  char *list_iterator = list;
+  char *p;
+  register struct variable *var=0;
+  int doneany =0;
+
+  push_new_variable_scope ();
+  var = define_variable (varname, strlen (varname), "", o_automatic, 0);
+
+  /* loop through LIST,  put the value in VAR and expand BODY */
+  while ((p = find_next_token (&list_iterator, &len)) != 0)
+    {
+      char *result = 0;
+
+      {
+       char save = p[len];
+
+       p[len] = '\0';
+       free (var->value);
+       var->value = (char *) xstrdup ((char*) p);
+       p[len] = save;
+      }
+
+      result = allocated_variable_expand (body);
+
+      o = variable_buffer_output (o, result, strlen (result));
+      o = variable_buffer_output (o, " ", 1);
+      doneany = 1;
+      free (result);
+    }
+
+  if (doneany)
+    /* Kill the last space.  */
+    --o;
+
+  pop_variable_scope ();
+  free (varname);
+  free (list);
+  free (body);
+
+  return o;
+}
+
+struct a_word
+{
+  struct a_word *next;
+  char *str;
+  int matched;
+};
+
+static char *
+func_filter_filterout (o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  struct  a_word   *wordhead =0;
+  struct  a_word *wordtail =0;
+
+  int is_filter = streq (funcname, "filter");
+  char *patterns = argv[0];
+
+
+  char *p;
+  int len;
+  char *word_iterator = argv[1];
+
+  /* Chop ARGV[1] up into words and then run each pattern through.  */
+  while ((p = find_next_token (&word_iterator, &len)) != 0)
+    {
+      struct a_word *w = (struct a_word *)alloca(sizeof(struct a_word));
+      if (wordhead == 0)
+       wordhead = w;
+      else
+       wordtail->next = w;
+      wordtail = w;
+
+      if (*word_iterator != '\0')
+       ++word_iterator;
+      p[len] = '\0';
+      w->str = p;
+      w->matched = 0;
+    }
+
+  if (wordhead != 0)
+    {
+      struct  a_word *wp =0;
+      char *pat_iterator = patterns;
+      int doneany = 0;
+
+      wordtail->next = 0;
+
+
+      /* Run each pattern through the words, killing words.  */
+      while ((p = find_next_token (&pat_iterator, &len)) != 0)
+       {
+         char *percent;
+         char save = p[len];
+         p[len] = '\0';
+
+         percent = find_percent (p);
+         for (wp = wordhead; wp != 0; wp = wp->next)
+           wp->matched |= (percent == 0 ? streq (p, wp->str)
+                           : pattern_matches (p, percent, wp->str));
+
+         p[len] = save;
+       }
+
+      /* Output the words that matched (or didn't, for filter-out).  */
+      for (wp = wordhead; wp != 0; wp = wp->next)
+       if (is_filter ? wp->matched : !wp->matched)
+         {
+           o = variable_buffer_output (o, wp->str, strlen (wp->str));
+           o = variable_buffer_output (o, " ", 1);
+           doneany = 1;
+         }
+
+      if (doneany)
+       /* Kill the last space.  */
+       --o;
+    }
+
+  return o;
+}
+
+
+static char *
+func_strip(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  char *p = argv[0];
+  int doneany =0;
+
+  while (*p != '\0')
+    {
+      int i=0;
+      char *word_start=0;
+
+      while (isspace(*p))
+       ++p;
+      word_start = p;
+      for (i=0; *p != '\0' && !isspace(*p); ++p, ++i)
+       {}
+      if (!i)
+       break;
+      o = variable_buffer_output (o, word_start, i);
+      o = variable_buffer_output (o, " ", 1);
+      doneany = 1;
+    }
+
+  if (doneany)
+    /* Kill the last space.  */
+    --o;
+  return o;
+}
+
+/*
+  Print a warning or fatal message.
+*/
+static char *
+func_error (o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  char **argvp;
+  char *msg, *p;
+  int len;
+
+  /* The arguments will be broken on commas.  Rather than create yet
+     another special case where function arguments aren't broken up,
+     just create a format string that puts them back together.  */
+  for (len=0, argvp=argv; *argvp != 0; ++argvp)
+    len += strlen(*argvp) + 2;
+
+  p = msg = alloca (len + 1);
+
+  for (argvp=argv; argvp[1] != 0; ++argvp)
+    {
+      strcpy(p, *argvp);
+      p += strlen(*argvp);
+      *(p++) = ',';
+      *(p++) = ' ';
+    }
+  strcpy(p, *argvp);
+
+  if (*funcname == 'e')
+    fatal (reading_file, "%s", msg);
+
+  /* The warning function expands to the empty string.  */
+  error (reading_file, "%s", msg);
+
+  return o;
+}
+
+
+/*
+  chop argv[0] into words, and sort them.
+ */
+static char *
+func_sort (o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  char **words = 0;
+  int nwords = 0;
+  register int wordi = 0;
+
+  /* Chop ARGV[0] into words and put them in WORDS.  */
+  char *t = argv[0];
+  char *p=0;
+  int len;
+  while ((p = find_next_token (&t, &len)) != 0)
+    {
+      if (wordi >= nwords - 1)
+       {
+         nwords = 2* nwords + 5;
+         words = (char **) xrealloc ((char *) words,
+                                     nwords * sizeof (char *));
+       }
+      words[wordi++] = savestring (p, len);
+    }
+
+  if (wordi > 0)
+    {
+      int i;
+      /* Now sort the list of words.  */
+      qsort ((char *) words, wordi, sizeof (char *), alpha_compare);
+
+      /* Now write the sorted list.  */
+      for (i = 0; i < wordi; ++i)
+       {
+         len = strlen (words[i]);
+         if (i == wordi - 1 || strlen (words[i + 1]) != len
+             || strcmp (words[i], words[i + 1]))
+           {
+             o = variable_buffer_output (o, words[i], len);
+             o = variable_buffer_output (o, " ", 1);
+           }
+         free (words[i]);
+       }
+      /* Kill the last space.  */
+      --o;
+    }
+
+  free ((char *) words);
+  return o;
+}
+
+static char *
+func_wildcard(o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
 
 #ifdef _AMIGA
-      o = wildcard_expansion (text, o);
+   o = wildcard_expansion (argv[0], o);
 #else
-      p = string_glob (text);
-      o = variable_buffer_output (o, p, strlen (p));
+   char *p = string_glob (argv[0]);
+   o = variable_buffer_output (o, p, strlen (p));
 #endif
+   return o;
+}
 
-      free (text);
-      break;
+/*
+  \r  is replaced on UNIX as well. Is this desirable?
+ */
+void
+fold_newlines (buffer, length)
+     char *buffer;
+     int *length;
+{
+  char *dst = buffer;
+  char *src = buffer;
+  char *last_nonnl = buffer -1;
+  src[*length] = 0;
+  for (; *src != '\0'; ++src)
+    {
+      if (src[0] == '\r' && src[1] == '\n')
+       continue;
+      if (*src == '\n')
+       {
+         *dst++ = ' ';
+       }
+      else
+       {
+         last_nonnl = dst;
+         *dst++ = *src;
+       }
+    }
+  *(++last_nonnl) = '\0';
+  *length = last_nonnl - buffer;
+}
+
+
+
+int shell_function_pid = 0, shell_function_completed;
+
+
+#ifdef WINDOWS32
+/*untested*/
+
+#include <windows.h>
+#include <io.h>
+#include "sub_proc.h"
+
+
+void
+windows32_openpipe (int *pipedes, int *pid_p, char **command_argv, char **envp)
+{
+  SECURITY_ATTRIBUTES saAttr;
+  HANDLE hIn;
+  HANDLE hErr;
+  HANDLE hChildOutRd;
+  HANDLE hChildOutWr;
+  HANDLE hProcess;
+
+
+  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+  saAttr.bInheritHandle = TRUE;
+  saAttr.lpSecurityDescriptor = NULL;
+
+  if (DuplicateHandle(GetCurrentProcess(),
+                     GetStdHandle(STD_INPUT_HANDLE),
+                     GetCurrentProcess(),
+                     &hIn,
+                     0,
+                     TRUE,
+                     DUPLICATE_SAME_ACCESS) == FALSE) {
+    fatal (NILF, "create_child_process: DuplicateHandle(In) failed (e=%d)\n",
+          GetLastError());
+
+  }
+  if (DuplicateHandle(GetCurrentProcess(),
+                     GetStdHandle(STD_ERROR_HANDLE),
+                     GetCurrentProcess(),
+                     &hErr,
+                     0,
+                     TRUE,
+                     DUPLICATE_SAME_ACCESS) == FALSE) {
+    fatal (NILF, "create_child_process: DuplicateHandle(Err) failed (e=%d)\n",
+          GetLastError());
+  }
+
+  if (!CreatePipe(&hChildOutRd, &hChildOutWr, &saAttr, 0))
+    fatal (NILF, "CreatePipe() failed (e=%d)\n", GetLastError());
+
+
+
+  hProcess = process_init_fd(hIn, hChildOutWr, hErr);
+
+  if (!hProcess)
+    fatal (NILF, "windows32_openpipe (): process_init_fd() failed\n");
+
+  else
+    process_register(hProcess);
+
+  /* make sure that CreateProcess() has Path it needs */
+  sync_Path_environment();
+
+  if (!process_begin(hProcess, command_argv, envp, command_argv[0], NULL))
+    *pid_p = (int) hProcess;
+  else
+    fatal (NILF, "windows32_openpipe (): unable to launch process (e=%d)\n",
+          process_last_err(hProcess));
+
+  /* set up to read data from child */
+  pipedes[0] = _open_osfhandle((long) hChildOutRd, O_RDONLY);
+
+  /* this will be closed almost right away */
+  pipedes[1] = _open_osfhandle((long) hChildOutWr, O_APPEND);
+}
+#endif
+
+
+#ifdef __MSDOS__
+/*
+  untested
+*/
+int
+msdos_openpipe (int* pipedes, int *pidp, char *text)
+{
+  FILE *fpipe=0;
+  /* MSDOS can't fork, but it has `popen'.
+     (Bwt, why isn't `popen' used in all the versions?) */
+  struct variable *sh = lookup_variable ("SHELL", 5);
+  int e;
+  extern int dos_command_running, dos_status;
+
+  /* Make sure not to bother processing an empty line.  */
+  while (isblank (*text))
+    ++text;
+  if (*text == '\0')
+    return 0;
+
+  if (sh)
+    {
+      char buf[PATH_MAX + 7];
+      /* This makes sure $SHELL value is used by $(shell), even
+        though the target environment is not passed to it.  */
+      sprintf (buf, "SHELL=%s", sh->value);
+      putenv (buf);
+    }
+
+  e = errno;
+  errno = 0;
+  dos_command_running = 1;
+  dos_status = 0;
+  fpipe = popen (text, "rt");
+  dos_command_running = 0;
+  if (!fpipe || dos_status)
+    {
+      pipedes[0] = -1;
+      *pidp = -1;
+      if (dos_status)
+       errno = EINTR;
+      else if (errno == 0)
+       errno = ENOMEM;
+      shell_function_completed = -1;
+    }
+  else
+    {
+      pipedes[0] = fileno (fpipe);
+      *pidp = 42;              /* uh? The meaning of Life?*/
+      errno = e;
+      shell_function_completed = 1;
+    }
+  return fpipe;
+}
+#endif
+
+/*
+  Do shell spawning, with the naughty bits for different OSes.
+ */
+
+#ifdef VMS
+
+/* VMS can't do $(shell ...)  */
+#define func_shell 0
+
+#else
+#ifndef _AMIGA
+static char *
+func_shell (o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  char* batch_filename = NULL;
+  int i;
+
+#ifdef __MSDOS__
+  FILE *fpipe;
+#endif
+  char **command_argv;
+  char *error_prefix;
+  char **envp;
+  int pipedes[2];
+  int pid;
+
+#ifndef __MSDOS__
+  /* Construct the argument list.  */
+  command_argv = construct_command_argv (argv[0],
+                                        (char **) NULL, (struct file *) 0,
+                                         &batch_filename);
+  if (command_argv == 0)
+    return o;
+#endif
+
+  /* Using a target environment for `shell' loses in cases like:
+     export var = $(shell echo foobie)
+     because target_environment hits a loop trying to expand $(var)
+     to put it in the environment.  This is even more confusing when
+     var was not explicitly exported, but just appeared in the
+     calling environment.  */
+
+  envp = environ;
+
+  /* For error messages.  */
+  if (reading_file != 0)
+    {
+       error_prefix = (char *) alloca (strlen(reading_file->filenm)+100);
+
+      sprintf (error_prefix,
+              "%s:%lu: ", reading_file->filenm, reading_file->lineno);
+
+    }
+  else
+    error_prefix = "";
+
+#ifdef WINDOWS32
+  windows32_openpipe (pipedes, &pid, command_argv, envp);
+#else /* WINDOWS32 */
+
+# ifdef __MSDOS__
+  fpipe = msdos_openpipe (pipedes, argv[0], command_argv, envp);
+  if (!fpipe || pipedes[0] < 0)
+    {
+       perror_with_name (error_prefix, "pipe");
+       return o;
+    }
+# else
+  if (pipe (pipedes) < 0)
+    {
+      perror_with_name (error_prefix, "pipe");
+      return o;
+    }
 
-    case function_subst:
-      /* Get three comma-separated arguments and expand each one.  */
-      count = 0;
-      for (p = text; p < end; ++p)
-       {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
-       }
-      if (p == end)
-       BADARGS ("subst");
+  pid = vfork ();
+  if (pid < 0)
+    perror_with_name (error_prefix, "fork");
+  else if (pid == 0)
+    child_execute_job (0, pipedes[1], command_argv, envp);
+  else
+# endif /* ! __MSDOS__ */
 
-      p2 = p;
-      count = 0;
-      for (++p; p < end; ++p)
-       {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
-       }
-      if (p == end)
-       BADARGS ("subst");
+#endif /* WINDOWS32 */
+    {
+      /* We are the parent.  */
 
-      text = expand_argument (text, p2);
-      p3 = expand_argument (p2 + 1, p);
-      p2 = expand_argument (p + 1, end);
+      char *buffer;
+      unsigned int maxlen;
+      int cc;
 
-      o = subst_expand (o, p2, text, p3, strlen (text), strlen (p3), 0, 0);
+      /* Record the PID for reap_children.  */
+      shell_function_pid = pid;
+#ifndef  __MSDOS__
+      shell_function_completed = 0;
 
-      free (text);
-      free (p3);
-      free (p2);
-      break;
+      /* Free the storage only the child needed.  */
+      free (command_argv[0]);
+      free ((char *) command_argv);
 
-    case function_firstword:
-      /* Expand the argument.  */
-      text = expand_argument (text, end);
+      /* Close the write side of the pipe.  */
+      (void) close (pipedes[1]);
+#endif
 
-      /* Find the first word in TEXT.  */
-      p2 = text;
-      p = find_next_token (&p2, &i);
-      if (p != 0)
-       o = variable_buffer_output (o, p, i);
+      /* Set up and read from the pipe.  */
 
-      free (text);
-      break;
+      maxlen = 200;
+      buffer = (char *) xmalloc (maxlen + 1);
 
-    case function_word:
-      /* Get two comma-separated arguments and expand each one.  */
-      count = 0;
-      for (p = text; p < end; ++p)
+      /* Read from the pipe until it gets EOF.  */
+      i = 0;
+      do
        {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
+         if (i == maxlen)
+           {
+             maxlen += 512;
+             buffer = (char *) xrealloc (buffer, maxlen + 1);
+           }
+
+         errno = 0;
+         cc = read (pipedes[0], &buffer[i], maxlen - i);
+         if (cc > 0)
+           i += cc;
        }
-      if (p == end)
-       BADARGS ("word");
-      text = expand_argument (text, p);
-
-      p3 = expand_argument (p + 1, end);
-
-      /* Check the first argument.  */
-      for (p2 = text; *p2 != '\0'; ++p2)
-       if (*p2 < '0' || *p2 > '9')
-          fatal (reading_file,
-                 "non-numeric first argument to `word' function");
-
-      i = (unsigned int) atoi (text);
-      if (i == 0)
-        fatal (reading_file,
-               "the `word' function takes a one-origin index argument");
-
-      p2 = p3;
-      while ((p = find_next_token (&p2, &len)) != 0)
-       if (--i == 0)
-         break;
-      if (i == 0)
-       o = variable_buffer_output (o, p, len);
-
-      free (text);
-      free (p3);
-      break;
+#ifdef EINTR
+      while (cc > 0 || errno == EINTR);
+#else
+      while (cc > 0);
+#endif
 
-    case function_words:
-      /* Expand the argument.  */
-      text = expand_argument (text, end);
+      /* Close the read side of the pipe.  */
+#ifdef  __MSDOS__
+      if (fpipe)
+       (void) pclose (fpipe);
+#else
+      (void) close (pipedes[0]);
+#endif
 
-      i = 0;
-      p2 = text;
-      while (find_next_token (&p2, (unsigned int *) 0) != 0)
-       ++i;
+      /* Loop until child_handler sets shell_function_completed
+        to the status of our child shell.  */
+      while (shell_function_completed == 0)
+       reap_children (1, 0);
 
-      {
-       char buf[20];
-       sprintf (buf, "%d", i);
-       o = variable_buffer_output (o, buf, strlen (buf));
+      if (batch_filename) {
+       if (debug_flag)
+         printf("Cleaning up temporary batch file %s\n", batch_filename);
+       remove(batch_filename);
+       free(batch_filename);
       }
+      shell_function_pid = 0;
 
-      free (text);
-      break;
+      /* The child_handler function will set shell_function_completed
+        to 1 when the child dies normally, or to -1 if it
+        dies with status 127, which is most likely an exec fail.  */
 
-    case function_wordlist:
-      /* Get two comma-separated arguments and expand each one.  */
-      count = 0;
-      for (p = text; p < end; ++p)
-       {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
-       }
-      if (p == end)
-       BADARGS ("wordlist");
-      text = expand_argument (text, p);
-
-      /* Check the first argument.  */
-      for (p2 = text; *p2 != '\0'; ++p2)
-       if (*p2 < '0' || *p2 > '9')
-          fatal (reading_file,
-                 "non-numeric first argument to `wordlist' function");
-      i = (unsigned int)atoi(text);
-      free (text);
-
-      /* Check the next argument */
-      for (p2 = p + 1; isblank(*p2); ++p2)
-       {}
-      count = 0;
-      for (p = p2; p < end; ++p)
+      if (shell_function_completed == -1)
        {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
+         /* This most likely means that the execvp failed,
+            so we should just write out the error message
+            that came in over the pipe from the child.  */
+         fputs (buffer, stderr);
+         fflush (stderr);
        }
-      if (p == end)
-       BADARGS ("wordlist");
-      text = expand_argument (p2, p);
-
-      for (p2 = text; *p2 != '\0'; ++p2)
-       if (*p2 < '0' || *p2 > '9')
-          fatal (reading_file,
-                 "non-numeric second argument to `wordlist' function");
-      j = (unsigned int)atoi(text);
-      free (text);
-
-      if (j > i)
-       j -= i;
       else
        {
-         unsigned int k;
-         k = j;
-         j = i - j;
-         i = k;
+         /* The child finished normally.  Replace all
+            newlines in its output with spaces, and put
+            that in the variable output buffer.  */
+         fold_newlines (buffer, &i);
+         o = variable_buffer_output (o, buffer, i);
        }
-      ++j;
 
-      /* Extract the requested words */
-      text = expand_argument (p + 1, end);
-      p2 = text;
+      free (buffer);
+    }
 
-      while (((p = find_next_token (&p2, &len)) != 0) && --i)
-       {}
-      if (p)
-       {
-         while (--j && (find_next_token (&p2, &len) != 0))
-           {}
-         o = variable_buffer_output (o, p, p2 - p);
-       }
+  return o;
+}
 
-      free (text);
-      break;
+#else  /* _AMIGA */
 
-    case function_findstring:
-      /* Get two comma-separated arguments and expand each one.  */
-      count = 0;
-      for (p = text; p < end; ++p)
-       {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
-       }
-      if (p == end)
-       BADARGS ("findstring");
-      text = expand_argument (text, p);
+/* Do the Amiga version of func_shell.  */
 
-      p = expand_argument (p + 1, end);
+static char *
+func_shell (char *o, char **argv, const char *funcname)
+{
+  /* Amiga can't fork nor spawn, but I can start a program with
+     redirection of my choice.  However, this means that we
+     don't have an opportunity to reopen stdout to trap it.  Thus,
+     we save our own stdout onto a new descriptor and dup a temp
+     file's descriptor onto our stdout temporarily.  After we
+     spawn the shell program, we dup our own stdout back to the
+     stdout descriptor.  The buffer reading is the same as above,
+     except that we're now reading from a file.  */
 
-      /* Find the first occurrence of the first string in the second.  */
-      i = strlen (text);
-      if (sindex (p, 0, text, i) != 0)
-       o = variable_buffer_output (o, text, i);
+#include <dos/dos.h>
+#include <proto/dos.h>
 
-      free (p);
-      free (text);
-      break;
+  BPTR child_stdout;
+  char tmp_output[FILENAME_MAX];
+  unsigned int maxlen = 200;
+  int cc, i;
+  char * buffer, * ptr;
+  char ** aptr;
+  int len = 0;
+  char* batch_filename = NULL;
 
-    case function_addsuffix:
-    case function_addprefix:
-      /* Get two comma-separated arguments and expand each one.  */
-      count = 0;
-      for (p = text; p < end; ++p)
-       {
-         if (*p == startparen)
-           ++count;
-         else if (*p == endparen)
-           --count;
-         else if (*p == ',' && count <= 0)
-           break;
-       }
-      if (p == end)
-       BADARGS (function == function_addsuffix ? "addsuffix" : "addprefix");
-      text = expand_argument (text, p);
-      i = strlen (text);
+  /* Construct the argument list.  */
+  command_argv = construct_command_argv (argv[0], (char **) NULL,
+                                         (struct file *) 0, &batch_filename);
+  if (command_argv == 0)
+    return o;
 
-      p2 = expand_argument (p + 1, end);
 
-      p3 = p2;
-      while ((p = find_next_token (&p3, &len)) != 0)
-       {
-         if (function == function_addprefix)
-           o = variable_buffer_output (o, text, i);
-         o = variable_buffer_output (o, p, len);
-         if (function == function_addsuffix)
-           o = variable_buffer_output (o, text, i);
-         o = variable_buffer_output (o, " ", 1);
-         doneany = 1;
-       }
-      if (doneany)
-       /* Kill last space.  */
-       --o;
+  strcpy (tmp_output, "t:MakeshXXXXXXXX");
+  mktemp (tmp_output);
+  child_stdout = Open (tmp_output, MODE_NEWFILE);
 
-      free (p2);
-      free (text);
-      break;
+  for (aptr=command_argv; *aptr; aptr++)
+    len += strlen (*aptr) + 1;
 
-    case function_dir:
-    case function_basename:
-      /* Expand the argument.  */
-      text = expand_argument (text, end);
+  buffer = xmalloc (len + 1);
+  ptr = buffer;
 
-      p3 = text;
-      while ((p2 = find_next_token (&p3, &len)) != 0)
-       {
-         p = p2 + len;
-#ifdef VMS
-         while (p >= p2 && *p != ']'
-                && (function != function_basename || *p != '.'))
-#else
-# ifdef __MSDOS__
-         while (p >= p2 && *p != '/' && *p != '\\'
-                && (function != function_basename || *p != '.'))
-# else
-         while (p >= p2 && *p != '/'
-                && (function != function_basename || *p != '.'))
-# endif
-#endif
-           --p;
-         if (p >= p2 && (function == function_dir))
-           o = variable_buffer_output (o, p2, ++p - p2);
-         else if (p >= p2 && (*p == '.'))
-           o = variable_buffer_output (o, p2, p - p2);
-#if defined(WINDOWS32) || defined(__MSDOS__)
-       /* Handle the "d:foobar" case */
-         else if (p2[0] && p2[1] == ':' && function == function_dir)
-           o = variable_buffer_output (o, p2, 2);
-#endif
-         else if (function == function_dir)
-#ifdef VMS
-           o = variable_buffer_output (o, "[]", 2);
-#else
-#ifndef _AMIGA
-           o = variable_buffer_output (o, "./", 2);
-#else
-           /* o = o */; /* Just a nop...  */
-#endif /* AMIGA */
-#endif /* !VMS */
-         else
-           /* The entire name is the basename.  */
-           o = variable_buffer_output (o, p2, len);
+  for (aptr=command_argv; *aptr; aptr++)
+    {
+      strcpy (ptr, *aptr);
+      ptr += strlen (ptr) + 1;
+      *ptr ++ = ' ';
+      *ptr = 0;
+    }
 
-         o = variable_buffer_output (o, " ", 1);
-         doneany = 1;
-       }
-      if (doneany)
-       /* Kill last space.  */
-       --o;
+  ptr[-1] = '\n';
 
-      free (text);
-      break;
+  Execute (buffer, NULL, child_stdout);
+  free (buffer);
 
-    case function_notdir:
-    case function_suffix:
-      /* Expand the argument.  */
-      text = expand_argument (text, end);
+  Close (child_stdout);
 
-      p3 = text;
-      while ((p2 = find_next_token (&p3, &len)) != 0)
-       {
-         p = p2 + len;
-#ifdef VMS
-         while (p >= p2 && *p != ']'
-                && (function != function_suffix || *p != '.'))
-#else
-# ifdef __MSDOS__
-         while (p >= p2 && *p != '/' && *p != '\\'
-                && (function != function_suffix || *p != '.'))
-# else
-         while (p >= p2 && *p != '/'
-                && (function != function_suffix || *p != '.'))
-# endif
-#endif
-           --p;
-         if (p >= p2)
-           {
-             if (function == function_notdir)
-               ++p;
-             else if (*p != '.')
-               continue;
-             o = variable_buffer_output (o, p, len - (p - p2));
-           }
-#if defined(WINDOWS32) || defined(__MSDOS__)
-         /* Handle the case of "d:foo/bar".  */
-         else if (function == function_notdir && p2[0] && p2[1] == ':')
-           {
-             p = p2 + 2;
-             o = variable_buffer_output (o, p, len - (p - p2));
-           }
-#endif
-         else if (function == function_notdir)
-           o = variable_buffer_output (o, p2, len);
+  child_stdout = Open (tmp_output, MODE_OLDFILE);
 
-         if (function == function_notdir || p >= p2)
-           {
-             o = variable_buffer_output (o, " ", 1);
-             doneany = 1;
-           }
+  buffer = xmalloc (maxlen);
+  i = 0;
+  do
+    {
+      if (i == maxlen)
+       {
+         maxlen += 512;
+         buffer = (char *) xrealloc (buffer, maxlen + 1);
        }
-      if (doneany)
-       /* Kill last space.  */
-       --o;
 
-      free (text);
-      break;
-    }
+      cc = Read (child_stdout, &buffer[i], maxlen - i);
+      if (cc > 0)
+       i += cc;
+    } while (cc > 0);
+
+  Close (child_stdout);
 
+  fold_newlines (buffer, &i);
+  o = variable_buffer_output (o, buffer, i);
+  free (buffer);
   return o;
 }
-\f
-/* Check for a function invocation in *STRINGP.  *STRINGP points at the
-   opening ( or { and is not null-terminated.  If a function invocation
-   is found, expand it into the buffer at *OP, updating *OP, incrementing
-   *STRINGP past the reference and returning nonzero.  If not, return zero.  */
+#endif  /* _AMIGA */
+#endif  /* !VMS */
 
-int
-handle_function (op, stringp)
-     char **op;
-     char **stringp;
+#ifdef EXPERIMENTAL
 
+/*
+  equality. Return is string-boolean, ie, the empty string is false.
+ */
+static char *
+func_eq (char* o, char **argv, char *funcname)
 {
-  register unsigned int code;
-  unsigned int maxlen;
-  char *beg = *stringp + 1;
-  char *endref;
+  int result = ! strcmp (argv[0], argv[1]);
+  o = variable_buffer_output (o,  result ? "1" : "", result);
+  return o;
+}
 
-  endref = lindex (beg, beg + MAXFUNCTIONLEN, '\0');
-  maxlen = endref != 0 ? endref - beg : MAXFUNCTIONLEN;
 
-  for (code = 0; function_table[code].name != 0; ++code)
-    {
-      if (maxlen < function_table[code].len)
-       continue;
-      endref = beg + function_table[code].len;
-      if (isblank (*endref)
-         && !strncmp (function_table[code].name, beg,
-                      function_table[code].len))
-       break;
-    }
-  if (function_table[code].name != 0)
-    {
-      /* We have found a call to an expansion-time function.
-        Find the end of the arguments, and do the function.  */
+/*
+  string-boolean not operator.
+ */
+static char *
+func_not (char* o, char **argv, char *funcname)
+{
+  char * s = argv[0];
+  int result = 0;
+  while (isspace (*s))
+    s++;
+  result = ! (*s);
+  o = variable_buffer_output (o,  result ? "1" : "", result);
+  return o;
+}
 
-      char openparen = beg[-1], closeparen = openparen == '(' ? ')' : '}';
-      int count = 0;
-      char *argbeg;
-      register char *p;
 
-      /* Space after function name isn't part of the args.  */
-      p = next_token (endref);
-      argbeg = p;
 
-      /* Count nested use of whichever kind of parens we use,
-        so that nested calls and variable refs work.  */
+/*
+ This is an experimental conditional function.
 
-      for (; *p != '\0'; ++p)
-       {
-         if (*p == openparen)
-           ++count;
-         else if (*p == closeparen && --count < 0)
-           break;
-       }
+ Syntax:
 
-      if (count >= 0)
-        fatal (reading_file,
-               "unterminated call to function `%s': missing `%c'",
-               function_table[code].name, closeparen);
+    $(if condition, true-part, false-part)
 
-      /* We found the end; expand the function call.  */
+ This is fully not consistent with make's syntax, but more in line
+ with `normal' programming languages.
 
-      *op = expand_function (*op, function_table[code].function, argbeg, p);
-      *stringp = p;
-      return 1;
-    }
+ Semantics:
 
-  return 0;
-}
-\f
-/* Glob-expand LINE.  The returned pointer is
-   only good until the next call to string_glob.  */
+ - CONDITION is false iff it evaluates to an empty string.  White
+ space before and after condition are stripped before evaluation.
+
+ - If CONDITION is true, then TRUE-PART is evaluated, otherwise
+ FALSE-PART is evaluated.  Because only one of the two PARTs is
+ evaluated, you can use $(if ) to create side-effects with the
+ $(shell ) function
 
+ */
 static char *
-string_glob (line)
-     char *line;
+func_if (char* o, char **argv, char *funcname)
 {
-  static char *result = 0;
-  static unsigned int length;
-  register struct nameseq *chain;
-  register unsigned int idx;
+  char *begp = argv[0];
+  char *endp = argv[1]-2;
+  char *expansion =0;
+  int result = 0;
 
-  chain = multi_glob (parse_file_seq
-                     (&line, '\0', sizeof (struct nameseq),
-                      /* We do not want parse_file_seq to strip `./'s.
-                         That would break examples like:
-                         $(patsubst ./%.c,obj/%.o,$(wildcard ./*.c)).  */
-                      0),
-                     sizeof (struct nameseq));
+  strip_whitespace (&begp, &endp);
+  if(begp <= endp)
+    expansion = expand_argument (begp, endp + 1);
 
-  if (result == 0)
-    {
-      length = 100;
-      result = (char *) xmalloc (100);
-    }
+  result = expansion
+    ? strlen (expansion)
+    : 0;
 
-  idx = 0;
-  while (chain != 0)
+  result = !result;
+  free (expansion);
+
+  expansion = expand_argument (argv[1 + result], argv[2+result] -1);
+  o = variable_buffer_output (o, expansion, strlen (expansion));
+
+  return o;
+}
+#endif
+
+/* This might not be very useful, but the code was simple to
+  implement, I just had to do it.
+
+  Here goes anyway
+
+  Apply & User defined functions.
+
+  SYNTAX
+
+    $(apply funcname, arg1, arg2, .. )
+
+  SEMANTICS
+
+  You can specify a builtin function, for funcname, eg
+
+    f = addprefix
+    $(apply addprefix,a, b c d)
+
+  This will result in
+
+    ab ac ad
+
+  You can specify your own functions, eg
+
+    funcname=BODY
+
+  BODY contains $(1) .. $(N) as argument markers.
+  upon expansions the strings ARG1 .. ARGN are substituted for $(1) .. $(N)
+  into BODY
+
+  Because the funcname is computed as well you can combine this do some
+  funky things, eg
+
+    map=$(foreach a, $(2), $(apply $(1), $(a)))
+
+
+  LIMITATIONS.
+
+  Make has no support for nested lists (or tuples), so you can't do
+  stuff like (Haskell notation):
+
+    f :: (a,b) -> c                   -- type of F
+    map :: (a->b) -> [a] -> b         -- type of MAP
+
+    map f [(1,2), (2,3)]              -- map F over list containing (1,2) and (2,3)
+
+  to get
+
+    [f (1, 2), f (2, 3)]
+
+
+  If only we had nested lists and quotes, we could duplicate LISP in make by
+  transforming
+
+    $(a, b, c) <->  (a b c)
+    $(quote $(a, b, c)) <-> '(a b c)
+
+  (or something alike ;-) (We could have automatic integration of
+  GUILE and make :-)
+
+  [Actually -- why should this be a joke?  If we could somehow integrate the
+  rules and targets into a functional model make could be a lot cleaner in
+  concept. ]
+
+*/
+char *
+func_apply (o, argv, funcname)
+     char *o;
+     char **argv;
+     const char *funcname;
+{
+  char *userfunc_name;
+  int func_len;
+  char *body = 0;
+  char *expanded_body = 0;
+  int i;
+  const struct function_table_entry *entry_p;
+
+  userfunc_name = argv[0];
+  while (isspace (*userfunc_name))
+    ++userfunc_name;
+
+  entry_p = lookup_function (function_table, userfunc_name);
+
+  /* builtin function? */
+  if (entry_p)
     {
-      register char *name = chain->name;
-      unsigned int len = strlen (name);
+      for (i=0; argv[i+1]; i++)
+       ;
 
-      struct nameseq *next = chain->next;
-      free ((char *) chain);
-      chain = next;
+      o = expand_builtin_function (o, i, argv + 1, entry_p);
+      return o;
+    }
 
-      /* multi_glob will pass names without globbing metacharacters
-        through as is, but we want only files that actually exist.  */
-      if (file_exists_p (name))
-       {
-         if (idx + len + 1 > length)
-           {
-             length += (len + 1) * 2;
-             result = (char *) xrealloc (result, length);
-           }
-         bcopy (name, &result[idx], len);
-         idx += len;
-         result[idx++] = ' ';
-       }
+  func_len = strlen (userfunc_name);
+  body = xmalloc (func_len + 4);
+  strcpy (body + 2, userfunc_name);
+  body [func_len+2]=')';
+  body [func_len+3]= 0;
+  body [1]='(';
+  body [0]='$';
 
-      free (name);
+  push_new_variable_scope ();
+
+  /* set up arguments $(1) .. $(N) */
+  for (i=0; argv[i]; i++)
+    {
+      char num[10];
+      struct variable* var;
+      sprintf (num, "%d", i);
+      var = define_variable (num, strlen (num), argv[i], o_automatic, 0);
     }
 
-  /* Kill the last space and terminate the string.  */
-  if (idx == 0)
-    result[0] = '\0';
-  else
-    result[idx - 1] = '\0';
+  expanded_body =  allocated_variable_expand (body);
+  o = variable_buffer_output (o, expanded_body, strlen (expanded_body));
+  free (expanded_body);
+  pop_variable_scope ();
 
-  return result;
+  free (body);
+  return o;
 }
+\f
+
+#define STRING_SIZE_TUPLE(s) (s), (sizeof(s)-1)
+
+/* Lookup table for builtin functions.
+
+   This doesn't have to be sorted; we use a straight lookup.  We might gain
+   some efficiency by moving most often used functions to the start of the
+   table.
+
+   REQUIRED_ARGUMENTS is the minimum number of arguments.  A function
+   can have more, but they will be ignored.
+
+   EXPAND_ALL_ARGUMENTS means that all arguments should be expanded
+   before invocation.  Functions that do namespace tricks (foreach)
+   don't automatically expand.  */
+
+static struct function_table_entry function_table[] =
+{
+ /* Name/size */                    /* ARG EXP? Function */
+  { STRING_SIZE_TUPLE("addprefix"),     2,  1,  func_addsuffix_addprefix},
+  { STRING_SIZE_TUPLE("addsuffix"),     2,  1,  func_addsuffix_addprefix},
+  { STRING_SIZE_TUPLE("basename"),      1,  1,  func_basename_dir},
+  { STRING_SIZE_TUPLE("dir"),           1,  1,  func_basename_dir},
+  { STRING_SIZE_TUPLE("notdir"),        1,  1,  func_notdir_suffix},
+  { STRING_SIZE_TUPLE("subst"),         3,  1,  func_subst},
+  { STRING_SIZE_TUPLE("suffix"),        1,  1,  func_notdir_suffix},
+  { STRING_SIZE_TUPLE("filter"),        2,  1,  func_filter_filterout},
+  { STRING_SIZE_TUPLE("filter-out"),    2,  1,  func_filter_filterout},
+  { STRING_SIZE_TUPLE("findstring"),    2,  1,  func_findstring},
+  { STRING_SIZE_TUPLE("firstword"),     1,  1,  func_firstword},
+  { STRING_SIZE_TUPLE("join"),          2,  1,  func_join},
+  { STRING_SIZE_TUPLE("patsubst"),      3,  1,  func_patsubst},
+  { STRING_SIZE_TUPLE("shell"),         1,  1,  func_shell},
+  { STRING_SIZE_TUPLE("sort"),          1,  1,  func_sort},
+  { STRING_SIZE_TUPLE("strip"),         1,  1,  func_strip},
+  { STRING_SIZE_TUPLE("wildcard"),      1,  1,  func_wildcard},
+  { STRING_SIZE_TUPLE("word"),          2,  1,  func_word},
+  { STRING_SIZE_TUPLE("wordlist"),      3,  1,  func_wordlist},
+  { STRING_SIZE_TUPLE("words"),         1,  1,  func_words},
+  { STRING_SIZE_TUPLE("origin"),        1,  1,  func_origin},
+  { STRING_SIZE_TUPLE("error"),         1,  1,  func_error},
+  { STRING_SIZE_TUPLE("warning"),       1,  1,  func_error},
+  { STRING_SIZE_TUPLE("foreach"),       3,  0,  func_foreach},
+#ifdef EXPERIMENTAL
+  { STRING_SIZE_TUPLE("apply"),         1,  1,  func_apply},
+  { STRING_SIZE_TUPLE("eq"),            2,  1,  func_eq},
+  { STRING_SIZE_TUPLE("if"),            3,  0,  func_if},
+  { STRING_SIZE_TUPLE("not"),           1,  1,  func_not},
+#endif
+  { 0 }
+};
diff --git a/job.c b/job.c
index 87f22a2..5999558 100644 (file)
--- a/job.c
+++ b/job.c
@@ -2030,8 +2030,35 @@ construct_command_argv_internal (line, restp, shell, ifs, batch_filename_ptr)
                  }
              }
            else if (p[1] != '\0')
-             /* Copy and skip the following char.  */
-             *ap++ = *++p;
+              {
+#if defined(__MSDOS__) || defined(WINDOWS32)
+                /* Only remove backslashes before characters special
+                   to Unixy shells.  All other backslashes are copied
+                   verbatim, since they are probably DOS-style
+                   directory separators.  This still leaves a small
+                   window for problems, but at least it should work
+                   for the vast majority of naive users.  */
+
+#ifdef __MSDOS__
+                /* A dot is only special as part of the "..."
+                   wildcard.  */
+                if (strncmp (p + 1, ".\\.\\.", 5) == 0)
+                  {
+                    *ap++ = '.';
+                    *ap++ = '.';
+                    p += 4;
+                  }
+                else
+#endif
+                  if (p[1] != '\\' && p[1] != '\'' && !isspace (p[1])
+                      && (index (sh_chars_sh, p[1]) == 0))
+                    /* back up one notch, to copy the backslash */
+                    --p;
+
+#endif  /* __MSDOS__ || WINDOWS32 */
+                /* Copy and skip the following char.  */
+                *ap++ = *++p;
+              }
            break;
 
          case '\'':
index 246707c..632ba48 100644 (file)
@@ -8,10 +8,10 @@
 @c FSF publishers: format makebook.texi instead of using this file directly.
 
 @set RCSID $Id$
-@set EDITION 0.52
-@set VERSION 3.77
-@set UPDATED 20 May 1998
-@set UPDATE-MONTH May 1998
+@set EDITION 0.53
+@set VERSION 3.78
+@set UPDATED 22 March 1999
+@set UPDATE-MONTH March 1999
 @comment The ISBN number might need to change on next publication.
 @set ISBN 1-882114-80-9 @c CHANGE THIS BEFORE PRINTING AGAIN! --psmith 16jul98
 
@@ -37,7 +37,7 @@ and issues the commands to recompile them.
 This is Edition @value{EDITION}, last updated @value{UPDATED},
 of @cite{The GNU Make Manual}, for @code{make}, Version @value{VERSION}.
 
-Copyright (C) 1988, '89, '90, '91, '92, '93, '94, '95, '96, '97, '98
+Copyright (C) 1988, '89, '90, '91, '92, '93, '94, '95, '96, '97, '98, '99
        Free Software Foundation, Inc.
 
 Permission is granted to make and distribute verbatim copies of
@@ -5166,6 +5166,7 @@ call, just as a variable might be substituted.
 * Foreach Function::            Repeat some text with controlled variation.
 * Origin Function::             Find where a variable got its value.
 * Shell Function::              Substitute the output of a shell command.
+* Make Control Functions::      Functions that control how make runs.
 @end menu
 
 @node Syntax of Functions, Text Functions,  , Functions
@@ -5907,7 +5908,7 @@ Here the redefinition takes place if @samp{$(origin bletch)} returns either
 @samp{environment} or @samp{environment override}.
 @xref{Text Functions, , Functions for String Substitution and Analysis}.
 
-@node Shell Function,  , Origin Function, Functions
+@node Shell Function, Make Control Functions, Origin Function, Functions
 @section The @code{shell} Function
 @findex shell
 @cindex commands, expansion
@@ -5953,6 +5954,60 @@ sets @code{files} to the expansion of @samp{*.c}.  Unless @code{make} is
 using a very strange shell, this has the same result as
 @w{@samp{$(wildcard *.c)}}.@refill
 
+@node Make Control Functions,  , Shell Function, Functions
+@section Functions That Control Make
+@cindex functions, for controlling make
+@cindex controlling make
+
+These functions control the way make runs.  Generally, they are used to
+provide information to the user of the makefile or to cause make to stop
+if some sort of environmental error is detected.
+
+@table @code
+@item $(error @var{text}@dots{})
+@findex error
+@cindex error, stopping on
+@cindex stopping make
+Generates a fatal error where the message is @var{text}.  Note that the
+error is generated whenever this function is evaluated.  So, if you put
+it inside a command script or on the right side of a recursive variable
+assignment, it won't be evaluated until later.  The @var{text} will be
+expanded before the error is generated.
+
+For example,
+
+@example
+ifdef ERROR1
+$(error error is $(ERROR1))
+endif
+@end example
+
+@noindent
+will generate a fatal error during the read of the makefile if the
+@code{make} variable @code{ERROR1} is defined.  Or,
+
+@example
+ERR = $(error found an error!)
+
+.PHONY: err
+err: ; $(ERR)
+@end example
+
+@noindent
+will generate a fatal error while @code{make} is running, if the
+@code{err} target is invoked.
+
+@item $(warning @var{text}@dots{})
+@findex warning
+@cindex warnings, printing
+@cindex printing user warnings
+This function works similarly to the @code{error} function, above,
+except that @code{make} doesn't exit.  Instead, @var{text} is expanded
+and the resulting message is displayed, but processing of the makefile
+continues.
+
+The result of the expansion of this function is the empty string.
+
 @node Running, Implicit Rules, Functions, Top
 @chapter How to Run @code{make}