root/usr.bin/make/parse.c
/*      $OpenBSD: parse.c,v 1.137 2024/06/18 02:11:03 millert Exp $     */
/*      $NetBSD: parse.c,v 1.29 1997/03/10 21:20:04 christos Exp $      */

/*
 * Copyright (c) 1999 Marc Espie.
 *
 * Extensive code changes for the OpenBSD project.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OPENBSD
 * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright (c) 1988, 1989, 1990, 1993
 *      The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1989 by Berkeley Softworks
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Adam de Boor.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <assert.h>
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ohash.h>
#include "defines.h"
#include "dir.h"
#include "direxpand.h"
#include "job.h"
#include "buf.h"
#include "for.h"
#include "lowparse.h"
#include "arch.h"
#include "cond.h"
#include "suff.h"
#include "parse.h"
#include "var.h"
#include "targ.h"
#include "error.h"
#include "str.h"
#include "main.h"
#include "gnode.h"
#include "memory.h"
#include "extern.h"
#include "lst.h"
#include "parsevar.h"
#include "stats.h"
#include "garray.h"
#include "node_int.h"
#include "nodehashconsts.h"


/* gsources and gtargets should be local to some functions, but they're
 * set as persistent arrays for performance reasons.
 */
static struct growableArray gsources, gtargets;
static struct ohash htargets;
static bool htargets_setup = false;
#define SOURCES_SIZE    128
#define TARGETS_SIZE    32

static LIST     theUserIncPath;/* list of directories for "..." includes */
static LIST     theSysIncPath;  /* list of directories for <...> includes */
Lst systemIncludePath = &theSysIncPath;
Lst userIncludePath = &theUserIncPath;

static GNode        *mainNode;  /* The main target to create. This is the
                                 * first target on the first dependency
                                 * line in the first makefile */
/*-
 * specType contains the special TYPE of the current target. It is
 * SPECIAL_NONE if the target is unspecial. If it *is* special, however,
 * the children are linked as children of the parent but not vice versa.
 * This variable is set in ParseDoDependency
 */

static unsigned int specType;
static int waiting;

/*
 * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER
 * seen, then set to each successive source on the line.
 */
static GNode    *predecessor;

static void ParseLinkSrc(GNode *, GNode *);
static int ParseDoOp(GNode **, unsigned int);
static void ParseDoSpecial(GNode *, unsigned int);
static int ParseAddDep(GNode *, GNode *);
static void ParseDoSrc(struct growableArray *, struct growableArray *, int,
    const char *, const char *);
static int ParseFindMain(void *, void *);
static void ParseClearPath(void *);

static void add_target_node(const char *, const char *);
static void add_target_nodes(const char *, const char *);
static void apply_op(struct growableArray *, unsigned int, GNode *);
static void ParseDoDependency(const char *);
static void ParseAddCmd(void *, void *);
static void ParseHasCommands(void *);
static bool handle_poison(const char *);
static bool handle_for_loop(Buffer, const char *);
static bool handle_undef(const char *);
#define ParseReadLoopLine(linebuf) Parse_ReadUnparsedLine(linebuf, "for loop")
static bool handle_bsd_command(Buffer, Buffer, const char *);
static bool register_target(GNode *, struct ohash *);
static char *strip_comments(Buffer, const char *);
static char *resolve_include_filename(const char *, const char *, bool);
static void handle_include_file(const char *, const char *, bool, bool);
static bool lookup_bsd_include(const char *);
static void lookup_sysv_style_include(const char *, const char *, bool);
static void lookup_sysv_include(const char *, const char *);
static void lookup_conditional_include(const char *, const char *);
static bool parse_as_special_line(Buffer, Buffer, const char *);
static unsigned int parse_operator(const char **);

static const char *parse_do_targets(Lst, unsigned int *, const char *);
static void parse_target_line(struct growableArray *, const char *,
    const char *, bool *);

static void finish_commands(struct growableArray *);
static void parse_commands(struct growableArray *, const char *);
static void create_special_nodes(void);
static bool found_delimiter(const char *);
static unsigned int handle_special_targets(Lst);
static void dump_targets(void);
static void dedup_targets(struct growableArray *);
static void build_target_group(struct growableArray *, struct ohash *t);
static void reset_target_hash(void);


#define P(k) k, sizeof(k), K_##k

static struct {
        const char *keyword;
        size_t sz;
        uint32_t hv;
        unsigned int special;
        unsigned int special_op;
} specials[] = {
    { P(NODE_EXEC),             SPECIAL_DEPRECATED,     0 },
    { P(NODE_IGNORE),           SPECIAL_IGNORE,         OP_IGNORE },
    { P(NODE_INCLUDES),         SPECIAL_DEPRECATED,     0 },
    { P(NODE_INVISIBLE),        SPECIAL_DEPRECATED,     0 },
    { P(NODE_JOIN),             SPECIAL_DEPRECATED,     0 },
    { P(NODE_LIBS),             SPECIAL_DEPRECATED,     0 },
    { P(NODE_MADE),             SPECIAL_DEPRECATED,     0 },
    { P(NODE_MAIN),             SPECIAL_MAIN,           0 },
    { P(NODE_MAKE),             SPECIAL_MAKE,           OP_MAKE },
    { P(NODE_MAKEFLAGS),        SPECIAL_MFLAGS,         0 },
    { P(NODE_MFLAGS),           SPECIAL_MFLAGS,         0 },
    { P(NODE_NOTMAIN),          SPECIAL_NOTMAIN,        OP_NOTMAIN },
    { P(NODE_NOTPARALLEL),      SPECIAL_NOTPARALLEL,    0 },
    { P(NODE_NO_PARALLEL),      SPECIAL_NOTPARALLEL,    0 },
    { P(NODE_NULL),             SPECIAL_DEPRECATED,     0 },
    { P(NODE_OPTIONAL),         SPECIAL_OPTIONAL,       OP_OPTIONAL },
    { P(NODE_ORDER),            SPECIAL_ORDER,          0 },
    { P(NODE_PARALLEL),         SPECIAL_PARALLEL,       0 },
    { P(NODE_PATH),             SPECIAL_PATH,           0 },
    { P(NODE_PHONY),            SPECIAL_PHONY,          OP_PHONY },
    { P(NODE_PRECIOUS),         SPECIAL_PRECIOUS,       OP_PRECIOUS },
    { P(NODE_RECURSIVE),        SPECIAL_MAKE,           OP_MAKE },
    { P(NODE_SILENT),           SPECIAL_SILENT,         OP_SILENT },
    { P(NODE_SINGLESHELL),      SPECIAL_NOTHING,        0 },
    { P(NODE_SUFFIXES),         SPECIAL_SUFFIXES,       0 },
    { P(NODE_USE),              SPECIAL_USE,            OP_USE },
    { P(NODE_WAIT),             SPECIAL_WAIT,           0 },
    { P(NODE_CHEAP),            SPECIAL_CHEAP,          OP_CHEAP },
    { P(NODE_EXPENSIVE),        SPECIAL_EXPENSIVE,      OP_EXPENSIVE },
    { P(NODE_POSIX),            SPECIAL_NOTHING,        0 },
    { P(NODE_SCCS_GET),         SPECIAL_NOTHING,        0 },
};

#undef P

static void
create_special_nodes(void)
{
        unsigned int i;

        for (i = 0; i < sizeof(specials)/sizeof(specials[0]); i++) {
                (void)Targ_mk_special_node(specials[i].keyword,
                    specials[i].sz, specials[i].hv,
                    OP_ZERO, specials[i].special, specials[i].special_op);
        }
}

/*-
 *---------------------------------------------------------------------
 * ParseLinkSrc  --
 *      Link the parent node to its new child. Used by
 *      ParseDoDependency. If the specType isn't 'Not', the parent
 *      isn't linked as a parent of the child.
 *
 * Side Effects:
 *      New elements are added to the parents list of cgn and the
 *      children list of cgn. the children_left field of pgn is updated
 *      to reflect the additional child.
 *---------------------------------------------------------------------
 */
static void
ParseLinkSrc(GNode *pgn, GNode *cgn)
{
        if (Lst_AddNew(&pgn->children, cgn)) {
                if (specType == SPECIAL_NONE)
                        Lst_AtEnd(&cgn->parents, pgn);
                pgn->children_left++;
        }
}

static char *
operator_string(int op)
{
        /* XXX we don't bother freeing this, it's used for a fatal error
         * anyways
         */
        char *result = emalloc(5);
        char *t = result;
        if (op & OP_DEPENDS) {
                *t++ = ':';
        }
        if (op & OP_FORCE) {
                *t++ = '!';
        }
        if (op & OP_DOUBLEDEP) {
                *t++ = ':';
                *t++ = ':';
        }
        *t = 0;
        return result;
}

/*-
 *---------------------------------------------------------------------
 * ParseDoOp  --
 *      Apply the parsed operator to the given target node. Used in a
 *      Array_Find call by ParseDoDependency once all targets have
 *      been found and their operator parsed. If the previous and new
 *      operators are incompatible, a major error is taken, and the find
 *      stops early
 *
 * Side Effects:
 *      The node gets the right dependency operator.
 *      Cohorts may be created for double dep.
 *---------------------------------------------------------------------
 */
static int
ParseDoOp(GNode **gnp, unsigned int op)
{
        GNode *gn = *gnp;

        assert(op == (op & OP_OPMASK));

        /* if the node didn't already appear on the left hand side (no known
         * dependency operator), we don't need to do much. */
        if (!OP_NOP(gn->type)) {
                /*
                 * If the dependency mask of the operator and the node don't
                 * match and the node has actually had an operator applied to
                 * it before, and the operator actually has some dependency
                 * information in it, complain.  */
                if (op != (gn->type & OP_OPMASK)) {
                        Parse_Error(PARSE_FATAL, 
                            "Inconsistent dependency operator for target %s\n"
                            "\t(was %s%s, now %s%s)",
                            gn->name, gn->name, operator_string(gn->type), 
                            gn->name, operator_string(op));
                        return 0;
                }

                if (op == OP_DOUBLEDEP) {
                        /* If the node was the object of a :: operator, we need
                         * to create a new instance of it for the children and
                         * commands on this dependency line. The new instance
                         * is placed on the 'cohorts' list of the initial one
                         * (note the initial one is not on its own cohorts
                         * list) and the new instance is linked to all parents
                         * of the initial instance.  */
                        GNode *cohort;
                        LstNode ln;

                        cohort = Targ_NewGN(gn->name);
                        /* Duplicate links to parents so graph traversal is
                         * simple.  Perhaps some type bits should be
                         * duplicated?
                         *
                         * Make the cohort invisible as well to avoid
                         * duplicating it into other variables. True, parents
                         * of this target won't tend to do anything with their
                         * local variables, but better safe than sorry.  */
                        for (ln = Lst_First(&gn->parents); ln != NULL; 
                            ln = Lst_Adv(ln))
                                ParseLinkSrc(Lst_Datum(ln), cohort);
                        cohort->type = OP_DOUBLEDEP|OP_INVISIBLE;
                        Lst_AtEnd(&gn->cohorts, cohort);

                        /* Replace the node in the targets list with the new
                         * copy */
                        *gnp = cohort;
                        gn = cohort;
                }
        }
        /* Preserve possible special flags already applied to the operator */
        gn->type |= op;
        return 1;
}

static void
ParseDoSpecial(GNode *gn, unsigned int special_op)
{
        gn->type |= special_op;
}

/*-
 *---------------------------------------------------------------------
 * ParseAddDep  --
 *      Check if the pair of GNodes given needs to be synchronized.
 *      This has to be when two nodes are on different sides of a
 *      .WAIT directive.
 *
 * Results:
 *      Returns 0 if the two targets need to be ordered, 1 otherwise.
 *      If it returns 0, the search can stop.
 *
 * Side Effects:
 *      A dependency can be added between the two nodes.
 *
 *---------------------------------------------------------------------
 */
static int
ParseAddDep(GNode *p, GNode *s)
{
        if (p->order < s->order) {
                /* XXX: This can cause cycles but finding them is hard
                 * and debugging output will show the problem.  */
                Lst_AtEnd(&s->predecessors, p);
                Lst_AtEnd(&p->successors, s);
                return 1;
        } else
                return 0;
}

static void
apply_op(struct growableArray *targets, unsigned int op, GNode *gn)
{
        if (op)
                gn->type |= op;
        else
                Array_ForEach(targets, ParseLinkSrc, gn);
}

/*-
 *---------------------------------------------------------------------
 * 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'
 *
 * 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(
    struct growableArray *targets,
    struct growableArray *sources,
    int         tOp,    /* operator (if any) from special targets */
    const char  *src,   /* name of the source to handle */
    const char *esrc)
{
        GNode *gn = Targ_FindNodei(src, esrc, TARG_CREATE);
        if (gn->special == SPECIAL_DEPRECATED) {
                Parse_Error(PARSE_FATAL, "Deprecated keyword found %s\n",
                    gn->name);
                return;
        }
        if (gn->special_op) {
                Array_ForEach(targets, ParseDoSpecial, gn->special_op);
                return;
        }
        if (gn->special == SPECIAL_WAIT) {
                waiting++;
                return;
        }

        switch (specType) {
        case SPECIAL_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.  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_AtEnd(create, gn->name);
                /*
                 * Add the name to the .TARGETS variable as well, so the user
                 * can employ that, if desired.
                 */
                Var_Append(".TARGETS", gn->name);
                return;

        case SPECIAL_ORDER:
                /*
                 * Create proper predecessor/successor links between the
                 * previous source and the current one.
                 */
                if (predecessor != NULL) {
                        Lst_AtEnd(&predecessor->successors, gn);
                        Lst_AtEnd(&gn->predecessors, predecessor);
                }
                predecessor = gn;
                break;

        default:
                /*
                 * 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.
                 */
                apply_op(targets, tOp, gn);
                if ((gn->type & OP_OPMASK) == OP_DOUBLEDEP) {
                        LstNode ln;

                        for (ln=Lst_First(&gn->cohorts); ln != NULL;
                            ln = Lst_Adv(ln)){
                                apply_op(targets, tOp, Lst_Datum(ln));
                        }
                }
                break;
        }

        gn->order = waiting;
        Array_AtEnd(sources, gn);
        if (waiting)
                Array_Find(sources, ParseAddDep, gn);
}

/*-
 *-----------------------------------------------------------------------
 * ParseFindMain --
 *      Find a real target in the list and set it to be the main one.
 *      Called by ParseDoDependency when a main target hasn't been found
 *      yet.
 *
 * Results:
 *      1 if main not found yet, 0 if it is.
 *
 * Side Effects:
 *      mainNode is changed and.
 *-----------------------------------------------------------------------
 */
static int
ParseFindMain(void *gnp, void *dummy UNUSED)
{
        GNode *gn = gnp;

        if ((gn->type & OP_NOTARGET) == 0 && gn->special == SPECIAL_NONE) {
                mainNode = gn;
                return 0;
        } else {
                return 1;
        }
}

/*-
 *-----------------------------------------------------------------------
 * ParseClearPath --
 *      Reinit path to an empty path
 *-----------------------------------------------------------------------
 */
static void
ParseClearPath(void *p)
{
        Lst path = p;

        Lst_Destroy(path, Dir_Destroy);
        Lst_Init(path);
}

static void
add_target_node(const char *line, const char *end)
{
        GNode *gn;

        gn = Suff_ParseAsTransform(line, end);

        if (gn == NULL) {
                gn = Targ_FindNodei(line, end, TARG_CREATE);
                gn->type &= ~OP_DUMMY;
        }

        Array_AtEnd(&gtargets, gn);
}

static void
add_target_nodes(const char *line, const char *end)
{

        if (Dir_HasWildcardsi(line, end)) {
                /*
                 * 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...
                 */
                char *targName;
                LIST emptyPath;
                LIST curTargs;

                Lst_Init(&emptyPath);
                Lst_Init(&curTargs);
                Dir_Expandi(line, end, &emptyPath, &curTargs);
                Lst_Destroy(&emptyPath, Dir_Destroy);
                while ((targName = Lst_DeQueue(&curTargs)) != NULL) {
                        add_target_node(targName, targName + strlen(targName));
                }
                Lst_Destroy(&curTargs, NOFREE);
        } else {
                add_target_node(line, end);
        }
}

/* special target line check: a proper delimiter is a ':' or '!', but
 * we don't want to end a target on such a character if there is a better
 * match later on.
 * By "better" I mean one that is followed by whitespace. This allows the
 * user to have targets like:
 *    fie::fi:fo: fum
 * where "fie::fi:fo" is the target.  In real life this is used for perl5
 * library man pages where "::" separates an object from its class.  Ie:
 * "File::Spec::Unix".
 * This behaviour is also consistent with other versions of make.
 */
static bool
found_delimiter(const char *s)
{
        if (*s == '!' || *s == ':') {
                const char *p = s + 1;

                if (*s == ':' && *p == ':')
                        p++;

                /* Found the best match already. */
                if (ISSPACE(*p) || *p == '\0')
                        return true;

                do {
                        p += strcspn(p, "!:");
                        if (*p == '\0')
                                break;
                        /* always prefer : to ! if not space separated */
                        if (*p == ':' && *s == '!')
                                return false;
                        p++;
                } while (*p != '\0' && !ISSPACE(*p));

                /* No better match later on... */
                if (*p == '\0')
                        return true;
        }
        return false;
}

static const char *
parse_do_targets(Lst paths, unsigned int *op, const char *line)
{
        const char *cp;

        do {
                for (cp = line; *cp && !ISSPACE(*cp) && *cp != '(';) {
                        if (*cp == '$')
                                /* Must be a dynamic source (would have been
                                 * expanded otherwise), so call the Var module
                                 * to parse the puppy so we can safely advance
                                 * beyond it...There should be no errors in
                                 * this, as they would have been discovered in
                                 * the initial Var_Subst and we wouldn't be
                                 * here.  */
                                Var_ParseSkip(&cp, NULL);
                        else {
                                if (found_delimiter(cp))
                                        break;
                                cp++;
                        }
                }

                if (*cp == '(') {
                        LIST temp;
                        Lst_Init(&temp);
                        /* Archives must be handled specially to make sure the
                         * OP_ARCHV flag is set in their 'type' field, for one
                         * thing, and because things like "archive(file1.o
                         * file2.o file3.o)" are permissible.
                         * Arch_ParseArchive will set 'line' to be the first
                         * non-blank after the archive-spec. It creates/finds
                         * nodes for the members and places them on the given
                         * list, returning true if all went well and false if
                         * there was an error in the specification. On error,
                         * line should remain untouched.  */
                        if (!Arch_ParseArchive(&line, &temp, NULL)) {
                                Parse_Error(PARSE_FATAL,
                                     "Error in archive specification: \"%s\"",
                                     line);
                                return NULL;
                        } else {
                                AppendList2Array(&temp, &gtargets);
                                Lst_Destroy(&temp, NOFREE);
                                cp = line;
                                continue;
                        }
                }
                if (*cp == '\0') {
                        /* Ending a dependency line without an operator is a
                         * Bozo no-no */
                        /* Deeper check for cvs conflicts */
                        if (gtargets.n > 0 &&
                            (strcmp(gtargets.a[0]->name, "<<<<<<<") == 0 ||
                            strcmp(gtargets.a[0]->name, ">>>>>>>") == 0)) {
                                Parse_Error(PARSE_FATAL,
    "Need an operator (likely from a cvs update conflict)");
                        } else {
                                Parse_Error(PARSE_FATAL, 
                                    "Need an operator in '%s'", line);
                        }
                        return NULL;
                }
                /*
                 * Have word in line. Get or create its nodes and stick it at
                 * the end of the targets list
                 */
                if (*line != '\0')
                        add_target_nodes(line, cp);

                while (ISSPACE(*cp))
                        cp++;
                line = cp;
        } while (*line != '!' && *line != ':' && *line);
        *op = handle_special_targets(paths);
        return cp;
}

static void
dump_targets(void)
{
        size_t i;
        for (i = 0; i < gtargets.n; i++)
                fprintf(stderr, "%s", gtargets.a[i]->name);
        fprintf(stderr, "\n");
}

static unsigned int
handle_special_targets(Lst paths)
{
        size_t i;
        int seen_path = 0;
        int seen_special = 0;
        int seen_normal = 0;
        int type;

        for (i = 0; i < gtargets.n; i++) {
                type = gtargets.a[i]->special;
                if (type == SPECIAL_DEPRECATED) {
                        Parse_Error(PARSE_FATAL, 
                            "Deprecated keyword found %s\n",
                            gtargets.a[i]->name);
                        specType = SPECIAL_ERROR;
                        return 0;
                }
                if (type == SPECIAL_PATH) {
                        seen_path++;
                        Lst_AtEnd(paths, find_suffix_path(gtargets.a[i]));
                } else if (type != 0)
                        seen_special++;
                else
                        seen_normal++;
        }
        if ((seen_path != 0) + (seen_special != 0) + (seen_normal != 0) > 1) {
                Parse_Error(PARSE_FATAL, "Wrong mix of special targets");
                dump_targets();
                specType = SPECIAL_ERROR;
                return 0;
        }
        if (seen_normal != 0) {
                specType = SPECIAL_NONE;
                return 0;
        } else if (seen_path != 0) {
                specType = SPECIAL_PATH;
                return 0;
        } else if (seen_special == 0) {
                specType = SPECIAL_NONE;
                return 0;
        } else if (seen_special != 1) {
                Parse_Error(PARSE_FATAL,
                    "Mixing special targets is not allowed");
                dump_targets();
                return 0;
        } else if (seen_special == 1) {
                specType = gtargets.a[0]->special;
                switch (specType) {
                case SPECIAL_MAIN:
                        if (!Lst_IsEmpty(create)) {
                                specType = SPECIAL_NONE;
                        }
                        break;
                case SPECIAL_NOTPARALLEL:
                        set_notparallel();
                        break;
                case SPECIAL_ORDER:
                        predecessor = NULL;
                        break;
                default:
                        break;
                }
                return gtargets.a[0]->special_op;
        } else {
                /* we're allowed to have 0 target */
                specType = SPECIAL_NONE;
                return 0;
        }
}

static unsigned int
parse_operator(const char **pos)
{
        const char *cp = *pos;
        unsigned int op = OP_ERROR;

        if (*cp == '!') {
                op = OP_FORCE;
        } else if (*cp == ':') {
                if (cp[1] == ':') {
                        op = OP_DOUBLEDEP;
                        cp++;
                } else {
                        op = OP_DEPENDS;
                }
        } else {
                Parse_Error(PARSE_FATAL, "Missing dependency operator");
                return OP_ERROR;
        }

        cp++;                   /* Advance beyond operator */

        /* Get to the first source */
        while (ISSPACE(*cp))
                cp++;
        *pos = cp;
        return op;
}

/*-
 *---------------------------------------------------------------------
 * ParseDoDependency  --
 *      Parse the dependency line in line.
 *
 * Side Effects:
 *      The nodes of the sources are linked as children to the nodes of the
 *      targets. Some nodes may be created.
 *
 *      We parse a dependency line by first extracting words from the line and
 * finding nodes in the list of all targets with that name. This is done
 * until a character is encountered which is an operator character. Currently
 * these are only ! and :. At this point the operator is parsed and the
 * pointer into the line advanced until the first source is encountered.
 *      The parsed operator is applied to each node in the 'targets' list,
 * which is where the nodes found for the targets are kept, by means of
 * the ParseDoOp function.
 *      The sources are read in much the same way as the targets were except
 * that now 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 are handled specially. These are the ones detailed
 * by the specType variable.
 *      The storing of transformation rules 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(const char *line)     /* the line to parse */
{
        const char *cp;         /* our current position */
        unsigned int op;        /* the operator on the line */
        LIST paths;             /* List of search paths to alter when parsing
                                * a list of .PATH targets */
        unsigned int tOp;               /* operator from special target */

        waiting = 0;
        Lst_Init(&paths);

        Array_Reset(&gsources);

        cp = parse_do_targets(&paths, &tOp, line);
        assert(specType == SPECIAL_PATH || Lst_IsEmpty(&paths));
        if (cp == NULL || specType == SPECIAL_ERROR) {
                /* invalidate targets for further processing */
                Array_Reset(&gtargets); 
                return;
        }

        op = parse_operator(&cp);
        if (op == OP_ERROR) {
                /* invalidate targets for further processing */
                Array_Reset(&gtargets); 
                return;
        }

        Array_FindP(&gtargets, ParseDoOp, op);
        dedup_targets(&gtargets);

        line = cp;

        /* Several special targets have specific semantics with no source:
         *      .SUFFIXES       clears out all old suffixes
         *      .PRECIOUS/.IGNORE/.SILENT
         *                      apply to all target
         *      .PATH           clears out all search paths.  */
        if (!*line) {
                switch (specType) {
                case SPECIAL_SUFFIXES:
                        Suff_DisableAllSuffixes();
                        break;
                case SPECIAL_PRECIOUS:
                        allPrecious = true;
                        break;
                case SPECIAL_IGNORE:
                        ignoreErrors = true;
                        break;
                case SPECIAL_SILENT:
                        beSilent = true;
                        break;
                case SPECIAL_PATH:
                        Lst_Every(&paths, ParseClearPath);
                        break;
                default:
                        break;
                }
        } else if (specType == SPECIAL_MFLAGS) {
                Main_ParseArgLine(line);
                return;
        } else if (specType == SPECIAL_NOTPARALLEL) {
                return;
        }

        /* NOW GO FOR THE SOURCES */
        if (specType == SPECIAL_SUFFIXES || specType == SPECIAL_PATH ||
            specType == SPECIAL_NOTHING) {
                while (*line) {
                    /* Some special targets take a list of space-separated
                     * words.  For each word,
                     *
                     * if .SUFFIXES, add it to the list of suffixes maintained
                     * by suff.c.
                     *
                     * if .PATHS, add it as a directory on the main search path.
                     *
                     * if .LIBS/.INCLUDE/.NULL... this has been deprecated,
                     * ignore
                     */
                    while (*cp && !ISSPACE(*cp))
                            cp++;
                    switch (specType) {
                    case SPECIAL_SUFFIXES:
                            Suff_AddSuffixi(line, cp);
                            break;
                    case SPECIAL_PATH:
                            {
                            LstNode ln;

                            for (ln = Lst_First(&paths); ln != NULL;
                                ln = Lst_Adv(ln))
                                    Dir_AddDiri(Lst_Datum(ln), line, cp);
                            break;
                            }
                    default:
                            break;
                    }
                    if (*cp != '\0')
                        cp++;
                    while (ISSPACE(*cp))
                        cp++;
                    line = cp;
                }
        } else {
                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.
                         */
                        while (*cp && !ISSPACE(*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;
                                } else {
                                        cp++;
                                }
                        }

                        if (*cp == '(') {
                                GNode *gn;
                                LIST sources;   /* list of archive source
                                                 * names after expansion */

                                Lst_Init(&sources);
                                if (!Arch_ParseArchive(&line, &sources, NULL)) {
                                        Parse_Error(PARSE_FATAL,
                                            "Error in source archive spec \"%s\"",
                                            line);
                                        return;
                                }

                                while ((gn = Lst_DeQueue(&sources)) != NULL)
                                        ParseDoSrc(&gtargets, &gsources, tOp,
                                            gn->name, NULL);
                                cp = line;
                        } else {
                                const char *endSrc = cp;

                                ParseDoSrc(&gtargets, &gsources, tOp, line,
                                    endSrc);
                                if (*cp)
                                        cp++;
                        }
                        while (ISSPACE(*cp))
                                cp++;
                        line = cp;
                }
        }

        /* If we're not in the SPEC_PATH case the list is empty, but who 
         * cares ?  This is cheap. 
         */
        Lst_Destroy(&paths, NOFREE);

        if (mainNode == NULL) {
                /* 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.  */
                Array_Find(&gtargets, ParseFindMain, NULL);
        }
}

/*-
 * ParseAddCmd  --
 *      Lst_ForEach function to add a command line to all targets
 *
 *      The new command may be added to the commands list of the node.
 *
 *      If the target already had commands, we ignore the new ones, but 
 *      we note that we got double commands (in case we actually get to run 
 *      that ambiguous target).
 *
 *      Note this does not apply to :: dependency lines, since those 
 *      will generate fresh cloned nodes and add them to the cohorts
 *      field of the main node.
 */
static void
ParseAddCmd(void *gnp, void *cmd)
{
        GNode *gn = gnp;

        if (!(gn->type & OP_HAS_COMMANDS))
                Lst_AtEnd(&gn->commands, cmd);
        else
                gn->type |= OP_DOUBLE;
}

/*-
 *-----------------------------------------------------------------------
 * ParseHasCommands --
 *      Record that the target gained commands through OP_HAS_COMMANDS,
 *      so that double command lists may be ignored.
 *-----------------------------------------------------------------------
 */
static void
ParseHasCommands(void *gnp)
{
        GNode *gn = gnp;
        gn->type |= OP_HAS_COMMANDS;

}


/* Strip comments from line. Build a copy in buffer if necessary, */
static char *
strip_comments(Buffer copy, const char *line)
{
        const char *comment;
        const char *p;

        comment = strchr(line, '#');
        if (comment == NULL)
                return (char *)line;
        else {
                Buf_Reset(copy);

                for (p = line; *p != '\0'; p++) {
                        if (*p == '\\') {
                                if (p[1] == '#') {
                                        Buf_Addi(copy, line, p);
                                        Buf_AddChar(copy, '#');
                                        line = p+2;
                                }
                                if (p[1] != '\0')
                                        p++;
                        } else if (*p == '#')
                                break;
                }
                Buf_Addi(copy, line, p);
                return Buf_Retrieve(copy);
        }
}



/***
 *** Support for various include constructs
 ***/


void
Parse_AddIncludeDir(const char  *dir)
{
        Dir_AddDir(userIncludePath, dir);
}

static char *
resolve_include_filename(const char *file, const char *efile, bool isSystem)
{
        char *fullname;

        /* Look up system files on the system path first */
        if (isSystem) {
                fullname = Dir_FindFileNoDoti(file, efile, systemIncludePath);
                if (fullname)
                        return fullname;
        }

        /* Handle non-system non-absolute files... */
        if (!isSystem && file[0] != '/') {
                /* ... by looking first under the same directory as the
                 * current file */
                char *slash = NULL;
                const char *fname;

                fname = Parse_Getfilename();

                if (fname != NULL)
                        slash = strrchr(fname, '/');

                if (slash != NULL) {
                        char *newName;

                        newName = Str_concati(fname, slash, file, efile, '/');
                        fullname = Dir_FindFile(newName, userIncludePath);
                        if (fullname == NULL)
                                fullname = Dir_FindFile(newName, defaultPath);
                        free(newName);
                        if (fullname)
                                return fullname;
                }
        }

        /* Now look first on the -I search path, then on the .PATH
         * search path, if not found in a -I directory.
         * XXX: Suffix specific?  */
        fullname = Dir_FindFilei(file, efile, userIncludePath);
        if (fullname)
                return fullname;
        fullname = Dir_FindFilei(file, efile, defaultPath);
        if (fullname)
                return fullname;

        /* Still haven't found the makefile. Look for it on the system
         * path as a last resort (if we haven't already). */
        if (isSystem)
                return NULL;
        else
                return Dir_FindFilei(file, efile, systemIncludePath);
}

static void
handle_include_file(const char *file, const char *efile, bool isSystem,
    bool errIfNotFound)
{
        char *fullname;

        fullname = resolve_include_filename(file, efile, isSystem);
        if (fullname == NULL && errIfNotFound)
                Parse_Error(PARSE_FATAL, "Could not find %.*s", 
                    (int)(efile - file), file);

        if (fullname != NULL) {
                FILE *f;

                f = fopen(fullname, "r");
                if (f == NULL && errIfNotFound)
                        Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
                else
                        Parse_FromFile(fullname, f);
        }
}

/* .include <file> (system) or .include "file" (normal) */
static bool
lookup_bsd_include(const char *file)
{
        char endc;
        const char *efile;
        char *file2;
        bool isSystem;

        /* find starting delimiter */
        while (ISSPACE(*file))
                file++;

        /* determine type of file */
        if (*file == '<') {
                isSystem = true;
                endc = '>';
        } else if (*file == '"') {
                isSystem = false;
                endc = '"';
        } else {
                Parse_Error(PARSE_WARNING,
                    ".include filename must be delimited by '\"' or '<'");
                return false;
        }

        /* delimit file name between file and efile */
        for (efile = ++file; *efile != endc; efile++) {
                if (*efile == '\0') {
                        Parse_Error(PARSE_WARNING,
                             "Unclosed .include filename. '%c' expected", endc);
                        return false;
                }
        }
        /* Substitute for any variables in the file name before trying to
         * find the thing. */
        file2 = Var_Substi(file, efile, NULL, false);
        handle_include_file(file2, strchr(file2, '\0'), isSystem, true);
        free(file2);
        return true;
}


static void
lookup_sysv_style_include(const char *line, const char *directive,
    bool errIfMissing)
{
        char *file;
        char *name;
        char *ename;
        bool okay = false;

        /* Substitute for any variables in the file name before trying to
         * find the thing. */
        file = Var_Subst(line, NULL, false);

        /* sys5 allows for list of files separated by spaces */
        name = file;
        while (1) {
                /* find beginning of name */
                while (ISSPACE(*name))
                        name++;
                if (*name == '\0')
                        break;
                for (ename = name; *ename != '\0' && !ISSPACE(*ename);)
                        ename++;
                handle_include_file(name, ename, true, errIfMissing);
                okay = true;
                name = ename;
        }

        free(file);
        if (!okay) {
                Parse_Error(PARSE_FATAL, "Filename missing from \"%s\"",
                directive);
        }
}


/* system V construct:  include file */
static void
lookup_sysv_include(const char *file, const char *directive)
{
        lookup_sysv_style_include(file, directive, true);
}


/* sinclude file and -include file */
static void
lookup_conditional_include(const char *file, const char *directive)
{
        lookup_sysv_style_include(file, directive, false);
}


/***
 ***   BSD-specific . constructs
 ***   They all follow the same pattern:
 ***    if the syntax matches BSD stuff, then we're committed to handle
 ***   them and report fatal errors (like, include file not existing)
 ***    otherwise, we return false, and hope somebody else will handle it.
 ***/

static bool
handle_poison(const char *line)
{
        const char *p = line;
        int type = POISON_NORMAL;
        bool not = false;
        bool paren_to_match = false;
        const char *name, *ename;

        while (ISSPACE(*p))
                p++;
        if (*p == '!') {
                not = true;
                p++;
        }
        while (ISSPACE(*p))
                p++;
        if (strncmp(p, "defined", 7) == 0) {
                type = POISON_DEFINED;
                p += 7;
        } else if (strncmp(p, "empty", 5) == 0) {
                type = POISON_EMPTY;
                p += 5;
        }
        while (ISSPACE(*p))
                p++;
        if (*p == '(') {
                paren_to_match = true;
                p++;
        }
        while (ISSPACE(*p))
                p++;
        name = ename = p;
        while (*p != '\0' && !ISSPACE(*p)) {
                if (*p == ')' && paren_to_match) {
                        paren_to_match = false;
                        p++;
                        break;
                }
                p++;
                ename = p;
        }
        while (ISSPACE(*p))
                p++;
        switch(type) {
        case POISON_NORMAL:
        case POISON_EMPTY:
                if (not)
                        type = POISON_INVALID;
                break;
        case POISON_DEFINED:
                if (not)
                        type = POISON_NOT_DEFINED;
                else
                        type = POISON_INVALID;
                break;
        }
        if ((*p != '\0' && *p != '#') || type == POISON_INVALID) {
                Parse_Error(PARSE_WARNING, "Invalid syntax for .poison: %s",
                    line);
                return false;
        } else {
                Var_Mark(name, ename, type);
                return true;
        }
}


static bool
handle_for_loop(Buffer linebuf, const char *line)
{
        For *loop;

        loop = For_Eval(line);
        if (loop != NULL) {
                bool ok;
                do {
                        /* Find the matching endfor.  */
                        line = ParseReadLoopLine(linebuf);
                        if (line == NULL) {
                            Parse_Error(PARSE_FATAL,
                                 "Unexpected end of file in for loop.\n");
                            return false;
                        }
                        ok = For_Accumulate(loop, line);
                } while (ok);
                For_Run(loop);
                return true;
        } else
                return false;
}

static bool
handle_undef(const char *line)
{
        const char *eline;

        while (ISSPACE(*line))
                line++;
        for (eline = line; !ISSPACE(*eline) && *eline != '\0';)
                eline++;
        Var_Deletei(line, eline);
        return true;
}

/* global hub for the construct */
static bool
handle_bsd_command(Buffer linebuf, Buffer copy, const char *line)
{
        char *stripped;

        while (ISSPACE(*line))
                line++;

        /* delegate basic classification to the conditional module */
        switch (Cond_Eval(line)) {
        case COND_SKIP:
                /* Skip to next conditional that evaluates to COND_PARSE.  */
                do {
                        line = Parse_ReadNextConditionalLine(linebuf);
                        if (line != NULL) {
                                while (ISSPACE(*line))
                                        line++;
                                stripped = strip_comments(copy, line);
                        }
                } while (line != NULL && Cond_Eval(stripped) != COND_PARSE);
                /* FALLTHROUGH */
        case COND_PARSE:
                return true;
        case COND_ISFOR:
                return handle_for_loop(linebuf, line + 3);
        case COND_ISINCLUDE:
                return lookup_bsd_include(line + 7);
        case COND_ISPOISON:
                return handle_poison(line + 6);
        case COND_ISUNDEF:
                return handle_undef(line + 5);
        default:
                break;
        }

        return false;
}

/* postprocess group of targets prior to linking stuff with them */
static bool 
register_target(GNode *gn, struct ohash *t)
{
        unsigned int slot;
        uint32_t hv;
        const char *ename = NULL;
        GNode *gn2;

        hv = ohash_interval(gn->name, &ename);

        slot = ohash_lookup_interval(t, gn->name, ename, hv);
        gn2 = ohash_find(t, slot);

        if (gn2 == NULL) {
                ohash_insert(t, slot, gn);
                return true;
        } else
                return false;
}

static void
build_target_group(struct growableArray *targets, struct ohash *t)
{
        LstNode ln;
        bool seen_target = false;
        unsigned int i;
        GNode *gn, *gn2;

        /* may be 0 if wildcard expansion resulted in zero match */
        if (targets->n <= 1)
                return;

        /* Perform checks to see if we must tie targets together */
        /* XXX */
        if (targets->a[0]->type & OP_TRANSFORM)
                return;

        for (ln = Lst_First(&targets->a[0]->commands); ln != NULL; 
            ln = Lst_Adv(ln)) {
                struct command *cmd = Lst_Datum(ln);
                if (Var_Check_for_target(cmd->string)) {
                        seen_target = true;
                        break;
                }
        }
        if (DEBUG(TARGGROUP)) {
                fprintf(stderr, 
                    seen_target ? "No target group at %lu: ": 
                    "Target group at %lu:", Parse_Getlineno());
                for (i = 0; i < targets->n; i++)
                        fprintf(stderr, " %s", targets->a[i]->name);
                fprintf(stderr, "\n");
        }
        if (seen_target)
                return;

        /* targets may already participate in groupling lists, 
         * so rebuild the circular list "from scratch"
         */

        for (i = 0; i < targets->n; i++) {
                gn = targets->a[i];
                for (gn2 = gn->groupling; gn2 != gn; gn2 = gn2->groupling) {    
                        if (!gn2)
                                break;
                        register_target(gn2, t);
                }
        }

        for (gn = ohash_first(t, &i); gn != NULL; gn = ohash_next(t, &i)) {
                gn->groupling = gn2;
                gn2 = gn;
        }
        gn = ohash_first(t, &i);
        gn->groupling = gn2;
}

static void
reset_target_hash(void)
{
        if (htargets_setup)
                ohash_delete(&htargets);
        ohash_init(&htargets, 5, &gnode_info);
        htargets_setup = true;
}

void
Parse_End(void)
{
        if (htargets_setup)
                ohash_delete(&htargets);
}

static void
dedup_targets(struct growableArray *targets)
{
        unsigned int i, j;

        if (targets->n <= 1)
                return;

        reset_target_hash();
        /* first let's de-dup the list */
        for (i = 0, j = 0; i < targets->n; i++) {
                GNode *gn = targets->a[i];
                if (register_target(gn, &htargets))
                        targets->a[j++] = targets->a[i];
        }
        targets->n = j;
}


/***
 *** handle a group of commands
 ***/

static void
finish_commands(struct growableArray *targets)
{
        build_target_group(targets, &htargets);
        Array_Every(targets, ParseHasCommands);
}

static void
parse_commands(struct growableArray *targets, const char *line)
{
        /* add the command to the list of
         * commands of all targets in the dependency spec */

        struct command *cmd;
        size_t len = strlen(line);

        cmd = emalloc(sizeof(struct command) + len);
        memcpy(&cmd->string, line, len+1);
        Parse_FillLocation(&cmd->location);

        Array_ForEach(targets, ParseAddCmd, cmd);
}

static bool
parse_as_special_line(Buffer buf, Buffer copy, const char *line)
{
        if (*line == '.' && handle_bsd_command(buf, copy, line+1))
                return true;
        if (strncmp(line, "include", 7) == 0 &&
            ISSPACE(line[7]) &&
            strchr(line, ':') == NULL) {
            /* It's an S3/S5-style "include".  */
                lookup_sysv_include(line + 7, "include");
                return true;
        }
        if (strncmp(line, "sinclude", 8) == 0 &&
            ISSPACE(line[8]) &&
            strchr(line, ':') == NULL) {
                lookup_conditional_include(line+8, "sinclude");
                return true;
        }
        if (strncmp(line, "-include", 8) == 0 &&
            ISSPACE(line[8]) &&
            strchr(line, ':') == NULL) {
                lookup_conditional_include(line+8, "-include");
                return true;
        }
        return false;
}

static void
parse_target_line(struct growableArray *targets, const char *line,
    const char *stripped, bool *pcommands_seen)
{
        size_t pos;
        char *end;
        char *cp;
        char *cmd;

        /* let's start a new set of commands */
        Array_Reset(targets);

        /* XXX this is a dirty heuristic to handle target: dep ; commands */
        cmd = NULL;
        /* First we need to find eventual dependencies */
        pos = strcspn(stripped, ":!");
        /* go over :!, and find ;  */
        if (stripped[pos] != '\0' &&
            (end = strchr(stripped+pos+1, ';')) != NULL) {
                if (line != stripped)
                        /* find matching ; in original... The
                         * original might be slightly longer.  */
                        cmd = strchr(line+(end-stripped), ';');
                else
                        cmd = end;
                /* kill end of line. */
                *end = '\0';
        }
        /* We now know it's a dependency line so it needs to
         * have all variables expanded before being parsed.
         */
        cp = Var_Subst(stripped, NULL, false);
        ParseDoDependency(cp);
        free(cp);

        /* Parse command if it's not empty. */
        if (cmd != NULL) {
                do {
                        cmd++;
                } while (ISSPACE(*cmd));
                if (*cmd != '\0') {
                        parse_commands(targets, cmd);
                        *pcommands_seen = true;
                }
        }
}

void
Parse_File(const char *filename, FILE *stream)
{
        char *line;
        bool expectingCommands = false;
        bool commands_seen = false;

        /* permanent spaces to shave time */
        static BUFFER buf;
        static BUFFER copy;

        Buf_Reinit(&buf, MAKE_BSIZE);
        Buf_Reinit(&copy, MAKE_BSIZE);

        Parse_FromFile(filename, stream);
        do {
                while ((line = Parse_ReadNormalLine(&buf)) != NULL) {
                        if (*line == '\t') {
                                if (expectingCommands) {
                                        commands_seen = true;
                                        parse_commands(&gtargets, line+1);
                                } else
                                        Parse_Error(PARSE_FATAL,
                                            "Unassociated shell command \"%s\"",
                                             line);
                        } else {
                                const char *stripped = strip_comments(&copy,
                                    line);
                                if (!parse_as_special_line(&buf, &copy,
                                    stripped)) {
                                        if (commands_seen)
                                                finish_commands(&gtargets);
                                        commands_seen = false;
                                        if (Parse_As_Var_Assignment(stripped))
                                                expectingCommands = false;
                                        else {
                                                parse_target_line(&gtargets,
                                                    line, stripped, 
                                                    &commands_seen);
                                                expectingCommands = true;
                                        }
                                }
                        }
                }
        } while (Parse_NextFile());

        if (commands_seen)
                finish_commands(&gtargets);
        /* Make sure conditionals are clean.  */
        Cond_End();

        Parse_ReportErrors();
}

void
Parse_Init(void)
{
        mainNode = NULL;
        Static_Lst_Init(userIncludePath);
        Static_Lst_Init(systemIncludePath);
        Array_Init(&gtargets, TARGETS_SIZE);
        Array_Init(&gsources, SOURCES_SIZE);
        create_special_nodes();
}

void
Parse_MainName(Lst listmain)    /* result list */
{
        if (mainNode == NULL) {
                dump_unreadable();
                Punt("no target to make.");
                /*NOTREACHED*/
        } else if (mainNode->type & OP_DOUBLEDEP) {
                Lst_AtEnd(listmain, mainNode);
                Lst_Concat(listmain, &mainNode->cohorts);
        }
        else
                Lst_AtEnd(listmain, mainNode);
}