Module Name:    src
Committed By:   rillig
Date:           Mon Oct  5 19:27:48 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): revert previous commit

It had accidentally reverted all the work from the past few days.


To generate a diff of this commit:
cvs rdiff -u -r1.101 -r1.102 src/usr.bin/make/Makefile
cvs rdiff -u -r1.131 -r1.132 src/usr.bin/make/arch.c
cvs rdiff -u -r1.164 -r1.165 src/usr.bin/make/compat.c
cvs rdiff -u -r1.159 -r1.160 src/usr.bin/make/cond.c
cvs rdiff -u -r1.157 -r1.158 src/usr.bin/make/dir.c
cvs rdiff -u -r1.11 -r1.12 src/usr.bin/make/enum.c
cvs rdiff -u -r1.91 -r1.92 src/usr.bin/make/for.c
cvs rdiff -u -r1.42 -r1.43 src/usr.bin/make/hash.c
cvs rdiff -u -r1.259 -r1.260 src/usr.bin/make/job.c
cvs rdiff -u -r1.368 -r1.369 src/usr.bin/make/main.c
cvs rdiff -u -r1.154 -r1.155 src/usr.bin/make/make.h
cvs rdiff -u -r1.22 -r1.23 src/usr.bin/make/make_malloc.c
cvs rdiff -u -r1.139 -r1.140 src/usr.bin/make/nonints.h
cvs rdiff -u -r1.367 -r1.368 src/usr.bin/make/parse.c
cvs rdiff -u -r1.67 -r1.68 src/usr.bin/make/str.c
cvs rdiff -u -r1.175 -r1.176 src/usr.bin/make/suff.c
cvs rdiff -u -r1.110 -r1.111 src/usr.bin/make/targ.c
cvs rdiff -u -r1.18 -r1.19 src/usr.bin/make/trace.c
cvs rdiff -u -r1.62 -r1.63 src/usr.bin/make/util.c
cvs rdiff -u -r1.566 -r1.567 src/usr.bin/make/var.c
cvs rdiff -u -r1.161 -r1.162 src/usr.bin/make/unit-tests/Makefile
cvs rdiff -u -r1.3 -r1.4 \
    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.4 -r1.5 \
    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.5 -r1.6 src/usr.bin/make/unit-tests/make-exported.mk \
    src/usr.bin/make/unit-tests/varname-dot-curdir.mk
cvs rdiff -u -r1.10 -r1.11 src/usr.bin/make/unit-tests/opt-debug-lint.exp
cvs rdiff -u -r1.9 -r1.10 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.101 src/usr.bin/make/Makefile:1.102
--- src/usr.bin/make/Makefile:1.101	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/Makefile	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.101 2020/10/05 19:24:29 rillig Exp $
+#	$NetBSD: Makefile,v 1.102 2020/10/05 19:27:47 rillig Exp $
 #	@(#)Makefile	5.2 (Berkeley) 12/28/90
 
 PROG=	make
@@ -189,3 +189,10 @@ 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.131 src/usr.bin/make/arch.c:1.132
--- src/usr.bin/make/arch.c:1.131	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/arch.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: arch.c,v 1.131 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: arch.c,v 1.132 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -123,18 +123,14 @@
 #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.131 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: arch.c,v 1.132 2020/10/05 19:27:47 rillig Exp $");
 
 #ifdef TARGET_MACHINE
 #undef MAKE_MACHINE
@@ -151,11 +147,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 *,
@@ -170,8 +166,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);
@@ -258,7 +254,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++;
@@ -270,7 +266,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;
@@ -324,11 +320,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 */
 
@@ -413,15 +409,9 @@ Arch_ParseArchive(char **linePtr, GNodeL
 	free(libName);
     }
 
-    /*
-     * 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));
-
+    cp++;			/* skip the ')' */
+    /* We promised that linePtr would be set up at the next non-space. */
+    pp_skip_whitespace(&cp);
     *linePtr = cp;
     return TRUE;
 }
@@ -449,15 +439,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
@@ -486,7 +476,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) {
@@ -506,11 +496,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);
@@ -533,8 +523,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));
@@ -545,7 +535,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...
@@ -560,7 +550,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));
@@ -579,15 +569,14 @@ 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';
 	    }
@@ -601,23 +590,24 @@ 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));
@@ -706,12 +696,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;
     }
 
@@ -746,13 +736,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) {
@@ -765,8 +755,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;
     }
 
     /*
@@ -785,13 +775,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,
@@ -800,7 +790,8 @@ 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 {
 		/*
@@ -866,7 +857,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);
@@ -901,8 +892,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),
@@ -912,7 +903,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);
@@ -961,13 +952,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);
@@ -1039,8 +1030,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.164 src/usr.bin/make/compat.c:1.165
--- src/usr.bin/make/compat.c:1.164	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/compat.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: compat.c,v 1.164 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: compat.c,v 1.165 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -82,26 +82,23 @@
  *			thems as need creatin'
  */
 
-#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"
+#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"
 
 /*	"@(#)compat.c	8.2 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: compat.c,v 1.164 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: compat.c,v 1.165 2020/10/05 19:27:47 rillig Exp $");
 
-static GNode	    *curTarg = NULL;
+static GNode *curTarg = NULL;
 static pid_t compatChild;
 static int compatSigno;
 
@@ -112,15 +109,15 @@ static int compatSigno;
 static void
 CompatDeleteTarget(GNode *gn)
 {
-    if ((gn != NULL) && !Targ_Precious (gn)) {
-	char *p1;
-	const char *file = Var_Value(TARGET, gn, &p1);
+    if (gn != NULL && !Targ_Precious(gn)) {
+	char *file_freeIt;
+	const char *file = Var_Value(TARGET, gn, &file_freeIt);
 
 	if (!noExecute && eunlink(file) != -1) {
 	    Error("*** %s removed", file);
 	}
 
-	bmake_free(p1);
+	bmake_free(file_freeIt);
     }
 }
 
@@ -139,7 +136,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
 	 */
@@ -150,11 +147,13 @@ 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) {
@@ -165,11 +164,8 @@ CompatInterrupt(int signo)
     }
 }
 
-/*-
- *-----------------------------------------------------------------------
- * 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.
+/* 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
@@ -177,11 +173,6 @@ 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)
@@ -215,13 +206,6 @@ 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;
@@ -241,7 +225,7 @@ Compat_RunCommand(const char *cmdp, stru
 	return 0;
     }
 
-    while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) {
+    while (*cmd == '@' || *cmd == '-' || *cmd == '+') {
 	switch (*cmd) {
 	case '@':
 	    silent = !DEBUG(LOUD);
@@ -439,8 +423,7 @@ 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 {
@@ -503,7 +486,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) == 0)) {
+    if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) {
 	/*
 	 * First mark ourselves to be made, then apply whatever transformations
 	 * the suffix module thinks are necessary. Once that's done, we can
@@ -514,19 +497,19 @@ Compat_Make(GNode *gn, GNode *pgn)
 	 */
 	gn->flags |= REMAKE;
 	gn->made = BEINGMADE;
-	if ((gn->type & OP_MADE) == 0)
+	if (!(gn->type & OP_MADE))
 	    Suff_FindDeps(gn);
 	MakeNodes(gn->children, gn);
-	if ((gn->flags & REMAKE) == 0) {
+	if (!(gn->flags & REMAKE)) {
 	    gn->made = ABORTED;
 	    pgn->flags &= ~(unsigned)REMAKE;
 	    goto cohorts;
 	}
 
 	if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) {
-	    char *p1;
-	    Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn);
-	    bmake_free(p1);
+	    char *target_freeIt;
+	    Var_Set(IMPSRC, Var_Value(TARGET, gn, &target_freeIt), pgn);
+	    bmake_free(target_freeIt);
 	}
 
 	/*
@@ -536,7 +519,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;
@@ -623,10 +606,10 @@ Compat_Make(GNode *gn, GNode *pgn)
 	pgn->flags &= ~(unsigned)REMAKE;
     } else {
 	if (Lst_FindDatum(gn->implicitParents, pgn) != NULL) {
-	    char *p1;
-	    const char *target = Var_Value(TARGET, gn, &p1);
+	    char *target_freeIt;
+	    const char *target = Var_Value(TARGET, gn, &target_freeIt);
 	    Var_Set(IMPSRC, target != NULL ? target : "", pgn);
-	    bmake_free(p1);
+	    bmake_free(target_freeIt);
 	}
 	switch(gn->made) {
 	    case BEINGMADE:

Index: src/usr.bin/make/cond.c
diff -u src/usr.bin/make/cond.c:1.159 src/usr.bin/make/cond.c:1.160
--- src/usr.bin/make/cond.c:1.159	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/cond.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: cond.c,v 1.159 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: cond.c,v 1.160 2020/10/05 19:27:47 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.159 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: cond.c,v 1.160 2020/10/05 19:27:47 rillig Exp $");
 
 /*
  * The parsing of conditional expressions is based on this grammar:
@@ -186,8 +186,7 @@ CondParser_PushBack(CondParser *par, Tok
 static void
 CondParser_SkipWhitespace(CondParser *par)
 {
-    while (ch_isspace(par->p[0]))
-	par->p++;
+    cpp_skip_whitespace(&par->p);
 }
 
 /* Parse the argument of a built-in function.
@@ -538,7 +537,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". */
@@ -697,8 +696,7 @@ ParseEmptyArg(const char **linePtr, Bool
     }
 
     /* A variable is empty when it just contains spaces... 4/15/92, christos */
-    while (ch_isspace(val[0]))
-	val++;
+    cpp_skip_whitespace(&val);
 
     /*
      * For consistency with the other functions we can't generate the
@@ -745,8 +743,7 @@ CondParser_Func(CondParser *par, Boolean
 	    continue;
 	cp += fn_def->fn_name_len;
 	/* There can only be whitespace before the '(' */
-	while (ch_isspace(*cp))
-	    cp++;
+	cpp_skip_whitespace(&cp);
 	if (*cp != '(')
 	    break;
 
@@ -776,8 +773,8 @@ CondParser_Func(CondParser *par, Boolean
      * expression.
      */
     arglen = ParseFuncArg(&cp, doEval, NULL, &arg);
-    for (cp1 = cp; ch_isspace(*cp1); cp1++)
-	continue;
+    cp1 = cp;
+    cpp_skip_whitespace(&cp1);
     if (*cp1 == '=' || *cp1 == '!')
 	return CondParser_Comparison(par, doEval);
     par->p = cp;
@@ -788,7 +785,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.157 src/usr.bin/make/dir.c:1.158
--- src/usr.bin/make/dir.c:1.157	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/dir.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: dir.c,v 1.157 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: dir.c,v 1.158 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -129,14 +129,13 @@
 
 #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.157 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: dir.c,v 1.158 2020/10/05 19:27:47 rillig Exp $");
 
 #define DIR_DEBUG0(text) DEBUG0(DIR, text)
 #define DIR_DEBUG1(fmt, arg1) DEBUG1(DIR, fmt, arg1)
@@ -219,7 +218,58 @@ typedef ListNode SearchPathNode;
 
 SearchPath *dirSearchPath;		/* main search path */
 
-static CachedDirList *openDirectories;	/* the list of all open directories */
+/* 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 */
 
 /*
  * Variables for gathering statistics on the efficiency of the hashing
@@ -337,7 +387,7 @@ void
 Dir_Init(void)
 {
     dirSearchPath = Lst_Init();
-    openDirectories = Lst_Init();
+    OpenDirs_Init(&openDirs);
     Hash_InitTable(&mtimes);
     Hash_InitTable(&lmtimes);
 }
@@ -387,11 +437,8 @@ void
 Dir_InitDot(void)
 {
     if (dot != NULL) {
-	CachedDirListNode *ln;
-
-	/* Remove old entry from openDirectories, but do not destroy. */
-	ln = Lst_FindDatum(openDirectories, dot);
-	Lst_Remove(openDirectories, ln);
+	/* Remove old entry from openDirs, but do not destroy. */
+	OpenDirs_Remove(&openDirs, dot->name);
     }
 
     dot = Dir_AddDir(NULL, ".");
@@ -424,8 +471,7 @@ Dir_End(void)
     Dir_Destroy(dot);
     Dir_ClearPath(dirSearchPath);
     Lst_Free(dirSearchPath);
-    Dir_ClearPath(openDirectories);
-    Lst_Free(openDirectories);
+    OpenDirs_Done(&openDirs);
     Hash_DeleteTable(&mtimes);
 #endif
 }
@@ -1482,13 +1528,12 @@ 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) {
-	ln = Lst_Find(path, DirFindName, name);
+	SearchPathNode *ln = Lst_Find(path, DirFindName, name);
 	if (ln != NULL)
 	    return LstNode_Datum(ln);
 
@@ -1497,9 +1542,8 @@ Dir_AddDir(SearchPath *path, const char 
     }
 
     if (path != NULL)
-	ln = Lst_Find(openDirectories, DirFindName, name);
-    if (ln != NULL) {
-	dir = LstNode_Datum(ln);
+	dir = OpenDirs_Find(&openDirs, name);
+    if (dir != NULL) {
 	if (Lst_FindDatum(path, dir) == NULL) {
 	    dir->refCount++;
 	    Lst_Append(path, dir);
@@ -1530,7 +1574,7 @@ Dir_AddDir(SearchPath *path, const char 
 	    (void)Hash_CreateEntry(&dir->files, dp->d_name, NULL);
 	}
 	(void)closedir(d);
-	Lst_Append(openDirectories, dir);
+	OpenDirs_Add(&openDirs, dir);
 	if (path != NULL)
 	    Lst_Append(path, dir);
     }
@@ -1623,11 +1667,7 @@ Dir_Destroy(void *dirp)
     dir->refCount--;
 
     if (dir->refCount == 0) {
-	CachedDirListNode *node;
-
-	node = Lst_FindDatum(openDirectories, dir);
-	if (node != NULL)
-	    Lst_Remove(openDirectories, node);
+	OpenDirs_Remove(&openDirs, dir->name);
 
 	Hash_DeleteTable(&dir->files);
 	free(dir->name);
@@ -1712,7 +1752,7 @@ Dir_PrintDirectories(void)
 		 percentage(hits, hits + bigmisses + nearmisses));
     debug_printf("# %-20s referenced\thits\n", "directory");
 
-    for (ln = openDirectories->first; ln != NULL; ln = ln->next) {
+    for (ln = openDirs.list->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.11 src/usr.bin/make/enum.c:1.12
--- src/usr.bin/make/enum.c:1.11	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/enum.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: enum.c,v 1.11 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: enum.c,v 1.12 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  Copyright (c) 2020 Roland Illig <[email protected]>
@@ -27,13 +27,9 @@
  POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
 #include "make.h"
 
-MAKE_RCSID("$NetBSD: enum.c,v 1.11 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: enum.c,v 1.12 2020/10/05 19:27:47 rillig Exp $");
 
 /* Convert a bitset into a string representation, showing the names of the
  * individual bits.
@@ -52,7 +48,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);
@@ -86,8 +82,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.91 src/usr.bin/make/for.c:1.92
--- src/usr.bin/make/for.c:1.91	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/for.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: for.c,v 1.91 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: for.c,v 1.92 2020/10/05 19:27:47 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.91 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: for.c,v 1.92 2020/10/05 19:27:47 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 */
-    for (ptr = line + 1; ch_isspace(*ptr); ptr++)
-	continue;
+    ptr = line + 1;
+    cpp_skip_whitespace(&ptr);
 
     /*
      * If we are not in a for loop quickly determine if the statement is
@@ -152,8 +152,7 @@ For_Eval(const char *line)
     while (TRUE) {
 	size_t len;
 
-	while (ch_isspace(*ptr))
-	    ptr++;
+	cpp_skip_whitespace(&ptr);
 	if (*ptr == '\0') {
 	    Parse_Error(PARSE_FATAL, "missing `in' in for");
 	    For_Free(new_for);
@@ -179,8 +178,7 @@ For_Eval(const char *line)
 	return -1;
     }
 
-    while (ch_isspace(*ptr))
-	ptr++;
+    cpp_skip_whitespace(&ptr);
 
     /*
      * Make a list with the remaining words.
@@ -267,9 +265,8 @@ For_Accum(const char *line)
     const char *ptr = line;
 
     if (*ptr == '.') {
-
-	for (ptr++; *ptr && ch_isspace(*ptr); ptr++)
-	    continue;
+	ptr++;
+	cpp_skip_whitespace(&ptr);
 
 	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.42 src/usr.bin/make/hash.c:1.43
--- src/usr.bin/make/hash.c:1.42	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/hash.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: hash.c,v 1.42 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: hash.c,v 1.43 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
@@ -79,38 +79,50 @@
 #include "make.h"
 
 /*	"@(#)hash.c	8.1 (Berkeley) 6/6/93"	*/
-MAKE_RCSID("$NetBSD: hash.c,v 1.42 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: hash.c,v 1.43 2020/10/05 19:27:47 rillig Exp $");
 
 /*
- * Forward references to local procedures that are used before they're
- * defined:
+ * The ratio of # entries to # buckets at which we rebuild the table to
+ * make it larger.
  */
+#define rebuildLimit 3
 
-static void RebuildTable(Hash_Table *);
+/* 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;
+}
 
-/*
- * The following defines the ratio of # entries to # buckets
- * at which we rebuild the table to make it larger.
- */
+static Hash_Entry *
+HashTable_Find(Hash_Table *t, unsigned int h, const char *key)
+{
+	Hash_Entry *e;
+	int chainlen = 0;
 
-#define rebuildLimit 3
+#ifdef DEBUG_HASH_LOOKUP
+	DEBUG4(HASH, "%s: %p h=%x key=%s\n", __func__, t, h, key);
+#endif
 
-/* The hash function(s) */
+	for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) {
+		chainlen++;
+		if (e->namehash == h && strcmp(e->name, key) == 0)
+			break;
+	}
 
-#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)
+	if (chainlen > t->maxchain)
+		t->maxchain = chainlen;
 
-#endif
+	return e;
+}
 
-/* Sets up the hash table.
- *
- * Input:
- *	t		Structure to to hold the table.
- */
+/* Sets up the hash table. */
 void
 Hash_InitTable(Hash_Table *t)
 {
@@ -162,35 +174,47 @@ Hash_DeleteTable(Hash_Table *t)
 Hash_Entry *
 Hash_FindEntry(Hash_Table *t, const char *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;
+	unsigned int h = hash(key, NULL);
+	return HashTable_Find(t, h, key);
 }
 
 void *
 Hash_FindValue(Hash_Table *t, const char *key)
 {
-    Hash_Entry *he = Hash_FindEntry(t, key);
-    return he != NULL ? he->value : NULL;
+	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;
 }
 
 /* Searches the hash table for an entry corresponding to the key.
@@ -207,34 +231,16 @@ Hash_CreateEntry(Hash_Table *t, const ch
 {
 	Hash_Entry *e;
 	unsigned h;
-	const char *p;
-	int keylen;
-	int chainlen;
+	size_t keylen;
 	struct Hash_Entry **hp;
 
-	/*
-	 * 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)
+	h = hash(key, &keylen);
+	e = HashTable_Find(t, h, key);
+	if (e) {
+		if (newPtr != NULL)
+			*newPtr = FALSE;
 		return e;
+	}
 
 	/*
 	 * The desired entry isn't there.  Before allocating a new entry,
@@ -243,13 +249,14 @@ 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;
-	(void)strcpy(e->name, p);
+	memcpy(e->name, key, keylen + 1);
 	t->numEntries++;
 
 	if (newPtr != NULL)
@@ -263,8 +270,6 @@ 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) {
@@ -274,7 +279,6 @@ Hash_DeleteEntry(Hash_Table *t, Hash_Ent
 			return;
 		}
 	}
-	(void)write(2, "bad call to Hash_DeleteEntry\n", 29);
 	abort();
 }
 
@@ -330,38 +334,6 @@ 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)
 {
@@ -377,6 +349,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.259 src/usr.bin/make/job.c:1.260
--- src/usr.bin/make/job.c:1.259	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/job.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: job.c,v 1.259 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: job.c,v 1.260 2020/10/05 19:27:47 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.259 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: job.c,v 1.260 2020/10/05 19:27:47 rillig Exp $");
 
 # define STATIC static
 
@@ -264,7 +264,7 @@ static Shell    shells[] = {
      */
 {
     "csh",
-    TRUE, "unset verbose", "set verbose", "unset verbose", 10,
+    TRUE, "unset verbose", "set verbose", "unset verbose", 13,
     FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#',
     "v", "e",
 },
@@ -655,8 +655,7 @@ JobPrintCommand(void *cmdp, void *jobp)
 	cmd++;
     }
 
-    while (ch_isspace(*cmd))
-	cmd++;
+    pp_skip_whitespace(&cmd);
 
     /*
      * If the shell doesn't have error control the alternate echo'ing will
@@ -1576,19 +1575,14 @@ JobStart(GNode *gn, int flags)
 }
 
 static char *
-JobOutput(Job *job, char *cp, char *endp, int msg)
+JobOutput(Job *job, char *cp, char *endp)
 {
     char *ecp;
 
-    if (commandShell->noPrint) {
-	ecp = Str_FindSubstring(cp, commandShell->noPrint);
-	while (ecp != NULL) {
+    if (commandShell->noPrint && commandShell->noPrint[0] != '\0') {
+	while ((ecp = strstr(cp, commandShell->noPrint)) != 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.
@@ -1609,7 +1603,6 @@ JobOutput(Job *job, char *cp, char *endp
 		while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
 		    cp++;
 		}
-		ecp = Str_FindSubstring(cp, commandShell->noPrint);
 	    } else {
 		return cp;
 	    }
@@ -1738,7 +1731,7 @@ end_loop:
 	if (i >= job->curPos) {
 	    char *cp;
 
-	    cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE);
+	    cp = JobOutput(job, job->outBuf, &job->outBuf[i]);
 
 	    /*
 	     * There's still more in that thar buffer. This time, though,
@@ -2213,8 +2206,7 @@ Job_ParseShell(char *line)
     Boolean	fullSpec = FALSE;
     Shell	*sh;
 
-    while (ch_isspace(*line))
-	line++;
+    pp_skip_whitespace(&line);
 
     free(shellArgv);
 

Index: src/usr.bin/make/main.c
diff -u src/usr.bin/make/main.c:1.368 src/usr.bin/make/main.c:1.369
--- src/usr.bin/make/main.c:1.368	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/main.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: main.c,v 1.368 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: main.c,v 1.369 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -106,16 +106,12 @@
 #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"
@@ -126,10 +122,11 @@
 #endif
 
 /*	"@(#)main.c	8.3 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: main.c,v 1.368 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: main.c,v 1.369 2020/10/05 19:27:47 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
@@ -270,6 +267,9 @@ 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,9 +673,10 @@ 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)
-		if (Parse_IsVar(argv[1])) {
-			Parse_DoVar(argv[1], VAR_CMD);
+	for (; argc > 1; ++argv, --argc) {
+		VarAssign var;
+		if (Parse_IsVar(argv[1], &var)) {
+			Parse_DoVar(&var, VAR_CMD);
 		} else {
 			if (!*argv[1])
 				Punt("illegal (null) argument.");
@@ -683,6 +684,7 @@ rearg:
 				goto rearg;
 			Lst_Append(create, bmake_strdup(argv[1]));
 		}
+	}
 
 	return;
 noarg:
@@ -971,6 +973,60 @@ 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
@@ -995,8 +1051,8 @@ main(int argc, char **argv)
 	struct stat sb, sa;
 	char *p1, *path;
 	char mdpath[MAXPATHLEN];
-	const char *machine = getenv("MACHINE");
-	const char *machine_arch = getenv("MACHINE_ARCH");
+	const char *machine;
+	const char *machine_arch;
 	char *syspath = getenv("MAKESYSPATH");
 	StringList *sysMkPath;		/* Path of sys.mk */
 	char *cp = NULL, *start;
@@ -1051,44 +1107,8 @@ main(int argc, char **argv)
 	 * Note that both MACHINE and MACHINE_ARCH are decided at
 	 * run-time.
 	 */
-	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
-	}
+	machine = init_machine(&utsname);
+	machine_arch = init_machine_arch();
 
 	myPid = getpid();		/* remember this for vFork() */
 
@@ -1715,16 +1735,10 @@ bad:
     return bmake_strdup("");
 }
 
-/*-
- * Error --
- *	Print an error message given its format.
+/* Print a printf-style error message.
  *
- * Results:
- *	None.
- *
- * Side Effects:
- *	The message is printed.
- */
+ * This error message has no consequences, in particular it does not affect
+ * the exit status. */
 void
 Error(const char *fmt, ...)
 {

Index: src/usr.bin/make/make.h
diff -u src/usr.bin/make/make.h:1.154 src/usr.bin/make/make.h:1.155
--- src/usr.bin/make/make.h:1.154	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/make.h	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: make.h,v 1.154 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: make.h,v 1.155 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -259,7 +259,9 @@ typedef enum {
     /* Already processed by Suff_FindDeps */
     OP_DEPS_FOUND	= 1 << 25,
     /* Node found while expanding .ALLSRC */
-    OP_MARK		= 1 << 24
+    OP_MARK		= 1 << 24,
+
+    OP_NOTARGET		= OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM
 } GNodeType;
 
 typedef enum {
@@ -358,15 +360,6 @@ 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
@@ -568,6 +561,22 @@ 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 {			\
@@ -616,6 +625,20 @@ 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.22 src/usr.bin/make/make_malloc.c:1.23
--- src/usr.bin/make/make_malloc.c:1.22	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/make_malloc.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: make_malloc.c,v 1.22 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: make_malloc.c,v 1.23 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*-
  * Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -26,14 +26,11 @@
  * 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.22 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: make_malloc.c,v 1.23 2020/10/05 19:27:47 rillig Exp $");
 
 #ifndef USE_EMALLOC
 

Index: src/usr.bin/make/nonints.h
diff -u src/usr.bin/make/nonints.h:1.139 src/usr.bin/make/nonints.h:1.140
--- src/usr.bin/make/nonints.h:1.139	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/nonints.h	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: nonints.h,v 1.139 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: nonints.h,v 1.140 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*-
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -120,9 +120,27 @@ 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 *);
-void Parse_DoVar(char *, GNode *);
+Boolean Parse_IsVar(const char *, VarAssign *out_var);
+void Parse_DoVar(VarAssign *, GNode *);
 void Parse_AddIncludeDir(const char *);
 void Parse_File(const char *, int);
 void Parse_Init(void);
@@ -147,7 +165,6 @@ 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 */
@@ -193,10 +210,13 @@ 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.367 src/usr.bin/make/parse.c:1.368
--- src/usr.bin/make/parse.c:1.367	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/parse.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: parse.c,v 1.367 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: parse.c,v 1.368 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -116,7 +116,6 @@
 #include <sys/stat.h>
 #include <errno.h>
 #include <stdarg.h>
-#include <stdio.h>
 #include <stdint.h>
 
 #ifndef MAP_FILE
@@ -132,7 +131,7 @@
 #include "pathnames.h"
 
 /*	"@(#)parse.c	8.3 (Berkeley) 3/19/94"	*/
-MAKE_RCSID("$NetBSD: parse.c,v 1.367 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: parse.c,v 1.368 2020/10/05 19:27:47 rillig Exp $");
 
 /* types and constants */
 
@@ -205,6 +204,8 @@ typedef enum {
     Attribute		/* Generic attribute */
 } ParseSpecial;
 
+typedef List SearchPathList;
+
 /* result data */
 
 /*
@@ -585,21 +586,8 @@ ParseMark(GNode *gn)
     gn->lineno = curFile->lineno;
 }
 
-/*-
- *----------------------------------------------------------------------
- * 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
- *----------------------------------------------------------------------
- */
+/* 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. */
 static int
 ParseFindKeyword(const char *str)
 {
@@ -660,8 +648,7 @@ 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)
@@ -768,8 +755,7 @@ ParseMessage(char *line)
 	line++;
     if (!ch_isspace(*line))
 	return FALSE;			/* not for us */
-    while (ch_isspace(*line))
-	line++;
+    pp_skip_whitespace(&line);
 
     (void)Var_Subst(line, VAR_CMD, VARE_WANTRES, &line);
     /* TODO: handle errors */
@@ -889,33 +875,12 @@ ApplyDependencyOperator(GNodeType op)
 	    break;
 }
 
-/*-
- *---------------------------------------------------------------------
- * 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)
+static Boolean
+ParseDoSrcKeyword(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);
@@ -923,7 +888,7 @@ ParseDoSrc(int tOp, const char *src, Par
 	    int op = parseKeywords[keywd].op;
 	    if (op != 0) {
 		ApplyDependencyOperator(op);
-		return;
+		return TRUE;
 	    }
 	    if (parseKeywords[keywd].spec == Wait) {
 		/*
@@ -944,82 +909,114 @@ ParseDoSrc(int tOp, const char *src, Par
 		    struct ParseLinkSrcArgs args = { gn, specType };
 		    Lst_ForEach(targets, ParseLinkSrc, &args);
 		}
-		return;
+		return TRUE;
 	    }
 	}
     }
+    return FALSE;
+}
 
-    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
+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);
+}
 
-    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);
-	    }
+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);
 	}
-	/*
-	 * The current source now becomes the predecessor for the next one.
-	 */
-	predecessor = gn;
-	break;
+    }
+    /*
+     * The current source now becomes the predecessor for the next one.
+     */
+    predecessor = gn;
+}
 
-    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.
-	 */
+static void
+ParseDoSrcOther(const char *src, GNodeType tOp, ParseSpecial specType)
+{
+    GNode *gn;
 
-	/* 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);
-	    }
+    /*
+     * 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);
 	}
-	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. */
@@ -1071,8 +1068,7 @@ ParseErrorNoDependency(const char *lstar
     else if (lstart[0] == '.') {
 	const char *dirstart = lstart + 1;
 	const char *dirend;
-	while (ch_isspace(*dirstart))
-	    dirstart++;
+	cpp_skip_whitespace(&dirstart);
 	dirend = dirstart;
 	while (ch_isalnum(*dirend) || *dirend == '-')
 	    dirend++;
@@ -1116,64 +1112,369 @@ ParseDependencyTargetWord(/*const*/ char
     *pp = cp;
 }
 
-/* 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.
+/*
+ * 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 void
-ParseDoDependency(char *line)
+static Boolean
+ParseDoDependencyTargetPath(const char *const line,
+			    SearchPathList **const inout_paths)
 {
-    typedef List SearchPathList;
+    SearchPath *path;
 
-    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;
+    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.
+ */
+static Boolean
+ParseDoDependencyTarget(const char *const line,
+			ParseSpecial *const inout_specType,
+			GNodeType *out_tOp,
+			SearchPathList **inout_paths)
+{
+    int keywd;
+
+    if (!(*line == '.' && ch_isupper(line[1])))
+	return TRUE;
 
     /*
-     * 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.
+     * See if the target is a special target that must have it
+     * or its sources handled specially.
      */
-    ParseSpecial specType = Not;
+    keywd = ParseFindKeyword(line);
+    if (keywd != -1) {
+	if (*inout_specType == ExPath && parseKeywords[keywd].spec != ExPath) {
+	    Parse_Error(PARSE_FATAL, "Mismatched special targets");
+	    return FALSE;
+	}
 
-    DEBUG1(PARSE, "ParseDoDependency(%s)\n", line);
-    tOp = 0;
+	*inout_specType = parseKeywords[keywd].spec;
+	*out_tOp = parseKeywords[keywd].op;
 
-    paths = NULL;
+	ParseDoDependencyTargetSpecial(inout_specType, line, inout_paths);
 
-    curTargs = Lst_Init();
+    } else if (strncmp(line, ".PATH", 5) == 0) {
+	*inout_specType = ExPath;
+	if (!ParseDoDependencyTargetPath(line, inout_paths))
+	    return FALSE;
+    }
+    return TRUE;
+}
 
-    /*
-     * First, grind through the targets.
-     */
+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;
+    }
+
+    {
+	const char *msg = lstart[0] == '.' ? "Unknown directive"
+					   : "Missing dependency operator";
+	Parse_Error(PARSE_FATAL, "%s", msg);
+	return FALSE;
+    }
+}
+
+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;
+    }
+}
+
+/*
+ * 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;
+    }
+}
+
+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;
 
     for (;;) {
 	/*
@@ -1202,8 +1503,8 @@ ParseDoDependency(char *line)
 	     */
 	    if (!Arch_ParseArchive(&line, targets, VAR_CMD)) {
 		Parse_Error(PARSE_FATAL,
-			     "Error in archive specification: \"%s\"", line);
-		goto out;
+			    "Error in archive specification: \"%s\"", line);
+		return FALSE;
 	    } else {
 		/* Done with this word; on to the next. */
 		cp = line;
@@ -1213,207 +1514,180 @@ ParseDoDependency(char *line)
 
 	if (!*cp) {
 	    ParseErrorNoDependency(lstart, line);
-	    goto out;
+	    return FALSE;
 	}
 
 	/* Insert a null terminator. */
 	savec = *cp;
 	*cp = '\0';
 
+	if (!ParseDoDependencyTarget(line, inout_specType, inout_tOp,
+				     inout_paths))
+	    return FALSE;
+
 	/*
-	 * Got the word. See if it's a special target and if so set
-	 * specType to match it.
+	 * Have word in line. Get or create its node and stick it at
+	 * the end of the targets list
 	 */
-	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;
-		}
+	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);
+	}
 
-		specType = parseKeywords[keywd].spec;
-		tOp = parseKeywords[keywd].op;
+	/* Don't need the inserted null terminator any more. */
+	*cp = savec;
 
-		/*
-		 * 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
-		 */
-		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;
+	/*
+	 * 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;
+    }
 
-		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);
-		}
-	    }
+    *inout_cp = cp;
+    *inout_line = line;
+    return TRUE;
+}
+
+static void
+ParseDoDependencySourcesSpecial(char *line, char *cp,
+				ParseSpecial specType, SearchPathList *paths)
+{
+    char savec;
+
+    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) {
 	/*
-	 * Have word in line. Get or create its node and stick it at
-	 * the end of the targets list
+	 * The targets take real sources, so we must beware of archive
+	 * specifications (i.e. things with left parentheses in them)
+	 * and handle them accordingly.
 	 */
-	if (specType == Not && *line != '\0') {
-	    if (Dir_HasWildcards(line)) {
+	for (; *cp && !ch_isspace(*cp); cp++) {
+	    if (*cp == '(' && cp > line && cp[-1] != '$') {
 		/*
-		 * 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...
+		 * 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).
 		 */
-		SearchPath *emptyPath = Lst_Init();
+		break;
+	    }
+	}
 
-		Dir_Expand(line, emptyPath, curTargs);
+	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;
+	    }
 
-		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);
+	    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++;
 	    }
 
-	    /* Apply the targets. */
+	    ParseDoSrc(tOp, line, specType);
+	}
+	pp_skip_whitespace(&cp);
+	line = cp;
+    }
+    return TRUE;
+}
+
+/* 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;
 
-	    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);
+    /*
+     * 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;
 
-		Lst_Append(targets, gn);
-	    }
-	} else if (specType == ExPath && *line != '.' && *line != '\0') {
-	    Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
-	}
+    DEBUG1(PARSE, "ParseDoDependency(%s)\n", line);
+    tOp = 0;
 
-	/* Don't need the inserted null terminator any more. */
-	*cp = savec;
+    paths = NULL;
 
-	/*
-	 * 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;
+    curTargs = Lst_Init();
 
-	    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;
-    }
+    /*
+     * First, grind through the targets.
+     */
+    if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, &paths,
+				  curTargs))
+	goto out;
 
     /*
      * Don't need the list of target names anymore...
@@ -1421,50 +1695,14 @@ ParseDoDependency(char *line)
     Lst_Free(curTargs);
     curTargs = NULL;
 
-    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;
-	}
-    }
+    if (!Lst_IsEmpty(targets))
+        ParseDoDependencyCheckSpec(specType);
 
     /*
-     * Have now parsed all the target names. Must parse the operator next. The
-     * result is left in  op .
+     * Have now parsed all the target names. Must parse the operator next.
      */
-    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++;
+    if (!ParseDoDependencyParseOp(&cp, lstart, &op))
+        goto out;
 
     /*
      * Apply the operator to the target. This is how we remember which
@@ -1479,9 +1717,7 @@ ParseDoDependency(char *line)
      * LINE will now point to the first source word, if any, or the
      * end of the string if not.
      */
-    while (*cp && ch_isspace(*cp)) {
-	cp++;
-    }
+    pp_skip_whitespace(&cp);
     line = cp;
 
     /*
@@ -1494,32 +1730,7 @@ ParseDoDependency(char *line)
      *	a .PATH removes all directories from the search path(s).
      */
     if (!*line) {
-	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;
-	}
+        ParseDoDependencySourcesEmpty(specType, paths);
     } else if (specType == MFlags) {
 	/*
 	 * Call on functions in main.c to deal with these arguments and
@@ -1546,71 +1757,7 @@ ParseDoDependency(char *line)
 	specType == Includes || specType == Libs ||
 	specType == Null || specType == ExObjdir)
     {
-	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;
-	}
+        ParseDoDependencySourcesSpecial(line, cp, specType, paths);
 	if (paths) {
 	    Lst_Free(paths);
 	    paths = NULL;
@@ -1619,51 +1766,8 @@ ParseDoDependency(char *line)
 	    Dir_SetPATH();
     } else {
 	assert(paths == NULL);
-	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;
-	}
+        if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp))
+            goto out;
     }
 
     FindMainTarget();
@@ -1675,42 +1779,37 @@ out:
 	Lst_Free(curTargs);
 }
 
-/*-
- *---------------------------------------------------------------------
- * 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
+/* Parse a variable assignment, consisting of a single-word variable name,
+ * optional whitespace, an assignment operator, optional whitespace and the
+ * variable value.
  *
- * Results:
- *	TRUE if it is. FALSE if it ain't
- *
- * Side Effects:
- *	none
- *---------------------------------------------------------------------
- */
+ * Used for both lines in a file and command line arguments. */
 Boolean
-Parse_IsVar(const char *line)
+Parse_IsVar(const char *p, VarAssign *out_var)
 {
-    Boolean wasSpace = FALSE;	/* set TRUE if found a space */
+    const char *firstSpace = NULL;
     char ch;
     int level = 0;
-#define ISEQOPERATOR(c) \
-	(((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!'))
 
-    /*
-     * Skip to variable name
-     */
-    while (*line == ' ' || *line == '\t')
-	line++;
+    /* 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
 
     /* Scan for one of the assignment operators outside a variable expansion */
-    while ((ch = *line++) != 0) {
+    while ((ch = *p++) != 0) {
 	if (ch == '(' || ch == '{') {
 	    level++;
 	    continue;
@@ -1719,158 +1818,123 @@ Parse_IsVar(const char *line)
 	    level--;
 	    continue;
 	}
+
 	if (level != 0)
 	    continue;
-	while (ch == ' ' || ch == '\t') {
-	    ch = *line++;
-	    wasSpace = TRUE;
-	}
+
+	if (ch == ' ' || ch == '\t')
+	    if (firstSpace == NULL)
+	        firstSpace = p - 1;
+	while (ch == ' ' || ch == '\t')
+	    ch = *p++;
+
 #ifdef SUNSHCMD
-	if (ch == ':' && strncmp(line, "sh", 2) == 0) {
-	    line += 2;
+	if (ch == ':' && strncmp(p, "sh", 2) == 0) {
+	    p += 2;
 	    continue;
 	}
 #endif
-	if (ch == '=')
+	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;
 	    return TRUE;
-	if (*line == '=' && ISEQOPERATOR(ch))
+	}
+	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;
 	    return TRUE;
-	if (wasSpace)
+	}
+	if (firstSpace != NULL)
 	    return FALSE;
     }
 
     return FALSE;
 }
 
-/*-
- *---------------------------------------------------------------------
- * 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)
+/* Determine the assignment operator and adjust the end of the variable
+ * name accordingly. */
+static void
+ParseVarassignOp(VarAssign *var)
 {
-    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;
-
-    /*
-     * Skip to variable name
-     */
-    while (*line == ' ' || *line == '\t')
-	line++;
-
-    /*
-     * 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';
-	}
-    }
-    opc = cp-1;		/* operator is the previous character */
-    *cp++ = '\0';	/* nuke the = */
-
-    /*
-     * 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;
+    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;
 
-	default:
+    } else {
+	type = VAR_NORMAL;
 #ifdef SUNSHCMD
-	    while (opc > line && *opc != ':')
-		opc--;
+	while (op > name && ch_isspace(op[-1]))
+	    op--;
 
-	    if (strncmp(opc, ":sh", 3) == 0) {
-		type = VAR_SHELL;
-		*opc = '\0';
-		break;
-	    }
+	if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' && op[-1] == 'h') {
+	    type = VAR_SHELL;
+	    op -= 3;
+	}
 #endif
-	    type = VAR_NORMAL;
-	    break;
     }
 
-    while (ch_isspace(*cp))
-	cp++;
+    {
+	const char *nameEnd = var->nameEndDraft < op ? var->nameEndDraft : op;
+	var->varname = bmake_strsedup(var->nameStart, nameEnd);
+	var->op = type;
+    }
+}
 
+static void
+VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *ctxt)
+{
     if (DEBUG(LINT)) {
-	if (type != VAR_SUBST && strchr(cp, '$') != NULL) {
-	    /* sanity check now */
+	if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) {
+	    /* Check for syntax errors such as unclosed expressions or
+	     * unknown modifiers. */
 	    char *expandedValue;
 
-	    (void)Var_Subst(cp, ctxt, VARE_ASSIGN, &expandedValue);
+	    (void)Var_Subst(uvalue, ctxt, VARE_NONE, &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(line, cp, ctxt);
+	Var_Append(name, uvalue, 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.
@@ -1890,59 +1954,99 @@ Parse_DoVar(char *line, GNode *ctxt)
 	 * make sure that we set the variable the first time to nothing
 	 * so that it gets substituted!
 	 */
-	if (!Var_Exists(line, ctxt))
-	    Var_Set(line, "", ctxt);
+	if (!Var_Exists(name, ctxt))
+	    Var_Set(name, "", ctxt);
 
-	(void)Var_Subst(cp, ctxt, VARE_WANTRES|VARE_ASSIGN, &cp);
+	(void)Var_Subst(uvalue, ctxt, VARE_WANTRES|VARE_ASSIGN, &evalue);
 	/* TODO: handle errors */
 	oldVars = oldOldVars;
-	freeCp = TRUE;
+	avalue = evalue;
+	avalue_freeIt = evalue;
 
-	Var_Set(line, cp, ctxt);
+	Var_Set(name, avalue, ctxt);
     } else if (type == VAR_SHELL) {
-	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);
+        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);
 	    /* TODO: handle errors */
-	    freeCp = TRUE;
+	    cmd = cmd_freeIt = ecmd;
 	}
 
-	res = Cmd_Exec(cp, &error);
-	Var_Set(line, res, ctxt);
-	free(res);
+	cmdOut = Cmd_Exec(cmd, &errfmt);
+	Var_Set(name, cmdOut, ctxt);
+	avalue = avalue_freeIt = cmdOut;
+
+	if (errfmt)
+	    Parse_Error(PARSE_WARNING, errfmt, cmd);
 
-	if (error)
-	    Parse_Error(PARSE_WARNING, error, cp);
+	free(cmd_freeIt);
     } else {
-	/*
-	 * Normal assignment -- just do it.
-	 */
-	Var_Set(line, cp, ctxt);
+	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);
     }
-    if (strcmp(line, MAKEOVERRIDES) == 0)
+
+    *out_avalue = avalue;
+    *out_avalue_freeIt = avalue_freeIt;
+    return TRUE;
+}
+
+static void
+VarAssignSpecial(const char *name, const char *avalue)
+{
+    if (strcmp(name, MAKEOVERRIDES) == 0)
 	Main_ExportMAKEFLAGS(FALSE);	/* re-export MAKEFLAGS */
-    else if (strcmp(line, ".CURDIR") == 0) {
+    else if (strcmp(name, ".CURDIR") == 0) {
 	/*
 	 * Someone is being (too?) clever...
 	 * Let's pretend they know what they are doing and
 	 * re-initialize the 'cur' CachedDir.
 	 */
-	Dir_InitCur(cp);
+	Dir_InitCur(avalue);
 	Dir_SetPATH();
-    } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) {
+    } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) {
 	Job_SetPrefix();
-    } else if (strcmp(line, MAKE_EXPORTED) == 0) {
-	Var_Export(cp, FALSE);
+    } else if (strcmp(name, MAKE_EXPORTED) == 0) {
+	Var_Export(avalue, FALSE);
     }
-    if (freeCp)
-	free(cp);
+}
+
+/* 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);
 }
 
 
@@ -2408,11 +2512,7 @@ ParseTraditionalInclude(char *line)
 
     DEBUG2(PARSE, "%s: %s\n", __func__, file);
 
-    /*
-     * Skip over whitespace
-     */
-    while (ch_isspace(*file))
-	file++;
+    pp_skip_whitespace(&file);
 
     /*
      * Substitute for any variables in the file name before trying to
@@ -2454,11 +2554,7 @@ ParseGmakeExport(char *line)
 
     DEBUG2(PARSE, "%s: %s\n", __func__, variable);
 
-    /*
-     * Skip over whitespace
-     */
-    while (ch_isspace(*variable))
-	variable++;
+    pp_skip_whitespace(&variable);
 
     for (value = variable; *value && *value != '='; value++)
 	continue;
@@ -2543,7 +2639,7 @@ ParseEOF(void)
 #define PARSE_SKIP 2
 
 static char *
-ParseGetLine(int flags, int *length)
+ParseGetLine(int flags)
 {
     IFile *cf = curFile;
     char *ptr;
@@ -2635,7 +2731,6 @@ ParseGetLine(int flags, int *length)
 
 	if (flags & PARSE_RAW) {
 	    /* Leave '\' (etc) in line buffer (eg 'for' lines) */
-	    *length = line_end - line;
 	    return line;
 	}
 
@@ -2655,10 +2750,8 @@ ParseGetLine(int flags, int *length)
     }
 
     /* If we didn't see a '\\' then the in-situ data is fine */
-    if (escaped == NULL) {
-	*length = line_end - line;
+    if (escaped == NULL)
 	return line;
-    }
 
     /* Remove escapes from '\n' and '#' */
     tp = ptr = escaped;
@@ -2701,7 +2794,6 @@ ParseGetLine(int flags, int *length)
 	tp--;
 
     *tp = 0;
-    *length = tp - line;
     return line;
 }
 
@@ -2717,12 +2809,11 @@ static char *
 ParseReadLine(void)
 {
     char *line;			/* Result */
-    int lineLength;		/* Length of result */
     int lineno;			/* Saved line # */
     int rval;
 
     for (;;) {
-	line = ParseGetLine(0, &lineLength);
+	line = ParseGetLine(0);
 	if (line == NULL)
 	    return NULL;
 
@@ -2737,7 +2828,7 @@ ParseReadLine(void)
 	case COND_SKIP:
 	    /* Skip to next conditional that evaluates to COND_PARSE.  */
 	    do {
-		line = ParseGetLine(PARSE_SKIP, &lineLength);
+		line = ParseGetLine(PARSE_SKIP);
 	    } while (line && Cond_EvalLine(line) != COND_PARSE);
 	    if (line == NULL)
 		break;
@@ -2757,7 +2848,7 @@ ParseReadLine(void)
 	    lineno = curFile->lineno;
 	    /* Accumulate loop lines until matching .endfor */
 	    do {
-		line = ParseGetLine(PARSE_RAW, &lineLength);
+		line = ParseGetLine(PARSE_RAW);
 		if (line == NULL) {
 		    Parse_Error(PARSE_FATAL,
 			     "Unexpected end of file in for loop.");
@@ -2793,9 +2884,7 @@ FinishDependencyGroup(void)
 static void
 ParseLine_ShellCommand(char *cp)
 {
-    for (; ch_isspace(*cp); cp++)
-	continue;
-
+    pp_skip_whitespace(&cp);
     if (*cp == '\0')
 	return;			/* skip empty commands */
 
@@ -2853,16 +2942,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 '.'.
 		 */
-		for (cp = line + 1; ch_isspace(*cp); cp++)
-		    continue;
+		cp = line + 1;
+		pp_skip_whitespace(&cp);
 		if (IsInclude(cp, FALSE)) {
 		    ParseDoInclude(cp);
 		    continue;
 		}
 		if (strncmp(cp, "undef", 5) == 0) {
 		    const char *varname;
-		    for (cp += 5; ch_isspace(*cp); cp++)
-			continue;
+		    cp += 5;
+		    pp_skip_whitespace(&cp);
 		    varname = cp;
 		    for (; !ch_isspace(*cp) && *cp != '\0'; cp++)
 			continue;
@@ -2872,8 +2961,8 @@ Parse_File(const char *name, int fd)
 		    /* TODO: use Str_Words, like everywhere else */
 		    continue;
 		} else if (strncmp(cp, "export", 6) == 0) {
-		    for (cp += 6; ch_isspace(*cp); cp++)
-			continue;
+		    cp += 6;
+		    pp_skip_whitespace(&cp);
 		    Var_Export(cp, TRUE);
 		    continue;
 		} else if (strncmp(cp, "unexport", 8) == 0) {
@@ -2917,10 +3006,13 @@ Parse_File(const char *name, int fd)
 		continue;
 	    }
 #endif
-	    if (Parse_IsVar(line)) {
-		FinishDependencyGroup();
-		Parse_DoVar(line, VAR_GLOBAL);
-		continue;
+	    {
+	        VarAssign var;
+		if (Parse_IsVar(line, &var)) {
+		    FinishDependencyGroup();
+		    Parse_DoVar(&var, VAR_GLOBAL);
+		    continue;
+		}
 	    }
 
 #ifndef POSIX
@@ -2933,8 +3025,7 @@ Parse_File(const char *name, int fd)
 	     */
 	    cp = line;
 	    if (ch_isspace(line[0])) {
-		while (ch_isspace(*cp))
-		    cp++;
+		pp_skip_whitespace(&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.67 src/usr.bin/make/str.c:1.68
--- src/usr.bin/make/str.c:1.67	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/str.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: str.c,v 1.67 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: str.c,v 1.68 2020/10/05 19:27:47 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.67 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: str.c,v 1.68 2020/10/05 19:27:47 rillig Exp $");
 
 /* Return the concatenation of s1 and s2, freshly allocated. */
 char *
@@ -269,46 +269,6 @@ 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.175 src/usr.bin/make/suff.c:1.176
--- src/usr.bin/make/suff.c:1.175	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/suff.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: suff.c,v 1.175 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: suff.c,v 1.176 2020/10/05 19:27:47 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.175 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: suff.c,v 1.176 2020/10/05 19:27:47 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.110 src/usr.bin/make/targ.c:1.111
--- src/usr.bin/make/targ.c:1.110	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/targ.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: targ.c,v 1.110 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: targ.c,v 1.111 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Copyright (c) 1988, 1989, 1990, 1993
@@ -115,14 +115,13 @@
  *			something for suffixes, too, but...
  */
 
-#include	  <stdio.h>
-#include	  <time.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.110 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: targ.c,v 1.111 2020/10/05 19:27:47 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.18 src/usr.bin/make/trace.c:1.19
--- src/usr.bin/make/trace.c:1.18	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/trace.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: trace.c,v 1.18 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: trace.c,v 1.19 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*-
  * Copyright (c) 2000 The NetBSD Foundation, Inc.
@@ -44,14 +44,11 @@
 
 #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.18 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: trace.c,v 1.19 2020/10/05 19:27:47 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.62 src/usr.bin/make/util.c:1.63
--- src/usr.bin/make/util.c:1.62	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/util.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: util.c,v 1.62 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: util.c,v 1.63 2020/10/05 19:27:47 rillig Exp $	*/
 
 /*
  * Missing stuff from OS's
@@ -10,13 +10,12 @@
 #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.62 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: util.c,v 1.63 2020/10/05 19:27:47 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.566 src/usr.bin/make/var.c:1.567
--- src/usr.bin/make/var.c:1.566	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/var.c	Mon Oct  5 19:27:47 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: var.c,v 1.566 2020/10/05 19:24:29 rillig Exp $	*/
+/*	$NetBSD: var.c,v 1.567 2020/10/05 19:27:47 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.566 2020/10/05 19:24:29 rillig Exp $");
+MAKE_RCSID("$NetBSD: var.c,v 1.567 2020/10/05 19:27:47 rillig Exp $");
 
 #define VAR_DEBUG1(fmt, arg1) DEBUG1(VAR, fmt, arg1)
 #define VAR_DEBUG2(fmt, arg1, arg2) DEBUG2(VAR, fmt, arg1, arg2)
@@ -470,6 +470,8 @@ 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.
@@ -601,14 +603,14 @@ Var_Export(const char *str, Boolean isEx
 	return;
     }
 
-    flags = 0;
-    if (strncmp(str, "-env", 4) == 0) {
+    if (isExport && strncmp(str, "-env", 4) == 0) {
 	str += 4;
-    } else if (strncmp(str, "-literal", 8) == 0) {
+    	flags = 0;
+    } else if (isExport && 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);
@@ -675,8 +677,7 @@ Var_UnExport(const char *str)
 	if (cp && *cp)
 	    setenv(MAKE_LEVEL_ENV, cp, 1);
     } else {
-	for (; ch_isspace(*str); str++)
-	    continue;
+	cpp_skip_whitespace(&str);
 	if (str[0] != '\0')
 	    varnames = str;
     }
@@ -1290,8 +1291,11 @@ ModifyWord_Subst(const char *word, SepBu
 	return;
     }
 
+    if (args->lhs[0] == '\0')
+    	goto nosub;
+
     /* unanchored case, may match more than once */
-    while ((match = Str_FindSubstring(word, args->lhs)) != NULL) {
+    while ((match = strstr(word, args->lhs)) != NULL) {
 	SepBuf_AddBytesBetween(buf, word, match);
 	SepBuf_AddBytes(buf, args->rhs, args->rhsLen);
 	args->matched = TRUE;
@@ -2063,6 +2067,16 @@ 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)
@@ -2925,6 +2939,17 @@ 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
@@ -3063,10 +3088,7 @@ ApplyModifier(const char **pp, ApplyModi
     case 'U':
 	return ApplyModifier_Defined(pp, st);
     case 'L':
-	ApplyModifiersState_Define(st);
-	st->newVal = bmake_strdup(st->v->name);
-	(*pp)++;
-	return AMR_OK;
+        return ApplyModifier_Literal(pp, st);
     case 'P':
 	return ApplyModifier_Path(pp, st);
     case '!':
@@ -3108,12 +3130,7 @@ ApplyModifier(const char **pp, ApplyModi
     case 'O':
 	return ApplyModifier_Order(pp, st);
     case 'u':
-	if ((*pp)[1] == st->endc || (*pp)[1] == ':') {
-	    st->newVal = VarUniq(st->val);
-	    (*pp)++;
-	    return AMR_OK;
-	} else
-	    return AMR_UNKNOWN;
+        return ApplyModifier_Unique(pp, st);
 #ifdef SUNSHCMD
     case 's':
 	return ApplyModifier_SunShell(pp, st);
@@ -3167,13 +3184,21 @@ ApplyModifiers(
 	    /* TODO: handle errors */
 
 	    /*
-	     * If we have not parsed up to st.endc or ':',
-	     * we are not interested.
+	     * 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 (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;
 	    }
 
@@ -3194,6 +3219,7 @@ ApplyModifiers(
 		}
 	    }
 	    free(freeIt);
+
 	    if (*p == ':')
 		p++;
 	    else if (*p == '\0' && endc != '\0') {
@@ -3250,6 +3276,10 @@ 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:
@@ -3738,11 +3768,7 @@ 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.
-	     * In such a case, we skip over the escape character and store the
-	     * dollar sign into the buffer directly.
-	     */
+	    /* A dollar sign may be escaped with another dollar sign. */
 	    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.161 src/usr.bin/make/unit-tests/Makefile:1.162
--- src/usr.bin/make/unit-tests/Makefile:1.161	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/Makefile	Mon Oct  5 19:27:48 2020
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.161 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: Makefile,v 1.162 2020/10/05 19:27:48 rillig Exp $
 #
 # Unit tests for make(1)
 #
@@ -168,6 +168,7 @@ TESTS+=		export-env
 TESTS+=		export-variants
 TESTS+=		forloop
 TESTS+=		forsubst
+TESTS+=		hanoi-include
 TESTS+=		impsrc
 TESTS+=		include-main
 TESTS+=		job-output-long-lines
@@ -229,6 +230,7 @@ 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
@@ -244,6 +246,10 @@ 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
@@ -264,6 +270,7 @@ 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
@@ -403,6 +410,10 @@ 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.3 src/usr.bin/make/unit-tests/directive-export-literal.exp:1.4
--- src/usr.bin/make/unit-tests/directive-export-literal.exp:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/directive-export-literal.exp	Mon Oct  5 19:27:48 2020
@@ -1 +1,2 @@
+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.3 src/usr.bin/make/unit-tests/directive-ifndef.exp:1.4
--- src/usr.bin/make/unit-tests/directive-ifndef.exp:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/directive-ifndef.exp	Mon Oct  5 19:27:48 2020
@@ -1 +1,2 @@
+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.3 src/usr.bin/make/unit-tests/directive-ifnmake.exp:1.4
--- src/usr.bin/make/unit-tests/directive-ifnmake.exp:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/directive-ifnmake.exp	Mon Oct  5 19:27:48 2020
@@ -1 +1,3 @@
+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.3 src/usr.bin/make/unit-tests/opt-debug-file.mk:1.4
--- src/usr.bin/make/unit-tests/opt-debug-file.mk:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-file.mk	Mon Oct  5 19:27:48 2020
@@ -1,9 +1,37 @@
-# $NetBSD: opt-debug-file.mk,v 1.3 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: opt-debug-file.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $
 #
 # Tests for the -dF command line option, which redirects the debug log
 # to a file instead of writing it to stderr.
 
-# TODO: Implementation
+# 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
 
 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.3 src/usr.bin/make/unit-tests/opt-debug-for.exp:1.4
--- src/usr.bin/make/unit-tests/opt-debug-for.exp:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-for.exp	Mon Oct  5 19:27:48 2020
@@ -1 +1,22 @@
+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.3 src/usr.bin/make/unit-tests/opt-debug-for.mk:1.4
--- src/usr.bin/make/unit-tests/opt-debug-for.mk:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-for.mk	Mon Oct  5 19:27:48 2020
@@ -1,9 +1,26 @@
-# $NetBSD: opt-debug-for.mk,v 1.3 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: opt-debug-for.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $
 #
 # Tests for the -df command line option, which adds debug logging for
 # parsing and evaluating .for loops.
 
-# TODO: Implementation
+.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
 
 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.3 src/usr.bin/make/unit-tests/opt-debug-jobs.exp:1.4
--- src/usr.bin/make/unit-tests/opt-debug-jobs.exp:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-jobs.exp	Mon Oct  5 19:27:48 2020
@@ -1 +1,25 @@
+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.3 src/usr.bin/make/unit-tests/opt-debug-jobs.mk:1.4
--- src/usr.bin/make/unit-tests/opt-debug-jobs.mk:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-jobs.mk	Mon Oct  5 19:27:48 2020
@@ -1,9 +1,26 @@
-# $NetBSD: opt-debug-jobs.mk,v 1.3 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: opt-debug-jobs.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $
 #
-# Tests for the -da command line option, which adds debug logging about
+# Tests for the -dj command line option, which adds debug logging about
 # running jobs in multiple shells.
 
-# TODO: Implementation
+.MAKEFLAGS: -dj
+
+# Run in parallel mode since the debug logging is more interesting there
+# than in compat mode.
+.MAKEFLAGS: -j1
 
 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.3 src/usr.bin/make/unit-tests/opt-debug-loud.exp:1.4
--- src/usr.bin/make/unit-tests/opt-debug-loud.exp:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-loud.exp	Mon Oct  5 19:27:48 2020
@@ -1 +1,3 @@
+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.3 src/usr.bin/make/unit-tests/opt-debug-loud.mk:1.4
--- src/usr.bin/make/unit-tests/opt-debug-loud.mk:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-loud.mk	Mon Oct  5 19:27:48 2020
@@ -1,13 +1,22 @@
-# $NetBSD: opt-debug-loud.mk,v 1.3 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: opt-debug-loud.mk,v 1.4 2020/10/05 19:27:48 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.
 
-# TODO: Implementation
+.MAKEFLAGS: -dl -s
+.SILENT:
 
-# TODO: What about ${:!cmd!}?
+# 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
 
-all:
-	@:;
+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
Index: src/usr.bin/make/unit-tests/opt-debug.exp
diff -u src/usr.bin/make/unit-tests/opt-debug.exp:1.3 src/usr.bin/make/unit-tests/opt-debug.exp:1.4
--- src/usr.bin/make/unit-tests/opt-debug.exp:1.3	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug.exp	Mon Oct  5 19:27:48 2020
@@ -1 +1,4 @@
+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.4 src/usr.bin/make/unit-tests/directive-export-literal.mk:1.5
--- src/usr.bin/make/unit-tests/directive-export-literal.mk:1.4	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/directive-export-literal.mk	Mon Oct  5 19:27:48 2020
@@ -1,8 +1,11 @@
-# $NetBSD: directive-export-literal.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: directive-export-literal.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $
 #
-# Tests for the .export-literal directive.
+# Tests for the .export-literal directive, which exports a variable value
+# without expanding it.
 
-# TODO: Implementation
+UT_VAR=		value with ${UNEXPANDED} expression
+
+.export-literal UT_VAR
 
 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.4 src/usr.bin/make/unit-tests/directive-ifndef.mk:1.5
--- src/usr.bin/make/unit-tests/directive-ifndef.mk:1.4	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/directive-ifndef.mk	Mon Oct  5 19:27:48 2020
@@ -1,8 +1,24 @@
-# $NetBSD: directive-ifndef.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: directive-ifndef.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $
 #
-# Tests for the .ifndef directive.
+# 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.
 
-# TODO: Implementation
+.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
 
 all:
 	@:;
Index: src/usr.bin/make/unit-tests/directive-ifnmake.mk
diff -u src/usr.bin/make/unit-tests/directive-ifnmake.mk:1.4 src/usr.bin/make/unit-tests/directive-ifnmake.mk:1.5
--- src/usr.bin/make/unit-tests/directive-ifnmake.mk:1.4	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/directive-ifnmake.mk	Mon Oct  5 19:27:48 2020
@@ -1,8 +1,22 @@
-# $NetBSD: directive-ifnmake.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: directive-ifnmake.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $
 #
-# Tests for the .ifnmake directive.
-
-# TODO: Implementation
+# Tests for the .ifnmake directive, which evaluates to true if its argument
+# is _not_ listed in the command-line targets to be created.
 
 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.4 src/usr.bin/make/unit-tests/make-exported.exp:1.5
--- src/usr.bin/make/unit-tests/make-exported.exp:1.4	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/make-exported.exp	Mon Oct  5 19:27:48 2020
@@ -1,3 +1,2 @@
--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.4 src/usr.bin/make/unit-tests/opt-debug.mk:1.5
--- src/usr.bin/make/unit-tests/opt-debug.mk:1.4	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug.mk	Mon Oct  5 19:27:48 2020
@@ -1,8 +1,14 @@
-# $NetBSD: opt-debug.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: opt-debug.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $
 #
-# Tests for the -d command line option.
+# Tests for the -d command line option, which controls debug logging.
 
-# TODO: Implementation
+# 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
 
 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.4 src/usr.bin/make/unit-tests/var-op-append.mk:1.5
--- src/usr.bin/make/unit-tests/var-op-append.mk:1.4	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/var-op-append.mk	Mon Oct  5 19:27:48 2020
@@ -1,9 +1,36 @@
-# $NetBSD: var-op-append.mk,v 1.4 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: var-op-append.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $
 #
 # Tests for the += variable assignment operator, which appends to a variable,
 # creating it if necessary.
 
-# TODO: Implementation
+# 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
 
 all:
 	@:;

Index: src/usr.bin/make/unit-tests/make-exported.mk
diff -u src/usr.bin/make/unit-tests/make-exported.mk:1.5 src/usr.bin/make/unit-tests/make-exported.mk:1.6
--- src/usr.bin/make/unit-tests/make-exported.mk:1.5	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/make-exported.mk	Mon Oct  5 19:27:48 2020
@@ -1,16 +1,25 @@
-# $NetBSD: make-exported.mk,v 1.5 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: make-exported.mk,v 1.6 2020/10/05 19:27:48 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
--literal=	make-exported-value
+-env=		make-exported-value-env
+-literal=	make-exported-value-literal
 UT_VAR=		${UNEXPANDED}
 
-# The following behavior is probably not intended.
-.MAKE.EXPORTED=		-env		# like .export-env
-.MAKE.EXPORTED=		-literal UT_VAR	# like .export-literal PATH
+# 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
 
 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.5 src/usr.bin/make/unit-tests/varname-dot-curdir.mk:1.6
--- src/usr.bin/make/unit-tests/varname-dot-curdir.mk:1.5	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/varname-dot-curdir.mk	Mon Oct  5 19:27:48 2020
@@ -1,8 +1,27 @@
-# $NetBSD: varname-dot-curdir.mk,v 1.5 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: varname-dot-curdir.mk,v 1.6 2020/10/05 19:27:48 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.10 src/usr.bin/make/unit-tests/opt-debug-lint.exp:1.11
--- src/usr.bin/make/unit-tests/opt-debug-lint.exp:1.10	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-lint.exp	Mon Oct  5 19:27:48 2020
@@ -1,5 +1,9 @@
 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.9 src/usr.bin/make/unit-tests/opt-debug-lint.mk:1.10
--- src/usr.bin/make/unit-tests/opt-debug-lint.mk:1.9	Mon Oct  5 19:24:29 2020
+++ src/usr.bin/make/unit-tests/opt-debug-lint.mk	Mon Oct  5 19:27:48 2020
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-lint.mk,v 1.9 2020/10/05 19:24:29 rillig Exp $
+# $NetBSD: opt-debug-lint.mk,v 1.10 2020/10/05 19:27:48 rillig Exp $
 #
 # Tests for the -dL command line option, which runs additional checks
 # to catch common mistakes, such as unclosed variable expressions.
@@ -56,5 +56,17 @@ ${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