Module Name:    src
Committed By:   rillig
Date:           Mon Oct  5 19:24:29 UTC 2020

Modified Files:
        src/usr.bin/make: Makefile arch.c compat.c cond.c dir.c enum.c for.c
            hash.c job.c main.c make.h make_malloc.c nonints.h parse.c str.c
            suff.c targ.c trace.c util.c var.c
        src/usr.bin/make/unit-tests: Makefile directive-export-literal.exp
            directive-export-literal.mk directive-ifndef.exp
            directive-ifndef.mk directive-ifnmake.exp directive-ifnmake.mk
            make-exported.exp make-exported.mk opt-debug-file.mk
            opt-debug-for.exp opt-debug-for.mk opt-debug-jobs.exp
            opt-debug-jobs.mk opt-debug-lint.exp opt-debug-lint.mk
            opt-debug-loud.exp opt-debug-loud.mk opt-debug.exp opt-debug.mk
            var-op-append.mk varname-dot-curdir.mk

Log Message:
make(1): fix double-free bug in -DCLEANUP mode (since 2020-10-02)

The bug had been introduced with dir.c 1.155 on 2020-10-02 22:20:25.  In
that commit, openDirectories was replaced with a combination of a list
with a hash table, for more efficient lookup by name.

Upon cleanup, OpenDirs_Done is called, which in turn called
Dir_ClearPath.  Dir_ClearPath takes full ownership of the given list and
empties it.  This was no problem before since afterwards the list was
empty and calling Lst_Free just frees the remaining list pointer.

With OpenDirs, this list was combined with a hash table, and the hash
table contains the list nodes, assuming that the OpenDirs functions have
full ownership of both the list and the hash table.  This assumption was
generally correct, except for the one moment during cleanup where full
ownership of the list was passed to Dir_ClearPath, while the hash table
still contained pointers to the (now freed) list nodes.  This by itself
was not a problem since the hash table would be freed afterwards.  But
as part of Dir_ClearPath, OpenDirs_Remove was called, which looked up
the freed directory by name and now found the freed list node, trying to
free it again.  Boom.

Fixed by replacing the call to Dir_ClearPath with code that only frees
the directories, without giving up control over the list.


To generate a diff of this commit:
cvs rdiff -u -r1.100 -r1.101 src/usr.bin/make/Makefile
cvs rdiff -u -r1.130 -r1.131 src/usr.bin/make/arch.c
cvs rdiff -u -r1.163 -r1.164 src/usr.bin/make/compat.c
cvs rdiff -u -r1.158 -r1.159 src/usr.bin/make/cond.c
cvs rdiff -u -r1.156 -r1.157 src/usr.bin/make/dir.c
cvs rdiff -u -r1.10 -r1.11 src/usr.bin/make/enum.c
cvs rdiff -u -r1.90 -r1.91 src/usr.bin/make/for.c
cvs rdiff -u -r1.41 -r1.42 src/usr.bin/make/hash.c
cvs rdiff -u -r1.258 -r1.259 src/usr.bin/make/job.c
cvs rdiff -u -r1.367 -r1.368 src/usr.bin/make/main.c
cvs rdiff -u -r1.153 -r1.154 src/usr.bin/make/make.h
cvs rdiff -u -r1.21 -r1.22 src/usr.bin/make/make_malloc.c
cvs rdiff -u -r1.138 -r1.139 src/usr.bin/make/nonints.h
cvs rdiff -u -r1.366 -r1.367 src/usr.bin/make/parse.c
cvs rdiff -u -r1.66 -r1.67 src/usr.bin/make/str.c
cvs rdiff -u -r1.174 -r1.175 src/usr.bin/make/suff.c
cvs rdiff -u -r1.109 -r1.110 src/usr.bin/make/targ.c
cvs rdiff -u -r1.17 -r1.18 src/usr.bin/make/trace.c
cvs rdiff -u -r1.61 -r1.62 src/usr.bin/make/util.c
cvs rdiff -u -r1.565 -r1.566 src/usr.bin/make/var.c
cvs rdiff -u -r1.160 -r1.161 src/usr.bin/make/unit-tests/Makefile
cvs rdiff -u -r1.2 -r1.3 \
    src/usr.bin/make/unit-tests/directive-export-literal.exp \
    src/usr.bin/make/unit-tests/directive-ifndef.exp \
    src/usr.bin/make/unit-tests/directive-ifnmake.exp \
    src/usr.bin/make/unit-tests/opt-debug-file.mk \
    src/usr.bin/make/unit-tests/opt-debug-for.exp \
    src/usr.bin/make/unit-tests/opt-debug-for.mk \
    src/usr.bin/make/unit-tests/opt-debug-jobs.exp \
    src/usr.bin/make/unit-tests/opt-debug-jobs.mk \
    src/usr.bin/make/unit-tests/opt-debug-loud.exp \
    src/usr.bin/make/unit-tests/opt-debug-loud.mk \
    src/usr.bin/make/unit-tests/opt-debug.exp
cvs rdiff -u -r1.3 -r1.4 \
    src/usr.bin/make/unit-tests/directive-export-literal.mk \
    src/usr.bin/make/unit-tests/directive-ifndef.mk \
    src/usr.bin/make/unit-tests/directive-ifnmake.mk \
    src/usr.bin/make/unit-tests/make-exported.exp \
    src/usr.bin/make/unit-tests/opt-debug.mk \
    src/usr.bin/make/unit-tests/var-op-append.mk
cvs rdiff -u -r1.4 -r1.5 src/usr.bin/make/unit-tests/make-exported.mk \
    src/usr.bin/make/unit-tests/varname-dot-curdir.mk
cvs rdiff -u -r1.9 -r1.10 src/usr.bin/make/unit-tests/opt-debug-lint.exp
cvs rdiff -u -r1.8 -r1.9 src/usr.bin/make/unit-tests/opt-debug-lint.mk

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.bin/make/Makefile
diff -u src/usr.bin/make/Makefile:1.100 src/usr.bin/make/Makefile:1.101
--- src/usr.bin/make/Makefile:1.100	Mon Oct  5 15:11:37 2020
+++ src/usr.bin/make/Makefile	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.100 2020/10/05 15:11:37 rillig Exp $
+#	$NetBSD: Makefile,v 1.101 2020/10/05 19:24:29 rillig Exp $
 #	@(#)Makefile	5.2 (Berkeley) 12/28/90
 
 PROG=	make
@@ -189,10 +189,3 @@ retest:
 	rm -f *.gcov *.gcda
 .endif
 	${.MAKE} test
-
-# Just out of curiosity, during development.
-.SUFFIXES: .cpre .casm
-.c.cpre:
-	${COMPILE.c:S,^-c$,-E,} ${.IMPSRC} -o ${.TARGET}
-.c.casm:
-	${COMPILE.c:S,^-c$,-S,} ${.IMPSRC} -o ${.TARGET}

Index: src/usr.bin/make/arch.c
diff -u src/usr.bin/make/arch.c:1.130 src/usr.bin/make/arch.c:1.131
--- src/usr.bin/make/arch.c:1.130	Sat Oct  3 21:52:50 2020
+++ src/usr.bin/make/arch.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: arch.c,v 1.130 2020/10/03 21:52:50 rillig Exp $	*/
+/*	$NetBSD: arch.c,v 1.131 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -123,14 +123,18 @@
 #include    <sys/param.h>
 
 #include    <ar.h>
+#include    <ctype.h>
+#include    <stdio.h>
+#include    <stdlib.h>
 #include    <utime.h>
 
 #include    "make.h"
+#include    "hash.h"
 #include    "dir.h"
 #include    "config.h"
 
 /*	"@(#)arch.c	8.2 (Berkeley) 1/2/94"	*/
-MAKE_RCSID("$NetBSD: arch.c,v 1.130 2020/10/03 21:52:50 rillig Exp $");
+MAKE_RCSID("$NetBSD: arch.c,v 1.131 2020/10/05 19:24:29 rillig Exp $");
 
 #ifdef TARGET_MACHINE
 #undef MAKE_MACHINE
@@ -147,11 +151,11 @@ typedef struct ListNode ArchListNode;
 static ArchList *archives;	/* The archives we've already examined */
 
 typedef struct Arch {
-    char *name;			/* Name of archive */
-    Hash_Table members;		/* All the members of the archive described
-				 * by <name, struct ar_hdr *> key/value pairs */
-    char *fnametab;		/* Extended name table strings */
-    size_t fnamesize;		/* Size of the string table */
+    char	  *name;      /* Name of archive */
+    Hash_Table	  members;    /* All the members of the archive described
+			       * by <name, struct ar_hdr *> key/value pairs */
+    char	  *fnametab;  /* Extended name table strings */
+    size_t	  fnamesize;  /* Size of the string table */
 } Arch;
 
 static FILE *ArchFindMember(const char *, const char *,
@@ -166,8 +170,8 @@ static void
 ArchFree(void *ap)
 {
     Arch *a = (Arch *)ap;
-    Hash_Search search;
-    Hash_Entry *entry;
+    Hash_Search	  search;
+    Hash_Entry	  *entry;
 
     /* Free memory from hash entries */
     for (entry = Hash_EnumFirst(&a->members, &search);
@@ -254,7 +258,7 @@ Arch_ParseArchive(char **linePtr, GNodeL
 	 * place and skip to the end of it (either white-space or
 	 * a close paren).
 	 */
-	Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */
+	Boolean	doSubst = FALSE; /* TRUE if need to substitute in memName */
 
 	while (*cp != '\0' && *cp != ')' && ch_isspace(*cp)) {
 	    cp++;
@@ -266,7 +270,7 @@ Arch_ParseArchive(char **linePtr, GNodeL
 		 * Variable spec, so call the Var module to parse the puppy
 		 * so we can safely advance beyond it...
 		 */
-		void *freeIt;
+		void	*freeIt;
 		const char *result;
 		Boolean isError;
 		const char *nested_p = cp;
@@ -320,11 +324,11 @@ Arch_ParseArchive(char **linePtr, GNodeL
 	 * later.
 	 */
 	if (doSubst) {
-	    char *buf;
-	    char *sacrifice;
-	    char *oldMemName = memName;
+	    char    *buf;
+	    char    *sacrifice;
+	    char    *oldMemName = memName;
 
-	    (void)Var_Subst(memName, ctxt, VARE_UNDEFERR|VARE_WANTRES,
+	    (void)Var_Subst(memName, ctxt, VARE_UNDEFERR | VARE_WANTRES,
 			    &memName);
 	    /* TODO: handle errors */
 
@@ -409,9 +413,15 @@ Arch_ParseArchive(char **linePtr, GNodeL
 	free(libName);
     }
 
-    cp++;			/* skip the ')' */
-    /* We promised that linePtr would be set up at the next non-space. */
-    pp_skip_whitespace(&cp);
+    /*
+     * We promised the pointer would be set up at the next non-space, so
+     * we must advance cp there before setting *linePtr... (note that on
+     * entrance to the loop, cp is guaranteed to point at a ')')
+     */
+    do {
+	cp++;
+    } while (*cp != '\0' && ch_isspace(*cp));
+
     *linePtr = cp;
     return TRUE;
 }
@@ -439,15 +449,15 @@ Arch_ParseArchive(char **linePtr, GNodeL
 static struct ar_hdr *
 ArchStatMember(const char *archive, const char *member, Boolean hash)
 {
-#define AR_MAX_NAME_LEN (sizeof(arh.ar_name) - 1)
-    FILE *arch;			/* Stream to archive */
-    size_t size;		/* Size of archive member */
-    char magic[SARMAG];
+#define AR_MAX_NAME_LEN	    (sizeof(arh.ar_name)-1)
+    FILE *	  arch;	      /* Stream to archive */
+    size_t	  size;       /* Size of archive member */
+    char	  magic[SARMAG];
     ArchListNode *ln;
-    Arch *ar;			/* Archive descriptor */
-    struct ar_hdr arh;		/* archive-member header for reading archive */
-    char memName[MAXPATHLEN + 1];
-				/* Current member name while hashing. */
+    Arch	  *ar;	      /* Archive descriptor */
+    struct ar_hdr arh;        /* archive-member header for reading archive */
+    char	  memName[MAXPATHLEN+1];
+			    /* Current member name while hashing. */
 
     /*
      * Because of space constraints and similar things, files are archived
@@ -476,7 +486,7 @@ ArchStatMember(const char *archive, cons
 
 	{
 	    /* Try truncated name */
-	    char copy[AR_MAX_NAME_LEN + 1];
+	    char copy[AR_MAX_NAME_LEN+1];
 	    size_t len = strlen(member);
 
 	    if (len > AR_MAX_NAME_LEN) {
@@ -496,11 +506,11 @@ ArchStatMember(const char *archive, cons
 	 * no need to allocate extra room for the header we're returning,
 	 * so just declare it static.
 	 */
-	static struct ar_hdr sarh;
+	 static struct ar_hdr	sarh;
 
-	arch = ArchFindMember(archive, member, &sarh, "r");
+	 arch = ArchFindMember(archive, member, &sarh, "r");
 
-	if (arch == NULL) {
+	 if (arch == NULL) {
 	    return NULL;
 	} else {
 	    fclose(arch);
@@ -523,8 +533,8 @@ ArchStatMember(const char *archive, cons
      */
     if ((fread(magic, SARMAG, 1, arch) != 1) ||
 	(strncmp(magic, ARMAG, SARMAG) != 0)) {
-	fclose(arch);
-	return NULL;
+	    fclose(arch);
+	    return NULL;
     }
 
     ar = bmake_malloc(sizeof(Arch));
@@ -535,7 +545,7 @@ ArchStatMember(const char *archive, cons
     memName[AR_MAX_NAME_LEN] = '\0';
 
     while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) {
-	if (strncmp(arh.ar_fmag, ARFMAG, sizeof(arh.ar_fmag)) != 0) {
+	if (strncmp( arh.ar_fmag, ARFMAG, sizeof(arh.ar_fmag)) != 0) {
 	    /*
 	     * The header is bogus, so the archive is bad
 	     * and there's no way we can recover...
@@ -550,7 +560,7 @@ ArchStatMember(const char *archive, cons
 	     * boundary, so we need to extract the size of the file from the
 	     * 'size' field of the header and round it up during the seek.
 	     */
-	    arh.ar_size[sizeof(arh.ar_size) - 1] = '\0';
+	    arh.ar_size[sizeof(arh.ar_size)-1] = '\0';
 	    size = (size_t)strtol(arh.ar_size, NULL, 10);
 
 	    memcpy(memName, arh.ar_name, sizeof(arh.ar_name));
@@ -569,14 +579,15 @@ ArchStatMember(const char *archive, cons
 		 * svr4 magic mode; handle it
 		 */
 		switch (ArchSVR4Entry(ar, memName, size, arch)) {
-		case -1:	/* Invalid data */
+		case -1:  /* Invalid data */
 		    goto badarch;
-		case 0:		/* List of files entry */
+		case 0:	  /* List of files entry */
 		    continue;
-		default:	/* Got the entry */
+		default:  /* Got the entry */
 		    break;
 		}
-	    } else {
+	    }
+	    else {
 		if (nameend[0] == '/')
 		    nameend[0] = '\0';
 	    }
@@ -590,24 +601,23 @@ ArchStatMember(const char *archive, cons
 	    if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
 		ch_isdigit(memName[sizeof(AR_EFMT1) - 1])) {
 
-		int elen = atoi(&memName[sizeof(AR_EFMT1) - 1]);
+		int elen = atoi(&memName[sizeof(AR_EFMT1)-1]);
 
 		if ((unsigned int)elen > MAXPATHLEN)
-		    goto badarch;
+			goto badarch;
 		if (fread(memName, (size_t)elen, 1, arch) != 1)
-		    goto badarch;
+			goto badarch;
 		memName[elen] = '\0';
 		if (fseek(arch, -elen, SEEK_CUR) != 0)
-		    goto badarch;
+			goto badarch;
 		if (DEBUG(ARCH) || DEBUG(MAKE)) {
-		    debug_printf("ArchStat: Extended format entry for %s\n",
-				 memName);
+		    debug_printf("ArchStat: Extended format entry for %s\n", memName);
 		}
 	    }
 #endif
 
 	    {
-		Hash_Entry *he;
+	        Hash_Entry *he;
 		he = Hash_CreateEntry(&ar->members, memName, NULL);
 		Hash_SetValue(he, bmake_malloc(sizeof(struct ar_hdr)));
 		memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr));
@@ -696,12 +706,12 @@ ArchSVR4Entry(Arch *ar, char *name, size
 
     entry = (size_t)strtol(&name[1], &eptr, 0);
     if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) {
-	DEBUG1(ARCH, "Could not parse SVR4 name %s\n", name);
+        DEBUG1(ARCH, "Could not parse SVR4 name %s\n", name);
 	return 2;
     }
     if (entry >= ar->fnamesize) {
 	DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n",
-	       name, (unsigned long)ar->fnamesize);
+		   name, (unsigned long)ar->fnamesize);
 	return 2;
     }
 
@@ -736,13 +746,13 @@ ArchSVR4Entry(Arch *ar, char *name, size
  */
 static FILE *
 ArchFindMember(const char *archive, const char *member, struct ar_hdr *arhPtr,
-	       const char *mode)
+    const char *mode)
 {
-    FILE *arch;			/* Stream to archive */
-    int size;			/* Size of archive member */
-    char magic[SARMAG];
-    size_t len, tlen;
-    const char *base;
+    FILE *	  arch;	      /* Stream to archive */
+    int		  size;       /* Size of archive member */
+    char	  magic[SARMAG];
+    size_t	  len, tlen;
+    const char *  base;
 
     arch = fopen(archive, mode);
     if (arch == NULL) {
@@ -755,8 +765,8 @@ ArchFindMember(const char *archive, cons
      */
     if ((fread(magic, SARMAG, 1, arch) != 1) ||
 	(strncmp(magic, ARMAG, SARMAG) != 0)) {
-	fclose(arch);
-	return NULL;
+	    fclose(arch);
+	    return NULL;
     }
 
     /*
@@ -775,13 +785,13 @@ ArchFindMember(const char *archive, cons
     }
 
     while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) {
-	if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof(arhPtr->ar_fmag)) != 0) {
-	    /*
-	     * The header is bogus, so the archive is bad
-	     * and there's no way we can recover...
-	     */
-	    fclose(arch);
-	    return NULL;
+	if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof(arhPtr->ar_fmag) ) != 0) {
+	     /*
+	      * The header is bogus, so the archive is bad
+	      * and there's no way we can recover...
+	      */
+	     fclose(arch);
+	     return NULL;
 	} else if (strncmp(member, arhPtr->ar_name, tlen) == 0) {
 	    /*
 	     * If the member's name doesn't take up the entire 'name' field,
@@ -790,8 +800,7 @@ ArchFindMember(const char *archive, cons
 	     * of the matched string is anything but a space, this isn't the
 	     * member we sought.
 	     */
-	    if (tlen != sizeof(arhPtr->ar_name) &&
-		arhPtr->ar_name[tlen] != ' ') {
+	    if (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){
 		goto skip;
 	    } else {
 		/*
@@ -857,7 +866,7 @@ skip:
 	     * extract the size of the file from the 'size' field of the
 	     * header and round it up during the seek.
 	     */
-	    arhPtr->ar_size[sizeof(arhPtr->ar_size) - 1] = '\0';
+	    arhPtr->ar_size[sizeof(arhPtr->ar_size)-1] = '\0';
 	    size = (int)strtol(arhPtr->ar_size, NULL, 10);
 	    if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) {
 		fclose(arch);
@@ -892,8 +901,8 @@ skip:
 void
 Arch_Touch(GNode *gn)
 {
-    FILE *arch;		/* Stream open to archive, positioned properly */
-    struct ar_hdr arh;	/* Current header describing member */
+    FILE *	  arch;	  /* Stream open to archive, positioned properly */
+    struct ar_hdr arh;	  /* Current header describing member */
     char *p1, *p2;
 
     arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1),
@@ -903,7 +912,7 @@ Arch_Touch(GNode *gn)
     bmake_free(p1);
     bmake_free(p2);
 
-    snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long)now);
+    snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now);
 
     if (arch != NULL) {
 	(void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch);
@@ -952,13 +961,13 @@ Arch_TouchLib(GNode *gn)
 time_t
 Arch_MTime(GNode *gn)
 {
-    struct ar_hdr *arhPtr;	/* Header of desired member */
-    time_t modTime;		/* Modification time as an integer */
+    struct ar_hdr *arhPtr;    /* Header of desired member */
+    time_t	  modTime;    /* Modification time as an integer */
     char *p1, *p2;
 
     arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1),
-			    Var_Value(MEMBER, gn, &p2),
-			    TRUE);
+			     Var_Value(MEMBER, gn, &p2),
+			     TRUE);
 
     bmake_free(p1);
     bmake_free(p2);
@@ -1030,8 +1039,8 @@ Arch_MemMTime(GNode *gn)
 void
 Arch_FindLib(GNode *gn, SearchPath *path)
 {
-    char *libName;		/* file name for archive */
-    size_t sz = strlen(gn->name) + 6 - 2;
+    char	    *libName;   /* file name for archive */
+    size_t	     sz = strlen(gn->name) + 6 - 2;
 
     libName = bmake_malloc(sz);
     snprintf(libName, sz, "lib%s.a", &gn->name[2]);

Index: src/usr.bin/make/compat.c
diff -u src/usr.bin/make/compat.c:1.163 src/usr.bin/make/compat.c:1.164
--- src/usr.bin/make/compat.c:1.163	Mon Oct  5 18:04:57 2020
+++ src/usr.bin/make/compat.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: compat.c,v 1.163 2020/10/05 18:04:57 rillig Exp $	*/
+/*	$NetBSD: compat.c,v 1.164 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -82,23 +82,26 @@
  *			thems as need creatin'
  */
 
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-
-#include <errno.h>
-#include <signal.h>
-
-#include "make.h"
-#include "dir.h"
-#include "job.h"
-#include "metachar.h"
-#include "pathnames.h"
+#include    <sys/types.h>
+#include    <sys/stat.h>
+#include    <sys/wait.h>
+
+#include    <ctype.h>
+#include    <errno.h>
+#include    <signal.h>
+#include    <stdio.h>
+
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "job.h"
+#include    "metachar.h"
+#include    "pathnames.h"
 
 /*	"@(#)compat.c	8.2 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: compat.c,v 1.163 2020/10/05 18:04:57 rillig Exp $");
+MAKE_RCSID("$NetBSD: compat.c,v 1.164 2020/10/05 19:24:29 rillig Exp $");
 
-static GNode *curTarg = NULL;
+static GNode	    *curTarg = NULL;
 static pid_t compatChild;
 static int compatSigno;
 
@@ -109,15 +112,15 @@ static int compatSigno;
 static void
 CompatDeleteTarget(GNode *gn)
 {
-    if (gn != NULL && !Targ_Precious(gn)) {
-	char *file_freeIt;
-	const char *file = Var_Value(TARGET, gn, &file_freeIt);
+    if ((gn != NULL) && !Targ_Precious (gn)) {
+	char *p1;
+	const char *file = Var_Value(TARGET, gn, &p1);
 
 	if (!noExecute && eunlink(file) != -1) {
 	    Error("*** %s removed", file);
 	}
 
-	bmake_free(file_freeIt);
+	bmake_free(p1);
     }
 }
 
@@ -136,7 +139,7 @@ CompatInterrupt(int signo)
 
     CompatDeleteTarget(curTarg);
 
-    if (curTarg != NULL && !Targ_Precious(curTarg)) {
+    if ((curTarg != NULL) && !Targ_Precious (curTarg)) {
 	/*
 	 * Run .INTERRUPT only if hit with interrupt signal
 	 */
@@ -147,13 +150,11 @@ CompatInterrupt(int signo)
 	    }
 	}
     }
-
     if (signo == SIGQUIT)
 	_exit(signo);
-
     /*
-     * If there is a child running, pass the signal on.
-     * We will exist after it has exited.
+     * If there is a child running, pass the signal on
+     * we will exist after it has exited.
      */
     compatSigno = signo;
     if (compatChild > 0) {
@@ -164,8 +165,11 @@ CompatInterrupt(int signo)
     }
 }
 
-/* Execute the next command for a target. If the command returns an error,
- * the node's made field is set to ERROR and creation stops.
+/*-
+ *-----------------------------------------------------------------------
+ * CompatRunCommand --
+ *	Execute the next command for a target. If the command returns an
+ *	error, the node's made field is set to ERROR and creation stops.
  *
  * Input:
  *	cmdp		Command to execute
@@ -173,6 +177,11 @@ CompatInterrupt(int signo)
  *
  * Results:
  *	0 if the command succeeded, 1 if an error occurred.
+ *
+ * Side Effects:
+ *	The node's 'made' field may be set to ERROR.
+ *
+ *-----------------------------------------------------------------------
  */
 int
 Compat_RunCommand(const char *cmdp, struct GNode *gn)
@@ -206,6 +215,13 @@ Compat_RunCommand(const char *cmdp, stru
     (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart);
     /* TODO: handle errors */
 
+    /*
+     * brk_string will return an argv with a NULL in av[0], thus causing
+     * execvp to choke and die horribly. Besides, how can we execute a null
+     * command? In any case, we warn the user that the command expanded to
+     * nothing (is this the right thing to do?).
+     */
+
     if (*cmdStart == '\0') {
 	free(cmdStart);
 	return 0;
@@ -225,7 +241,7 @@ Compat_RunCommand(const char *cmdp, stru
 	return 0;
     }
 
-    while (*cmd == '@' || *cmd == '-' || *cmd == '+') {
+    while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) {
 	switch (*cmd) {
 	case '@':
 	    silent = !DEBUG(LOUD);
@@ -423,7 +439,8 @@ Compat_RunCommand(const char *cmdp, stru
 	    gn->made = ERROR;
 	    if (keepgoing) {
 		/*
-		 * Abort the current target, but let others continue.
+		 * Abort the current target, but let others
+		 * continue.
 		 */
 		printf(" (continuing)\n");
 	    } else {
@@ -486,7 +503,7 @@ Compat_Make(GNode *gn, GNode *pgn)
 {
     if (!shellName)		/* we came here from jobs */
 	Shell_Init();
-    if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
+    if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) {
 	/*
 	 * First mark ourselves to be made, then apply whatever transformations
 	 * the suffix module thinks are necessary. Once that's done, we can
@@ -497,19 +514,19 @@ Compat_Make(GNode *gn, GNode *pgn)
 	 */
 	gn->flags |= REMAKE;
 	gn->made = BEINGMADE;
-	if (!(gn->type & OP_MADE))
+	if ((gn->type & OP_MADE) == 0)
 	    Suff_FindDeps(gn);
 	MakeNodes(gn->children, gn);
-	if (!(gn->flags & REMAKE)) {
+	if ((gn->flags & REMAKE) == 0) {
 	    gn->made = ABORTED;
 	    pgn->flags &= ~(unsigned)REMAKE;
 	    goto cohorts;
 	}
 
 	if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) {
-	    char *target_freeIt;
-	    Var_Set(IMPSRC, Var_Value(TARGET, gn, &target_freeIt), pgn);
-	    bmake_free(target_freeIt);
+	    char *p1;
+	    Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn);
+	    bmake_free(p1);
 	}
 
 	/*
@@ -519,7 +536,7 @@ Compat_Make(GNode *gn, GNode *pgn)
 	 * are defined by the Make_OODate function.
 	 */
 	DEBUG1(MAKE, "Examining %s...", gn->name);
-	if (!Make_OODate(gn)) {
+	if (! Make_OODate(gn)) {
 	    gn->made = UPTODATE;
 	    DEBUG0(MAKE, "up-to-date.\n");
 	    goto cohorts;
@@ -606,10 +623,10 @@ Compat_Make(GNode *gn, GNode *pgn)
 	pgn->flags &= ~(unsigned)REMAKE;
     } else {
 	if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) {
-	    char *target_freeIt;
-	    const char *target = Var_Value(TARGET, gn, &target_freeIt);
+	    char *p1;
+	    const char *target = Var_Value(TARGET, gn, &p1);
 	    Var_Set(IMPSRC, target != NULL ? target : "", pgn);
-	    bmake_free(target_freeIt);
+	    bmake_free(p1);
 	}
 	switch(gn->made) {
 	    case BEINGMADE:

Index: src/usr.bin/make/cond.c
diff -u src/usr.bin/make/cond.c:1.158 src/usr.bin/make/cond.c:1.159
--- src/usr.bin/make/cond.c:1.158	Mon Oct  5 18:29:20 2020
+++ src/usr.bin/make/cond.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: cond.c,v 1.158 2020/10/05 18:29:20 rillig Exp $	*/
+/*	$NetBSD: cond.c,v 1.159 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -93,7 +93,7 @@
 #include "dir.h"
 
 /*	"@(#)cond.c	8.2 (Berkeley) 1/2/94"	*/
-MAKE_RCSID("$NetBSD: cond.c,v 1.158 2020/10/05 18:29:20 rillig Exp $");
+MAKE_RCSID("$NetBSD: cond.c,v 1.159 2020/10/05 19:24:29 rillig Exp $");
 
 /*
  * The parsing of conditional expressions is based on this grammar:
@@ -186,7 +186,8 @@ CondParser_PushBack(CondParser *par, Tok
 static void
 CondParser_SkipWhitespace(CondParser *par)
 {
-    cpp_skip_whitespace(&par->p);
+    while (ch_isspace(par->p[0]))
+	par->p++;
 }
 
 /* Parse the argument of a built-in function.
@@ -537,7 +538,7 @@ EvalNotEmpty(CondParser *par, const char
 	return lhs[0] != 0;
 
     /* Otherwise action default test ... */
-    return par->if_info->defProc(strlen(lhs), lhs) == !par->if_info->doNot;
+    return par->if_info->defProc(strlen(lhs), lhs) != par->if_info->doNot;
 }
 
 /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */
@@ -696,7 +697,8 @@ ParseEmptyArg(const char **linePtr, Bool
     }
 
     /* A variable is empty when it just contains spaces... 4/15/92, christos */
-    cpp_skip_whitespace(&val);
+    while (ch_isspace(val[0]))
+	val++;
 
     /*
      * For consistency with the other functions we can't generate the
@@ -743,7 +745,8 @@ CondParser_Func(CondParser *par, Boolean
 	    continue;
 	cp += fn_def->fn_name_len;
 	/* There can only be whitespace before the '(' */
-	cpp_skip_whitespace(&cp);
+	while (ch_isspace(*cp))
+	    cp++;
 	if (*cp != '(')
 	    break;
 
@@ -773,8 +776,8 @@ CondParser_Func(CondParser *par, Boolean
      * expression.
      */
     arglen = ParseFuncArg(&cp, doEval, NULL, &arg);
-    cp1 = cp;
-    cpp_skip_whitespace(&cp1);
+    for (cp1 = cp; ch_isspace(*cp1); cp1++)
+	continue;
     if (*cp1 == '=' || *cp1 == '!')
 	return CondParser_Comparison(par, doEval);
     par->p = cp;
@@ -785,7 +788,7 @@ CondParser_Func(CondParser *par, Boolean
      * after .if must have been taken literally, so the argument cannot
      * be empty - even if it contained a variable expansion.
      */
-    t = !doEval || par->if_info->defProc(arglen, arg) == !par->if_info->doNot;
+    t = !doEval || par->if_info->defProc(arglen, arg) != par->if_info->doNot;
     free(arg);
     return t;
 }

Index: src/usr.bin/make/dir.c
diff -u src/usr.bin/make/dir.c:1.156 src/usr.bin/make/dir.c:1.157
--- src/usr.bin/make/dir.c:1.156	Sat Oct  3 21:52:50 2020
+++ src/usr.bin/make/dir.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: dir.c,v 1.156 2020/10/03 21:52:50 rillig Exp $	*/
+/*	$NetBSD: dir.c,v 1.157 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -129,13 +129,14 @@
 
 #include <dirent.h>
 #include <errno.h>
+#include <stdio.h>
 
 #include "make.h"
 #include "dir.h"
 #include "job.h"
 
 /*	"@(#)dir.c	8.2 (Berkeley) 1/2/94"	*/
-MAKE_RCSID("$NetBSD: dir.c,v 1.156 2020/10/03 21:52:50 rillig Exp $");
+MAKE_RCSID("$NetBSD: dir.c,v 1.157 2020/10/05 19:24:29 rillig Exp $");
 
 #define DIR_DEBUG0(text) DEBUG0(DIR, text)
 #define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1)
@@ -218,58 +219,7 @@ typedef ListNode SearchPathNode;
 
 SearchPath *dirSearchPath;		/* main search path */
 
-/* A list of cached directories, with fast lookup by directory name. */
-typedef struct OpenDirs {
-    CachedDirList *list;
-    Hash_Table /* of CachedDirListNode */ table;
-} OpenDirs;
-
-static void
-OpenDirs_Init(OpenDirs *odirs)
-{
-    odirs->list = Lst_Init();
-    Hash_InitTable(&odirs->table);
-}
-
-static void MAKE_ATTR_UNUSED
-OpenDirs_Done(OpenDirs *odirs)
-{
-    Dir_ClearPath(odirs->list);
-    Lst_Free(odirs->list);
-    Hash_DeleteTable(&odirs->table);
-}
-
-static CachedDir *
-OpenDirs_Find(OpenDirs *odirs, const char *name)
-{
-    CachedDirListNode *ln = Hash_FindValue(&odirs->table, name);
-    return ln != NULL ? ln->datum : NULL;
-}
-
-static void
-OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir)
-{
-    Hash_Entry *he = Hash_FindEntry(&odirs->table, cdir->name);
-    if (he != NULL)
-	return;
-    he = Hash_CreateEntry(&odirs->table, cdir->name, NULL);
-    Lst_Append(odirs->list, cdir);
-    Hash_SetValue(he, odirs->list->last);
-}
-
-static void
-OpenDirs_Remove(OpenDirs *odirs, const char *name)
-{
-    Hash_Entry *he = Hash_FindEntry(&odirs->table, name);
-    CachedDirListNode *ln;
-    if (he == NULL)
-	return;
-    ln = Hash_GetValue(he);
-    Hash_DeleteEntry(&odirs->table, he);
-    Lst_Remove(odirs->list, ln);
-}
-
-static OpenDirs openDirs;	/* the list of all open directories */
+static CachedDirList *openDirectories;	/* the list of all open directories */
 
 /*
  * Variables for gathering statistics on the efficiency of the hashing
@@ -387,7 +337,7 @@ void
 Dir_Init(void)
 {
     dirSearchPath = Lst_Init();
-    OpenDirs_Init(&openDirs);
+    openDirectories = Lst_Init();
     Hash_InitTable(&mtimes);
     Hash_InitTable(&lmtimes);
 }
@@ -437,8 +387,11 @@ void
 Dir_InitDot(void)
 {
     if (dot != NULL) {
-	/* Remove old entry from openDirs, but do not destroy. */
-	OpenDirs_Remove(&openDirs, dot->name);
+	CachedDirListNode *ln;
+
+	/* Remove old entry from openDirectories, but do not destroy. */
+	ln = Lst_FindDatum(openDirectories, dot);
+	Lst_Remove(openDirectories, ln);
     }
 
     dot = Dir_AddDir(NULL, ".");
@@ -471,7 +424,8 @@ Dir_End(void)
     Dir_Destroy(dot);
     Dir_ClearPath(dirSearchPath);
     Lst_Free(dirSearchPath);
-    OpenDirs_Done(&openDirs);
+    Dir_ClearPath(openDirectories);
+    Lst_Free(openDirectories);
     Hash_DeleteTable(&mtimes);
 #endif
 }
@@ -1528,12 +1482,13 @@ Dir_MTime(GNode *gn, Boolean recheck)
 CachedDir *
 Dir_AddDir(SearchPath *path, const char *name)
 {
+    SearchPathNode *ln = NULL;
     CachedDir *dir = NULL;	/* the added directory */
     DIR *d;
     struct dirent *dp;
 
     if (path != NULL && strcmp(name, ".DOTLAST") == 0) {
-	SearchPathNode *ln = Lst_Find(path, DirFindName, name);
+	ln = Lst_Find(path, DirFindName, name);
 	if (ln != NULL)
 	    return LstNode_Datum(ln);
 
@@ -1542,8 +1497,9 @@ Dir_AddDir(SearchPath *path, const char 
     }
 
     if (path != NULL)
-	dir = OpenDirs_Find(&openDirs, name);
-    if (dir != NULL) {
+	ln = Lst_Find(openDirectories, DirFindName, name);
+    if (ln != NULL) {
+	dir = LstNode_Datum(ln);
 	if (Lst_FindDatum(path, dir) == NULL) {
 	    dir->refCount++;
 	    Lst_Append(path, dir);
@@ -1574,7 +1530,7 @@ Dir_AddDir(SearchPath *path, const char 
 	    (void)Hash_CreateEntry(&dir->files, dp->d_name, NULL);
 	}
 	(void)closedir(d);
-	OpenDirs_Add(&openDirs, dir);
+	Lst_Append(openDirectories, dir);
 	if (path != NULL)
 	    Lst_Append(path, dir);
     }
@@ -1667,7 +1623,11 @@ Dir_Destroy(void *dirp)
     dir->refCount--;
 
     if (dir->refCount == 0) {
-	OpenDirs_Remove(&openDirs, dir->name);
+	CachedDirListNode *node;
+
+	node = Lst_FindDatum(openDirectories, dir);
+	if (node != NULL)
+	    Lst_Remove(openDirectories, node);
 
 	Hash_DeleteTable(&dir->files);
 	free(dir->name);
@@ -1752,7 +1712,7 @@ Dir_PrintDirectories(void)
 		 percentage(hits, hits + bigmisses + nearmisses));
     debug_printf("# %-20s referenced\thits\n", "directory");
 
-    for (ln = openDirs.list->first; ln != NULL; ln = ln->next) {
+    for (ln = openDirectories->first; ln != NULL; ln = ln->next) {
 	CachedDir *dir = ln->datum;
 	debug_printf("# %-20s %10d\t%4d\n", dir->name, dir->refCount,
 		     dir->hits);

Index: src/usr.bin/make/enum.c
diff -u src/usr.bin/make/enum.c:1.10 src/usr.bin/make/enum.c:1.11
--- src/usr.bin/make/enum.c:1.10	Sat Oct  3 21:52:50 2020
+++ src/usr.bin/make/enum.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: enum.c,v 1.10 2020/10/03 21:52:50 rillig Exp $	*/
+/*	$NetBSD: enum.c,v 1.11 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  Copyright (c) 2020 Roland Illig <ril...@netbsd.org>
@@ -27,9 +27,13 @@
  POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
 #include "make.h"
 
-MAKE_RCSID("$NetBSD: enum.c,v 1.10 2020/10/03 21:52:50 rillig Exp $");
+MAKE_RCSID("$NetBSD: enum.c,v 1.11 2020/10/05 19:24:29 rillig Exp $");
 
 /* Convert a bitset into a string representation, showing the names of the
  * individual bits.
@@ -48,7 +52,7 @@ Enum_FlagsToString(char *buf, size_t buf
 		size_t name_len;
 
 		if ((value & spec->es_value) != spec->es_value)
-			continue;
+			    continue;
 		value &= ~spec->es_value;
 
 		assert(buf_size >= sep_len + 1);
@@ -82,8 +86,8 @@ const char *
 Enum_ValueToString(int value, const EnumToStringSpec *spec)
 {
 	for (; spec->es_name[0] != '\0'; spec++) {
-		if (value == spec->es_value)
-			return spec->es_name;
+	    if (value == spec->es_value)
+		return spec->es_name;
 	}
 	abort(/* unknown enum value */);
 }

Index: src/usr.bin/make/for.c
diff -u src/usr.bin/make/for.c:1.90 src/usr.bin/make/for.c:1.91
--- src/usr.bin/make/for.c:1.90	Sat Oct  3 21:19:54 2020
+++ src/usr.bin/make/for.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: for.c,v 1.90 2020/10/03 21:19:54 rillig Exp $	*/
+/*	$NetBSD: for.c,v 1.91 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1992, The Regents of the University of California.
@@ -61,7 +61,7 @@
 #include    "strlist.h"
 
 /*	"@(#)for.c	8.1 (Berkeley) 6/6/93"	*/
-MAKE_RCSID("$NetBSD: for.c,v 1.90 2020/10/03 21:19:54 rillig Exp $");
+MAKE_RCSID("$NetBSD: for.c,v 1.91 2020/10/05 19:24:29 rillig Exp $");
 
 typedef enum {
     FOR_SUB_ESCAPE_CHAR = 0x0001,
@@ -119,8 +119,8 @@ For_Eval(const char *line)
     Words words;
 
     /* Skip the '.' and any following whitespace */
-    ptr = line + 1;
-    cpp_skip_whitespace(&ptr);
+    for (ptr = line + 1; ch_isspace(*ptr); ptr++)
+	continue;
 
     /*
      * If we are not in a for loop quickly determine if the statement is
@@ -152,7 +152,8 @@ For_Eval(const char *line)
     while (TRUE) {
 	size_t len;
 
-	cpp_skip_whitespace(&ptr);
+	while (ch_isspace(*ptr))
+	    ptr++;
 	if (*ptr == '\0') {
 	    Parse_Error(PARSE_FATAL, "missing `in' in for");
 	    For_Free(new_for);
@@ -178,7 +179,8 @@ For_Eval(const char *line)
 	return -1;
     }
 
-    cpp_skip_whitespace(&ptr);
+    while (ch_isspace(*ptr))
+	ptr++;
 
     /*
      * Make a list with the remaining words.
@@ -265,8 +267,9 @@ For_Accum(const char *line)
     const char *ptr = line;
 
     if (*ptr == '.') {
-	ptr++;
-	cpp_skip_whitespace(&ptr);
+
+	for (ptr++; *ptr && ch_isspace(*ptr); ptr++)
+	    continue;
 
 	if (strncmp(ptr, "endfor", 6) == 0 && (ch_isspace(ptr[6]) || !ptr[6])) {
 	    DEBUG1(FOR, "For: end for %d\n", forLevel);

Index: src/usr.bin/make/hash.c
diff -u src/usr.bin/make/hash.c:1.41 src/usr.bin/make/hash.c:1.42
--- src/usr.bin/make/hash.c:1.41	Sun Oct  4 18:16:09 2020
+++ src/usr.bin/make/hash.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: hash.c,v 1.41 2020/10/04 18:16:09 rillig Exp $	*/
+/*	$NetBSD: hash.c,v 1.42 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -79,50 +79,38 @@
 #include "make.h"
 
 /*	"@(#)hash.c	8.1 (Berkeley) 6/6/93"	*/
-MAKE_RCSID("$NetBSD: hash.c,v 1.41 2020/10/04 18:16:09 rillig Exp $");
+MAKE_RCSID("$NetBSD: hash.c,v 1.42 2020/10/05 19:24:29 rillig Exp $");
 
 /*
- * The ratio of # entries to # buckets at which we rebuild the table to
- * make it larger.
+ * Forward references to local procedures that are used before they're
+ * defined:
  */
-#define rebuildLimit 3
 
-/* This hash function matches Gosling's emacs. */
-static unsigned int
-hash(const char *key, size_t *out_keylen)
-{
-	unsigned h = 0;
-	const char *p = key;
-	while (*p != '\0')
-		h = (h << 5) - h + (unsigned char)*p++;
-	if (out_keylen != NULL)
-		*out_keylen = (size_t)(p - key);
-	return h;
-}
+static void RebuildTable(Hash_Table *);
 
-static Hash_Entry *
-HashTable_Find(Hash_Table *t, unsigned int h, const char *key)
-{
-	Hash_Entry *e;
-	int chainlen = 0;
+/*
+ * The following defines the ratio of # entries to # buckets
+ * at which we rebuild the table to make it larger.
+ */
 
-#ifdef DEBUG_HASH_LOOKUP
-	DEBUG4(HASH, "%s: %p h=%x key=%s\n", __func__, t, h, key);
-#endif
+#define rebuildLimit 3
 
-	for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) {
-		chainlen++;
-		if (e->namehash == h && strcmp(e->name, key) == 0)
-			break;
-	}
+/* The hash function(s) */
 
-	if (chainlen > t->maxchain)
-		t->maxchain = chainlen;
+#ifndef HASH
+/* The default: this one matches Gosling's emacs */
+#define HASH(h, key, p) do { \
+	for (h = 0, p = key; *p;) \
+		h = (h << 5) - h + (unsigned char)*p++; \
+	} while (0)
 
-	return e;
-}
+#endif
 
-/* Sets up the hash table. */
+/* Sets up the hash table.
+ *
+ * Input:
+ *	t		Structure to to hold the table.
+ */
 void
 Hash_InitTable(Hash_Table *t)
 {
@@ -174,47 +162,35 @@ Hash_DeleteTable(Hash_Table *t)
 Hash_Entry *
 Hash_FindEntry(Hash_Table *t, const char *key)
 {
-	unsigned int h = hash(key, NULL);
-	return HashTable_Find(t, h, key);
+	Hash_Entry *e;
+	unsigned h;
+	const char *p;
+	int chainlen;
+
+	if (t == NULL || t->buckets == NULL) {
+	    return NULL;
+	}
+	HASH(h, key, p);
+	p = key;
+	chainlen = 0;
+#ifdef DEBUG_HASH_LOOKUP
+	DEBUG4(HASH, "%s: %p h=%x key=%s\n", __func__, t, h, key);
+#endif
+	for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) {
+		chainlen++;
+		if (e->namehash == h && strcmp(e->name, p) == 0)
+			break;
+	}
+	if (chainlen > t->maxchain)
+		t->maxchain = chainlen;
+	return e;
 }
 
 void *
 Hash_FindValue(Hash_Table *t, const char *key)
 {
-	Hash_Entry *he = Hash_FindEntry(t, key);
-	return he != NULL ? he->value : NULL;
-}
-
-/* Makes a new hash table that is larger than the old one. The entire hash
- * table is moved, so any bucket numbers from the old table become invalid. */
-static void
-RebuildTable(Hash_Table *t)
-{
-	Hash_Entry *e, *next = NULL, **hp, **xp;
-	int i, mask;
-	Hash_Entry **oldhp;
-	int oldsize;
-
-	oldhp = t->buckets;
-	oldsize = i = t->bucketsSize;
-	i <<= 1;
-	t->bucketsSize = i;
-	t->bucketsMask = mask = i - 1;
-	t->buckets = hp = bmake_malloc(sizeof(*hp) * i);
-	while (--i >= 0)
-		*hp++ = NULL;
-	for (hp = oldhp, i = oldsize; --i >= 0;) {
-		for (e = *hp++; e != NULL; e = next) {
-			next = e->next;
-			xp = &t->buckets[e->namehash & mask];
-			e->next = *xp;
-			*xp = e;
-		}
-	}
-	free(oldhp);
-	DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n",
-	       __func__, t, t->bucketsSize, t->numEntries, t->maxchain);
-	t->maxchain = 0;
+    Hash_Entry *he = Hash_FindEntry(t, key);
+    return he != NULL ? he->value : NULL;
 }
 
 /* Searches the hash table for an entry corresponding to the key.
@@ -231,16 +207,34 @@ Hash_CreateEntry(Hash_Table *t, const ch
 {
 	Hash_Entry *e;
 	unsigned h;
-	size_t keylen;
+	const char *p;
+	int keylen;
+	int chainlen;
 	struct Hash_Entry **hp;
 
-	h = hash(key, &keylen);
-	e = HashTable_Find(t, h, key);
-	if (e) {
-		if (newPtr != NULL)
-			*newPtr = FALSE;
-		return e;
+	/*
+	 * Hash the key.  As a side effect, save the length (strlen) of the
+	 * key in case we need to create the entry.
+	 */
+	HASH(h, key, p);
+	keylen = p - key;
+	p = key;
+	chainlen = 0;
+#ifdef DEBUG_HASH_LOOKUP
+	DEBUG4(HASH, "%s: %p h=%x key=%s\n", __func__, t, h, key);
+#endif
+	for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) {
+		chainlen++;
+		if (e->namehash == h && strcmp(e->name, p) == 0) {
+			if (newPtr != NULL)
+				*newPtr = FALSE;
+			break;
+		}
 	}
+	if (chainlen > t->maxchain)
+		t->maxchain = chainlen;
+	if (e)
+		return e;
 
 	/*
 	 * The desired entry isn't there.  Before allocating a new entry,
@@ -249,14 +243,13 @@ Hash_CreateEntry(Hash_Table *t, const ch
 	 */
 	if (t->numEntries >= rebuildLimit * t->bucketsSize)
 		RebuildTable(t);
-
 	e = bmake_malloc(sizeof(*e) + keylen);
 	hp = &t->buckets[h & t->bucketsMask];
 	e->next = *hp;
 	*hp = e;
 	Hash_SetValue(e, NULL);
 	e->namehash = h;
-	memcpy(e->name, key, keylen + 1);
+	(void)strcpy(e->name, p);
 	t->numEntries++;
 
 	if (newPtr != NULL)
@@ -270,6 +263,8 @@ Hash_DeleteEntry(Hash_Table *t, Hash_Ent
 {
 	Hash_Entry **hp, *p;
 
+	if (e == NULL)
+		return;
 	for (hp = &t->buckets[e->namehash & t->bucketsMask];
 	     (p = *hp) != NULL; hp = &p->next) {
 		if (p == e) {
@@ -279,6 +274,7 @@ Hash_DeleteEntry(Hash_Table *t, Hash_Ent
 			return;
 		}
 	}
+	(void)write(2, "bad call to Hash_DeleteEntry\n", 29);
 	abort();
 }
 
@@ -334,6 +330,38 @@ Hash_EnumNext(Hash_Search *searchPtr)
 	return e;
 }
 
+/* Makes a new hash table that is larger than the old one. The entire hash
+ * table is moved, so any bucket numbers from the old table become invalid. */
+static void
+RebuildTable(Hash_Table *t)
+{
+	Hash_Entry *e, *next = NULL, **hp, **xp;
+	int i, mask;
+	Hash_Entry **oldhp;
+	int oldsize;
+
+	oldhp = t->buckets;
+	oldsize = i = t->bucketsSize;
+	i <<= 1;
+	t->bucketsSize = i;
+	t->bucketsMask = mask = i - 1;
+	t->buckets = hp = bmake_malloc(sizeof(*hp) * i);
+	while (--i >= 0)
+		*hp++ = NULL;
+	for (hp = oldhp, i = oldsize; --i >= 0;) {
+		for (e = *hp++; e != NULL; e = next) {
+			next = e->next;
+			xp = &t->buckets[e->namehash & mask];
+			e->next = *xp;
+			*xp = e;
+		}
+	}
+	free(oldhp);
+	DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n",
+	       __func__, t, t->bucketsSize, t->numEntries, t->maxchain);
+	t->maxchain = 0;
+}
+
 void
 Hash_ForEach(Hash_Table *t, void (*action)(void *, void *), void *data)
 {
@@ -349,6 +377,6 @@ Hash_ForEach(Hash_Table *t, void (*actio
 void
 Hash_DebugStats(Hash_Table *t, const char *name)
 {
-	DEBUG4(HASH, "Hash_Table %s: size=%d numEntries=%d maxchain=%d\n",
-	       name, t->bucketsSize, t->numEntries, t->maxchain);
+    DEBUG4(HASH, "Hash_Table %s: size=%d numEntries=%d maxchain=%d\n",
+	   name, t->bucketsSize, t->numEntries, t->maxchain);
 }

Index: src/usr.bin/make/job.c
diff -u src/usr.bin/make/job.c:1.258 src/usr.bin/make/job.c:1.259
--- src/usr.bin/make/job.c:1.258	Sun Oct  4 16:50:37 2020
+++ src/usr.bin/make/job.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: job.c,v 1.258 2020/10/04 16:50:37 rillig Exp $	*/
+/*	$NetBSD: job.c,v 1.259 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -143,7 +143,7 @@
 #include "trace.h"
 
 /*	"@(#)job.c	8.2 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: job.c,v 1.258 2020/10/04 16:50:37 rillig Exp $");
+MAKE_RCSID("$NetBSD: job.c,v 1.259 2020/10/05 19:24:29 rillig Exp $");
 
 # define STATIC static
 
@@ -264,7 +264,7 @@ static Shell    shells[] = {
      */
 {
     "csh",
-    TRUE, "unset verbose", "set verbose", "unset verbose", 13,
+    TRUE, "unset verbose", "set verbose", "unset verbose", 10,
     FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#',
     "v", "e",
 },
@@ -655,7 +655,8 @@ JobPrintCommand(void *cmdp, void *jobp)
 	cmd++;
     }
 
-    pp_skip_whitespace(&cmd);
+    while (ch_isspace(*cmd))
+	cmd++;
 
     /*
      * If the shell doesn't have error control the alternate echo'ing will
@@ -1575,14 +1576,19 @@ JobStart(GNode *gn, int flags)
 }
 
 static char *
-JobOutput(Job *job, char *cp, char *endp)
+JobOutput(Job *job, char *cp, char *endp, int msg)
 {
     char *ecp;
 
-    if (commandShell->noPrint && commandShell->noPrint[0] != '\0') {
-	while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) {
+    if (commandShell->noPrint) {
+	ecp = Str_FindSubstring(cp, commandShell->noPrint);
+	while (ecp != NULL) {
 	    if (cp != ecp) {
 		*ecp = '\0';
+		if (!beSilent && msg && job->node != lastNode) {
+		    MESSAGE(stdout, job->node);
+		    lastNode = job->node;
+		}
 		/*
 		 * The only way there wouldn't be a newline after
 		 * this line is if it were the last in the buffer.
@@ -1603,6 +1609,7 @@ JobOutput(Job *job, char *cp, char *endp
 		while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
 		    cp++;
 		}
+		ecp = Str_FindSubstring(cp, commandShell->noPrint);
 	    } else {
 		return cp;
 	    }
@@ -1731,7 +1738,7 @@ end_loop:
 	if (i >= job->curPos) {
 	    char *cp;
 
-	    cp = JobOutput(job, job->outBuf, &job->outBuf[i]);
+	    cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE);
 
 	    /*
 	     * There's still more in that thar buffer. This time, though,
@@ -2206,7 +2213,8 @@ Job_ParseShell(char *line)
     Boolean	fullSpec = FALSE;
     Shell	*sh;
 
-    pp_skip_whitespace(&line);
+    while (ch_isspace(*line))
+	line++;
 
     free(shellArgv);
 

Index: src/usr.bin/make/main.c
diff -u src/usr.bin/make/main.c:1.367 src/usr.bin/make/main.c:1.368
--- src/usr.bin/make/main.c:1.367	Mon Oct  5 17:33:21 2020
+++ src/usr.bin/make/main.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: main.c,v 1.367 2020/10/05 17:33:21 rillig Exp $	*/
+/*	$NetBSD: main.c,v 1.368 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -106,12 +106,16 @@
 #include <sys/utsname.h>
 #include <sys/wait.h>
 
+#include <ctype.h>
 #include <errno.h>
 #include <signal.h>
 #include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <time.h>
 
 #include "make.h"
+#include "hash.h"
 #include "dir.h"
 #include "job.h"
 #include "pathnames.h"
@@ -122,11 +126,10 @@
 #endif
 
 /*	"@(#)main.c	8.3 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: main.c,v 1.367 2020/10/05 17:33:21 rillig Exp $");
+MAKE_RCSID("$NetBSD: main.c,v 1.368 2020/10/05 19:24:29 rillig Exp $");
 #if defined(MAKE_NATIVE) && !defined(lint)
-__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 "
-	    "The Regents of the University of California.  "
-	    "All rights reserved.");
+__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\
+ The Regents of the University of California.  All rights reserved.");
 #endif
 
 #ifndef	DEFMAXLOCAL
@@ -267,9 +270,6 @@ parse_debug_options(const char *argvalue
 
 	for (modules = argvalue; *modules; ++modules) {
 		switch (*modules) {
-		case '0':	/* undocumented, only intended for tests */
-			debug &= DEBUG_LINT;
-			break;
 		case 'A':
 			debug = ~(0|DEBUG_LINT);
 			break;
@@ -673,10 +673,9 @@ rearg:
 	 * perform them if so. Else take them to be targets and stuff them
 	 * on the end of the "create" list.
 	 */
-	for (; argc > 1; ++argv, --argc) {
-		VarAssign var;
-		if (Parse_IsVar(argv[1], &var)) {
-			Parse_DoVar(&var, VAR_CMD);
+	for (; argc > 1; ++argv, --argc)
+		if (Parse_IsVar(argv[1])) {
+			Parse_DoVar(argv[1], VAR_CMD);
 		} else {
 			if (!*argv[1])
 				Punt("illegal (null) argument.");
@@ -684,7 +683,6 @@ rearg:
 				goto rearg;
 			Lst_Append(create, bmake_strdup(argv[1]));
 		}
-	}
 
 	return;
 noarg:
@@ -973,60 +971,6 @@ InitVarTargets(void)
 	}
 }
 
-static const char *
-init_machine(const struct utsname *utsname)
-{
-	const char *machine = getenv("MACHINE");
-	if (machine != NULL)
-		return machine;
-
-#ifdef MAKE_NATIVE
-	return utsname->machine;
-#else
-#ifdef MAKE_MACHINE
-	return MAKE_MACHINE;
-#else
-	return "unknown";
-#endif
-#endif
-}
-
-static const char *
-init_machine_arch(void)
-{
-	const char *env = getenv("MACHINE_ARCH");
-	if (env != NULL)
-		return env;
-
-#ifdef MAKE_NATIVE
-	{
-		struct utsname utsname;
-		static char machine_arch_buf[sizeof(utsname.machine)];
-		const int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
-		size_t len = sizeof(machine_arch_buf);
-
-		if (sysctl(mib, __arraycount(mib), machine_arch_buf,
-			&len, NULL, 0) < 0) {
-		    (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname,
-			strerror(errno));
-		    exit(2);
-		}
-
-		return machine_arch_buf;
-	}
-#else
-#ifndef MACHINE_ARCH
-#ifdef MAKE_MACHINE_ARCH
-	return MAKE_MACHINE_ARCH;
-#else
-	return "unknown";
-#endif
-#else
-	return MACHINE_ARCH;
-#endif
-#endif
-}
-
 /*-
  * main --
  *	The main function, for obvious reasons. Initializes variables
@@ -1051,8 +995,8 @@ main(int argc, char **argv)
 	struct stat sb, sa;
 	char *p1, *path;
 	char mdpath[MAXPATHLEN];
-	const char *machine;
-	const char *machine_arch;
+	const char *machine = getenv("MACHINE");
+	const char *machine_arch = getenv("MACHINE_ARCH");
 	char *syspath = getenv("MAKESYSPATH");
 	StringList *sysMkPath;		/* Path of sys.mk */
 	char *cp = NULL, *start;
@@ -1107,8 +1051,44 @@ main(int argc, char **argv)
 	 * Note that both MACHINE and MACHINE_ARCH are decided at
 	 * run-time.
 	 */
-	machine = init_machine(&utsname);
-	machine_arch = init_machine_arch();
+	if (!machine) {
+#ifdef MAKE_NATIVE
+	    machine = utsname.machine;
+#else
+#ifdef MAKE_MACHINE
+	    machine = MAKE_MACHINE;
+#else
+	    machine = "unknown";
+#endif
+#endif
+	}
+
+	if (!machine_arch) {
+#ifdef MAKE_NATIVE
+	    static char machine_arch_buf[sizeof(utsname.machine)];
+	    const int mib[2] = { CTL_HW, HW_MACHINE_ARCH };
+	    size_t len = sizeof(machine_arch_buf);
+
+	    if (sysctl(mib, __arraycount(mib), machine_arch_buf,
+		    &len, NULL, 0) < 0) {
+		(void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname,
+		    strerror(errno));
+		exit(2);
+	    }
+
+	    machine_arch = machine_arch_buf;
+#else
+#ifndef MACHINE_ARCH
+#ifdef MAKE_MACHINE_ARCH
+	    machine_arch = MAKE_MACHINE_ARCH;
+#else
+	    machine_arch = "unknown";
+#endif
+#else
+	    machine_arch = MACHINE_ARCH;
+#endif
+#endif
+	}
 
 	myPid = getpid();		/* remember this for vFork() */
 
@@ -1735,10 +1715,16 @@ bad:
     return bmake_strdup("");
 }
 
-/* Print a printf-style error message.
+/*-
+ * Error --
+ *	Print an error message given its format.
  *
- * This error message has no consequences, in particular it does not affect
- * the exit status. */
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The message is printed.
+ */
 void
 Error(const char *fmt, ...)
 {

Index: src/usr.bin/make/make.h
diff -u src/usr.bin/make/make.h:1.153 src/usr.bin/make/make.h:1.154
--- src/usr.bin/make/make.h:1.153	Mon Oct  5 15:14:24 2020
+++ src/usr.bin/make/make.h	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: make.h,v 1.153 2020/10/05 15:14:24 rillig Exp $	*/
+/*	$NetBSD: make.h,v 1.154 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -259,9 +259,7 @@ typedef enum {
     /* Already processed by Suff_FindDeps */
     OP_DEPS_FOUND	= 1 << 25,
     /* Node found while expanding .ALLSRC */
-    OP_MARK		= 1 << 24,
-
-    OP_NOTARGET		= OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM
+    OP_MARK		= 1 << 24
 } GNodeType;
 
 typedef enum {
@@ -360,6 +358,15 @@ typedef struct GNode {
     int lineno;
 } GNode;
 
+#define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute)
+/*
+ * OP_NOP will return TRUE if the node with the given type was not the
+ * object of a dependency operator
+ */
+#define OP_NOP(t)	(((t) & OP_OPMASK) == 0x00000000)
+
+#define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM)
+
 /*
  * Error levels for parsing. PARSE_FATAL means the process cannot continue
  * once the makefile has been parsed. PARSE_WARNING means it can. Passed
@@ -561,22 +568,6 @@ int mkTempFile(const char *, char **);
 int str2Lst_Append(StringList *, char *, const char *);
 void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *);
 
-static Boolean MAKE_ATTR_UNUSED
-NoExecute(GNode *gn)
-{
-    return (gn->type & OP_MAKE) ? noRecursiveExecute : noExecute;
-}
-
-/*
- * See if the node with the given type was not the object of a dependency
- * operator.
- */
-static Boolean MAKE_ATTR_UNUSED
-OP_NOP(GNodeType t)
-{
-    return !(t & OP_OPMASK);
-}
-
 #ifdef __GNUC__
 #define UNCONST(ptr)	({		\
     union __unconst {			\
@@ -625,20 +616,6 @@ static inline MAKE_ATTR_UNUSED char ch_t
 static inline MAKE_ATTR_UNUSED char ch_toupper(char ch)
 { return (char)toupper((unsigned char)ch); }
 
-static inline MAKE_ATTR_UNUSED void
-cpp_skip_whitespace(const char **pp)
-{
-    while (ch_isspace(**pp))
-	(*pp)++;
-}
-
-static inline MAKE_ATTR_UNUSED void
-pp_skip_whitespace(char **pp)
-{
-    while (ch_isspace(**pp))
-	(*pp)++;
-}
-
 #ifndef MAKE_NATIVE
 #define MAKE_RCSID(id) static volatile char rcsid[] = id
 #else

Index: src/usr.bin/make/make_malloc.c
diff -u src/usr.bin/make/make_malloc.c:1.21 src/usr.bin/make/make_malloc.c:1.22
--- src/usr.bin/make/make_malloc.c:1.21	Sat Oct  3 21:52:50 2020
+++ src/usr.bin/make/make_malloc.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: make_malloc.c,v 1.21 2020/10/03 21:52:50 rillig Exp $	*/
+/*	$NetBSD: make_malloc.c,v 1.22 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*-
  * Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -26,11 +26,14 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <errno.h>
 
 #include "make.h"
 
-MAKE_RCSID("$NetBSD: make_malloc.c,v 1.21 2020/10/03 21:52:50 rillig Exp $");
+MAKE_RCSID("$NetBSD: make_malloc.c,v 1.22 2020/10/05 19:24:29 rillig Exp $");
 
 #ifndef USE_EMALLOC
 

Index: src/usr.bin/make/nonints.h
diff -u src/usr.bin/make/nonints.h:1.138 src/usr.bin/make/nonints.h:1.139
--- src/usr.bin/make/nonints.h:1.138	Sun Oct  4 20:23:32 2020
+++ src/usr.bin/make/nonints.h	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: nonints.h,v 1.138 2020/10/04 20:23:32 rillig Exp $	*/
+/*	$NetBSD: nonints.h,v 1.139 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*-
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -120,27 +120,9 @@ Boolean getBoolean(const char *, Boolean
 char *cached_realpath(const char *, char *);
 
 /* parse.c */
-
-typedef enum VarAssignOp {
-    VAR_NORMAL,			/* = */
-    VAR_SUBST,			/* := */
-    VAR_SHELL,			/* != or :sh= */
-    VAR_APPEND,			/* += */
-    VAR_DEFAULT			/* ?= */
-} VarAssignOp;
-
-typedef struct VarAssign {
-    const char *nameStart;	/* unexpanded */
-    const char *nameEndDraft;	/* before operator adjustment */
-    char *varname;
-    const char *eq;		/* the '=' of the assignment operator */
-    VarAssignOp op;
-    const char *value;		/* unexpanded */
-} VarAssign;
-
 void Parse_Error(int, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3);
-Boolean Parse_IsVar(const char *, VarAssign *out_var);
-void Parse_DoVar(VarAssign *, GNode *);
+Boolean Parse_IsVar(const char *);
+void Parse_DoVar(char *, GNode *);
 void Parse_AddIncludeDir(const char *);
 void Parse_File(const char *, int);
 void Parse_Init(void);
@@ -165,6 +147,7 @@ Words_Free(Words w) {
 char *str_concat2(const char *, const char *);
 char *str_concat3(const char *, const char *, const char *);
 char *str_concat4(const char *, const char *, const char *, const char *);
+char *Str_FindSubstring(const char *, const char *);
 Boolean Str_Match(const char *, const char *);
 
 /* suff.c */
@@ -210,13 +193,10 @@ void Targ_Propagate(void);
 /* var.c */
 
 typedef enum {
-    VARE_NONE		= 0,
     /* Treat undefined variables as errors. */
     VARE_UNDEFERR	= 0x01,
     /* Expand and evaluate variables during parsing. */
     VARE_WANTRES	= 0x02,
-    /* In an assignment using the ':=' operator, keep '$$' as '$$' instead
-     * of reducing it to a single '$'. */
     VARE_ASSIGN		= 0x04
 } VarEvalFlags;
 

Index: src/usr.bin/make/parse.c
diff -u src/usr.bin/make/parse.c:1.366 src/usr.bin/make/parse.c:1.367
--- src/usr.bin/make/parse.c:1.366	Mon Oct  5 16:54:41 2020
+++ src/usr.bin/make/parse.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: parse.c,v 1.366 2020/10/05 16:54:41 rillig Exp $	*/
+/*	$NetBSD: parse.c,v 1.367 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -116,6 +116,7 @@
 #include <sys/stat.h>
 #include <errno.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdint.h>
 
 #ifndef MAP_FILE
@@ -131,7 +132,7 @@
 #include "pathnames.h"
 
 /*	"@(#)parse.c	8.3 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: parse.c,v 1.366 2020/10/05 16:54:41 rillig Exp $");
+MAKE_RCSID("$NetBSD: parse.c,v 1.367 2020/10/05 19:24:29 rillig Exp $");
 
 /* types and constants */
 
@@ -204,8 +205,6 @@ typedef enum {
     Attribute		/* Generic attribute */
 } ParseSpecial;
 
-typedef List SearchPathList;
-
 /* result data */
 
 /*
@@ -586,8 +585,21 @@ ParseMark(GNode *gn)
     gn->lineno = curFile->lineno;
 }
 
-/* Look in the table of keywords for one matching the given string.
- * Return the index of the keyword, or -1 if it isn't there. */
+/*-
+ *----------------------------------------------------------------------
+ * ParseFindKeyword --
+ *	Look in the table of keywords for one matching the given string.
+ *
+ * Input:
+ *	str		String to find
+ *
+ * Results:
+ *	The index of the keyword, or -1 if it isn't there.
+ *
+ * Side Effects:
+ *	None
+ *----------------------------------------------------------------------
+ */
 static int
 ParseFindKeyword(const char *str)
 {
@@ -648,7 +660,8 @@ PrintLocation(FILE *f, const char *filen
  *
  * Increment "fatals" if the level is PARSE_FATAL, and continue parsing
  * until the end of the current top-level makefile, then exit (see
- * Parse_File). */
+ * Parse_File).
+ */
 static void
 ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type,
     const char *fmt, va_list ap)
@@ -755,7 +768,8 @@ ParseMessage(char *line)
 	line++;
     if (!ch_isspace(*line))
 	return FALSE;			/* not for us */
-    pp_skip_whitespace(&line);
+    while (ch_isspace(*line))
+	line++;
 
     (void)Var_Subst(line, VAR_CMD, VARE_WANTRES, &line);
     /* TODO: handle errors */
@@ -875,12 +889,33 @@ ApplyDependencyOperator(GNodeType op)
 	    break;
 }
 
-static Boolean
-ParseDoSrcKeyword(const char *src, ParseSpecial specType)
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoSrc  --
+ *	Given the name of a source, figure out if it is an attribute
+ *	and apply it to the targets if it is. Else decide if there is
+ *	some attribute which should be applied *to* the source because
+ *	of some special target and apply it if so. Otherwise, make the
+ *	source be a child of the targets in the list 'targets'
+ *
+ * Input:
+ *	tOp		operator (if any) from special targets
+ *	src		name of the source to handle
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Operator bits may be added to the list of targets or to the source.
+ *	The targets may have a new source added to their lists of children.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoSrc(int tOp, const char *src, ParseSpecial specType)
 {
+    GNode	*gn = NULL;
     static int wait_number = 0;
     char wait_src[16];
-    GNode *gn;
 
     if (*src == '.' && ch_isupper(src[1])) {
 	int keywd = ParseFindKeyword(src);
@@ -888,7 +923,7 @@ ParseDoSrcKeyword(const char *src, Parse
 	    int op = parseKeywords[keywd].op;
 	    if (op != 0) {
 		ApplyDependencyOperator(op);
-		return TRUE;
+		return;
 	    }
 	    if (parseKeywords[keywd].spec == Wait) {
 		/*
@@ -909,114 +944,82 @@ ParseDoSrcKeyword(const char *src, Parse
 		    struct ParseLinkSrcArgs args = { gn, specType };
 		    Lst_ForEach(targets, ParseLinkSrc, &args);
 		}
-		return TRUE;
+		return;
 	    }
 	}
     }
-    return FALSE;
-}
 
-static void
-ParseDoSrcMain(const char *src)
-{
-    /*
-     * If we have noted the existence of a .MAIN, it means we need
-     * to add the sources of said target to the list of things
-     * to create. The string 'src' is likely to be free, so we
-     * must make a new copy of it. Note that this will only be
-     * invoked if the user didn't specify a target on the command
-     * line. This is to allow #ifmake's to succeed, or something...
-     */
-    Lst_Append(create, bmake_strdup(src));
-    /*
-     * Add the name to the .TARGETS variable as well, so the user can
-     * employ that, if desired.
-     */
-    Var_Append(".TARGETS", src, VAR_GLOBAL);
-}
+    switch (specType) {
+    case Main:
+	/*
+	 * If we have noted the existence of a .MAIN, it means we need
+	 * to add the sources of said target to the list of things
+	 * to create. The string 'src' is likely to be free, so we
+	 * must make a new copy of it. Note that this will only be
+	 * invoked if the user didn't specify a target on the command
+	 * line. This is to allow #ifmake's to succeed, or something...
+	 */
+	Lst_Append(create, bmake_strdup(src));
+	/*
+	 * Add the name to the .TARGETS variable as well, so the user can
+	 * employ that, if desired.
+	 */
+	Var_Append(".TARGETS", src, VAR_GLOBAL);
+	return;
 
-static void
-ParseDoSrcOrder(const char *src)
-{
-    GNode *gn;
-    /*
-     * Create proper predecessor/successor links between the previous
-     * source and the current one.
-     */
-    gn = Targ_GetNode(src);
-    if (doing_depend)
-	ParseMark(gn);
-    if (predecessor != NULL) {
-	Lst_Append(predecessor->order_succ, gn);
-	Lst_Append(gn->order_pred, predecessor);
-	if (DEBUG(PARSE)) {
-	    debug_printf("# %s: added Order dependency %s - %s\n",
-			 __func__, predecessor->name, gn->name);
-	    Targ_PrintNode(predecessor, 0);
-	    Targ_PrintNode(gn, 0);
+    case Order:
+	/*
+	 * Create proper predecessor/successor links between the previous
+	 * source and the current one.
+	 */
+	gn = Targ_GetNode(src);
+	if (doing_depend)
+	    ParseMark(gn);
+	if (predecessor != NULL) {
+	    Lst_Append(predecessor->order_succ, gn);
+	    Lst_Append(gn->order_pred, predecessor);
+	    if (DEBUG(PARSE)) {
+		debug_printf("# %s: added Order dependency %s - %s\n",
+			     __func__, predecessor->name, gn->name);
+		Targ_PrintNode(predecessor, 0);
+		Targ_PrintNode(gn, 0);
+	    }
 	}
-    }
-    /*
-     * The current source now becomes the predecessor for the next one.
-     */
-    predecessor = gn;
-}
-
-static void
-ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType)
-{
-    GNode *gn;
+	/*
+	 * The current source now becomes the predecessor for the next one.
+	 */
+	predecessor = gn;
+	break;
 
-    /*
-     * If the source is not an attribute, we need to find/create
-     * a node for it. After that we can apply any operator to it
-     * from a special target or link it to its parents, as
-     * appropriate.
-     *
-     * In the case of a source that was the object of a :: operator,
-     * the attribute is applied to all of its instances (as kept in
-     * the 'cohorts' list of the node) or all the cohorts are linked
-     * to all the targets.
-     */
+    default:
+	/*
+	 * If the source is not an attribute, we need to find/create
+	 * a node for it. After that we can apply any operator to it
+	 * from a special target or link it to its parents, as
+	 * appropriate.
+	 *
+	 * In the case of a source that was the object of a :: operator,
+	 * the attribute is applied to all of its instances (as kept in
+	 * the 'cohorts' list of the node) or all the cohorts are linked
+	 * to all the targets.
+	 */
 
-    /* Find/create the 'src' node and attach to all targets */
-    gn = Targ_GetNode(src);
-    if (doing_depend)
-	ParseMark(gn);
-    if (tOp) {
-	gn->type |= tOp;
-    } else {
-	{
-	    struct ParseLinkSrcArgs args = { gn, specType };
-	    Lst_ForEach(targets, ParseLinkSrc, &args);
+	/* Find/create the 'src' node and attach to all targets */
+	gn = Targ_GetNode(src);
+	if (doing_depend)
+	    ParseMark(gn);
+	if (tOp) {
+	    gn->type |= tOp;
+	} else {
+	    {
+	        struct ParseLinkSrcArgs args = { gn, specType };
+		Lst_ForEach(targets, ParseLinkSrc, &args);
+	    }
 	}
+	break;
     }
 }
 
-/* Given the name of a source in a dependency line, figure out if it is an
- * attribute (such as .SILENT) and apply it to the targets if it is. Else
- * decide if there is some attribute which should be applied *to* the source
- * because of some special target (such as .PHONY) and apply it if so.
- * Otherwise, make the source a child of the targets in the list 'targets'.
- *
- * Input:
- *	tOp		operator (if any) from special targets
- *	src		name of the source to handle
- */
-static void
-ParseDoSrc(GNodeType tOp, const char *src, ParseSpecial specType)
-{
-    if (ParseDoSrcKeyword(src, specType))
-        return;
-
-    if (specType == Main)
-        ParseDoSrcMain(src);
-    else if (specType == Order)
-        ParseDoSrcOrder(src);
-    else
-        ParseDoSrcOther(src, tOp, specType);
-}
-
 /* If we have yet to decide on a main target to make, in the absence of any
  * user input, we want the first target on the first dependency line that is
  * actually a real target (i.e. isn't a .USE or .EXEC rule) to be made. */
@@ -1068,7 +1071,8 @@ ParseErrorNoDependency(const char *lstar
     else if (lstart[0] == '.') {
 	const char *dirstart = lstart + 1;
 	const char *dirend;
-	cpp_skip_whitespace(&dirstart);
+	while (ch_isspace(*dirstart))
+	    dirstart++;
 	dirend = dirstart;
 	while (ch_isalnum(*dirend) || *dirend == '-')
 	    dirend++;
@@ -1112,369 +1116,64 @@ ParseDependencyTargetWord(/*const*/ char
     *pp = cp;
 }
 
-/*
- * Certain special targets have special semantics:
- *	.PATH		Have to set the dirSearchPath
- *			variable too
- *	.MAIN		Its sources are only used if
- *			nothing has been specified to
- *			create.
- *	.DEFAULT	Need to create a node to hang
- *			commands on, but we don't want
- *			it in the graph, nor do we want
- *			it to be the Main Target, so we
- *			create it, set OP_NOTMAIN and
- *			add it to the list, setting
- *			DEFAULT to the new node for
- *			later use. We claim the node is
- *			A transformation rule to make
- *			life easier later, when we'll
- *			use Make_HandleUse to actually
- *			apply the .DEFAULT commands.
- *	.PHONY		The list of targets
- *	.NOPATH		Don't search for file in the path
- *	.STALE
- *	.BEGIN
- *	.END
- *	.ERROR
- *	.DELETE_ON_ERROR
- *	.INTERRUPT	Are not to be considered the
- *			main target.
- *	.NOTPARALLEL	Make only one target at a time.
- *	.SINGLESHELL	Create a shell for each command.
- *	.ORDER		Must set initial predecessor to NULL
- */
-static void
-ParseDoDependencyTargetSpecial(ParseSpecial *const inout_specType,
-			       const char *const line,
-			       SearchPathList **const inout_paths)
-{
-    switch (*inout_specType) {
-    case ExPath:
-	if (*inout_paths == NULL) {
-	    *inout_paths = Lst_Init();
-	}
-	Lst_Append(*inout_paths, dirSearchPath);
-	break;
-    case Main:
-	if (!Lst_IsEmpty(create)) {
-	    *inout_specType = Not;
-	}
-	break;
-    case Begin:
-    case End:
-    case Stale:
-    case dotError:
-    case Interrupt: {
-	GNode *gn = Targ_GetNode(line);
-	if (doing_depend)
-	    ParseMark(gn);
-	gn->type |= OP_NOTMAIN|OP_SPECIAL;
-	Lst_Append(targets, gn);
-	break;
-    }
-    case Default: {
-	GNode *gn = Targ_NewGN(".DEFAULT");
-	gn->type |= OP_NOTMAIN|OP_TRANSFORM;
-	Lst_Append(targets, gn);
-	DEFAULT = gn;
-	break;
-    }
-    case DeleteOnError:
-	deleteOnError = TRUE;
-	break;
-    case NotParallel:
-	maxJobs = 1;
-	break;
-    case SingleShell:
-	compatMake = TRUE;
-	break;
-    case Order:
-	predecessor = NULL;
-	break;
-    default:
-	break;
-    }
-}
-
-/*
- * .PATH<suffix> has to be handled specially.
- * Call on the suffix module to give us a path to modify.
- */
-static Boolean
-ParseDoDependencyTargetPath(const char *const line,
-			    SearchPathList **const inout_paths)
-{
-    SearchPath *path;
-
-    path = Suff_GetPath(&line[5]);
-    if (path == NULL) {
-	Parse_Error(PARSE_FATAL,
-		    "Suffix '%s' not defined (yet)",
-		    &line[5]);
-	return FALSE;
-    } else {
-	if (*inout_paths == NULL) {
-	    *inout_paths = Lst_Init();
-	}
-	Lst_Append(*inout_paths, path);
-    }
-    return TRUE;
-}
-
-/*
- * See if it's a special target and if so set specType to match it.
+/* Parse a dependency line consisting of targets, followed by a dependency
+ * operator, optionally followed by sources.
+ *
+ * The nodes of the sources are linked as children to the nodes of the
+ * targets. Nodes are created as necessary.
+ *
+ * The operator is applied to each node in the global 'targets' list,
+ * which is where the nodes found for the targets are kept, by means of
+ * the ParseDoOp function.
+ *
+ * The sources are parsed in much the same way as the targets, except
+ * that they are expanded using the wildcarding scheme of the C-Shell,
+ * and all instances of the resulting words in the list of all targets
+ * are found. Each of the resulting nodes is then linked to each of the
+ * targets as one of its children.
+ *
+ * Certain targets and sources such as .PHONY or .PRECIOUS are handled
+ * specially. These are the ones detailed by the specType variable.
+ *
+ * The storing of transformation rules such as '.c.o' is also taken care of
+ * here. A target is recognized as a transformation rule by calling
+ * Suff_IsTransform. If it is a transformation rule, its node is gotten
+ * from the suffix module via Suff_AddTransform rather than the standard
+ * Targ_FindNode in the target module.
  */
-static Boolean
-ParseDoDependencyTarget(const char *const line,
-			ParseSpecial *const inout_specType,
-			GNodeType *out_tOp,
-			SearchPathList **inout_paths)
+static void
+ParseDoDependency(char *line)
 {
-    int keywd;
+    typedef List SearchPathList;
 
-    if (!(*line == '.' && ch_isupper(line[1])))
-	return TRUE;
+    char *cp;			/* our current position */
+    GNodeType op;		/* the operator on the line */
+    char            savec;	/* a place to save a character */
+    SearchPathList *paths;	/* search paths to alter when parsing
+				 * a list of .PATH targets */
+    int tOp;			/* operator from special target */
+    GNodeList *sources;		/* archive sources after expansion */
+    StringList *curTargs;	/* target names to be found and added
+				 * to the targets list */
+    char	   *lstart = line;
 
     /*
-     * See if the target is a special target that must have it
-     * or its sources handled specially.
+     * specType contains the SPECial TYPE of the current target. It is Not
+     * if the target is unspecial. If it *is* special, however, the children
+     * are linked as children of the parent but not vice versa.
      */
-    keywd = ParseFindKeyword(line);
-    if (keywd != -1) {
-	if (*inout_specType == ExPath && parseKeywords[keywd].spec != ExPath) {
-	    Parse_Error(PARSE_FATAL, "Mismatched special targets");
-	    return FALSE;
-	}
-
-	*inout_specType = parseKeywords[keywd].spec;
-	*out_tOp = parseKeywords[keywd].op;
-
-	ParseDoDependencyTargetSpecial(inout_specType, line, inout_paths);
-
-    } else if (strncmp(line, ".PATH", 5) == 0) {
-	*inout_specType = ExPath;
-	if (!ParseDoDependencyTargetPath(line, inout_paths))
-	    return FALSE;
-    }
-    return TRUE;
-}
-
-static void
-ParseDoDependencyTargetMundane(char *const line,
-			       StringList *const curTargs)
-{
-    if (Dir_HasWildcards(line)) {
-	/*
-	 * Targets are to be sought only in the current directory,
-	 * so create an empty path for the thing. Note we need to
-	 * use Dir_Destroy in the destruction of the path as the
-	 * Dir module could have added a directory to the path...
-	 */
-	SearchPath *emptyPath = Lst_Init();
-
-	Dir_Expand(line, emptyPath, curTargs);
-
-	Lst_Destroy(emptyPath, Dir_Destroy);
-    } else {
-	/*
-	 * No wildcards, but we want to avoid code duplication,
-	 * so create a list with the word on it.
-	 */
-	Lst_Append(curTargs, line);
-    }
-
-    /* Apply the targets. */
-
-    while(!Lst_IsEmpty(curTargs)) {
-	char *targName = Lst_Dequeue(curTargs);
-	GNode *gn = Suff_IsTransform(targName)
-		    ? Suff_AddTransform(targName)
-		    : Targ_GetNode(targName);
-	if (doing_depend)
-	    ParseMark(gn);
-
-	Lst_Append(targets, gn);
-    }
-}
-
-static void
-ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart)
-{
-    Boolean warning = FALSE;
-    char *cp = *pp;
-
-    while (*cp && (ParseIsEscaped(lstart, cp) ||
-		   (*cp != '!' && *cp != ':'))) {
-	if (ParseIsEscaped(lstart, cp) ||
-	    (*cp != ' ' && *cp != '\t')) {
-	    warning = TRUE;
-	}
-	cp++;
-    }
-    if (warning) {
-	Parse_Error(PARSE_WARNING, "Extra target ignored");
-    }
-    *pp = cp;
-}
-
-static void
-ParseDoDependencyCheckSpec(ParseSpecial const specType)
-{
-    switch(specType) {
-    default:
-	Parse_Error(PARSE_WARNING,
-		    "Special and mundane targets don't mix. Mundane ones ignored");
-	break;
-    case Default:
-    case Stale:
-    case Begin:
-    case End:
-    case dotError:
-    case Interrupt:
-	/*
-	 * These four create nodes on which to hang commands, so
-	 * targets shouldn't be empty...
-	 */
-    case Not:
-	/*
-	 * Nothing special here -- targets can be empty if it wants.
-	 */
-	break;
-    }
-}
-
-static Boolean
-ParseDoDependencyParseOp(char **const pp, const char *const lstart,
-			 GNodeType *const out_op)
-{
-    const char *cp = *pp;
-
-    if (*cp == '!') {
-	*out_op = OP_FORCE;
-	(*pp)++;
-	return TRUE;
-    }
-
-    if (*cp == ':') {
-	if (cp[1] == ':') {
-	    *out_op = OP_DOUBLEDEP;
-	    (*pp) += 2;
-	} else {
-	    *out_op = OP_DEPENDS;
-	    (*pp)++;
-	}
-	return TRUE;
-    }
+    ParseSpecial specType = Not;
 
-    {
-	const char *msg = lstart[0] == '.' ? "Unknown directive"
-					   : "Missing dependency operator";
-	Parse_Error(PARSE_FATAL, "%s", msg);
-	return FALSE;
-    }
-}
+    DEBUG1(PARSE, "ParseDoDependency(%s)\n", line);
+    tOp = 0;
 
-static void
-ParseDoDependencySourcesEmpty(ParseSpecial const specType,
-			      SearchPathList *const paths)
-{
-    switch (specType) {
-    case Suffixes:
-	Suff_ClearSuffixes();
-	break;
-    case Precious:
-	allPrecious = TRUE;
-	break;
-    case Ignore:
-	ignoreErrors = TRUE;
-	break;
-    case Silent:
-	beSilent = TRUE;
-	break;
-    case ExPath:
-	if (paths != NULL)
-	    Lst_ForEach(paths, ParseClearPath, NULL);
-	Dir_SetPATH();
-	break;
-#ifdef POSIX
-    case Posix:
-	Var_Set("%POSIX", "1003.2", VAR_GLOBAL);
-	break;
-#endif
-    default:
-	break;
-    }
-}
+    paths = NULL;
 
-/*
- * If the target was one that doesn't take files as its sources
- * but takes something like suffixes, we take each
- * space-separated word on the line as a something and deal
- * with it accordingly.
- *
- * If the target was .SUFFIXES, we take each source as a
- * suffix and add it to the list of suffixes maintained by the
- * Suff module.
- *
- * If the target was a .PATH, we add the source as a directory
- * to search on the search path.
- *
- * If it was .INCLUDES, the source is taken to be the suffix of
- * files which will be #included and whose search path should
- * be present in the .INCLUDES variable.
- *
- * If it was .LIBS, the source is taken to be the suffix of
- * files which are considered libraries and whose search path
- * should be present in the .LIBS variable.
- *
- * If it was .NULL, the source is the suffix to use when a file
- * has no valid suffix.
- *
- * If it was .OBJDIR, the source is a new definition for .OBJDIR,
- * and will cause make to do a new chdir to that path.
- */
-static void
-ParseDoDependencySourceSpecial(ParseSpecial const specType, char *const line,
-			       SearchPathList *const paths)
-{
-    switch (specType) {
-    case Suffixes:
-	Suff_AddSuffix(line, &mainNode);
-	break;
-    case ExPath:
-	if (paths != NULL)
-	    Lst_ForEach(paths, ParseAddDir, line);
-	break;
-    case Includes:
-	Suff_AddInclude(line);
-	break;
-    case Libs:
-	Suff_AddLib(line);
-	break;
-    case Null:
-	Suff_SetNull(line);
-	break;
-    case ExObjdir:
-	Main_SetObjdir("%s", line);
-	break;
-    default:
-	break;
-    }
-}
+    curTargs = Lst_Init();
 
-static Boolean
-ParseDoDependencyTargets(char **const inout_cp,
-			 char **const inout_line,
-			 const char *const lstart,
-			 ParseSpecial *const inout_specType,
-			 GNodeType *const inout_tOp,
-			 SearchPathList **const inout_paths,
-			 StringList *const curTargs)
-{
-    char *cp = *inout_cp;
-    char *line = *inout_line;
-    char savec;
+    /*
+     * First, grind through the targets.
+     */
 
     for (;;) {
 	/*
@@ -1503,8 +1202,8 @@ ParseDoDependencyTargets(char **const in
 	     */
 	    if (!Arch_ParseArchive(&line, targets, VAR_CMD)) {
 		Parse_Error(PARSE_FATAL,
-			    "Error in archive specification: \"%s\"", line);
-		return FALSE;
+			     "Error in archive specification: \"%s\"", line);
+		goto out;
 	    } else {
 		/* Done with this word; on to the next. */
 		cp = line;
@@ -1514,180 +1213,207 @@ ParseDoDependencyTargets(char **const in
 
 	if (!*cp) {
 	    ParseErrorNoDependency(lstart, line);
-	    return FALSE;
+	    goto out;
 	}
 
 	/* Insert a null terminator. */
 	savec = *cp;
 	*cp = '\0';
 
-	if (!ParseDoDependencyTarget(line, inout_specType, inout_tOp,
-				     inout_paths))
-	    return FALSE;
-
 	/*
-	 * Have word in line. Get or create its node and stick it at
-	 * the end of the targets list
+	 * Got the word. See if it's a special target and if so set
+	 * specType to match it.
 	 */
-	if (*inout_specType == Not && *line != '\0') {
-	    ParseDoDependencyTargetMundane(line, curTargs);
-	} else if (*inout_specType == ExPath && *line != '.' && *line != '\0') {
-	    Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
-	}
-
-	/* Don't need the inserted null terminator any more. */
-	*cp = savec;
-
-	/*
-	 * If it is a special type and not .PATH, it's the only target we
-	 * allow on this line...
-	 */
-	if (*inout_specType != Not && *inout_specType != ExPath) {
-	    ParseDoDependencyTargetExtraWarn(&cp, lstart);
-	} else {
-	    pp_skip_whitespace(&cp);
-	}
-	line = cp;
-	if (*line == '\0')
-	    break;
-	if ((*line == '!' || *line == ':') && !ParseIsEscaped(lstart, line))
-	    break;
-    }
-
-    *inout_cp = cp;
-    *inout_line = line;
-    return TRUE;
-}
+	if (*line == '.' && ch_isupper(line[1])) {
+	    /*
+	     * See if the target is a special target that must have it
+	     * or its sources handled specially.
+	     */
+	    int keywd = ParseFindKeyword(line);
+	    if (keywd != -1) {
+		if (specType == ExPath && parseKeywords[keywd].spec != ExPath) {
+		    Parse_Error(PARSE_FATAL, "Mismatched special targets");
+		    goto out;
+		}
 
-static void
-ParseDoDependencySourcesSpecial(char *line, char *cp,
-				ParseSpecial specType, SearchPathList *paths)
-{
-    char savec;
+		specType = parseKeywords[keywd].spec;
+		tOp = parseKeywords[keywd].op;
 
-    while (*line) {
-	while (*cp && !ch_isspace(*cp)) {
-	    cp++;
-	}
-	savec = *cp;
-	*cp = '\0';
-	ParseDoDependencySourceSpecial(specType, line, paths);
-	*cp = savec;
-	if (savec != '\0') {
-	    cp++;
-	}
-	pp_skip_whitespace(&cp);
-	line = cp;
-    }
-}
-
-static Boolean
-ParseDoDependencySourcesMundane(char *line, char *cp,
-			 ParseSpecial specType, GNodeType tOp)
-{
-    while (*line) {
-	/*
-	 * The targets take real sources, so we must beware of archive
-	 * specifications (i.e. things with left parentheses in them)
-	 * and handle them accordingly.
-	 */
-	for (; *cp && !ch_isspace(*cp); cp++) {
-	    if (*cp == '(' && cp > line && cp[-1] != '$') {
 		/*
-		 * Only stop for a left parenthesis if it isn't at the
-		 * start of a word (that'll be for variable changes
-		 * later) and isn't preceded by a dollar sign (a dynamic
-		 * source).
+		 * Certain special targets have special semantics:
+		 *	.PATH		Have to set the dirSearchPath
+		 *			variable too
+		 *	.MAIN		Its sources are only used if
+		 *			nothing has been specified to
+		 *			create.
+		 *	.DEFAULT	Need to create a node to hang
+		 *			commands on, but we don't want
+		 *			it in the graph, nor do we want
+		 *			it to be the Main Target, so we
+		 *			create it, set OP_NOTMAIN and
+		 *			add it to the list, setting
+		 *			DEFAULT to the new node for
+		 *			later use. We claim the node is
+		 *			A transformation rule to make
+		 *			life easier later, when we'll
+		 *			use Make_HandleUse to actually
+		 *			apply the .DEFAULT commands.
+		 *	.PHONY		The list of targets
+		 *	.NOPATH		Don't search for file in the path
+		 *	.STALE
+		 *	.BEGIN
+		 *	.END
+		 *	.ERROR
+		 *	.DELETE_ON_ERROR
+		 *	.INTERRUPT	Are not to be considered the
+		 *			main target.
+		 *	.NOTPARALLEL	Make only one target at a time.
+		 *	.SINGLESHELL	Create a shell for each command.
+		 *	.ORDER		Must set initial predecessor to NULL
 		 */
-		break;
-	    }
-	}
-
-	if (*cp == '(') {
-	    GNodeList *sources = Lst_Init();
-	    if (!Arch_ParseArchive(&line, sources, VAR_CMD)) {
-		Parse_Error(PARSE_FATAL,
-			    "Error in source archive spec \"%s\"", line);
-		return FALSE;
-	    }
-
-	    while (!Lst_IsEmpty(sources)) {
-		GNode *gn = Lst_Dequeue(sources);
-		ParseDoSrc(tOp, gn->name, specType);
-	    }
-	    Lst_Free(sources);
-	    cp = line;
-	} else {
-	    if (*cp) {
-		*cp = '\0';
-		cp++;
-	    }
-
-	    ParseDoSrc(tOp, line, specType);
-	}
-	pp_skip_whitespace(&cp);
-	line = cp;
-    }
-    return TRUE;
-}
+		switch (specType) {
+		case ExPath:
+		    if (paths == NULL) {
+			paths = Lst_Init();
+		    }
+		    Lst_Append(paths, dirSearchPath);
+		    break;
+		case Main:
+		    if (!Lst_IsEmpty(create)) {
+			specType = Not;
+		    }
+		    break;
+		case Begin:
+		case End:
+		case Stale:
+		case dotError:
+		case Interrupt: {
+		    GNode *gn = Targ_GetNode(line);
+		    if (doing_depend)
+			ParseMark(gn);
+		    gn->type |= OP_NOTMAIN|OP_SPECIAL;
+		    Lst_Append(targets, gn);
+		    break;
+		}
+		case Default: {
+		    GNode *gn = Targ_NewGN(".DEFAULT");
+		    gn->type |= OP_NOTMAIN|OP_TRANSFORM;
+		    Lst_Append(targets, gn);
+		    DEFAULT = gn;
+		    break;
+		}
+		case DeleteOnError:
+		    deleteOnError = TRUE;
+		    break;
+		case NotParallel:
+		    maxJobs = 1;
+		    break;
+		case SingleShell:
+		    compatMake = TRUE;
+		    break;
+		case Order:
+		    predecessor = NULL;
+		    break;
+		default:
+		    break;
+		}
+	    } else if (strncmp(line, ".PATH", 5) == 0) {
+		/*
+		 * .PATH<suffix> has to be handled specially.
+		 * Call on the suffix module to give us a path to
+		 * modify.
+		 */
+		SearchPath *path;
 
-/* Parse a dependency line consisting of targets, followed by a dependency
- * operator, optionally followed by sources.
- *
- * The nodes of the sources are linked as children to the nodes of the
- * targets. Nodes are created as necessary.
- *
- * The operator is applied to each node in the global 'targets' list,
- * which is where the nodes found for the targets are kept, by means of
- * the ParseDoOp function.
- *
- * The sources are parsed in much the same way as the targets, except
- * that they are expanded using the wildcarding scheme of the C-Shell,
- * and all instances of the resulting words in the list of all targets
- * are found. Each of the resulting nodes is then linked to each of the
- * targets as one of its children.
- *
- * Certain targets and sources such as .PHONY or .PRECIOUS are handled
- * specially. These are the ones detailed by the specType variable.
- *
- * The storing of transformation rules such as '.c.o' is also taken care of
- * here. A target is recognized as a transformation rule by calling
- * Suff_IsTransform. If it is a transformation rule, its node is gotten
- * from the suffix module via Suff_AddTransform rather than the standard
- * Targ_FindNode in the target module.
- */
-static void
-ParseDoDependency(char *line)
-{
-    char *cp;			/* our current position */
-    GNodeType op;		/* the operator on the line */
-    SearchPathList *paths;	/* search paths to alter when parsing
-				 * a list of .PATH targets */
-    int tOp;			/* operator from special target */
-    StringList *curTargs;	/* target names to be found and added
-				 * to the targets list */
-    char	   *lstart = line;
+		specType = ExPath;
+		path = Suff_GetPath(&line[5]);
+		if (path == NULL) {
+		    Parse_Error(PARSE_FATAL,
+				 "Suffix '%s' not defined (yet)",
+				 &line[5]);
+		    goto out;
+		} else {
+		    if (paths == NULL) {
+			paths = Lst_Init();
+		    }
+		    Lst_Append(paths, path);
+		}
+	    }
+	}
 
-    /*
-     * specType contains the SPECial TYPE of the current target. It is Not
-     * if the target is unspecial. If it *is* special, however, the children
-     * are linked as children of the parent but not vice versa.
-     */
-    ParseSpecial specType = Not;
+	/*
+	 * Have word in line. Get or create its node and stick it at
+	 * the end of the targets list
+	 */
+	if (specType == Not && *line != '\0') {
+	    if (Dir_HasWildcards(line)) {
+		/*
+		 * Targets are to be sought only in the current directory,
+		 * so create an empty path for the thing. Note we need to
+		 * use Dir_Destroy in the destruction of the path as the
+		 * Dir module could have added a directory to the path...
+		 */
+		SearchPath *emptyPath = Lst_Init();
 
-    DEBUG1(PARSE, "ParseDoDependency(%s)\n", line);
-    tOp = 0;
+		Dir_Expand(line, emptyPath, curTargs);
 
-    paths = NULL;
+		Lst_Destroy(emptyPath, Dir_Destroy);
+	    } else {
+		/*
+		 * No wildcards, but we want to avoid code duplication,
+		 * so create a list with the word on it.
+		 */
+		Lst_Append(curTargs, line);
+	    }
 
-    curTargs = Lst_Init();
+	    /* Apply the targets. */
 
-    /*
-     * First, grind through the targets.
-     */
-    if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, &paths,
-				  curTargs))
-	goto out;
+	    while(!Lst_IsEmpty(curTargs)) {
+		char *targName = Lst_Dequeue(curTargs);
+		GNode *gn = Suff_IsTransform(targName)
+			    ? Suff_AddTransform(targName)
+			    : Targ_GetNode(targName);
+		if (doing_depend)
+		    ParseMark(gn);
+
+		Lst_Append(targets, gn);
+	    }
+	} else if (specType == ExPath && *line != '.' && *line != '\0') {
+	    Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
+	}
+
+	/* Don't need the inserted null terminator any more. */
+	*cp = savec;
+
+	/*
+	 * If it is a special type and not .PATH, it's the only target we
+	 * allow on this line...
+	 */
+	if (specType != Not && specType != ExPath) {
+	    Boolean warning = FALSE;
+
+	    while (*cp && (ParseIsEscaped(lstart, cp) ||
+		(*cp != '!' && *cp != ':'))) {
+		if (ParseIsEscaped(lstart, cp) ||
+		    (*cp != ' ' && *cp != '\t')) {
+		    warning = TRUE;
+		}
+		cp++;
+	    }
+	    if (warning) {
+		Parse_Error(PARSE_WARNING, "Extra target ignored");
+	    }
+	} else {
+	    while (*cp && ch_isspace(*cp)) {
+		cp++;
+	    }
+	}
+	line = cp;
+	if (*line == '\0')
+	    break;
+	if ((*line == '!' || *line == ':') && !ParseIsEscaped(lstart, line))
+	    break;
+    }
 
     /*
      * Don't need the list of target names anymore...
@@ -1695,14 +1421,50 @@ ParseDoDependency(char *line)
     Lst_Free(curTargs);
     curTargs = NULL;
 
-    if (!Lst_IsEmpty(targets))
-        ParseDoDependencyCheckSpec(specType);
+    if (!Lst_IsEmpty(targets)) {
+	switch(specType) {
+	    default:
+		Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored");
+		break;
+	    case Default:
+	    case Stale:
+	    case Begin:
+	    case End:
+	    case dotError:
+	    case Interrupt:
+		/*
+		 * These four create nodes on which to hang commands, so
+		 * targets shouldn't be empty...
+		 */
+	    case Not:
+		/*
+		 * Nothing special here -- targets can be empty if it wants.
+		 */
+		break;
+	}
+    }
 
     /*
-     * Have now parsed all the target names. Must parse the operator next.
+     * Have now parsed all the target names. Must parse the operator next. The
+     * result is left in  op .
      */
-    if (!ParseDoDependencyParseOp(&cp, lstart, &op))
-        goto out;
+    if (*cp == '!') {
+	op = OP_FORCE;
+    } else if (*cp == ':') {
+	if (cp[1] == ':') {
+	    op = OP_DOUBLEDEP;
+	    cp++;
+	} else {
+	    op = OP_DEPENDS;
+	}
+    } else {
+	Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive"
+		    : "Missing dependency operator");
+	goto out;
+    }
+
+    /* Advance beyond the operator */
+    cp++;
 
     /*
      * Apply the operator to the target. This is how we remember which
@@ -1717,7 +1479,9 @@ ParseDoDependency(char *line)
      * LINE will now point to the first source word, if any, or the
      * end of the string if not.
      */
-    pp_skip_whitespace(&cp);
+    while (*cp && ch_isspace(*cp)) {
+	cp++;
+    }
     line = cp;
 
     /*
@@ -1730,7 +1494,32 @@ ParseDoDependency(char *line)
      *	a .PATH removes all directories from the search path(s).
      */
     if (!*line) {
-        ParseDoDependencySourcesEmpty(specType, paths);
+	switch (specType) {
+	    case Suffixes:
+		Suff_ClearSuffixes();
+		break;
+	    case Precious:
+		allPrecious = TRUE;
+		break;
+	    case Ignore:
+		ignoreErrors = TRUE;
+		break;
+	    case Silent:
+		beSilent = TRUE;
+		break;
+	    case ExPath:
+		if (paths != NULL)
+		    Lst_ForEach(paths, ParseClearPath, NULL);
+		Dir_SetPATH();
+		break;
+#ifdef POSIX
+	    case Posix:
+		Var_Set("%POSIX", "1003.2", VAR_GLOBAL);
+		break;
+#endif
+	    default:
+		break;
+	}
     } else if (specType == MFlags) {
 	/*
 	 * Call on functions in main.c to deal with these arguments and
@@ -1757,7 +1546,71 @@ ParseDoDependency(char *line)
 	specType == Includes || specType == Libs ||
 	specType == Null || specType == ExObjdir)
     {
-        ParseDoDependencySourcesSpecial(line, cp, specType, paths);
+	while (*line) {
+	    /*
+	     * If the target was one that doesn't take files as its sources
+	     * but takes something like suffixes, we take each
+	     * space-separated word on the line as a something and deal
+	     * with it accordingly.
+	     *
+	     * If the target was .SUFFIXES, we take each source as a
+	     * suffix and add it to the list of suffixes maintained by the
+	     * Suff module.
+	     *
+	     * If the target was a .PATH, we add the source as a directory
+	     * to search on the search path.
+	     *
+	     * If it was .INCLUDES, the source is taken to be the suffix of
+	     * files which will be #included and whose search path should
+	     * be present in the .INCLUDES variable.
+	     *
+	     * If it was .LIBS, the source is taken to be the suffix of
+	     * files which are considered libraries and whose search path
+	     * should be present in the .LIBS variable.
+	     *
+	     * If it was .NULL, the source is the suffix to use when a file
+	     * has no valid suffix.
+	     *
+	     * If it was .OBJDIR, the source is a new definition for .OBJDIR,
+	     * and will cause make to do a new chdir to that path.
+	     */
+	    while (*cp && !ch_isspace(*cp)) {
+		cp++;
+	    }
+	    savec = *cp;
+	    *cp = '\0';
+	    switch (specType) {
+		case Suffixes:
+		    Suff_AddSuffix(line, &mainNode);
+		    break;
+		case ExPath:
+		    if (paths != NULL)
+			Lst_ForEach(paths, ParseAddDir, line);
+		    break;
+		case Includes:
+		    Suff_AddInclude(line);
+		    break;
+		case Libs:
+		    Suff_AddLib(line);
+		    break;
+		case Null:
+		    Suff_SetNull(line);
+		    break;
+		case ExObjdir:
+		    Main_SetObjdir("%s", line);
+		    break;
+		default:
+		    break;
+	    }
+	    *cp = savec;
+	    if (savec != '\0') {
+		cp++;
+	    }
+	    while (*cp && ch_isspace(*cp)) {
+		cp++;
+	    }
+	    line = cp;
+	}
 	if (paths) {
 	    Lst_Free(paths);
 	    paths = NULL;
@@ -1766,8 +1619,51 @@ ParseDoDependency(char *line)
 	    Dir_SetPATH();
     } else {
 	assert(paths == NULL);
-        if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp))
-            goto out;
+	while (*line) {
+	    /*
+	     * The targets take real sources, so we must beware of archive
+	     * specifications (i.e. things with left parentheses in them)
+	     * and handle them accordingly.
+	     */
+	    for (; *cp && !ch_isspace(*cp); cp++) {
+		if (*cp == '(' && cp > line && cp[-1] != '$') {
+		    /*
+		     * Only stop for a left parenthesis if it isn't at the
+		     * start of a word (that'll be for variable changes
+		     * later) and isn't preceded by a dollar sign (a dynamic
+		     * source).
+		     */
+		    break;
+		}
+	    }
+
+	    if (*cp == '(') {
+		sources = Lst_Init();
+		if (!Arch_ParseArchive(&line, sources, VAR_CMD)) {
+		    Parse_Error(PARSE_FATAL,
+				 "Error in source archive spec \"%s\"", line);
+		    goto out;
+		}
+
+		while (!Lst_IsEmpty(sources)) {
+		    GNode *gn = Lst_Dequeue(sources);
+		    ParseDoSrc(tOp, gn->name, specType);
+		}
+		Lst_Free(sources);
+		cp = line;
+	    } else {
+		if (*cp) {
+		    *cp = '\0';
+		    cp++;
+		}
+
+		ParseDoSrc(tOp, line, specType);
+	    }
+	    while (*cp && ch_isspace(*cp)) {
+		cp++;
+	    }
+	    line = cp;
+	}
     }
 
     FindMainTarget();
@@ -1779,37 +1675,42 @@ out:
 	Lst_Free(curTargs);
 }
 
-/* Parse a variable assignment, consisting of a single-word variable name,
- * optional whitespace, an assignment operator, optional whitespace and the
- * variable value.
+/*-
+ *---------------------------------------------------------------------
+ * Parse_IsVar  --
+ *	Return TRUE if the passed line is a variable assignment. A variable
+ *	assignment consists of a single word followed by optional whitespace
+ *	followed by either a += or an = operator.
+ *	This function is used both by the Parse_File function and main when
+ *	parsing the command-line arguments.
+ *
+ * Input:
+ *	line		the line to check
  *
- * Used for both lines in a file and command line arguments. */
+ * Results:
+ *	TRUE if it is. FALSE if it ain't
+ *
+ * Side Effects:
+ *	none
+ *---------------------------------------------------------------------
+ */
 Boolean
-Parse_IsVar(const char *p, VarAssign *out_var)
+Parse_IsVar(const char *line)
 {
-    const char *firstSpace = NULL;
+    Boolean wasSpace = FALSE;	/* set TRUE if found a space */
     char ch;
     int level = 0;
+#define ISEQOPERATOR(c) \
+	(((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!'))
 
-    /* Skip to variable name */
-    while (*p == ' ' || *p == '\t')
-	p++;
-
-    /* During parsing, the '+' of the '+=' operator is initially parsed
-     * as part of the variable name.  It is later corrected, as is the ':sh'
-     * modifier. Of these two (nameEnd and op), the earlier one determines the
-     * actual end of the variable name. */
-    out_var->nameStart = p;
-#ifdef CLEANUP
-    out_var->nameEndDraft = NULL;
-    out_var->varname = NULL;
-    out_var->eq = NULL;
-    out_var->op = VAR_NORMAL;
-    out_var->value = NULL;
-#endif
+    /*
+     * Skip to variable name
+     */
+    while (*line == ' ' || *line == '\t')
+	line++;
 
     /* Scan for one of the assignment operators outside a variable expansion */
-    while ((ch = *p++) != 0) {
+    while ((ch = *line++) != 0) {
 	if (ch == '(' || ch == '{') {
 	    level++;
 	    continue;
@@ -1818,123 +1719,158 @@ Parse_IsVar(const char *p, VarAssign *ou
 	    level--;
 	    continue;
 	}
-
 	if (level != 0)
 	    continue;
-
-	if (ch == ' ' || ch == '\t')
-	    if (firstSpace == NULL)
-	        firstSpace = p - 1;
-	while (ch == ' ' || ch == '\t')
-	    ch = *p++;
-
+	while (ch == ' ' || ch == '\t') {
+	    ch = *line++;
+	    wasSpace = TRUE;
+	}
 #ifdef SUNSHCMD
-	if (ch == ':' && strncmp(p, "sh", 2) == 0) {
-	    p += 2;
+	if (ch == ':' && strncmp(line, "sh", 2) == 0) {
+	    line += 2;
 	    continue;
 	}
 #endif
-	if (ch == '=') {
-	    out_var->eq = p - 1;
-	    out_var->nameEndDraft = firstSpace != NULL ? firstSpace : p - 1;
-	    out_var->op = VAR_NORMAL;
-	    cpp_skip_whitespace(&p);
-	    out_var->value = p;
+	if (ch == '=')
 	    return TRUE;
-	}
-	if (*p == '=' && (ch == '+' || ch == ':' || ch == '?' || ch == '!')) {
-	    out_var->eq = p;
-	    out_var->nameEndDraft = firstSpace != NULL ? firstSpace : p;
-	    out_var->op = ch == '+' ? VAR_APPEND :
-			  ch == ':' ? VAR_SUBST :
-			  ch == '?' ? VAR_DEFAULT : VAR_SHELL;
-	    p++;
-	    cpp_skip_whitespace(&p);
-	    out_var->value = p;
+	if (*line == '=' && ISEQOPERATOR(ch))
 	    return TRUE;
-	}
-	if (firstSpace != NULL)
+	if (wasSpace)
 	    return FALSE;
     }
 
     return FALSE;
 }
 
-/* Determine the assignment operator and adjust the end of the variable
- * name accordingly. */
-static void
-ParseVarassignOp(VarAssign *var)
+/*-
+ *---------------------------------------------------------------------
+ * Parse_DoVar  --
+ *	Take the variable assignment in the passed line and do it in the
+ *	global context.
+ *
+ *	Note: There is a lexical ambiguity with assignment modifier characters
+ *	in variable names. This routine interprets the character before the =
+ *	as a modifier. Therefore, an assignment like
+ *	    C++=/usr/bin/CC
+ *	is interpreted as "C+ +=" instead of "C++ =".
+ *
+ * Input:
+ *	line		a line guaranteed to be a variable assignment.
+ *			This reduces error checks
+ *	ctxt		Context in which to do the assignment
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	the variable structure of the given variable name is altered in the
+ *	global context.
+ *---------------------------------------------------------------------
+ */
+void
+Parse_DoVar(char *line, GNode *ctxt)
 {
-    const char *op = var->eq;
-    const char * const name = var->nameStart;
-    VarAssignOp type;
-
-    if (op > name && op[-1] == '+') {
-	type = VAR_APPEND;
-	op--;
-
-    } else if (op > name && op[-1] == '?') {
-        op--;
-        type = VAR_DEFAULT;
-
-    } else if (op > name && op[-1] == ':') {
-	op--;
-	type = VAR_SUBST;
-
-    } else if (op > name && op[-1] == '!') {
-	op--;
-	type = VAR_SHELL;
+    char *cp;			/* pointer into line */
+    enum {
+	VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL
+    } type;			/* Type of assignment */
+    char *opc;			/* ptr to operator character to
+				 * null-terminate the variable name */
+    Boolean	   freeCp = FALSE; /* TRUE if cp needs to be freed,
+				    * i.e. if any variable expansion was
+				    * performed */
+    int depth;
 
-    } else {
-	type = VAR_NORMAL;
-#ifdef SUNSHCMD
-	while (op > name && ch_isspace(op[-1]))
-	    op--;
+    /*
+     * Skip to variable name
+     */
+    while (*line == ' ' || *line == '\t')
+	line++;
 
-	if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' && op[-1] == 'h') {
-	    type = VAR_SHELL;
-	    op -= 3;
+    /*
+     * Skip to operator character, nulling out whitespace as we go
+     * XXX Rather than counting () and {} we should look for $ and
+     * then expand the variable.
+     */
+    for (depth = 0, cp = line; depth > 0 || *cp != '='; cp++) {
+	if (*cp == '(' || *cp == '{') {
+	    depth++;
+	    continue;
+	}
+	if (*cp == ')' || *cp == '}') {
+	    depth--;
+	    continue;
+	}
+	if (depth == 0 && ch_isspace(*cp)) {
+	    *cp = '\0';
 	}
-#endif
     }
+    opc = cp-1;		/* operator is the previous character */
+    *cp++ = '\0';	/* nuke the = */
 
-    {
-	const char *nameEnd = var->nameEndDraft < op ? var->nameEndDraft : op;
-	var->varname = bmake_strsedup(var->nameStart, nameEnd);
-	var->op = type;
+    /*
+     * Check operator type
+     */
+    switch (*opc) {
+	case '+':
+	    type = VAR_APPEND;
+	    *opc = '\0';
+	    break;
+
+	case '?':
+	    /*
+	     * If the variable already has a value, we don't do anything.
+	     */
+	    *opc = '\0';
+	    if (Var_Exists(line, ctxt)) {
+		return;
+	    } else {
+		type = VAR_NORMAL;
+	    }
+	    break;
+
+	case ':':
+	    type = VAR_SUBST;
+	    *opc = '\0';
+	    break;
+
+	case '!':
+	    type = VAR_SHELL;
+	    *opc = '\0';
+	    break;
+
+	default:
+#ifdef SUNSHCMD
+	    while (opc > line && *opc != ':')
+		opc--;
+
+	    if (strncmp(opc, ":sh", 3) == 0) {
+		type = VAR_SHELL;
+		*opc = '\0';
+		break;
+	    }
+#endif
+	    type = VAR_NORMAL;
+	    break;
     }
-}
 
-static void
-VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *ctxt)
-{
+    while (ch_isspace(*cp))
+	cp++;
+
     if (DEBUG(LINT)) {
-	if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) {
-	    /* Check for syntax errors such as unclosed expressions or
-	     * unknown modifiers. */
+	if (type != VAR_SUBST && strchr(cp, '$') != NULL) {
+	    /* sanity check now */
 	    char *expandedValue;
 
-	    (void)Var_Subst(uvalue, ctxt, VARE_NONE, &expandedValue);
+	    (void)Var_Subst(cp, ctxt, VARE_ASSIGN, &expandedValue);
 	    /* TODO: handle errors */
 	    free(expandedValue);
 	}
     }
-}
-
-static Boolean
-VarAssign_Eval(VarAssign *var, GNode *ctxt,
-	       const char **out_avalue, void **out_avalue_freeIt)
-{
-    const char *uvalue = var->value;
-    const char *name = var->varname;
-    const VarAssignOp type = var->op;
-    const char *avalue = uvalue;
-    void *avalue_freeIt = NULL;
 
     if (type == VAR_APPEND) {
-	Var_Append(name, uvalue, ctxt);
+	Var_Append(line, cp, ctxt);
     } else if (type == VAR_SUBST) {
-        char *evalue;
 	/*
 	 * Allow variables in the old value to be undefined, but leave their
 	 * expressions alone -- this is done by forcing oldVars to be false.
@@ -1954,99 +1890,59 @@ VarAssign_Eval(VarAssign *var, GNode *ct
 	 * make sure that we set the variable the first time to nothing
 	 * so that it gets substituted!
 	 */
-	if (!Var_Exists(name, ctxt))
-	    Var_Set(name, "", ctxt);
+	if (!Var_Exists(line, ctxt))
+	    Var_Set(line, "", ctxt);
 
-	(void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_ASSIGN, &evalue);
+	(void)Var_Subst(cp, ctxt, VARE_WANTRES|VARE_ASSIGN, &cp);
 	/* TODO: handle errors */
 	oldVars = oldOldVars;
-	avalue = evalue;
-	avalue_freeIt = evalue;
+	freeCp = TRUE;
 
-	Var_Set(name, avalue, ctxt);
+	Var_Set(line, cp, ctxt);
     } else if (type == VAR_SHELL) {
-        const char *cmd, *errfmt;
-        char *cmdOut;
-        void *cmd_freeIt = NULL;
-
-	cmd = uvalue;
-	if (strchr(cmd, '$') != NULL) {
-	    char *ecmd;
-	    (void)Var_Subst(cmd, VAR_CMD, VARE_UNDEFERR|VARE_WANTRES, &ecmd);
+	char *res;
+	const char *error;
+
+	if (strchr(cp, '$') != NULL) {
+	    /*
+	     * There's a dollar sign in the command, so perform variable
+	     * expansion on the whole thing. The resulting string will need
+	     * freeing when we're done.
+	     */
+	    (void)Var_Subst(cp, VAR_CMD, VARE_UNDEFERR|VARE_WANTRES, &cp);
 	    /* TODO: handle errors */
-	    cmd = cmd_freeIt = ecmd;
+	    freeCp = TRUE;
 	}
 
-	cmdOut = Cmd_Exec(cmd, &errfmt);
-	Var_Set(name, cmdOut, ctxt);
-	avalue = avalue_freeIt = cmdOut;
-
-	if (errfmt)
-	    Parse_Error(PARSE_WARNING, errfmt, cmd);
+	res = Cmd_Exec(cp, &error);
+	Var_Set(line, res, ctxt);
+	free(res);
 
-	free(cmd_freeIt);
+	if (error)
+	    Parse_Error(PARSE_WARNING, error, cp);
     } else {
-	if (type == VAR_DEFAULT && Var_Exists(var->varname, ctxt)) {
-	    *out_avalue_freeIt = NULL;
-	    return FALSE;
-	}
-
-	/* Normal assignment -- just do it. */
-	Var_Set(name, uvalue, ctxt);
+	/*
+	 * Normal assignment -- just do it.
+	 */
+	Var_Set(line, cp, ctxt);
     }
-
-    *out_avalue = avalue;
-    *out_avalue_freeIt = avalue_freeIt;
-    return TRUE;
-}
-
-static void
-VarAssignSpecial(const char *name, const char *avalue)
-{
-    if (strcmp(name, MAKEOVERRIDES) == 0)
+    if (strcmp(line, MAKEOVERRIDES) == 0)
 	Main_ExportMAKEFLAGS(FALSE);	/* re-export MAKEFLAGS */
-    else if (strcmp(name, ".CURDIR") == 0) {
+    else if (strcmp(line, ".CURDIR") == 0) {
 	/*
 	 * Someone is being (too?) clever...
 	 * Let's pretend they know what they are doing and
 	 * re-initialize the 'cur' CachedDir.
 	 */
-	Dir_InitCur(avalue);
+	Dir_InitCur(cp);
 	Dir_SetPATH();
-    } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) {
+    } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) {
 	Job_SetPrefix();
-    } else if (strcmp(name, MAKE_EXPORTED) == 0) {
-	Var_Export(avalue, FALSE);
+    } else if (strcmp(line, MAKE_EXPORTED) == 0) {
+	Var_Export(cp, FALSE);
     }
-}
-
-/* Take the variable assignment in the passed line and execute it.
- *
- * Note: There is a lexical ambiguity with assignment modifier characters
- * in variable names. This routine interprets the character before the =
- * as a modifier. Therefore, an assignment like
- *	C++=/usr/bin/CC
- * is interpreted as "C+ +=" instead of "C++ =".
- *
- * Input:
- *	p		A line guaranteed to be a variable assignment
- *			(see Parse_IsVar).
- *	ctxt		Context in which to do the assignment
- */
-void
-Parse_DoVar(VarAssign *var, GNode *ctxt)
-{
-    const char *avalue;		/* actual value (maybe expanded) */
-    void *avalue_freeIt;
-
-    ParseVarassignOp(var);
-
-    VarCheckSyntax(var->op, var->value, ctxt);
-    if (VarAssign_Eval(var, ctxt, &avalue, &avalue_freeIt))
-	VarAssignSpecial(var->varname, avalue);
-
-    free(avalue_freeIt);
-    free(var->varname);
+    if (freeCp)
+	free(cp);
 }
 
 
@@ -2512,7 +2408,11 @@ ParseTraditionalInclude(char *line)
 
     DEBUG2(PARSE, "%s: %s\n", __func__, file);
 
-    pp_skip_whitespace(&file);
+    /*
+     * Skip over whitespace
+     */
+    while (ch_isspace(*file))
+	file++;
 
     /*
      * Substitute for any variables in the file name before trying to
@@ -2554,7 +2454,11 @@ ParseGmakeExport(char *line)
 
     DEBUG2(PARSE, "%s: %s\n", __func__, variable);
 
-    pp_skip_whitespace(&variable);
+    /*
+     * Skip over whitespace
+     */
+    while (ch_isspace(*variable))
+	variable++;
 
     for (value = variable; *value && *value != '='; value++)
 	continue;
@@ -2639,7 +2543,7 @@ ParseEOF(void)
 #define PARSE_SKIP 2
 
 static char *
-ParseGetLine(int flags)
+ParseGetLine(int flags, int *length)
 {
     IFile *cf = curFile;
     char *ptr;
@@ -2731,6 +2635,7 @@ ParseGetLine(int flags)
 
 	if (flags & PARSE_RAW) {
 	    /* Leave '\' (etc) in line buffer (eg 'for' lines) */
+	    *length = line_end - line;
 	    return line;
 	}
 
@@ -2750,8 +2655,10 @@ ParseGetLine(int flags)
     }
 
     /* If we didn't see a '\\' then the in-situ data is fine */
-    if (escaped == NULL)
+    if (escaped == NULL) {
+	*length = line_end - line;
 	return line;
+    }
 
     /* Remove escapes from '\n' and '#' */
     tp = ptr = escaped;
@@ -2794,6 +2701,7 @@ ParseGetLine(int flags)
 	tp--;
 
     *tp = 0;
+    *length = tp - line;
     return line;
 }
 
@@ -2809,11 +2717,12 @@ static char *
 ParseReadLine(void)
 {
     char *line;			/* Result */
+    int lineLength;		/* Length of result */
     int lineno;			/* Saved line # */
     int rval;
 
     for (;;) {
-	line = ParseGetLine(0);
+	line = ParseGetLine(0, &lineLength);
 	if (line == NULL)
 	    return NULL;
 
@@ -2828,7 +2737,7 @@ ParseReadLine(void)
 	case COND_SKIP:
 	    /* Skip to next conditional that evaluates to COND_PARSE.  */
 	    do {
-		line = ParseGetLine(PARSE_SKIP);
+		line = ParseGetLine(PARSE_SKIP, &lineLength);
 	    } while (line && Cond_EvalLine(line) != COND_PARSE);
 	    if (line == NULL)
 		break;
@@ -2848,7 +2757,7 @@ ParseReadLine(void)
 	    lineno = curFile->lineno;
 	    /* Accumulate loop lines until matching .endfor */
 	    do {
-		line = ParseGetLine(PARSE_RAW);
+		line = ParseGetLine(PARSE_RAW, &lineLength);
 		if (line == NULL) {
 		    Parse_Error(PARSE_FATAL,
 			     "Unexpected end of file in for loop.");
@@ -2884,7 +2793,9 @@ FinishDependencyGroup(void)
 static void
 ParseLine_ShellCommand(char *cp)
 {
-    pp_skip_whitespace(&cp);
+    for (; ch_isspace(*cp); cp++)
+	continue;
+
     if (*cp == '\0')
 	return;			/* skip empty commands */
 
@@ -2942,16 +2853,16 @@ Parse_File(const char *name, int fd)
 		 * On the other hand they can be suffix rules (.c.o: ...)
 		 * or just dependencies for filenames that start '.'.
 		 */
-		cp = line + 1;
-		pp_skip_whitespace(&cp);
+		for (cp = line + 1; ch_isspace(*cp); cp++)
+		    continue;
 		if (IsInclude(cp, FALSE)) {
 		    ParseDoInclude(cp);
 		    continue;
 		}
 		if (strncmp(cp, "undef", 5) == 0) {
 		    const char *varname;
-		    cp += 5;
-		    pp_skip_whitespace(&cp);
+		    for (cp += 5; ch_isspace(*cp); cp++)
+			continue;
 		    varname = cp;
 		    for (; !ch_isspace(*cp) && *cp != '\0'; cp++)
 			continue;
@@ -2961,8 +2872,8 @@ Parse_File(const char *name, int fd)
 		    /* TODO: use Str_Words, like everywhere else */
 		    continue;
 		} else if (strncmp(cp, "export", 6) == 0) {
-		    cp += 6;
-		    pp_skip_whitespace(&cp);
+		    for (cp += 6; ch_isspace(*cp); cp++)
+			continue;
 		    Var_Export(cp, TRUE);
 		    continue;
 		} else if (strncmp(cp, "unexport", 8) == 0) {
@@ -3006,13 +2917,10 @@ Parse_File(const char *name, int fd)
 		continue;
 	    }
 #endif
-	    {
-	        VarAssign var;
-		if (Parse_IsVar(line, &var)) {
-		    FinishDependencyGroup();
-		    Parse_DoVar(&var, VAR_GLOBAL);
-		    continue;
-		}
+	    if (Parse_IsVar(line)) {
+		FinishDependencyGroup();
+		Parse_DoVar(line, VAR_GLOBAL);
+		continue;
 	    }
 
 #ifndef POSIX
@@ -3025,7 +2933,8 @@ Parse_File(const char *name, int fd)
 	     */
 	    cp = line;
 	    if (ch_isspace(line[0])) {
-		pp_skip_whitespace(&cp);
+		while (ch_isspace(*cp))
+		    cp++;
 		while (*cp && (ParseIsEscaped(line, cp) ||
 			*cp != ':' && *cp != '!')) {
 		    cp++;

Index: src/usr.bin/make/str.c
diff -u src/usr.bin/make/str.c:1.66 src/usr.bin/make/str.c:1.67
--- src/usr.bin/make/str.c:1.66	Sat Oct  3 15:00:57 2020
+++ src/usr.bin/make/str.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: str.c,v 1.66 2020/10/03 15:00:57 rillig Exp $	*/
+/*	$NetBSD: str.c,v 1.67 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*-
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -71,7 +71,7 @@
 #include "make.h"
 
 /*	"@(#)str.c	5.8 (Berkeley) 6/1/90"	*/
-MAKE_RCSID("$NetBSD: str.c,v 1.66 2020/10/03 15:00:57 rillig Exp $");
+MAKE_RCSID("$NetBSD: str.c,v 1.67 2020/10/05 19:24:29 rillig Exp $");
 
 /* Return the concatenation of s1 and s2, freshly allocated. */
 char *
@@ -269,6 +269,46 @@ done:
 }
 
 /*
+ * Str_FindSubstring -- See if a string contains a particular substring.
+ *
+ * Input:
+ *	string		String to search.
+ *	substring	Substring to find in string.
+ *
+ * Results: If string contains substring, the return value is the location of
+ * the first matching instance of substring in string.  If string doesn't
+ * contain substring, the return value is NULL.  Matching is done on an exact
+ * character-for-character basis with no wildcards or special characters.
+ *
+ * Side effects: None.
+ */
+char *
+Str_FindSubstring(const char *string, const char *substring)
+{
+	const char *a, *b;
+
+	/*
+	 * First scan quickly through the two strings looking for a single-
+	 * character match.  When it's found, then compare the rest of the
+	 * substring.
+	 */
+
+	for (b = substring; *string != 0; string++) {
+		if (*string != *b)
+			continue;
+		a = string;
+		for (;;) {
+			if (*b == 0)
+				return UNCONST(string);
+			if (*a++ != *b++)
+				break;
+		}
+		b = substring;
+	}
+	return NULL;
+}
+
+/*
  * Str_Match -- Test if a string matches a pattern like "*.[ch]".
  *
  * XXX this function does not detect or report malformed patterns.

Index: src/usr.bin/make/suff.c
diff -u src/usr.bin/make/suff.c:1.174 src/usr.bin/make/suff.c:1.175
--- src/usr.bin/make/suff.c:1.174	Sat Oct  3 21:52:50 2020
+++ src/usr.bin/make/suff.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: suff.c,v 1.174 2020/10/03 21:52:50 rillig Exp $	*/
+/*	$NetBSD: suff.c,v 1.175 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -125,11 +125,11 @@
  *			find the node.
  */
 
-#include "make.h"
-#include "dir.h"
+#include	  "make.h"
+#include	  "dir.h"
 
 /*	"@(#)suff.c	8.4 (Berkeley) 3/21/94"	*/
-MAKE_RCSID("$NetBSD: suff.c,v 1.174 2020/10/03 21:52:50 rillig Exp $");
+MAKE_RCSID("$NetBSD: suff.c,v 1.175 2020/10/05 19:24:29 rillig Exp $");
 
 #define SUFF_DEBUG0(text) DEBUG0(SUFF, text)
 #define SUFF_DEBUG1(fmt, arg1) DEBUG1(SUFF, fmt, arg1)

Index: src/usr.bin/make/targ.c
diff -u src/usr.bin/make/targ.c:1.109 src/usr.bin/make/targ.c:1.110
--- src/usr.bin/make/targ.c:1.109	Sat Oct  3 21:52:50 2020
+++ src/usr.bin/make/targ.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: targ.c,v 1.109 2020/10/03 21:52:50 rillig Exp $	*/
+/*	$NetBSD: targ.c,v 1.110 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -115,13 +115,14 @@
  *			something for suffixes, too, but...
  */
 
-#include <time.h>
+#include	  <stdio.h>
+#include	  <time.h>
 
-#include "make.h"
-#include "dir.h"
+#include	  "make.h"
+#include	  "dir.h"
 
 /*	"@(#)targ.c	8.2 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: targ.c,v 1.109 2020/10/03 21:52:50 rillig Exp $");
+MAKE_RCSID("$NetBSD: targ.c,v 1.110 2020/10/05 19:24:29 rillig Exp $");
 
 static GNodeList *allTargets;	/* the list of all targets found so far */
 #ifdef CLEANUP

Index: src/usr.bin/make/trace.c
diff -u src/usr.bin/make/trace.c:1.17 src/usr.bin/make/trace.c:1.18
--- src/usr.bin/make/trace.c:1.17	Sat Oct  3 21:52:50 2020
+++ src/usr.bin/make/trace.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: trace.c,v 1.17 2020/10/03 21:52:50 rillig Exp $	*/
+/*	$NetBSD: trace.c,v 1.18 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*-
  * Copyright (c) 2000 The NetBSD Foundation, Inc.
@@ -44,11 +44,14 @@
 
 #include <sys/time.h>
 
+#include <stdio.h>
+#include <unistd.h>
+
 #include "make.h"
 #include "job.h"
 #include "trace.h"
 
-MAKE_RCSID("$NetBSD: trace.c,v 1.17 2020/10/03 21:52:50 rillig Exp $");
+MAKE_RCSID("$NetBSD: trace.c,v 1.18 2020/10/05 19:24:29 rillig Exp $");
 
 static FILE *trfile;
 static pid_t trpid;

Index: src/usr.bin/make/util.c
diff -u src/usr.bin/make/util.c:1.61 src/usr.bin/make/util.c:1.62
--- src/usr.bin/make/util.c:1.61	Sat Oct  3 21:52:50 2020
+++ src/usr.bin/make/util.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: util.c,v 1.61 2020/10/03 21:52:50 rillig Exp $	*/
+/*	$NetBSD: util.c,v 1.62 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Missing stuff from OS's
@@ -10,12 +10,13 @@
 #include <sys/param.h>
 
 #include <errno.h>
+#include <stdio.h>
 #include <time.h>
 #include <signal.h>
 
 #include "make.h"
 
-MAKE_RCSID("$NetBSD: util.c,v 1.61 2020/10/03 21:52:50 rillig Exp $");
+MAKE_RCSID("$NetBSD: util.c,v 1.62 2020/10/05 19:24:29 rillig Exp $");
 
 #if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR)
 extern int errno, sys_nerr;

Index: src/usr.bin/make/var.c
diff -u src/usr.bin/make/var.c:1.565 src/usr.bin/make/var.c:1.566
--- src/usr.bin/make/var.c:1.565	Sun Oct  4 10:35:25 2020
+++ src/usr.bin/make/var.c	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: var.c,v 1.565 2020/10/04 10:35:25 rillig Exp $	*/
+/*	$NetBSD: var.c,v 1.566 2020/10/05 19:24:29 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -121,7 +121,7 @@
 #include    "metachar.h"
 
 /*	"@(#)var.c	8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: var.c,v 1.565 2020/10/04 10:35:25 rillig Exp $");
+MAKE_RCSID("$NetBSD: var.c,v 1.566 2020/10/05 19:24:29 rillig Exp $");
 
 #define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1)
 #define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2)
@@ -470,8 +470,6 @@ Var_Export1(const char *name, VarExportF
 
     if (name[0] == '.')
 	return FALSE;		/* skip internals */
-    if (name[0] == '-')
-	return FALSE;		/* skip misnamed variables */
     if (name[1] == '\0') {
 	/*
 	 * A single char.
@@ -603,14 +601,14 @@ Var_Export(const char *str, Boolean isEx
 	return;
     }
 
-    if (isExport && strncmp(str, "-env", 4) == 0) {
+    flags = 0;
+    if (strncmp(str, "-env", 4) == 0) {
 	str += 4;
-    	flags = 0;
-    } else if (isExport && strncmp(str, "-literal", 8) == 0) {
+    } else if (strncmp(str, "-literal", 8) == 0) {
 	str += 8;
-	flags = VAR_EXPORT_LITERAL;
+	flags |= VAR_EXPORT_LITERAL;
     } else {
-	flags = VAR_EXPORT_PARENT;
+	flags |= VAR_EXPORT_PARENT;
     }
 
     (void)Var_Subst(str, VAR_GLOBAL, VARE_WANTRES, &val);
@@ -677,7 +675,8 @@ Var_UnExport(const char *str)
 	if (cp && *cp)
 	    setenv(MAKE_LEVEL_ENV, cp, 1);
     } else {
-	cpp_skip_whitespace(&str);
+	for (; ch_isspace(*str); str++)
+	    continue;
 	if (str[0] != '\0')
 	    varnames = str;
     }
@@ -1291,11 +1290,8 @@ ModifyWord_Subst(const char *word, SepBu
 	return;
     }
 
-    if (args->lhs[0] == '\0')
-    	goto nosub;
-
     /* unanchored case, may match more than once */
-    while ((match = strstr(word, args->lhs)) != NULL) {
+    while ((match = Str_FindSubstring(word, args->lhs)) != NULL) {
 	SepBuf_AddBytesBetween(buf, word, match);
 	SepBuf_AddBytes(buf, args->rhs, args->rhsLen);
 	args->matched = TRUE;
@@ -2067,16 +2063,6 @@ ApplyModifier_Defined(const char **pp, A
     return AMR_OK;
 }
 
-/* :L */
-static ApplyModifierResult
-ApplyModifier_Literal(const char **pp, ApplyModifiersState *st)
-{
-    ApplyModifiersState_Define(st);
-    st->newVal = bmake_strdup(st->v->name);
-    (*pp)++;
-    return AMR_OK;
-}
-
 /* :gmtime */
 static ApplyModifierResult
 ApplyModifier_Gmtime(const char **pp, ApplyModifiersState *st)
@@ -2939,17 +2925,6 @@ ApplyModifier_WordFunc(const char **pp, 
     return AMR_OK;
 }
 
-static ApplyModifierResult
-ApplyModifier_Unique(const char **pp, ApplyModifiersState *st)
-{
-    if ((*pp)[1] == st->endc || (*pp)[1] == ':') {
-	st->newVal = VarUniq(st->val);
-	(*pp)++;
-	return AMR_OK;
-    } else
-	return AMR_UNKNOWN;
-}
-
 #ifdef SYSVVARSUB
 /* :from=to */
 static ApplyModifierResult
@@ -3088,7 +3063,10 @@ ApplyModifier(const char **pp, ApplyModi
     case 'U':
 	return ApplyModifier_Defined(pp, st);
     case 'L':
-        return ApplyModifier_Literal(pp, st);
+	ApplyModifiersState_Define(st);
+	st->newVal = bmake_strdup(st->v->name);
+	(*pp)++;
+	return AMR_OK;
     case 'P':
 	return ApplyModifier_Path(pp, st);
     case '!':
@@ -3130,7 +3108,12 @@ ApplyModifier(const char **pp, ApplyModi
     case 'O':
 	return ApplyModifier_Order(pp, st);
     case 'u':
-        return ApplyModifier_Unique(pp, st);
+	if ((*pp)[1] == st->endc || (*pp)[1] == ':') {
+	    st->newVal = VarUniq(st->val);
+	    (*pp)++;
+	    return AMR_OK;
+	} else
+	    return AMR_UNKNOWN;
 #ifdef SUNSHCMD
     case 's':
 	return ApplyModifier_SunShell(pp, st);
@@ -3184,21 +3167,13 @@ ApplyModifiers(
 	    /* TODO: handle errors */
 
 	    /*
-	     * If we have not parsed up to st.endc or ':', we are not
-	     * interested.  This means the expression ${VAR:${M_1}${M_2}}
-	     * is not accepted, but ${VAR:${M_1}:${M_2}} is.
+	     * If we have not parsed up to st.endc or ':',
+	     * we are not interested.
 	     */
 	    if (rval[0] != '\0' &&
 		(c = *nested_p) != '\0' && c != ':' && c != st.endc) {
-		if (DEBUG(LINT))
-		    Parse_Error(PARSE_FATAL,
-				"Missing delimiter ':' after indirect modifier \"%.*s\"",
-				(int)(nested_p - p), p);
-
 		free(freeIt);
 		/* XXX: apply_mods doesn't sound like "not interested". */
-		/* XXX: Why is the indirect modifier parsed again by
-		 * apply_mods?  If any, p should be advanced to nested_p. */
 		goto apply_mods;
 	    }
 
@@ -3219,7 +3194,6 @@ ApplyModifiers(
 		}
 	    }
 	    free(freeIt);
-
 	    if (*p == ':')
 		p++;
 	    else if (*p == '\0' && endc != '\0') {
@@ -3276,10 +3250,6 @@ ApplyModifiers(
 		  st.endc, st.v->name, st.val, *mod);
 	} else if (*p == ':') {
 	    p++;
-	} else if (DEBUG(LINT) && *p != '\0' && *p != endc) {
-	    Parse_Error(PARSE_FATAL,
-			"Missing delimiter ':' after modifier \"%.*s\"",
-			(int)(p - mod), mod);
 	}
     }
 out:
@@ -3768,7 +3738,11 @@ Var_Subst(const char *str, GNode *ctxt, 
 
     while (*p != '\0') {
 	if (p[0] == '$' && p[1] == '$') {
-	    /* A dollar sign may be escaped with another dollar sign. */
+	    /*
+	     * A dollar sign may be escaped with another dollar sign.
+	     * In such a case, we skip over the escape character and store the
+	     * dollar sign into the buffer directly.
+	     */
 	    if (save_dollars && (eflags & VARE_ASSIGN))
 		Buf_AddByte(&buf, '$');
 	    Buf_AddByte(&buf, '$');

Index: src/usr.bin/make/unit-tests/Makefile
diff -u src/usr.bin/make/unit-tests/Makefile:1.160 src/usr.bin/make/unit-tests/Makefile:1.161
--- src/usr.bin/make/unit-tests/Makefile:1.160	Sun Oct  4 06:53:15 2020
+++ src/usr.bin/make/unit-tests/Makefile	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.160 2020/10/04 06:53:15 rillig Exp $
+# $NetBSD: Makefile,v 1.161 2020/10/05 19:24:29 rillig Exp $
 #
 # Unit tests for make(1)
 #
@@ -168,7 +168,6 @@ TESTS+=		export-env
 TESTS+=		export-variants
 TESTS+=		forloop
 TESTS+=		forsubst
-TESTS+=		hanoi-include
 TESTS+=		impsrc
 TESTS+=		include-main
 TESTS+=		job-output-long-lines
@@ -230,7 +229,6 @@ TESTS+=		opt-warnings-as-errors
 TESTS+=		opt-where-am-i
 TESTS+=		opt-x-reduce-exported
 TESTS+=		order
-TESTS+=		parse-var
 TESTS+=		phony-end
 TESTS+=		posix
 TESTS+=		# posix1	# broken by reverting POSIX changes
@@ -246,10 +244,6 @@ TESTS+=		sh-leading-plus
 TESTS+=		sh-meta-chars
 TESTS+=		sh-multi-line
 TESTS+=		sh-single-line
-TESTS+=		shell-csh
-TESTS+=		shell-custom
-TESTS+=		shell-ksh
-TESTS+=		shell-sh
 TESTS+=		# suffixes	# runs into an endless loop (try -dA)
 TESTS+=		suff-rebuild
 TESTS+=		sunshcmd
@@ -270,7 +264,6 @@ TESTS+=		var-op-assign
 TESTS+=		var-op-default
 TESTS+=		var-op-expand
 TESTS+=		var-op-shell
-TESTS+=		var-op-sunsh
 TESTS+=		varcmd
 TESTS+=		vardebug
 TESTS+=		varfind
@@ -410,10 +403,6 @@ SED_CMDS.opt-debug-graph1= \
 SED_CMDS.opt-debug-graph1+= \
 			-e '/Global Variables:/,/Suffixes:/d'
 SED_CMDS.sh-dots=	-e 's,^.*\.\.\.:.*,<normalized: ...: not found>,'
-SED_CMDS.opt-debug-jobs=	-e 's,([0-9][0-9]*),(<pid>),'
-SED_CMDS.opt-debug-jobs+=	-e 's,pid [0-9][0-9]*,pid <pid>,'
-SED_CMDS.opt-debug-jobs+=	-e 's,Process [0-9][0-9]*,Process <pid>,'
-SED_CMDS.opt-debug-jobs+=	-e 's,JobFinish: [0-9][0-9]*,JobFinish: <pid>,'
 SED_CMDS.varmod-subst-regex+= \
 			-e 's,\(Regex compilation error:\).*,\1 (details omitted),'
 SED_CMDS.varmod-edge+=	-e 's, line [0-9]*:, line omitted:,'

Index: src/usr.bin/make/unit-tests/directive-export-literal.exp
diff -u src/usr.bin/make/unit-tests/directive-export-literal.exp:1.2 src/usr.bin/make/unit-tests/directive-export-literal.exp:1.3
--- src/usr.bin/make/unit-tests/directive-export-literal.exp:1.2	Sat Oct  3 09:37:04 2020
+++ src/usr.bin/make/unit-tests/directive-export-literal.exp	Mon Oct  5 19:24:29 2020
@@ -1,2 +1 @@
-value with ${UNEXPANDED} expression
 exit status 0
Index: src/usr.bin/make/unit-tests/directive-ifndef.exp
diff -u src/usr.bin/make/unit-tests/directive-ifndef.exp:1.2 src/usr.bin/make/unit-tests/directive-ifndef.exp:1.3
--- src/usr.bin/make/unit-tests/directive-ifndef.exp:1.2	Sun Oct  4 22:41:18 2020
+++ src/usr.bin/make/unit-tests/directive-ifndef.exp	Mon Oct  5 19:24:29 2020
@@ -1,2 +1 @@
-make: "directive-ifndef.mk" line 10: guarded section
 exit status 0
Index: src/usr.bin/make/unit-tests/directive-ifnmake.exp
diff -u src/usr.bin/make/unit-tests/directive-ifnmake.exp:1.2 src/usr.bin/make/unit-tests/directive-ifnmake.exp:1.3
--- src/usr.bin/make/unit-tests/directive-ifnmake.exp:1.2	Sun Oct  4 22:41:18 2020
+++ src/usr.bin/make/unit-tests/directive-ifnmake.exp	Mon Oct  5 19:24:29 2020
@@ -1,3 +1 @@
-Don't forget to run the tests (1)
-Running the tests
 exit status 0
Index: src/usr.bin/make/unit-tests/opt-debug-file.mk
diff -u src/usr.bin/make/unit-tests/opt-debug-file.mk:1.2 src/usr.bin/make/unit-tests/opt-debug-file.mk:1.3
--- src/usr.bin/make/unit-tests/opt-debug-file.mk:1.2	Sat Oct  3 09:10:26 2020
+++ src/usr.bin/make/unit-tests/opt-debug-file.mk	Mon Oct  5 19:24:29 2020
@@ -1,37 +1,9 @@
-# $NetBSD: opt-debug-file.mk,v 1.2 2020/10/03 09:10:26 rillig Exp $
+# $NetBSD: opt-debug-file.mk,v 1.3 2020/10/05 19:24:29 rillig Exp $
 #
 # Tests for the -dF command line option, which redirects the debug log
 # to a file instead of writing it to stderr.
 
-# Enable debug logging for variable assignments and evaluation (-dv)
-# and redirect the debug logging to the given file.
-.MAKEFLAGS: -dvFopt-debug-file.debuglog
-
-# This output goes to the debug log file.
-VAR=	value ${:Uexpanded}
-
-# Hide the logging output for the remaining actions.
-# As of 2020-10-03, it is not possible to disable debug logging again.
-.MAKEFLAGS: -dF/dev/null
-
-# Make sure that the debug logging file contains some logging.
-DEBUG_OUTPUT:=	${:!cat opt-debug-file.debuglog!}
-# Grmbl.  Because of the := operator in the above line, the variable
-# value contains ${:Uexpanded}.  This variable expression is expanded
-# upon further processing.  Therefore, don't read from untrusted input.
-#.MAKEFLAGS: -dc -dFstderr
-.if !${DEBUG_OUTPUT:tW:M*VAR = value expanded*}
-.  error ${DEBUG_OUTPUT}
-.endif
-
-# To get the unexpanded text that was actually written to the debug log
-# file, the content of that log file must not be stored in a variable.
-# XXX: In the :M modifier, a dollar is escaped as '$$', not '\$'.
-.if !${:!cat opt-debug-file.debuglog!:tW:M*VAR = value $${:Uexpanded}*}
-.  error
-.endif
-
-_!=	rm opt-debug-file.debuglog
+# TODO: Implementation
 
 all:
 	@:;
Index: src/usr.bin/make/unit-tests/opt-debug-for.exp
diff -u src/usr.bin/make/unit-tests/opt-debug-for.exp:1.2 src/usr.bin/make/unit-tests/opt-debug-for.exp:1.3
--- src/usr.bin/make/unit-tests/opt-debug-for.exp:1.2	Sat Oct  3 08:29:32 2020
+++ src/usr.bin/make/unit-tests/opt-debug-for.exp	Mon Oct  5 19:24:29 2020
@@ -1,22 +1 @@
-For: new loop 2
-For: end for 2
-For: end for 1
-For: loop body:
-.  for inner in 1 2
-VAR.${:Ua}${inner}=	value
-.  endfor
-For: end for 1
-For: loop body:
-VAR.${:Ua}${:U1}=	value
-For: loop body:
-VAR.${:Ua}${:U2}=	value
-For: loop body:
-.  for inner in 1 2
-VAR.${:Ub}${inner}=	value
-.  endfor
-For: end for 1
-For: loop body:
-VAR.${:Ub}${:U1}=	value
-For: loop body:
-VAR.${:Ub}${:U2}=	value
 exit status 0
Index: src/usr.bin/make/unit-tests/opt-debug-for.mk
diff -u src/usr.bin/make/unit-tests/opt-debug-for.mk:1.2 src/usr.bin/make/unit-tests/opt-debug-for.mk:1.3
--- src/usr.bin/make/unit-tests/opt-debug-for.mk:1.2	Sat Oct  3 08:29:32 2020
+++ src/usr.bin/make/unit-tests/opt-debug-for.mk	Mon Oct  5 19:24:29 2020
@@ -1,26 +1,9 @@
-# $NetBSD: opt-debug-for.mk,v 1.2 2020/10/03 08:29:32 rillig Exp $
+# $NetBSD: opt-debug-for.mk,v 1.3 2020/10/05 19:24:29 rillig Exp $
 #
 # Tests for the -df command line option, which adds debug logging for
 # parsing and evaluating .for loops.
 
-.MAKEFLAGS: -df
-
-# XXX: In the debug log, the "new loop 2" appears out of context.
-# There should be a "begin loop 1" before, and all these messages should
-# contain line number information.
-#
-# XXX: The "loop body" should print the nesting level as well.
-#
-# XXX: It is hard to extract any information from the debug log since
-# the "begin" and "end" events are not balanced and the nesting level
-# is not printed consistently.  It would also be helpful to mention the
-# actual substitutions, such as "For 1: outer=b".
-#
-.for outer in a b
-.  for inner in 1 2
-VAR.${outer}${inner}=	value
-.  endfor
-.endfor
+# TODO: Implementation
 
 all:
 	@:;
Index: src/usr.bin/make/unit-tests/opt-debug-jobs.exp
diff -u src/usr.bin/make/unit-tests/opt-debug-jobs.exp:1.2 src/usr.bin/make/unit-tests/opt-debug-jobs.exp:1.3
--- src/usr.bin/make/unit-tests/opt-debug-jobs.exp:1.2	Sat Oct  3 08:16:53 2020
+++ src/usr.bin/make/unit-tests/opt-debug-jobs.exp	Mon Oct  5 19:24:29 2020
@@ -1,25 +1 @@
-job_pipe -1 -1, maxjobs 1, tokens 1, compat 0
-Job_TokenWithdraw(<pid>): aborting 0, running 0
-(<pid>) withdrew token
-echo ": expanded expression"
-{ : expanded expression 
-} || exit $?
-echo ":  variable"
-{ :  variable 
-} || exit $?
-echo ": 'single' and \"double\" quotes"
-{ : 'single' and "double" quotes 
-} || exit $?
-Running all locally
-	Command: sh -q 
-JobExec(all): pid <pid> added to jobs table
-job table @ job started
-job 0, status 3, flags 0, pid <pid>
-: expanded expression
-:  variable
-: 'single' and "double" quotes
-Process <pid> exited/stopped status 0.
-JobFinish: <pid> [all], status 0
-Job_TokenWithdraw(<pid>): aborting 0, running 0
-(<pid>) withdrew token
 exit status 0
Index: src/usr.bin/make/unit-tests/opt-debug-jobs.mk
diff -u src/usr.bin/make/unit-tests/opt-debug-jobs.mk:1.2 src/usr.bin/make/unit-tests/opt-debug-jobs.mk:1.3
--- src/usr.bin/make/unit-tests/opt-debug-jobs.mk:1.2	Sat Oct  3 08:16:53 2020
+++ src/usr.bin/make/unit-tests/opt-debug-jobs.mk	Mon Oct  5 19:24:29 2020
@@ -1,26 +1,9 @@
-# $NetBSD: opt-debug-jobs.mk,v 1.2 2020/10/03 08:16:53 rillig Exp $
+# $NetBSD: opt-debug-jobs.mk,v 1.3 2020/10/05 19:24:29 rillig Exp $
 #
-# Tests for the -dj command line option, which adds debug logging about
+# Tests for the -da command line option, which adds debug logging about
 # running jobs in multiple shells.
 
-.MAKEFLAGS: -dj
-
-# Run in parallel mode since the debug logging is more interesting there
-# than in compat mode.
-.MAKEFLAGS: -j1
+# TODO: Implementation
 
 all:
-	# Only the actual command is logged.
-	# To see the evaluation of the variable expressions, use -dv.
-	: ${:Uexpanded} expression
-
-	# Undefined variables expand to empty strings.
-	# Multiple spaces are preserved in the command, as they might be
-	# significant.
-	: ${UNDEF} variable
-
-	# In the debug output, single quotes are not escaped, even though
-	# the whole command is enclosed in single quotes as well.
-	# This allows to copy and paste the whole command, without having
-	# to unescape anything.
-	: 'single' and "double" quotes
+	@:;
Index: src/usr.bin/make/unit-tests/opt-debug-loud.exp
diff -u src/usr.bin/make/unit-tests/opt-debug-loud.exp:1.2 src/usr.bin/make/unit-tests/opt-debug-loud.exp:1.3
--- src/usr.bin/make/unit-tests/opt-debug-loud.exp:1.2	Sat Oct  3 07:52:30 2020
+++ src/usr.bin/make/unit-tests/opt-debug-loud.exp	Mon Oct  5 19:24:29 2020
@@ -1,3 +1 @@
-echo all-word
-all-word
 exit status 0
Index: src/usr.bin/make/unit-tests/opt-debug-loud.mk
diff -u src/usr.bin/make/unit-tests/opt-debug-loud.mk:1.2 src/usr.bin/make/unit-tests/opt-debug-loud.mk:1.3
--- src/usr.bin/make/unit-tests/opt-debug-loud.mk:1.2	Sat Oct  3 07:52:30 2020
+++ src/usr.bin/make/unit-tests/opt-debug-loud.mk	Mon Oct  5 19:24:29 2020
@@ -1,22 +1,13 @@
-# $NetBSD: opt-debug-loud.mk,v 1.2 2020/10/03 07:52:30 rillig Exp $
+# $NetBSD: opt-debug-loud.mk,v 1.3 2020/10/05 19:24:29 rillig Exp $
 #
 # Tests for the -dl command line option, which prints the commands before
 # running them, ignoring the command line option for silent mode (-s) as
 # well as the .SILENT special source and target, as well as the '@' prefix
 # for shell commands.
 
-.MAKEFLAGS: -dl -s
-.SILENT:
+# TODO: Implementation
 
-# The -dl command line option does not affect commands that are run during
-# variable expansion, such as :!cmd! or :sh.
-.if ${:!echo word!} != "word"
-.  error
-.endif
+# TODO: What about ${:!cmd!}?
 
-all: .SILENT
-	# Even though the command line option -s is given, .SILENT is set
-	# for all targets and for this target in particular, the command
-	# is still printed.  The -dl debugging option is stronger than all
-	# of these.
-	@echo all-word
+all:
+	@:;
Index: src/usr.bin/make/unit-tests/opt-debug.exp
diff -u src/usr.bin/make/unit-tests/opt-debug.exp:1.2 src/usr.bin/make/unit-tests/opt-debug.exp:1.3
--- src/usr.bin/make/unit-tests/opt-debug.exp:1.2	Sat Oct  3 13:06:56 2020
+++ src/usr.bin/make/unit-tests/opt-debug.exp	Mon Oct  5 19:24:29 2020
@@ -1,4 +1 @@
-Global:VAR = value
-Global:.MAKEFLAGS =  -r -k -d v -d
-Global:.MAKEFLAGS =  -r -k -d v -d 0
 exit status 0

Index: src/usr.bin/make/unit-tests/directive-export-literal.mk
diff -u src/usr.bin/make/unit-tests/directive-export-literal.mk:1.3 src/usr.bin/make/unit-tests/directive-export-literal.mk:1.4
--- src/usr.bin/make/unit-tests/directive-export-literal.mk:1.3	Sat Oct  3 09:37:04 2020
+++ src/usr.bin/make/unit-tests/directive-export-literal.mk	Mon Oct  5 19:24:29 2020
@@ -1,11 +1,8 @@
-# $NetBSD: directive-export-literal.mk,v 1.3 2020/10/03 09:37:04 rillig Exp $
+# $NetBSD: directive-export-literal.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
 #
-# Tests for the .export-literal directive, which exports a variable value
-# without expanding it.
+# Tests for the .export-literal directive.
 
-UT_VAR=		value with ${UNEXPANDED} expression
-
-.export-literal UT_VAR
+# TODO: Implementation
 
 all:
-	@echo "$$UT_VAR"
+	@:;
Index: src/usr.bin/make/unit-tests/directive-ifndef.mk
diff -u src/usr.bin/make/unit-tests/directive-ifndef.mk:1.3 src/usr.bin/make/unit-tests/directive-ifndef.mk:1.4
--- src/usr.bin/make/unit-tests/directive-ifndef.mk:1.3	Sun Oct  4 22:41:18 2020
+++ src/usr.bin/make/unit-tests/directive-ifndef.mk	Mon Oct  5 19:24:29 2020
@@ -1,24 +1,8 @@
-# $NetBSD: directive-ifndef.mk,v 1.3 2020/10/04 22:41:18 rillig Exp $
+# $NetBSD: directive-ifndef.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
 #
-# Tests for the .ifndef directive, which can be used for multiple-inclusion
-# guards.  In contrast to C, where #ifndef and #define nicely line up the
-# macro name, there is no such syntax in make.  Therefore, it is more
-# common to use .if !defined(GUARD) instead.
+# Tests for the .ifndef directive.
 
-.ifndef GUARD
-GUARD=	# defined
-.info guarded section
-.endif
-
-.ifndef GUARD
-GUARD=	# defined
-.info guarded section
-.endif
-
-.if !defined(GUARD)
-GUARD=	# defined
-.info guarded section
-.endif
+# TODO: Implementation
 
 all:
 	@:;
Index: src/usr.bin/make/unit-tests/directive-ifnmake.mk
diff -u src/usr.bin/make/unit-tests/directive-ifnmake.mk:1.3 src/usr.bin/make/unit-tests/directive-ifnmake.mk:1.4
--- src/usr.bin/make/unit-tests/directive-ifnmake.mk:1.3	Sun Oct  4 22:41:18 2020
+++ src/usr.bin/make/unit-tests/directive-ifnmake.mk	Mon Oct  5 19:24:29 2020
@@ -1,22 +1,8 @@
-# $NetBSD: directive-ifnmake.mk,v 1.3 2020/10/04 22:41:18 rillig Exp $
+# $NetBSD: directive-ifnmake.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
 #
-# Tests for the .ifnmake directive, which evaluates to true if its argument
-# is _not_ listed in the command-line targets to be created.
+# Tests for the .ifnmake directive.
+
+# TODO: Implementation
 
 all:
 	@:;
-
-.ifnmake(test)
-.BEGIN:
-	@echo "Don't forget to run the tests (1)"
-.endif
-
-.MAKEFLAGS: test
-
-.ifnmake(test)
-.BEGIN:
-	@echo "Don't forget to run the tests (2)"
-.endif
-
-test:
-	@echo "Running the tests"
Index: src/usr.bin/make/unit-tests/make-exported.exp
diff -u src/usr.bin/make/unit-tests/make-exported.exp:1.3 src/usr.bin/make/unit-tests/make-exported.exp:1.4
--- src/usr.bin/make/unit-tests/make-exported.exp:1.3	Sat Oct  3 10:31:05 2020
+++ src/usr.bin/make/unit-tests/make-exported.exp	Mon Oct  5 19:24:29 2020
@@ -1,2 +1,3 @@
+-literal=make-exported-value
 UT_VAR=
 exit status 0
Index: src/usr.bin/make/unit-tests/opt-debug.mk
diff -u src/usr.bin/make/unit-tests/opt-debug.mk:1.3 src/usr.bin/make/unit-tests/opt-debug.mk:1.4
--- src/usr.bin/make/unit-tests/opt-debug.mk:1.3	Sat Oct  3 13:06:56 2020
+++ src/usr.bin/make/unit-tests/opt-debug.mk	Mon Oct  5 19:24:29 2020
@@ -1,14 +1,8 @@
-# $NetBSD: opt-debug.mk,v 1.3 2020/10/03 13:06:56 rillig Exp $
+# $NetBSD: opt-debug.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
 #
-# Tests for the -d command line option, which controls debug logging.
+# Tests for the -d command line option.
 
-# Enable debug logging for the variables (var.c).
-.MAKEFLAGS: -dv
-
-VAR=	value
-
-# Disable all debug logging again.
-.MAKEFLAGS: -d0			# -d0 is available since 2020-10-03
+# TODO: Implementation
 
 all:
 	@:;
Index: src/usr.bin/make/unit-tests/var-op-append.mk
diff -u src/usr.bin/make/unit-tests/var-op-append.mk:1.3 src/usr.bin/make/unit-tests/var-op-append.mk:1.4
--- src/usr.bin/make/unit-tests/var-op-append.mk:1.3	Sun Oct  4 10:16:09 2020
+++ src/usr.bin/make/unit-tests/var-op-append.mk	Mon Oct  5 19:24:29 2020
@@ -1,36 +1,9 @@
-# $NetBSD: var-op-append.mk,v 1.3 2020/10/04 10:16:09 rillig Exp $
+# $NetBSD: var-op-append.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
 #
 # Tests for the += variable assignment operator, which appends to a variable,
 # creating it if necessary.
 
-# Appending to an undefined variable is possible.
-# The variable is created, and no extra space is added before the value.
-VAR+=	one
-.if ${VAR} != "one"
-.  error
-.endif
-
-# Appending to an existing variable adds a single space and the value.
-VAR+=	two
-.if ${VAR} != "one two"
-.  error
-.endif
-
-# Appending an empty string nevertheless adds a single space.
-VAR+=	# empty
-.if ${VAR} != "one two "
-.  error
-.endif
-
-# Variable names may contain '+', and this character is also part of the
-# '+=' assignment operator.  As far as possible, the '+' is interpreted as
-# part of the assignment operator.
-#
-# See Parse_DoVar
-C++=value
-.if ${C+} != "value" || defined(C++)
-.  error
-.endif
+# TODO: Implementation
 
 all:
 	@:;

Index: src/usr.bin/make/unit-tests/make-exported.mk
diff -u src/usr.bin/make/unit-tests/make-exported.mk:1.4 src/usr.bin/make/unit-tests/make-exported.mk:1.5
--- src/usr.bin/make/unit-tests/make-exported.mk:1.4	Sat Oct  3 10:42:09 2020
+++ src/usr.bin/make/unit-tests/make-exported.mk	Mon Oct  5 19:24:29 2020
@@ -1,25 +1,16 @@
-# $NetBSD: make-exported.mk,v 1.4 2020/10/03 10:42:09 rillig Exp $
+# $NetBSD: make-exported.mk,v 1.5 2020/10/05 19:24:29 rillig Exp $
 #
 # As of 2020-08-09, the code in Var_Export is shared between the .export
 # directive and the .MAKE.EXPORTED variable.  This leads to non-obvious
 # behavior for certain variable assignments.
 
--env=		make-exported-value-env
--literal=	make-exported-value-literal
+-env=		make-exported-value
+-literal=	make-exported-value
 UT_VAR=		${UNEXPANDED}
 
-# Before 2020-10-03, the following line took the code path of .export-env,
-# which was surprising behavior.  Since 2020-10-03 this line tries to
-# export the variable named "-env", but that is rejected because the
-# variable name starts with a hyphen.
-.MAKE.EXPORTED=		-env
-
-# Before 2020-10-03, if the value of .MAKE.EXPORTED started with "-literal",
-# make behaved like a mixture of .export-literal and a regular .export.
-#
-# Since 2020-10-03, the "variable" named "-literal" is not exported anymore,
-# it is just ignored since its name starts with '-'.
-.MAKE.EXPORTED=		-literal UT_VAR
+# The following behavior is probably not intended.
+.MAKE.EXPORTED=		-env		# like .export-env
+.MAKE.EXPORTED=		-literal UT_VAR	# like .export-literal PATH
 
 all:
 	@env | sort | grep -E '^UT_|make-exported-value' || true
Index: src/usr.bin/make/unit-tests/varname-dot-curdir.mk
diff -u src/usr.bin/make/unit-tests/varname-dot-curdir.mk:1.4 src/usr.bin/make/unit-tests/varname-dot-curdir.mk:1.5
--- src/usr.bin/make/unit-tests/varname-dot-curdir.mk:1.4	Sun Oct  4 21:53:28 2020
+++ src/usr.bin/make/unit-tests/varname-dot-curdir.mk	Mon Oct  5 19:24:29 2020
@@ -1,27 +1,8 @@
-# $NetBSD: varname-dot-curdir.mk,v 1.4 2020/10/04 21:53:28 rillig Exp $
+# $NetBSD: varname-dot-curdir.mk,v 1.5 2020/10/05 19:24:29 rillig Exp $
 #
 # Tests for the special .CURDIR variable.
 
 # TODO: Implementation
 
-# Until 2020-10-04, assigning the result of a shell assignment to .CURDIR
-# tried to add the shell command ("echo /") to the .PATH instead of the
-# output of the shell command ("/").  Since "echo /" does not exist, the
-# .PATH was left unmodified.  See VarAssign_Eval.
-#
-# Since 2020-10-04, the output of the shell command is added to .PATH.
-.CURDIR!=	echo /
-.if ${.PATH:M/} != "/"
-.  error
-.endif
-
-# A normal assignment works fine, as does a substitution assignment.
-# Appending to .CURDIR does not make sense, therefore it doesn't matter that
-# this code path is buggy as well.
-.CURDIR=	/
-.if ${.PATH:M/} != "/"
-.  error
-.endif
-
 all:
 	@:;

Index: src/usr.bin/make/unit-tests/opt-debug-lint.exp
diff -u src/usr.bin/make/unit-tests/opt-debug-lint.exp:1.9 src/usr.bin/make/unit-tests/opt-debug-lint.exp:1.10
--- src/usr.bin/make/unit-tests/opt-debug-lint.exp:1.9	Sat Oct  3 12:30:17 2020
+++ src/usr.bin/make/unit-tests/opt-debug-lint.exp	Mon Oct  5 19:24:29 2020
@@ -1,9 +1,5 @@
 make: "opt-debug-lint.mk" line 19: Variable "X" is undefined
 make: "opt-debug-lint.mk" line 41: Variable "UNDEF" is undefined
-make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "L"
-make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "P"
-make: "opt-debug-lint.mk" line 67: Missing delimiter ':' after indirect modifier "${:UL}"
-make: Unknown modifier '$'
 make: Fatal errors encountered -- cannot continue
 make: stopped in unit-tests
 exit status 1

Index: src/usr.bin/make/unit-tests/opt-debug-lint.mk
diff -u src/usr.bin/make/unit-tests/opt-debug-lint.mk:1.8 src/usr.bin/make/unit-tests/opt-debug-lint.mk:1.9
--- src/usr.bin/make/unit-tests/opt-debug-lint.mk:1.8	Sat Oct  3 12:26:11 2020
+++ src/usr.bin/make/unit-tests/opt-debug-lint.mk	Mon Oct  5 19:24:29 2020
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-lint.mk,v 1.8 2020/10/03 12:26:11 rillig Exp $
+# $NetBSD: opt-debug-lint.mk,v 1.9 2020/10/05 19:24:29 rillig Exp $
 #
 # Tests for the -dL command line option, which runs additional checks
 # to catch common mistakes, such as unclosed variable expressions.
@@ -56,17 +56,5 @@ ${UNDEF}: ${UNDEF}
 .  error
 .endif
 
-# Since 2020-10-03, in lint mode the variable modifier must be separated
-# by colons.  See varparse-mod.mk.
-.if ${value:LPL} != "value"
-.  error
-.endif
-
-# Since 2020-10-03, in lint mode the variable modifier must be separated
-# by colons.  See varparse-mod.mk.
-.if ${value:${:UL}PL} != "LPL}"		# FIXME: "LPL}" is unexpected here.
-.  error ${value:${:UL}PL}
-.endif
-
 all:
 	@:;

Reply via email to