#ifdef CONFIG_WITH_COMPILER
/* $Id: kmk_cc_exec.c 3233 2018-09-24 10:39:36Z bird $ */
/** @file
 * kmk_cc - Make "Compiler".
 */

/*
 * Copyright (c) 2015-2017 knut st. osmundsen <bird-kBuild-spam-xiiv@anduin.net>
 *
 * This file is part of kBuild.
 *
 * kBuild is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * kBuild is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with kBuild.  If not, see <http://www.gnu.org/licenses/>
 *
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include "makeint.h"

#include "dep.h"
#include "variable.h"
#include "rule.h"
#include "debug.h"
#include "hash.h"
#include <ctype.h>
#ifdef HAVE_STDINT_H
# include <stdint.h>
#endif
#include <stdarg.h>
#include <assert.h>
#include "k/kDefs.h"
#include "k/kTypes.h"


/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
/** @def KMK_CC_WITH_STATS
 * Enables the collection of extra statistics. */
#ifndef KMK_CC_WITH_STATS
# ifdef CONFIG_WITH_MAKE_STATS
#  define KMK_CC_WITH_STATS
# endif
#endif

/** @def KMK_CC_STRICT
 * Indicates whether assertions and other checks are enabled. */
#ifndef KMK_CC_STRICT
# ifndef NDEBUG
#  define KMK_CC_STRICT
# endif
#endif

#ifdef KMK_CC_STRICT
# ifdef _MSC_VER
#  define KMK_CC_ASSERT(a_TrueExpr)         do { if (!(a_TrueExpr)) __debugbreak(); } while (0)
# elif defined(__GNUC__) && (defined(KBUILD_ARCH_X86) || defined(KBUILD_ARCH_AMD64))
#  define KMK_CC_ASSERT(a_TrueExpr)         do { if (!(a_TrueExpr)) __asm__ __volatile__("int3;nop"); } while (0)
# else
#  define KMK_CC_ASSERT(a_TrueExpr)         assert(a_TrueExpr)
# endif
#else
# define KMK_CC_ASSERT(a_TrueExpr)          do {} while (0)
#endif
#define KMK_CC_ASSERT_ALIGNED(a_uValue, a_uAlignment) \
    KMK_CC_ASSERT( ((a_uValue) & ((a_uAlignment) - 1)) == 0 )


/** @def KMK_CC_OFFSETOF
 * Offsetof for simple stuff.  */
#if defined(__GNUC__)
# define KMK_CC_OFFSETOF(a_Struct, a_Member)        __builtin_offsetof(a_Struct, a_Member)
#else
# define KMK_CC_OFFSETOF(a_Struct, a_Member)        ( (uintptr_t)&( ((a_Struct *)(void *)0)->a_Member) )
#endif

/** def KMK_CC_SIZEOF_MEMBER   */
#define KMK_CC_SIZEOF_MEMBER(a_Struct, a_Member)    ( sizeof( ((a_Struct *)(void *)0x1000)->a_Member) )

/** @def KMK_CC_SIZEOF_VAR_STRUCT
 * Size of a struct with a variable sized array as the final member. */
#define KMK_CC_SIZEOF_VAR_STRUCT(a_Struct, a_FinalArrayMember, a_cArray) \
    ( KMK_CC_OFFSETOF(a_Struct, a_FinalArrayMember) + KMK_CC_SIZEOF_MEMBER(a_Struct, a_FinalArrayMember) * (a_cArray) )



/** @def KMK_CC_STATIC_ASSERT_EX
 * Compile time assertion with text.
 */
#ifdef _MSC_VER_
# if _MSC_VER >= 1600
#  define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl) static_assert(a_Expr, a_szExpl)
# else
#  define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl) typedef int RTASSERTVAR[(a_Expr) ? 1 : 0]
# endif
#elif defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__)
# define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl)     static_assert(a_Expr, a_szExpl)
#elif !defined(__GNUC__) && !defined(__IBMC__) && !defined(__IBMCPP__)
# define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl)  typedef int KMK_CC_STATIC_ASSERT_EX_TYPE[(a_Expr) ? 1 : 0]
#else
# define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl)  extern int KMK_CC_STATIC_ASSERT_EX_VAR[(a_Expr) ? 1 : 0]
extern int KMK_CC_STATIC_ASSERT_EX_VAR[1];
#endif
/** @def KMK_CC_STATIC_ASSERT
 * Compile time assertion, simple variant.
 */
#define KMK_CC_STATIC_ASSERT(a_Expr)                KMK_CC_STATIC_ASSERT_EX(a_Expr, #a_Expr)


/** Aligns a size for the block allocator. */
#define KMK_CC_BLOCK_ALIGN_SIZE(a_cb)               ( ((a_cb) + (sizeof(void *) - 1U)) & ~(uint32_t)(sizeof(void *) - 1U) )

/** How to declare a no-return function.
 * Place between scope (if any) and return type.  */
#ifdef _MSC_VER
# define KMK_CC_FN_NO_RETURN                        __declspec(noreturn)
#elif defined(__GNUC__)
# define KMK_CC_FN_NO_RETURN                        __attribute__((__noreturn__))
#endif

/** Block allocator logging.   */
//#define KMK_CC_BLOCK_LOGGING_ENABLED
#ifdef KMK_CC_BLOCK_LOGGING_ENABLED
# define KMK_CC_BLOCK_DPRINTF_UNPACK(...)           __VA_ARGS__
# define KMK_CC_BLOCK_DPRINTF(a)                    fprintf(stderr, KMK_CC_BLOCK_DPRINTF_UNPACK a)
#else
# define KMK_CC_BLOCK_DPRINTF(a)                    do { } while (0)
#endif


/** @defgroup grp_kmk_cc_evalprog Makefile Evaluation
 * @{
 */
#define KMK_CC_EVAL_LOGGING_ENABLED
#ifdef KMK_CC_EVAL_LOGGING_ENABLED
# define KMK_CC_EVAL_DPRINTF_UNPACK(...)            __VA_ARGS__
# define KMK_CC_EVAL_DPRINTF(a)                     fprintf(stderr, KMK_CC_EVAL_DPRINTF_UNPACK a)
#else
# define KMK_CC_EVAL_DPRINTF(a)                     do { } while (0)
#endif

/** @name KMK_CC_EVAL_QUALIFIER_XXX - Variable qualifiers.
 * @{ */
#define KMK_CC_EVAL_QUALIFIER_LOCAL         1
#define KMK_CC_EVAL_QUALIFIER_EXPORT        2
#define KMK_CC_EVAL_QUALIFIER_OVERRIDE      4
#define KMK_CC_EVAL_QUALIFIER_PRIVATE       8
/** @} */

/** Eval: Max makefile size we accept as input (in bytes). */
#define KMK_CC_EVAL_MAX_COMPILE_SIZE        (16*1024*1024)

/** Eval: Max nesting depth of makefile conditionals.
 * Affects stack usage in kmk_cc_eval_compile_worker.  */
#define KMK_CC_EVAL_MAX_IF_DEPTH            32
/** Eval: Maximum number of escaped end of line sequences to track.
 * Affects stack usage in kmk_cc_eval_compile_worker, but not the actual
 * number of consequtive escaped newlines in the input file/variable. */
#define KMK_CC_EVAL_MAX_ESC_EOLS            2

/** Minimum keyword length. */
#define KMK_CC_EVAL_KEYWORD_MIN             2
/** Maximum keyword length. */
#define KMK_CC_EVAL_KEYWORD_MAX             16

/** @name KMK_CC_EVAL_CH_XXX - flags found in g_abEvalCcChars.
 * @{ */
/** Normal character, nothing special. */
#define KMK_CC_EVAL_CH_NORMAL                       UINT16_C(0)
/** Blank character. */
#define KMK_CC_EVAL_CH_BLANK                        UINT16_C(1)
#define KMK_CC_EVAL_IS_BLANK(a_ch)                  (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_BLANK)
/** Space character. */
#define KMK_CC_EVAL_CH_SPACE                        UINT16_C(2)
#define KMK_CC_EVAL_IS_SPACE(a_ch)                  (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_SPACE)
/** Space character or potential EOL escape backslash. */
#define KMK_CC_EVAL_CH_SPACE_OR_BACKSLASH           UINT16_C(4)
#define KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(a_ch)     (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_SPACE_OR_BACKSLASH)
/** Anything we need to take notice of when parsing something could be a
 * variable name or a recipe.
 * All space characters, backslash (EOL escape), variable expansion dollar,
 * variable assignment operator chars, recipe colon and recipe percent. */
#define KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE          UINT16_C(8)
#define KMK_CC_EVAL_IS_SPACE_VAR_OR_RECIPE(a_ch)    (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE)
/** Dollar character (possible variable expansion). */
#define KMK_CC_EVAL_CH_DOLLAR                       UINT16_C(16)
#define KMK_CC_EVAL_IS_DOLLAR(a_ch)                 (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_DOLLAR)
/** Dollar character (possible variable expansion). */
#define KMK_CC_EVAL_CH_BACKSLASH                    UINT16_C(32)
#define KMK_CC_EVAL_IS_BACKSLASH(a_ch)              (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_BACKSLASH)
/** Possible EOL character. */
#define KMK_CC_EVAL_CH_EOL_CANDIDATE                UINT16_C(64)
#define KMK_CC_EVAL_IS_EOL_CANDIDATE(a_ch)          (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_EOL_CANDIDATE)
/** First character in a keyword. */
#define KMK_CC_EVAL_CH_1ST_IN_KEYWORD               UINT16_C(128)
#define KMK_CC_EVAL_IS_1ST_IN_KEYWORD(a_ch)         (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_1ST_IN_KEYWORD)
/** Second character in a keyword. */
#define KMK_CC_EVAL_CH_2ND_IN_KEYWORD               UINT16_C(256)
#define KMK_CC_EVAL_IS_2ND_IN_KEYWORD(a_ch)         (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_2ND_IN_KEYWORD)
/** First character in a variable qualifier keyword or 'define'. */
#define KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD      UINT16_C(512)
#define KMK_CC_EVAL_IS_1ST_IN_VARIABLE_KEYWORD(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD)
/** Used when parsing variable names, looking for the end of a nested
 *  variable reference.  Matches parentheses and backslash (escaped eol). */
#define KMK_CC_EVAL_CH_PAREN_OR_SLASH               UINT16_C(1024)
#define KMK_CC_EVAL_IS_PAREN_OR_SLASH(a_ch)         (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_PAREN_OR_SLASH)
/** Used when parsing ifeq/ifneq (,) sequences.
 * Matches parentheses, comma and dollar (for non-plain string detection). */
#define KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR        UINT16_C(2048)
#define KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(a_ch)  (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR)

/** Test of space or dollar characters. */
#define KMK_CC_EVAL_IS_SPACE_OR_DOLLAR(a_ch)        (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & (KMK_CC_EVAL_CH_SPACE | KMK_CC_EVAL_CH_DOLLAR))
/** Test of space, dollar or backslash (possible EOL escape) characters. */
#define KMK_CC_EVAL_IS_SPACE_DOLLAR_OR_SLASH(a_ch)  (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & (KMK_CC_EVAL_CH_SPACE | KMK_CC_EVAL_CH_DOLLAR | KMK_CC_EVAL_CH_BACKSLASH))
/** Test of space, dollar, backslash (possible EOL escape) or variable
 * assingment characters. */
#define KMK_CC_EVAL_IS_SPACE_DOLLAR_SLASH_OR_ASSIGN(a_ch)   \
    (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & (KMK_CC_EVAL_CH_SPACE | KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE | KMK_CC_EVAL_CH_DOLLAR))
/** @} */

/** Sets a bitmap entry.
 * @param   a_abBitmap      Typically g_abEvalCcChars.
 * @param   a_ch            The character to set.
 * @param   a_uVal          The value to OR in.  */
#define KMK_CC_EVAL_BM_OR(g_abBitmap, a_ch, a_uVal) do { (g_abBitmap)[(unsigned char)(a_ch)] |= (a_uVal); } while (0)

/** Gets a bitmap entry.
 * @returns The value corresponding to @a a_ch.
 * @param   a_abBitmap      Typically g_abEvalCcChars.
 * @param   a_ch            The character to set. */
#define KMK_CC_EVAL_BM_GET(g_abBitmap, a_ch)        ( (g_abBitmap)[(unsigned char)(a_ch)] )

/** @} */


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
/**
 * Block of instructions.
 *
 * To avoid wasting space on "next" pointers, as well as a lot of time walking
 * these chains when destroying programs, we work with blocks of instructions.
 */
typedef struct kmk_cc_block
{
    /** The pointer to the next block (LIFO). */
    struct kmk_cc_block        *pNext;
    /** The size of this block. */
    uint32_t                    cbBlock;
    /** The offset of the next free byte in the block.  When set to cbBlock the
     *  block is 100% full. */
    uint32_t                    offNext;
} KMKCCBLOCK;
typedef KMKCCBLOCK *PKMKCCBLOCK;


/** @defgroup grp_kmk_cc_exp   String Expansion
 * @{*/

/**
 * String expansion statistics.
 */
typedef struct KMKCCEXPSTATS
{
    /** Recent average size. */
    uint32_t                    cchAvg;
} KMKCCEXPSTATS;
typedef KMKCCEXPSTATS *PKMKCCEXPSTATS;

/**
 * Expansion instructions.
 */
typedef enum KMKCCEXPINSTR
{
    /** Copy a plain string. */
    kKmkCcExpInstr_CopyString = 0,
    /** Insert an expanded variable value, which name we already know.  */
    kKmkCcExpInstr_PlainVariable,
    /** Insert an expanded variable value, the name is dynamic (sub prog). */
    kKmkCcExpInstr_DynamicVariable,
    /** Insert an expanded variable value, which name we already know, doing
     * search an replace on a string. */
    kKmkCcExpInstr_SearchAndReplacePlainVariable,
    /** Insert the output of function that requires no argument expansion. */
    kKmkCcExpInstr_PlainFunction,
    /** Insert the output of function that requires dynamic expansion of one ore
     * more arguments.  (Dynamic is perhaps not such a great name, but whatever.) */
    kKmkCcExpInstr_DynamicFunction,
    /** Jump to a new instruction block. */
    kKmkCcExpInstr_Jump,
    /** We're done, return.  Has no specific structure. */
    kKmkCcExpInstr_Return,
    /** The end of valid instructions (exclusive). */
    kKmkCcExpInstr_End
} KMKCCEXPINSTR;

/** Instruction core. */
typedef struct kmk_cc_exp_core
{
    /** The instruction opcode number (KMKCCEXPINSTR). */
    KMKCCEXPINSTR           enmOpcode;
} KMKCCEXPCORE;
typedef KMKCCEXPCORE *PKMKCCEXPCORE;

/**
 * String expansion subprogram.
 */
#pragma pack(1) /* save some precious bytes */
typedef struct kmk_cc_exp_subprog
{
    /** Pointer to the first instruction. */
    PKMKCCEXPCORE           pFirstInstr;
    /** Statistics. */
    KMKCCEXPSTATS           Stats;
} KMKCCEXPSUBPROG;
#pragma pack()
typedef KMKCCEXPSUBPROG *PKMKCCEXPSUBPROG;
KMK_CC_STATIC_ASSERT(sizeof(KMKCCEXPSUBPROG) == 12 || sizeof(void *) != 8);


/**
 * String expansion subprogram or plain string.
 */
#pragma pack(1) /* save some precious bytes */
typedef struct kmk_cc_exp_subprog_or_string
{
    /** Either a plain string pointer or a subprogram.   */
    union
    {
        /** Subprogram for expanding this argument. */
        KMKCCEXPSUBPROG     Subprog;
        /** Pointer to the plain string. */
        struct
        {
            /** Pointer to the string. */
            const char     *psz;
            /** String length. */
            uint32_t        cch;
        } Plain;
    } u;
    /** Set if subprogram (u.Subprog), clear if plain string (u.Plain). */
    uint8_t                 fSubprog;
    /** Set if the plain string is kept in the variable_strcache.
     * @remarks Here rather than in u.Plain to make use of alignment padding. */
    uint8_t                 fPlainIsInVarStrCache;
    /** Context/user specific. */
    uint8_t                 bUser;
    /** Context/user specific #2. */
    uint8_t                 bUser2;
} KMKCCEXPSUBPROGORPLAIN;
#pragma pack()
typedef KMKCCEXPSUBPROGORPLAIN *PKMKCCEXPSUBPROGORPLAIN;
KMK_CC_STATIC_ASSERT(  sizeof(void *) == 8
                     ? sizeof(KMKCCEXPSUBPROGORPLAIN) == 16
                     : sizeof(void *) == 4
                     ? sizeof(KMKCCEXPSUBPROGORPLAIN) == 12
                     : 1);

/**
 * kKmkCcExpInstr_CopyString instruction format.
 */
typedef struct kmk_cc_exp_copy_string
{
    /** The core instruction. */
    KMKCCEXPCORE            Core;
    /** The number of bytes to copy. */
    uint32_t                cchCopy;
    /** Pointer to the source string (not terminated at cchCopy). */
    const char             *pachSrc;
} KMKCCEXPCOPYSTRING;
typedef KMKCCEXPCOPYSTRING *PKMKCCEXPCOPYSTRING;

/**
 * kKmkCcExpInstr_PlainVariable instruction format.
 */
typedef struct kmk_cc_exp_plain_variable
{
    /** The core instruction. */
    KMKCCEXPCORE            Core;
    /** The name of the variable (points into variable_strcache). */
    const char             *pszName;
} KMKCCEXPPLAINVAR;
typedef KMKCCEXPPLAINVAR *PKMKCCEXPPLAINVAR;

/**
 * kKmkCcExpInstr_DynamicVariable instruction format.
 */
typedef struct kmk_cc_exp_dynamic_variable
{
    /** The core instruction. */
    KMKCCEXPCORE            Core;
    /** The subprogram that will give us the variable name. */
    KMKCCEXPSUBPROG         Subprog;
    /** Where to continue after this instruction.  (This is necessary since the
     * instructions of the subprogram are emitted after this instruction.) */
    PKMKCCEXPCORE           pNext;
} KMKCCEXPDYNVAR;
typedef KMKCCEXPDYNVAR *PKMKCCEXPDYNVAR;

/**
 * kKmkCcExpInstr_SearchAndReplacePlainVariable instruction format.
 */
typedef struct kmk_cc_exp_sr_plain_variable
{
    /** The core instruction. */
    KMKCCEXPCORE            Core;
    /** Where to continue after this instruction.  (This is necessary since the
     * instruction contains string data of variable size.) */
    PKMKCCEXPCORE           pNext;
    /** The name of the variable (points into variable_strcache). */
    const char             *pszName;
    /** Search pattern.  */
    const char             *pszSearchPattern;
    /** Replacement pattern. */
    const char             *pszReplacePattern;
    /** Offset into pszSearchPattern of the significant '%' char. */
    uint32_t                offPctSearchPattern;
    /** Offset into pszReplacePattern of the significant '%' char. */
    uint32_t                offPctReplacePattern;
} KMKCCEXPSRPLAINVAR;
typedef KMKCCEXPSRPLAINVAR *PKMKCCEXPSRPLAINVAR;

/**
 * Instruction format parts common to both kKmkCcExpInstr_PlainFunction and
 * kKmkCcExpInstr_DynamicFunction.
 */
typedef struct kmk_cc_exp_function_core
{
    /** The core instruction. */
    KMKCCEXPCORE            Core;
    /** Number of arguments. */
    uint32_t                cArgs; /**< @todo uint16_t to save 7 bytes of unecessary alignment padding on 64-bit systems, or merge fDirty into this member. */
    /** Set if the function could be modifying the input arguments. */
    uint8_t                 fDirty;
    /** Where to continue after this instruction.  (This is necessary since the
     * instructions are of variable size and may be followed by string data.) */
    PKMKCCEXPCORE           pNext;
    /**
     * Pointer to the function table entry.
     *
     * @returns New variable buffer position.
     * @param   pchDst      Current variable buffer position.
     * @param   papszArgs   Pointer to a NULL terminated array of argument strings.
     * @param   pszFuncName The name of the function being called.
     */
    char *                (*pfnFunction)(char *pchDst, char **papszArgs, const char *pszFuncName);
    /** Pointer to the function name in the variable string cache. */
    const char             *pszFuncName;
} KMKCCEXPFUNCCORE;
typedef KMKCCEXPFUNCCORE *PKMKCCEXPFUNCCORE;

/**
 * Instruction format for kKmkCcExpInstr_PlainFunction.
 */
typedef struct kmk_cc_exp_plain_function
{
    /** The bits comment to both plain and dynamic functions. */
    KMKCCEXPFUNCCORE        FnCore;
    /** Variable sized argument list (cArgs + 1 in length, last entry is NULL).
     * The string pointers are to memory following this instruction, to memory in
     * the next block or to memory in the variable / makefile we're working on
     * (if zero terminated appropriately). */
    const char             *apszArgs[1];
} KMKCCEXPPLAINFUNC;
typedef KMKCCEXPPLAINFUNC *PKMKCCEXPPLAINFUNC;
/** Calculates the size of an KMKCCEXPPLAINFUNC structure with the apszArgs
 * member holding a_cArgs entries plus a NULL terminator. */
#define KMKCCEXPPLAINFUNC_SIZE(a_cArgs) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEXPDYNFUNC, aArgs, (a_cArgs) + 1)

/**
 * Instruction format for kKmkCcExpInstr_DynamicFunction.
 */
typedef struct kmk_cc_exp_dyn_function
{
    /** The bits comment to both plain and dynamic functions. */
    KMKCCEXPFUNCCORE        FnCore;
    /** Variable sized argument list (FnCore.cArgs in length).
     * The subprograms / strings are allocated after this array (or in the next
     * block). */
    KMKCCEXPSUBPROGORPLAIN  aArgs[1];
} KMKCCEXPDYNFUNC;
typedef KMKCCEXPDYNFUNC *PKMKCCEXPDYNFUNC;
/** Calculates the size of an KMKCCEXPDYNFUNC structure with the apszArgs
 * member holding a_cArgs entries (no zero terminator). */
#define KMKCCEXPDYNFUNC_SIZE(a_cArgs)  KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEXPDYNFUNC, aArgs, a_cArgs)

/**
 * Instruction format for kKmkCcExpInstr_Jump.
 */
typedef struct kmk_cc_exp_jump
{
    /** The core instruction. */
    KMKCCEXPCORE            Core;
    /** Where to jump to (new instruction block, typically). */
    PKMKCCEXPCORE           pNext;
} KMKCCEXPJUMP;
typedef KMKCCEXPJUMP *PKMKCCEXPJUMP;

/**
 * String expansion program.
 */
typedef struct kmk_cc_expandprog
{
    /** Pointer to the first instruction for this program. */
    PKMKCCEXPCORE           pFirstInstr;
    /** List of blocks for this program (LIFO). */
    PKMKCCBLOCK             pBlockTail;
    /** Statistics. */
    KMKCCEXPSTATS           Stats;
#ifdef KMK_CC_STRICT
    /** The hash of the input string.  Used to check that we get all the change
     * notifications we require. */
    uint32_t                uInputHash;
#endif
    /** Reference count. */
    uint32_t volatile       cRefs;
} KMKCCEXPPROG;
/** Pointer to a string expansion program. */
typedef KMKCCEXPPROG *PKMKCCEXPPROG;

/** @} */


/** @addtogroup grp_kmk_cc_evalprog
 * @{  */

/** Pointer to a makefile evaluation program. */
typedef struct kmk_cc_evalprog *PKMKCCEVALPROG;

/**
 * Makefile evaluation instructions.
 */
typedef enum KMKCCEVALINSTR
{
    /** Jump instruction - KMKCCEVALJUMP. */
    kKmkCcEvalInstr_jump = 0,

    /** [local|override|export] variable  = value - KMKCCEVALASSIGN.
     * @note Can be used for target-specific variables. */
    kKmkCcEvalInstr_assign_recursive,
    /** [local|override|export] variable := value - KMKCCEVALASSIGN.
     * Also: [local|override|export] define variable := ... endef
     * @note Can be used for target-specific variables. */
    kKmkCcEvalInstr_assign_simple,
    /** [local|override|export] variable += value - KMKCCEVALASSIGN.
     * Also: [local|override|export] define variable += ... endef
     * @note Can be used for target-specific variables. */
    kKmkCcEvalInstr_assign_append,
    /** [local|override|export] variable <= value - KMKCCEVALASSIGN.
     * Also: [local|override|export] define variable <= ... endef
     * @note Can be used for target-specific variables. */
    kKmkCcEvalInstr_assign_prepend,
    /** [local|override|export] variable ?= value - KMKCCEVALASSIGN.
     * @note Can be used for target-specific variables. */
    kKmkCcEvalInstr_assign_if_new,
    /* [local|override|export] define variable[=] ... endef - KMKCCEVALASSIGNDEF. */
    kKmkCcEvalInstr_define_recursive,
    /* [local|override|export] define variable ?= ... endef - KMKCCEVALASSIGNDEF. */
    kKmkCcEvalInstr_define_if_new,

    /** export variable1 [variable2...] - KMKCCEVALVARIABLES. */
    kKmkCcEvalInstr_export,
    /** unexport variable1 [variable2...] - KMKCCEVALVARIABLES. */
    kKmkCcEvalInstr_unexport,
    /** export - KMKCCEVALCORE. */
    kKmkCcEvalInstr_export_all,
    /** unexport - KMKCCEVALCORE. */
    kKmkCcEvalInstr_unexport_all,
    /** [local|override] undefine - KMKCCEVALVARIABLES. */
    kKmkCcEvalInstr_undefine,

    /** [else] ifdef variable - KMKCCEVALIFDEFPLAIN. */
    kKmkCcEvalInstr_ifdef_plain,
    /** [else] ifndef variable - KMKCCEVALIFDEFPLAIN. */
    kKmkCcEvalInstr_ifndef_plain,
    /** [else] ifdef variable - KMKCCEVALIFDEFDYNAMIC. */
    kKmkCcEvalInstr_ifdef_dynamic,
    /** [else] ifndef variable - KMKCCEVALIFDEFDYNAMIC. */
    kKmkCcEvalInstr_ifndef_dynamic,
    /** [else] ifeq (a,b) - KMKCCEVALIFEQ. */
    kKmkCcEvalInstr_ifeq,
    /** [else] ifeq (a,b) - KMKCCEVALIFEQ. */
    kKmkCcEvalInstr_ifneq,
    /** [else] if1of (set-a,set-b) - KMKCCEVALIF1OF. */
    kKmkCcEvalInstr_if1of,
    /** [else] ifn1of (set-a,set-b) - KMKCCEVALIF1OF. */
    kKmkCcEvalInstr_ifn1of,
    /** [else] if expr - KMKCCEVALIFEXPR. */
    kKmkCcEvalInstr_if,

    /** include file1 [file2...] - KMKCCEVALINCLUDE. */
    kKmkCcEvalInstr_include,
    /** [sinclude|-include] file1 [file2...]  - KMKCCEVALINCLUDE. */
    kKmkCcEvalInstr_include_silent,
    /** includedep file1 [file2...] - KMKCCEVALINCLUDE. */
    kKmkCcEvalInstr_includedep,
    /** includedep-queue file1 [file2...] - KMKCCEVALINCLUDE. */
    kKmkCcEvalInstr_includedep_queue,
    /** includedep-flush file1 [file2...] - KMKCCEVALINCLUDE. */
    kKmkCcEvalInstr_includedep_flush,

    /** Recipe without commands (defines dependencies) - KMKCCEVALRECIPE. */
    kKmkCcEvalInstr_recipe_no_commands,
    /** Recipe with commands (defines dependencies) - KMKCCEVALRECIPE. */
    kKmkCcEvalInstr_recipe_start_normal,
    /** Recipe with commands (defines dependencies) - KMKCCEVALRECIPE. */
    kKmkCcEvalInstr_recipe_start_double_colon,
    /** Recipe with commands (defines dependencies) - KMKCCEVALRECIPE. */
    kKmkCcEvalInstr_recipe_start_pattern,
    /** Adds more commands to the current recipe - KMKCCEVALRECIPECOMMANDS. */
    kKmkCcEvalInstr_recipe_commands,
    /** Special instruction for indicating the end of the recipe commands - KMKCCEVALCORE. */
    kKmkCcEvalInstr_recipe_end,
    /** Cancel previously defined pattern rule - KMKCCEVALRECIPE.  */
    kKmkCcEvalInstr_recipe_cancel_pattern,

/** @todo target variables. */

    /** vpath pattern directories - KMKCCEVALVPATH. */
    kKmkCcEvalInstr_vpath,
    /** vpath pattern directories - KMKCCEVALVPATH. */
    kKmkCcEvalInstr_vpath_clear_pattern,
    /** vpath - KMKCCEVALCORE. */
    kKmkCcEvalInstr_vpath_clear_all,

    /** Make 'code' needing expanding and evaluation - KMKCCEVALEXPAND.
     * @note That this could in theory be used to start a recipe.  This will be
     *       detected by the interpreter and loading will for now fail.  A
     *       strategy for implement support for it would require picking up
     *       potential commands following the statements too. */
    kKmkCcEvalInstr_expand,

    /** The end of valid instructions (exclusive). */
    kKmkCcEvalInstr_End
} KMKCCEVALINSTR;

/**
 * Instruction core common to all instructions.
 */
typedef struct kmk_cc_eval_core
{
    /** The instruction opcode number (KMKCCEVALINSTR). */
    KMKCCEVALINSTR          enmOpcode;
    /** The line number in the source this statement is associated with. */
    unsigned                iLine;
} KMKCCEVALCORE;
/** Pointer to an instruction core structure. */
typedef KMKCCEVALCORE *PKMKCCEVALCORE;

/**
 * Instruction format for kKmkCcEvalInstr_jump.
 */
typedef struct kmk_cc_eval_jump
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** Where to jump to (new instruction block or endif, typically). */
    PKMKCCEVALCORE          pNext;
} KMKCCEVALJUMP;
typedef KMKCCEVALJUMP *PKMKCCEVALJUMP;

/**
 * Instruction format for kKmkCcEvalInstr_assign_recursive,
 * kKmkCcEvalInstr_assign_simple, kKmkCcEvalInstr_assign_append,
 * kKmkCcEvalInstr_assign_prepend and kKmkCcEvalInstr_assign_if_new.
 */
typedef struct kmk_cc_eval_assign
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** Whether the 'export' qualifier was used. */
    uint8_t                 fExport;
    /** Whether the 'override' qualifier was used. */
    uint8_t                 fOverride;
    /** Whether the 'local' qualifier was used. */
    uint8_t                 fLocal;
    /** Whether the 'private' qualifier was used. */
    uint8_t                 fPrivate;
    /** The variable name.
     * @remarks Plain text names are in variable_strcache. */
    KMKCCEXPSUBPROGORPLAIN  Variable;
    /** The value or value expression. */
    KMKCCEXPSUBPROGORPLAIN  Value;
    /** Pointer to the next instruction. */
    PKMKCCEVALCORE          pNext;
} KMKCCEVALASSIGN;
typedef KMKCCEVALASSIGN *PKMKCCEVALASSIGN;

/**
 * Instruction format for kKmkCcEvalInstr_define_recursive and
 * kKmkCcEvalInstr_define_if_new.
 */
typedef struct kmk_cc_eval_assign_define
{
    /** The assignment core structure. */
    KMKCCEVALASSIGN         AssignCore;
    /** Makefile evaluation program compiled from the define.
     * NULL if it does not compile.
     * @todo Let's see if this is actually doable... */
    PKMKCCEVALPROG          pEvalProg;
} KMKCCEVALASSIGNDEF;
typedef KMKCCEVALASSIGNDEF *PKMKCCEVALASSIGNDEF;

/**
 * Instruction format for kKmkCcEvalInstr_export, kKmkCcEvalInstr_unexport and
 * kKmkCcEvalInstr_undefine.
 */
typedef struct kmk_cc_eval_variables
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** The number of variables named in aVars. */
    uint32_t                cVars;
    /** Whether the 'local' qualifier was used (undefine only). */
    uint8_t                 fLocal;
    /** Pointer to the next instruction. */
    PKMKCCEVALCORE          pNext;
    /** The variable names.
     * Expressions will be expanded and split on space.
     * @remarks Plain text names are in variable_strcache. */
    KMKCCEXPSUBPROGORPLAIN  aVars[1];
} KMKCCEVALVARIABLES;
typedef KMKCCEVALVARIABLES *PKMKCCEVALVARIABLES;
/** Calculates the size of an KMKCCEVALVARIABLES structure for @a a_cVars. */
#define KMKCCEVALVARIABLES_SIZE(a_cVars) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALVARIABLES, aVars, a_cVars)

/**
 * Core structure for all conditionals (kKmkCcEvalInstr_if*).
 */
typedef struct kmk_cc_eval_if_core
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** Condition true: Pointer to the next instruction. */
    PKMKCCEVALCORE          pNextTrue;
    /** Condition false: Pointer to the next instruction (i.e. 'else if*'
     * or whatever follows 'else' / 'endif'. */
    PKMKCCEVALCORE          pNextFalse;
    /** Pointer to the previous conditional for 'else if*' directives.
     * This is only to assist the compilation process. */
    struct kmk_cc_eval_if_core *pPrevCond;
    /** Pointer to the jump out of the true block, if followed by 'else'.
     * This is only to assist the compilation process. */
    PKMKCCEVALJUMP          pTrueEndJump;
} KMKCCEVALIFCORE;
typedef KMKCCEVALIFCORE *PKMKCCEVALIFCORE;

/**
 * Instruction format for kKmkCcEvalInstr_ifdef_plain and
 * kKmkCcEvalInstr_ifndef_plain.
 * The variable name is known at compilation time.
 */
typedef struct kmk_cc_eval_ifdef_plain
{
    /** The 'if' core structure. */
    KMKCCEVALIFCORE         IfCore;
    /** The name of the variable (points into variable_strcache). */
    const char             *pszName;
} KMKCCEVALIFDEFPLAIN;
typedef KMKCCEVALIFDEFPLAIN *PKMKCCEVALIFDEFPLAIN;

/**
 * Instruction format for kKmkCcEvalInstr_ifdef_dynamic and
 * kKmkCcEvalInstr_ifndef_dynamic.
 * The variable name is dynamically expanded at run time.
 */
typedef struct kmk_cc_eval_ifdef_dynamic
{
    /** The 'if' core structure. */
    KMKCCEVALIFCORE         IfCore;
    /** Alignment padding, MBZ. */
    KU32                    uPadding;
    /** The subprogram that will give us the variable name. */
    KMKCCEXPSUBPROG         NameSubprog;
} KMKCCEVALIFDEFDYNAMIC;
typedef KMKCCEVALIFDEFDYNAMIC *PKMKCCEVALIFDEFDYNAMIC;

/**
 * Instruction format for kKmkCcEvalInstr_ifeq and kKmkCcEvalInstr_ifneq.
 */
typedef struct kmk_cc_eval_ifeq
{
    /** The 'if' core structure. */
    KMKCCEVALIFCORE         IfCore;
    /** The left hand side string expression (dynamic or plain). */
    KMKCCEXPSUBPROGORPLAIN  Left;
    /** The rigth hand side string expression (dynamic or plain). */
    KMKCCEXPSUBPROGORPLAIN  Right;
} KMKCCEVALIFEQ;
typedef KMKCCEVALIFEQ *PKMKCCEVALIFEQ;

/**
 * Instruction format for kKmkCcEvalInstr_if1of and kKmkCcEvalInstr_ifn1of.
 *
 * @todo This can be optimized further by pre-hashing plain text items.  One of
 *       the sides are usually plain text.
 */
typedef struct kmk_cc_eval_if1of
{
    /** The 'if' core structure. */
    KMKCCEVALIFCORE         IfCore;
    /** The left hand side string expression (dynamic or plain). */
    KMKCCEXPSUBPROGORPLAIN  Left;
    /** The rigth hand side string expression (dynamic or plain). */
    KMKCCEXPSUBPROGORPLAIN  Right;
} KMKCCEVALIF1OF;
typedef KMKCCEVALIF1OF *PKMKCCEVALIF1OF;

/**
 * Instruction format for kKmkCcEvalInstr_if.
 *
 * @todo Parse and compile the expression.  At least strip whitespace in it.
 */
typedef struct kmk_cc_eval_if_expr
{
    /** The 'if' core structure. */
    KMKCCEVALIFCORE         IfCore;
    /** The expression string length. */
    uint16_t                cchExpr;
    /** The expression string. */
    char                    szExpr[1];
} KMKCCEVALIFEXPR;
typedef KMKCCEVALIFEXPR *PKMKCCEVALIFEXPR;
/** Calculates the size of an KMKCCEVALIFEXPR structure for @a a_cchExpr long
 * expression string (terminator is automatically added).  */
#define KMKCCEVALIFEXPR_SIZE(a_cchExpr) KMK_CC_BLOCK_ALIGN_SIZE(KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALIFEXPR, szExpr, (a_cchExpr) + 1))

/**
 * Instruction format for kKmkCcEvalInstr_include,
 * kKmkCcEvalInstr_include_silent, kKmkCcEvalInstr_includedep,
 * kKmkCcEvalInstr_includedep_queue, kKmkCcEvalInstr_includedep_flush.
 */
typedef struct kmk_cc_eval_include
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** The number of files. */
    uint32_t                cFiles;
    /** Pointer to the next instruction (subprogs and strings after this one). */
    PKMKCCEVALCORE          pNext;
    /** The files to be included.
     * Expressions will be expanded and split on space.
     * @todo Plain text file name could be replaced by file string cache entries. */
    KMKCCEXPSUBPROGORPLAIN  aFiles[1];
} KMKCCEVALINCLUDE;
typedef KMKCCEVALINCLUDE *PKMKCCEVALINCLUDE;
/** Calculates the size of an KMKCCEVALINCLUDE structure for @a a_cFiles files. */
#define KMKCCEVALINCLUDE_SIZE(a_cFiles) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALINCLUDE, aFiles, a_cFiles)

/**
 * Instruction format for kKmkCcEvalInstr_recipe_no_commands,
 * kKmkCcEvalInstr_recipe_start_normal,
 * kKmkCcEvalInstr_recipe_start_double_colon, kKmkCcEvalInstr_includedep_queue,
 * kKmkCcEvalInstr_recipe_start_pattern.
 */
typedef struct kmk_cc_eval_recipe
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** The total number of files and dependencies in aFilesAndDeps. */
    uint16_t                cFilesAndDeps;

    /** Number of targets (from index 0).
     * This is always 1 if this is an explicit multitarget or pattern recipe,
     * indicating the main target. */
    uint16_t                cTargets;
    /** Explicit multitarget & patterns: First always made target. */
    uint16_t                iFirstAlwaysMadeTargets;
    /** Explicit multitarget & patterns: Number of always targets. */
    uint16_t                cAlwaysMadeTargets;
    /** Explicit multitarget: First maybe made target. */
    uint16_t                iFirstMaybeTarget;
    /** Explicit multitarget: Number of maybe made targets. */
    uint16_t                cMaybeTargets;

    /** First dependency. */
    uint16_t                iFirstDep;
    /** Number of ordinary dependencies. */
    uint16_t                cDeps;
    /** First order only dependency. */
    uint16_t                iFirstOrderOnlyDep;
    /** Number of ordinary dependencies. */
    uint16_t                cOrderOnlyDeps;

    /** Pointer to the next instruction (subprogs and strings after this one). */
    PKMKCCEVALCORE          pNext;
    /** The .MUST_MAKE variable value, if present.
     * If not present, this is a zero length plain string. */
    KMKCCEXPSUBPROGORPLAIN  MustMake;
    /** The target files and dependencies.
     * This is sorted into several sections, as defined by the above indexes and
     * counts.  Expressions will be expanded and split on space.
     *
     * The KMKCCEXPSUBPROGORPLAIN::bUser member one of KMKCCEVALRECIPE_FD_XXX.
     *
     * @todo Plain text file name could be replaced by file string cache entries. */
    KMKCCEXPSUBPROGORPLAIN  aFilesAndDeps[1];
} KMKCCEVALRECIPE;
typedef KMKCCEVALRECIPE *PKMKCCEVALRECIPE;
/** Calculates the size of an KMKCCEVALRECIPE structure for @a a_cFiles
 *  files. */
#define KMKCCEVALRECIPE_SIZE(a_cFilesAndDeps) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALRECIPE, aFilesAndDeps, a_cFilesAndDeps)
/** @name KMKCCEVALRECIPE_FD_XXX - Values for KMKCCEVALRECIPE::aFilesAndDeps[x].bUser
 * @{  */
#define KMKCCEVALRECIPE_FD_NORMAL                   0
#define KMKCCEVALRECIPE_FD_SEC_EXP                  1
#define KMKCCEVALRECIPE_FD_SPECIAL_POSIX            2
#define KMKCCEVALRECIPE_FD_SPECIAL_SECONDEXPANSION  3
#define KMKCCEVALRECIPE_FD_SPECIAL_ONESHELL         4
/** @} */


/**
 * Instruction format for kKmkCcEvalInstr_recipe_commands.
 */
typedef struct kmk_cc_eval_recipe_commands
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** The number of commands. */
    uint32_t                cCommands;
    /** Pointer to the next instruction (subprogs and strings after this one). */
    PKMKCCEVALCORE          pNext;
    /** Commands to add to the current recipe.
     * Expressions will be expanded and split on newline? */
    KMKCCEXPSUBPROGORPLAIN  aCommands[1];
} KMKCCEVALRECIPECOMMANDS;
typedef KMKCCEVALRECIPECOMMANDS *PKMKCCEVALRECIPECOMMANDS;
/** Calculates the size of an KMKCCEVALRECIPECOMMANDS structure for
 * @a a_cCommands commands. */
#define KMKCCEVALRECIPECOMMANDS_SIZE(a_cCommands) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALRECIPECOMMANDS, aCommands, a_cCommands)

/**
 * Instruction format for kKmkCcEvalInstr_vpath and
 * kKmkCcEvalInstr_vpath_clear_pattern.
 */
typedef struct kmk_cc_eval_vpath
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** The number of search directories.
     * This will be zero for kKmkCcEvalInstr_vpath_clear_pattern. */
    uint32_t                cDirs;
    /** Pointer to the next instruction (subprogs and strings after this one). */
    PKMKCCEVALCORE          pNext;
    /** The pattern. */
    KMKCCEXPSUBPROGORPLAIN  Pattern;
    /** The directory. Expressions will be expanded and split on space. */
    KMKCCEXPSUBPROGORPLAIN  aDirs[1];
} KMKCCEVALVPATH;
typedef KMKCCEVALVPATH *PKMKCCEVALVPATH;
/** Calculates the size of an KMKCCEVALVPATH structure for @a a_cFiles files. */
#define KMKCCEVALVPATH_SIZE(a_cFiles) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALVPATH, aDirs, a_cDirs)


/**
 * Instruction format for kKmkCcEvalInstr_expand.
 */
typedef struct kmk_cc_eval_expand
{
    /** The core instruction. */
    KMKCCEVALCORE           Core;
    /** Alignment padding, MBZ. */
    KU32                    uPadding;
    /** The expansion subprogram that to execute and evaluate the output of. */
    KMKCCEXPSUBPROG         Subprog;
} KMKCCEVALEXPAND;
typedef KMKCCEVALEXPAND *PKMKCCEVALEXPAND;


/**
 * Makefile evaluation program.
 */
typedef struct kmk_cc_evalprog
{
    /** Pointer to the first instruction for this program. */
    PKMKCCEVALCORE          pFirstInstr;
    /** List of blocks for this program (LIFO). */
    PKMKCCBLOCK             pBlockTail;
    /** The name of the file containing this program. */
    const char             *pszFilename;
    /** The name of the variable containing this program, if applicable.  */
    const char             *pszVarName;
#ifdef KMK_CC_STRICT
    /** The hash of the input string.  Used to check that we get all the change
     * notifications we require. */
    uint32_t                uInputHash;
#endif
    /** Reference count. */
    uint32_t volatile       cRefs;
} KMKCCEVALPROG;
typedef KMKCCEVALPROG *PKMKCCEVALPROG;

/** @} */


/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
static uint32_t g_cVarForExpandCompilations = 0;
static uint32_t g_cVarForExpandExecs = 0;
static uint32_t g_cVarForEvalCompilations = 0;
static uint32_t g_cVarForEvalExecs = 0;
static uint32_t g_cFileForEvalCompilations = 0;
static uint32_t g_cFileForEvalExecs = 0;
#ifdef KMK_CC_WITH_STATS
static uint32_t g_cBlockAllocated = 0;
static uint32_t g_cbAllocated = 0;

static uint32_t g_cBlocksAllocatedExpProgs = 0;
static uint32_t g_cbAllocatedExpProgs = 0;
static uint32_t g_cSingleBlockExpProgs = 0;
static uint32_t g_cTwoBlockExpProgs = 0;
static uint32_t g_cMultiBlockExpProgs = 0;
static uint32_t g_cbUnusedMemExpProgs = 0;

static uint32_t g_cBlocksAllocatedEvalProgs = 0;
static uint32_t g_cbAllocatedEvalProgs = 0;
static uint32_t g_cSingleBlockEvalProgs = 0;
static uint32_t g_cTwoBlockEvalProgs = 0;
static uint32_t g_cMultiBlockEvalProgs = 0;
static uint32_t g_cbUnusedMemEvalProgs = 0;

#endif

/** Generic character classification, taking an 'unsigned char' index.
 * ASSUMES unsigned char is 8-bits. */
static uint16_t g_abEvalCcChars[256];


/**
 * Makefile evaluation keywords.
 */
static const char * const g_apszEvalKeywords[] =
{
    "define",
    "export",
    "else",
    "endef",
    "endif",
    "ifdef",
    "ifndef",
    "ifeq",
    "ifneq",
    "if1of",
    "ifn1of",
    "if",
    "include",
    "includedep",
    "includedep-queue",
    "includedep-flush",
    "local",
    "override",
    "private",
    "sinclude",
    "unexport",
    "undefine",
    "vpath",
    "-include",
};


/** This is parallel to KMKCCEVALINSTR.   */
static const char * const g_apszEvalInstrNms[] =
{
    "jump",
    "assign_recursive",
    "assign_simple",
    "assign_append",
    "assign_prepend",
    "assign_if_new",
    "define_recursive",
    "define_if_new",
    "export",
    "unexport",
    "export_all",
    "unexport_all",
    "undefine",
    "ifdef_plain",
    "ifndef_plain",
    "ifdef_dynamic",
    "ifndef_dynamic",
    "ifeq",
    "ifneq",
    "if1of",
    "ifn1of",
    "if",
    "include",
    "include_silent",
    "includedep",
    "includedep_queue",
    "includedep_flush",
    "recipe_no_commands",
    "recipe_start_normal",
    "recipe_start_double_colon",
    "recipe_start_pattern",
    "recipe_commands",
    "recipe_end",
    "recipe_cancel_pattern",
    "vpath",
    "vpath_clear_pattern",
    "vpath_clear_all",
};

/*********************************************************************************************************************************
*   Internal Functions                                                                                                           *
*********************************************************************************************************************************/
static int kmk_cc_exp_compile_subprog(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr, PKMKCCEXPSUBPROG pSubprog);
static char *kmk_exec_expand_subprog_to_tmp(PKMKCCEXPSUBPROG pSubprog, uint32_t *pcch);


/**
 * Initializes global variables for the 'compiler'.
 */
void kmk_cc_init(void)
{
    unsigned i;

    /*
     * Initialize the bitmap.
     */
    memset(g_abEvalCcChars, 0, sizeof(g_abEvalCcChars));

    /* blank chars */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ' ',  KMK_CC_EVAL_CH_BLANK);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\t', KMK_CC_EVAL_CH_BLANK);

    /* space chars and zero terminator. */
#define MY_SPACE_BITS KMK_CC_EVAL_CH_SPACE | KMK_CC_EVAL_CH_SPACE_OR_BACKSLASH | KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ' ',  MY_SPACE_BITS);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\t', MY_SPACE_BITS);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\n', MY_SPACE_BITS | KMK_CC_EVAL_CH_EOL_CANDIDATE);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\v', MY_SPACE_BITS);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\f', MY_SPACE_BITS);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\r', MY_SPACE_BITS | KMK_CC_EVAL_CH_EOL_CANDIDATE);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\\', KMK_CC_EVAL_CH_SPACE_OR_BACKSLASH | KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE);
#undef MY_SPACE_BITS

    /* keywords  */
    for (i = 0; i < K_ELEMENTS(g_apszEvalKeywords); i++)
    {
#ifdef KMK_CC_STRICT
        size_t cch = strlen(g_apszEvalKeywords[i]);
        KMK_CC_ASSERT(cch >= KMK_CC_EVAL_KEYWORD_MIN);
        KMK_CC_ASSERT(cch <= KMK_CC_EVAL_KEYWORD_MAX);
#endif

        KMK_CC_EVAL_BM_OR(g_abEvalCcChars, g_apszEvalKeywords[i][0], KMK_CC_EVAL_CH_1ST_IN_KEYWORD);
        KMK_CC_EVAL_BM_OR(g_abEvalCcChars, g_apszEvalKeywords[i][1], KMK_CC_EVAL_CH_2ND_IN_KEYWORD);
    }
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'd', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* define */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'e', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* export, endef */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'l', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* local */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'o', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* override */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'p', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* private */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'u', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* undefine, unexport */

    /* Assignment punctuation and recipe stuff. */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '=', KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ':', KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE);

    /* For locating the end of variable expansion.  */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '(', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ')', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '{', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '}', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\\', KMK_CC_EVAL_CH_PAREN_OR_SLASH);

    /* For parsing ifeq and if1of expressions. (GNU weirdly does not respect {} style function references.)  */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '(', KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ')', KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ',', KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '$', KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR);

    /* Misc. */
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '$',  KMK_CC_EVAL_CH_DOLLAR);
    KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\\', KMK_CC_EVAL_CH_BACKSLASH);

    /*
     * Check that the eval instruction names match up.
     */
    KMK_CC_ASSERT(strcmp(g_apszEvalInstrNms[kKmkCcEvalInstr_ifneq], "ifneq") == 0);
    KMK_CC_ASSERT(strcmp(g_apszEvalInstrNms[kKmkCcEvalInstr_vpath_clear_all], "vpath_clear_all") == 0);
}


/**
 * Prints stats (for kmk -p).
 */
void kmk_cc_print_stats(void)
{
#ifdef KMK_CC_WITH_STATS
    uint32_t const cEvalCompilations = g_cFileForEvalCompilations + g_cVarForEvalCompilations;
#endif

    puts(_("\n# The kmk 'compiler' and kmk 'program executor':\n"));

    printf(_("# Variables compiled for string expansion: %6u\n"), g_cVarForExpandCompilations);
    printf(_("# Variables string expansion runs:         %6u\n"), g_cVarForExpandExecs);
    printf(_("# String expansion runs per compile:       %6u\n"), g_cVarForExpandExecs / g_cVarForExpandCompilations);
#ifdef KMK_CC_WITH_STATS
    printf(_("#          Single alloc block exp progs:   %6u (%u%%)\n"
             "#             Two alloc block exp progs:   %6u (%u%%)\n"
             "#   Three or more alloc block exp progs:   %6u (%u%%)\n"
             ),
           g_cSingleBlockExpProgs, (uint32_t)((uint64_t)g_cSingleBlockExpProgs * 100 / g_cVarForExpandCompilations),
           g_cTwoBlockExpProgs,    (uint32_t)((uint64_t)g_cTwoBlockExpProgs    * 100 / g_cVarForExpandCompilations),
           g_cMultiBlockExpProgs,  (uint32_t)((uint64_t)g_cMultiBlockExpProgs  * 100 / g_cVarForExpandCompilations));
    printf(_("#  Total amount of memory for exp progs: %8u bytes\n"
             "#                                    in:   %6u blocks\n"
             "#                        avg block size:   %6u bytes\n"
             "#                         unused memory: %8u bytes (%u%%)\n"
             "#           avg unused memory per block:   %6u bytes\n"
             "\n"),
           g_cbAllocatedExpProgs, g_cBlocksAllocatedExpProgs, g_cbAllocatedExpProgs / g_cBlocksAllocatedExpProgs,
           g_cbUnusedMemExpProgs, (uint32_t)((uint64_t)g_cbUnusedMemExpProgs * 100 / g_cbAllocatedExpProgs),
           g_cbUnusedMemExpProgs / g_cBlocksAllocatedExpProgs);
    puts("");
#endif
    printf(_("# Variables compiled for string eval:      %6u\n"), g_cVarForEvalCompilations);
    printf(_("# Variables string eval runs:              %6u\n"), g_cVarForEvalExecs);
    printf(_("# String evals runs per compile:           %6u\n"), g_cVarForEvalExecs / g_cVarForEvalCompilations);
    printf(_("# Files compiled:                          %6u\n"), g_cFileForEvalCompilations);
    printf(_("# Files runs:                              %6u\n"), g_cFileForEvalExecs);
    printf(_("# Files eval runs per compile:             %6u\n"), g_cFileForEvalExecs / g_cFileForEvalCompilations);
#ifdef KMK_CC_WITH_STATS
    printf(_("#         Single alloc block eval progs:   %6u (%u%%)\n"
             "#            Two alloc block eval progs:   %6u (%u%%)\n"
             "#  Three or more alloc block eval progs:   %6u (%u%%)\n"
             ),
           g_cSingleBlockEvalProgs, (uint32_t)((uint64_t)g_cSingleBlockEvalProgs * 100 / cEvalCompilations),
           g_cTwoBlockEvalProgs,    (uint32_t)((uint64_t)g_cTwoBlockEvalProgs    * 100 / cEvalCompilations),
           g_cMultiBlockEvalProgs,  (uint32_t)((uint64_t)g_cMultiBlockEvalProgs  * 100 / cEvalCompilations));
    printf(_("# Total amount of memory for eval progs: %8u bytes\n"
             "#                                    in:   %6u blocks\n"
             "#                        avg block size:   %6u bytes\n"
             "#                         unused memory: %8u bytes (%u%%)\n"
             "#           avg unused memory per block:   %6u bytes\n"
             "\n"),
           g_cbAllocatedEvalProgs, g_cBlocksAllocatedEvalProgs, g_cbAllocatedEvalProgs / g_cBlocksAllocatedEvalProgs,
           g_cbUnusedMemEvalProgs, (uint32_t)((uint64_t)g_cbUnusedMemEvalProgs * 100 / g_cbAllocatedEvalProgs),
           g_cbUnusedMemEvalProgs / g_cBlocksAllocatedEvalProgs);
    puts("");
    printf(_("#   Total amount of block mem allocated: %8u bytes\n"), g_cbAllocated);
    printf(_("#       Total number of block allocated: %8u\n"), g_cBlockAllocated);
    printf(_("#                    Average block size: %8u byte\n"), g_cbAllocated / g_cBlockAllocated);
#endif

    puts("");
}


/*
 *
 * Various utility functions.
 * Various utility functions.
 * Various utility functions.
 *
 */

/**
 * Counts the number of dollar chars in the string.
 *
 * @returns Number of dollar chars.
 * @param   pchStr      The string to search (does not need to be zero
 *                      terminated).
 * @param   cchStr      The length of the string.
 */
static uint32_t kmk_cc_count_dollars(const char *pchStr, uint32_t cchStr)
{
    uint32_t cDollars = 0;
    const char *pch;
    while ((pch = memchr(pchStr, '$', cchStr)) != NULL)
    {
        cDollars++;
        cchStr -= pch - pchStr + 1;
        pchStr  = pch + 1;
    }
    return cDollars;
}

#ifdef KMK_CC_STRICT
/**
 * Used to check that function arguments are left alone.
 * @returns Updated hash.
 * @param   uHash       The current hash value.
 * @param   psz         The string to hash.
 */
static uint32_t kmk_cc_debug_string_hash(uint32_t uHash, const char *psz)
{
    unsigned char ch;
    while ((ch = *(unsigned char const *)psz++) != '\0')
        uHash = (uHash << 6) + (uHash << 16) - uHash + (unsigned char)ch;
    return uHash;
}

/**
 * Used to check that function arguments are left alone.
 * @returns Updated hash.
 * @param   uHash       The current hash value.
 * @param   pch         The string to hash, not terminated.
 * @param   cch         The number of chars to hash.
 */
static uint32_t kmk_cc_debug_string_hash_n(uint32_t uHash, const char *pch, uint32_t cch)
{
    while (cch-- > 0)
    {
        unsigned char ch = *(unsigned char const *)pch++;
        uHash = (uHash << 6) + (uHash << 16) - uHash + (unsigned char)ch;
    }
    return uHash;
}

#endif



/*
 *
 * The allocator.
 * The allocator.
 * The allocator.
 *
 */


/**
 * For the first allocation using the block allocator.
 *
 * @returns Pointer to the first allocation (@a cbFirst in size).
 * @param   ppBlockTail         Where to return the pointer to the first block.
 * @param   cbFirst             The size of the first allocation.
 * @param   cbHint              Hint about how much memory we might be needing.
 */
static void *kmk_cc_block_alloc_first(PKMKCCBLOCK *ppBlockTail, size_t cbFirst, size_t cbHint)
{
    uint32_t        cbBlock;
    PKMKCCBLOCK     pNewBlock;

    KMK_CC_ASSERT_ALIGNED(cbFirst, sizeof(void *));
    KMK_CC_ASSERT(cbFirst <= 128);

    /*
     * Turn the hint into a block size.
     */
    cbHint += cbFirst;
    if (cbHint <= 512)
    {
        if (cbHint <= 256)
        {
            if (cbFirst <= 64)
                cbBlock = 128;
            else
                cbBlock = 256;
        }
        else
            cbBlock = 256;
    }
    else if (cbHint < 2048)
        cbBlock = 1024;
    else if (cbHint < 3072)
        cbBlock = 2048;
    else
        cbBlock = 4096;

    /*
     * Allocate and initialize the first block.
     */
    pNewBlock = (PKMKCCBLOCK)xmalloc(cbBlock);
    pNewBlock->cbBlock = cbBlock;
    pNewBlock->offNext = sizeof(*pNewBlock) + cbFirst;
    pNewBlock->pNext   = NULL;
    *ppBlockTail = pNewBlock;

#ifdef KMK_CC_WITH_STATS
    g_cBlockAllocated++;
    g_cbAllocated += cbBlock;
#endif

    return pNewBlock + 1;
}


/**
 * Used for getting the address of the next instruction.
 *
 * @returns Pointer to the next allocation.
 * @param   pBlockTail          The allocator tail pointer.
 */
static void *kmk_cc_block_get_next_ptr(PKMKCCBLOCK pBlockTail)
{
    return (char *)pBlockTail + pBlockTail->offNext;
}


/**
 * Realigns the allocator after doing byte or string allocations.
 *
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 */
static void kmk_cc_block_realign(PKMKCCBLOCK *ppBlockTail)
{
    PKMKCCBLOCK pBlockTail = *ppBlockTail;
    uint32_t    offNext    = pBlockTail->offNext;
    if (offNext & (sizeof(void *) - 1U))
    {
        pBlockTail->offNext = KMK_CC_BLOCK_ALIGN_SIZE(offNext);
        KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_realign: offNext=%#x -> %#x\n", offNext, pBlockTail->offNext));
        KMK_CC_ASSERT(pBlockTail->cbBlock - pBlockTail->offNext >= sizeof(KMKCCEXPJUMP));
    }
}


/**
 * Grows the allocation with another block, byte allocator case.
 *
 * @returns Pointer to the byte allocation.
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   cb                  The number of bytes to allocate.
 */
static void *kmk_cc_block_byte_alloc_grow(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
{
    PKMKCCBLOCK     pOldBlock  = *ppBlockTail;
    PKMKCCBLOCK     pPrevBlock = pOldBlock->pNext;
    PKMKCCBLOCK     pNewBlock;
    uint32_t        cbBlock;

    /*
     * Check if there accidentally is some space left in the previous block first.
     */
    if (   pPrevBlock
        && pPrevBlock->cbBlock - pPrevBlock->offNext >= cb)
    {
        void *pvRet = (char *)pPrevBlock + pPrevBlock->offNext;
        pPrevBlock->offNext += cb;
        KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_byte_alloc_grow: %p LB %#x offNext=%#x [prev]\n", pvRet, cb, pPrevBlock->offNext));
        return pvRet;
    }

    /*
     * Allocate a new block.
     */

    /* Figure the block size. */
    cbBlock = pOldBlock->cbBlock;
    while (cbBlock - sizeof(KMKCCEXPJUMP) - sizeof(*pNewBlock) < cb)
        cbBlock *= 2;

    /* Allocate and initialize the block it with the new instruction already accounted for. */
    pNewBlock = (PKMKCCBLOCK)xmalloc(cbBlock);
    pNewBlock->cbBlock = cbBlock;
    pNewBlock->offNext = sizeof(*pNewBlock) + cb;
    pNewBlock->pNext   = pOldBlock;
    *ppBlockTail = pNewBlock;

#ifdef KMK_CC_WITH_STATS
    g_cBlockAllocated++;
    g_cbAllocated += cbBlock;
#endif

    KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_byte_alloc_grow: %p LB %#x offNext=%#x\n", pNewBlock + 1, cb, pNewBlock->offNext));
    return pNewBlock + 1;
}


/**
 * Make a byte allocation.
 *
 * Must call kmk_cc_block_realign() when done doing byte and string allocations.
 *
 * @returns Pointer to the byte allocation (byte aligned).
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   cb                  The number of bytes to allocate.
 */
static void *kmk_cc_block_byte_alloc(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
{
    PKMKCCBLOCK pBlockTail = *ppBlockTail;
    uint32_t    cbLeft     = pBlockTail->cbBlock - pBlockTail->offNext;

    KMK_CC_ASSERT(cbLeft >= sizeof(KMKCCEXPJUMP));
    if (cbLeft >= cb + sizeof(KMKCCEXPJUMP))
    {
        void *pvRet = (char *)pBlockTail + pBlockTail->offNext;
        pBlockTail->offNext += cb;
        KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_byte_alloc: %p LB %#x offNext=%#x\n", pvRet, cb, pBlockTail->offNext));
        return pvRet;
    }
    return kmk_cc_block_byte_alloc_grow(ppBlockTail, cb);
}


/**
 * Duplicates the given string in a byte allocation.
 *
 * Must call kmk_cc_block_realign() when done doing byte and string allocations.
 *
 * @returns Pointer to the byte allocation (byte aligned).
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   cb                  The number of bytes to allocate.
 */
static const char *kmk_cc_block_strdup(PKMKCCBLOCK *ppBlockTail, const char *pachStr, uint32_t cchStr)
{
    char *pszCopy;
    if (cchStr)
    {
        pszCopy = kmk_cc_block_byte_alloc(ppBlockTail, cchStr + 1);
        memcpy(pszCopy, pachStr, cchStr);
        pszCopy[cchStr] = '\0';
        return pszCopy;
    }
    return "";
}


/**
 * Grows the allocation with another block, string expansion program case.
 *
 * @returns Pointer to a string expansion instruction core.
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   cb                  The number of bytes to allocate.
 */
static PKMKCCEXPCORE kmk_cc_block_alloc_exp_grow(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
{
    PKMKCCBLOCK     pOldBlock = *ppBlockTail;
    PKMKCCBLOCK     pNewBlock;
    PKMKCCEXPCORE   pRet;
    PKMKCCEXPJUMP   pJump;

    /* Figure the block size. */
    uint32_t cbBlock = !pOldBlock->pNext ? 128 : pOldBlock->cbBlock;
    while (cbBlock - sizeof(KMKCCEXPJUMP) - sizeof(*pNewBlock) < cb)
        cbBlock *= 2;

    /* Allocate and initialize the block it with the new instruction already accounted for. */
    pNewBlock = (PKMKCCBLOCK)xmalloc(cbBlock);
    pNewBlock->cbBlock = cbBlock;
    pNewBlock->offNext = sizeof(*pNewBlock) + cb;
    pNewBlock->pNext   = pOldBlock;
    *ppBlockTail = pNewBlock;

#ifdef KMK_CC_WITH_STATS
    g_cBlockAllocated++;
    g_cbAllocated += cbBlock;
#endif

    pRet = (PKMKCCEXPCORE)(pNewBlock + 1);
    KMK_CC_ASSERT(((size_t)pRet & (sizeof(void *) - 1)) == 0);

    /* Emit jump. */
    pJump = (PKMKCCEXPJUMP)((char *)pOldBlock + pOldBlock->offNext);
    pJump->Core.enmOpcode = kKmkCcExpInstr_Jump;
    pJump->pNext = pRet;
    pOldBlock->offNext += sizeof(*pJump);
    KMK_CC_ASSERT(pOldBlock->offNext <= pOldBlock->cbBlock);

    KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_alloc_exp_grow: %p LB %#x offNext=%#x\n", pRet, cb, pNewBlock->offNext));
    return pRet;
}


/**
 * Allocates a string expansion instruction of size @a cb.
 *
 * @returns Pointer to a string expansion instruction core.
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   cb                  The number of bytes to allocate.
 */
static PKMKCCEXPCORE kmk_cc_block_alloc_exp(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
{
    PKMKCCBLOCK pBlockTail = *ppBlockTail;
    uint32_t    cbLeft = pBlockTail->cbBlock - pBlockTail->offNext;

    KMK_CC_ASSERT(cbLeft >= sizeof(KMKCCEXPJUMP));
    KMK_CC_ASSERT( (cb & (sizeof(void *) - 1)) == 0 || cb == sizeof(KMKCCEXPCORE) /* final */ );
    KMK_CC_ASSERT((pBlockTail->offNext & (sizeof(void *) - 1)) == 0);

    if (cbLeft >= cb + sizeof(KMKCCEXPJUMP))
    {
        PKMKCCEXPCORE pRet = (PKMKCCEXPCORE)((char *)pBlockTail + pBlockTail->offNext);
        pBlockTail->offNext += cb;
        KMK_CC_ASSERT(((size_t)pRet & (sizeof(void *) - 1)) == 0);
        KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_alloc_exp: %p LB %#x offNext=%#x\n", pRet, cb, pBlockTail->offNext));
        return pRet;
    }
    return kmk_cc_block_alloc_exp_grow(ppBlockTail, cb);
}


/**
 * Grows the allocation with another block, makefile evaluation program case.
 *
 * @returns Pointer to a makefile evaluation instruction core.
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   cb                  The number of bytes to allocate.
 */
static PKMKCCEVALCORE kmk_cc_block_alloc_eval_grow(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
{
    PKMKCCBLOCK     pOldBlock = *ppBlockTail;
    PKMKCCBLOCK     pNewBlock;
    PKMKCCEVALCORE  pRet;
    PKMKCCEVALJUMP  pJump;

    /* Figure the block size. */
    uint32_t cbBlock = !pOldBlock->pNext ? 128 : pOldBlock->cbBlock;
    while (cbBlock - sizeof(KMKCCEVALJUMP) - sizeof(*pNewBlock) < cb)
        cbBlock *= 2;

    /* Allocate and initialize the block it with the new instruction already accounted for. */
    pNewBlock = (PKMKCCBLOCK)xmalloc(cbBlock);
    pNewBlock->cbBlock = cbBlock;
    pNewBlock->offNext = sizeof(*pNewBlock) + cb;
    pNewBlock->pNext   = pOldBlock;
    *ppBlockTail = pNewBlock;

#ifdef KMK_CC_WITH_STATS
    g_cBlockAllocated++;
    g_cbAllocated += cbBlock;
#endif

    pRet = (PKMKCCEVALCORE)(pNewBlock + 1);

    /* Emit jump. */
    pJump = (PKMKCCEVALJUMP)((char *)pOldBlock + pOldBlock->offNext);
    pJump->Core.enmOpcode = kKmkCcEvalInstr_jump;
    pJump->pNext = pRet;
    pOldBlock->offNext += sizeof(*pJump);
    KMK_CC_ASSERT(pOldBlock->offNext <= pOldBlock->cbBlock);
    KMK_CC_ASSERT((pNewBlock->offNext & (sizeof(void *) - 1)) == 0);

    KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_alloc_eval_grow: %p LB %#x offNext=%#x (*ppBlockTail=%p, was %p)\n",
                          pRet, cb, pNewBlock->offNext, *ppBlockTail, pOldBlock));
    return pRet;
}


/**
 * Allocates a makefile evaluation instruction of size @a cb.
 *
 * @returns Pointer to a makefile evaluation instruction core.
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   cb                  The number of bytes to allocate.
 */
static PKMKCCEVALCORE kmk_cc_block_alloc_eval(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
{
    PKMKCCBLOCK pBlockTail = *ppBlockTail;
    uint32_t    cbLeft = pBlockTail->cbBlock - pBlockTail->offNext;

    KMK_CC_ASSERT(cbLeft >= sizeof(KMKCCEVALJUMP));
    KMK_CC_ASSERT( (cb & (sizeof(void *) - 1)) == 0 );
    KMK_CC_ASSERT((pBlockTail->offNext & (sizeof(void *) - 1)) == 0);

    if (cbLeft >= cb + sizeof(KMKCCEVALJUMP))
    {
        PKMKCCEVALCORE pRet = (PKMKCCEVALCORE)((char *)pBlockTail + pBlockTail->offNext);
        pBlockTail->offNext += cb;
        KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_alloc_eval: %p LB %#x offNext=%#x\n", pRet, cb, pBlockTail->offNext));
        return pRet;
    }
    return kmk_cc_block_alloc_eval_grow(ppBlockTail, cb);
}


/**
 * Frees all memory used by an allocator.
 *
 * @param   ppBlockTail         The allocator tail pointer.
 */
static void kmk_cc_block_free_list(PKMKCCBLOCK pBlockTail)
{
    while (pBlockTail)
    {
        PKMKCCBLOCK pThis = pBlockTail;
        pBlockTail = pBlockTail->pNext;
        free(pThis);
    }
}


/*
 *
 * The string expansion compiler.
 * The string expansion compiler.
 * The string expansion compiler.
 *
 */


/**
 * Emits a kKmkCcExpInstr_Return.
 *
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 */
static void kmk_cc_exp_emit_return(PKMKCCBLOCK *ppBlockTail)
{
    PKMKCCEXPCORE pCore = kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pCore));
    pCore->enmOpcode = kKmkCcExpInstr_Return;
    kmk_cc_block_realign(ppBlockTail);
}


/**
 * Checks if a function is known to mess up the arguments its given.
 *
 * When executing calls to "dirty" functions, all arguments must be duplicated
 * on the heap.
 *
 * @returns 1 if dirty, 0 if clean.
 * @param   pszFunction         The function name.
 */
static uint8_t kmk_cc_is_dirty_function(const char *pszFunction)
{
    switch (pszFunction[0])
    {
        default:
            return 0;

        case 'e':
            if (!strcmp(pszFunction, "eval"))
                return 1;
            if (!strcmp(pszFunction, "evalctx"))
                return 1;
            return 0;

        case 'f':
            if (!strcmp(pszFunction, "filter"))
                return 1;
            if (!strcmp(pszFunction, "filter-out"))
                return 1;
            if (!strcmp(pszFunction, "for"))
                return 1;
            return 0;

        case 's':
            if (!strcmp(pszFunction, "sort"))
                return 1;
            return 0;
    }
}


/**
 * Emits a function call instruction taking arguments that needs expanding.
 *
 * @returns 0 on success, non-zero on failure.
 * @param   ppBlockTail     Pointer to the allocator tail pointer.
 * @param   pszFunction     The function name (const string from function.c).
 * @param   pchArgs         Pointer to the arguments expression string, leading
 *                          any blanks has been stripped.
 * @param   cchArgs         The length of the arguments expression string.
 * @param   cArgs           Number of arguments found.
 * @param   chOpen          The char used to open the function call.
 * @param   chClose         The char used to close the function call.
 * @param   pfnFunction     The function implementation.
 * @param   cMaxArgs        Maximum number of arguments the function takes.
 */
static int kmk_cc_exp_emit_dyn_function(PKMKCCBLOCK *ppBlockTail, const char *pszFunction,
                                        const char *pchArgs, uint32_t cchArgs, uint32_t cArgs, char chOpen, char chClose,
                                        make_function_ptr_t pfnFunction, unsigned char cMaxArgs)
{
    uint32_t iArg;

    /*
     * The function instruction has variable size.  The maximum argument count
     * isn't quite like the minium one.  Zero means no limit.  While a non-zero
     * value means that any commas beyond the max will be taken to be part of
     * the final argument.
     */
    uint32_t            cActualArgs = cArgs <= cMaxArgs || !cMaxArgs ? cArgs : cMaxArgs;
    PKMKCCEXPDYNFUNC    pInstr  = (PKMKCCEXPDYNFUNC)kmk_cc_block_alloc_exp(ppBlockTail, KMKCCEXPDYNFUNC_SIZE(cActualArgs));
    pInstr->FnCore.Core.enmOpcode = kKmkCcExpInstr_DynamicFunction;
    pInstr->FnCore.cArgs          = cActualArgs;
    pInstr->FnCore.pfnFunction    = pfnFunction;
    pInstr->FnCore.pszFuncName    = pszFunction;
    pInstr->FnCore.fDirty         = kmk_cc_is_dirty_function(pszFunction);

    /*
     * Parse the arguments.  Plain arguments gets duplicated in the program
     * memory so that they are terminated and no extra processing is necessary
     * later on.  ASSUMES that the function implementations do NOT change
     * argument memory.  Other arguments the compiled into their own expansion
     * sub programs.
     */
    iArg = 0;
    for (;;)
    {
        /* Find the end of the argument. Check for $. */
        char     ch         = '\0';
        uint8_t  fDollar    = 0;
        int32_t  cDepth     = 0;
        uint32_t cchThisArg = 0;
        while (cchThisArg < cchArgs)
        {
            ch = pchArgs[cchThisArg];
            if (ch == chClose)
            {
                KMK_CC_ASSERT(cDepth > 0);
                if (cDepth > 0)
                    cDepth--;
            }
            else if (ch == chOpen)
                cDepth++;
            else if (ch == ',' && cDepth == 0 && iArg + 1 < cActualArgs)
                break;
            else if (ch == '$')
                fDollar = 1;
            cchThisArg++;
        }

        pInstr->aArgs[iArg].fSubprog = fDollar;
        if (fDollar)
        {
            /* Compile it. */
            int rc;
            kmk_cc_block_realign(ppBlockTail);
            rc = kmk_cc_exp_compile_subprog(ppBlockTail, pchArgs, cchThisArg, &pInstr->aArgs[iArg].u.Subprog);
            if (rc != 0)
                return rc;
        }
        else
        {
            /* Duplicate it. */
            pInstr->aArgs[iArg].u.Plain.psz = kmk_cc_block_strdup(ppBlockTail, pchArgs, cchThisArg);
            pInstr->aArgs[iArg].u.Plain.cch = cchThisArg;
        }
        iArg++;
        if (ch != ',')
            break;
        pchArgs += cchThisArg + 1;
        cchArgs -= cchThisArg + 1;
    }
    KMK_CC_ASSERT(iArg == cActualArgs);

    /*
     * Realign the allocator and take down the address of the next instruction.
     */
    kmk_cc_block_realign(ppBlockTail);
    pInstr->FnCore.pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
    return 0;
}


/**
 * Emits a function call instruction taking plain arguments.
 *
 * @returns 0 on success, non-zero on failure.
 * @param   ppBlockTail     Pointer to the allocator tail pointer.
 * @param   pszFunction     The function name (const string from function.c).
 * @param   pchArgs         Pointer to the arguments string, leading any blanks
 *                          has been stripped.
 * @param   cchArgs         The length of the arguments string.
 * @param   cArgs           Number of arguments found.
 * @param   chOpen          The char used to open the function call.
 * @param   chClose         The char used to close the function call.
 * @param   pfnFunction     The function implementation.
 * @param   cMaxArgs        Maximum number of arguments the function takes.
 */
static void kmk_cc_exp_emit_plain_function(PKMKCCBLOCK *ppBlockTail, const char *pszFunction,
                                           const char *pchArgs, uint32_t cchArgs, uint32_t cArgs, char chOpen, char chClose,
                                           make_function_ptr_t pfnFunction, unsigned char cMaxArgs)
{
    uint32_t iArg;

    /*
     * The function instruction has variable size.  The maximum argument count
     * isn't quite like the minium one.  Zero means no limit.  While a non-zero
     * value means that any commas beyond the max will be taken to be part of
     * the final argument.
     */
    uint32_t            cActualArgs = cArgs <= cMaxArgs || !cMaxArgs ? cArgs : cMaxArgs;
    PKMKCCEXPPLAINFUNC  pInstr  = (PKMKCCEXPPLAINFUNC)kmk_cc_block_alloc_exp(ppBlockTail, KMKCCEXPPLAINFUNC_SIZE(cActualArgs));
    pInstr->FnCore.Core.enmOpcode = kKmkCcExpInstr_PlainFunction;
    pInstr->FnCore.cArgs          = cActualArgs;
    pInstr->FnCore.pfnFunction    = pfnFunction;
    pInstr->FnCore.pszFuncName    = pszFunction;
    pInstr->FnCore.fDirty         = kmk_cc_is_dirty_function(pszFunction);

    /*
     * Parse the arguments.  Plain arguments gets duplicated in the program
     * memory so that they are terminated and no extra processing is necessary
     * later on.  ASSUMES that the function implementations do NOT change
     * argument memory.
     */
    iArg = 0;
    for (;;)
    {
        /* Find the end of the argument. */
        char     ch         = '\0';
        int32_t  cDepth     = 0;
        uint32_t cchThisArg = 0;
        while (cchThisArg < cchArgs)
        {
            ch = pchArgs[cchThisArg];
            if (ch == chClose)
            {
                KMK_CC_ASSERT(cDepth > 0);
                if (cDepth > 0)
                    cDepth--;
            }
            else if (ch == chOpen)
                cDepth++;
            else if (ch == ',' && cDepth == 0 && iArg + 1 < cActualArgs)
                break;
            cchThisArg++;
        }

        /* Duplicate it. */
        pInstr->apszArgs[iArg++] = kmk_cc_block_strdup(ppBlockTail, pchArgs, cchThisArg);
        if (ch != ',')
            break;
        pchArgs += cchThisArg + 1;
        cchArgs -= cchThisArg + 1;
    }

    KMK_CC_ASSERT(iArg == cActualArgs);
    pInstr->apszArgs[iArg] = NULL;

    /*
     * Realign the allocator and take down the address of the next instruction.
     */
    kmk_cc_block_realign(ppBlockTail);
    pInstr->FnCore.pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
}


/**
 * Emits a kKmkCcExpInstr_DynamicVariable.
 *
 * @returns 0 on success, non-zero on failure.
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   pchNameExpr         The name of the variable (ASSUMED presistent
 *                              thru-out the program life time).
 * @param   cchNameExpr         The length of the variable name. If zero,
 *                              nothing will be emitted.
 */
static int kmk_cc_exp_emit_dyn_variable(PKMKCCBLOCK *ppBlockTail, const char *pchNameExpr, uint32_t cchNameExpr)
{
    PKMKCCEXPDYNVAR pInstr;
    int rc;
    KMK_CC_ASSERT(cchNameExpr > 0);

    pInstr = (PKMKCCEXPDYNVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
    pInstr->Core.enmOpcode = kKmkCcExpInstr_DynamicVariable;

    rc = kmk_cc_exp_compile_subprog(ppBlockTail, pchNameExpr, cchNameExpr, &pInstr->Subprog);

    pInstr->pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
    return rc;
}


/**
 * Emits either a kKmkCcExpInstr_PlainVariable or
 * kKmkCcExpInstr_SearchAndReplacePlainVariable instruction.
 *
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   pchName             The name of the variable.  (Does not need to be
 *                              valid beyond the call.)
 * @param   cchName             The length of the variable name. If zero,
 *                              nothing will be emitted.
 */
static void kmk_cc_exp_emit_plain_variable_maybe_sr(PKMKCCBLOCK *ppBlockTail, const char *pchName, uint32_t cchName)
{
    if (cchName > 0)
    {
        /*
         * Hopefully, we're not expected to do any search and replace on the
         * expanded variable string later...  Requires both ':' and '='.
         */
        const char *pchEqual;
        const char *pchColon = (const char *)memchr(pchName, ':', cchName);
        if (   pchColon == NULL
            || (pchEqual = (const char *)memchr(pchColon + 1, ':', cchName - (pchColon - pchName - 1))) == NULL
            || pchEqual == pchEqual + 1)
        {
            PKMKCCEXPPLAINVAR pInstr = (PKMKCCEXPPLAINVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
            pInstr->Core.enmOpcode = kKmkCcExpInstr_PlainVariable;
            pInstr->pszName = strcache2_add(&variable_strcache, pchName, cchName);
        }
        else if (pchColon != pchName)
        {
            /*
             * Okay, we need to do search and replace the variable value.
             * This is performed by patsubst_expand_pat using '%' patterns.
             */
            uint32_t            cchName2   = (uint32_t)(pchColon - pchName);
            uint32_t            cchSearch  = (uint32_t)(pchEqual - pchColon - 1);
            uint32_t            cchReplace = cchName - cchName2 - cchSearch - 2;
            const char         *pchPct;
            char               *psz;
            PKMKCCEXPSRPLAINVAR pInstr;

            pInstr = (PKMKCCEXPSRPLAINVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
            pInstr->Core.enmOpcode = kKmkCcExpInstr_SearchAndReplacePlainVariable;
            pInstr->pszName = strcache2_add(&variable_strcache, pchName, cchName2);

            /* Figure out the search pattern, unquoting percent chars.. */
            psz = (char *)kmk_cc_block_byte_alloc(ppBlockTail, cchSearch + 2);
            psz[0] = '%';
            memcpy(psz + 1, pchColon + 1, cchSearch);
            psz[1 + cchSearch] = '\0';
            pchPct = find_percent(psz + 1); /* also performs unquoting */
            if (pchPct)
            {
                pInstr->pszSearchPattern    = psz + 1;
                pInstr->offPctSearchPattern = (uint32_t)(pchPct - psz - 1);
            }
            else
            {
                pInstr->pszSearchPattern    = psz;
                pInstr->offPctSearchPattern = 0;
            }

            /* Figure out the replacement pattern, unquoting percent chars.. */
            if (cchReplace == 0)
            {
                pInstr->pszReplacePattern    = "%";
                pInstr->offPctReplacePattern = 0;
            }
            else
            {
                psz = (char *)kmk_cc_block_byte_alloc(ppBlockTail, cchReplace + 2);
                psz[0] = '%';
                memcpy(psz + 1, pchEqual + 1, cchReplace);
                psz[1 + cchReplace] = '\0';
                pchPct = find_percent(psz + 1); /* also performs unquoting */
                if (pchPct)
                {
                    pInstr->pszReplacePattern    = psz + 1;
                    pInstr->offPctReplacePattern = (uint32_t)(pchPct - psz - 1);
                }
                else
                {
                    pInstr->pszReplacePattern    = psz;
                    pInstr->offPctReplacePattern = 0;
                }
            }

            /* Note down where the next instruction is after realigning the allocator. */
            kmk_cc_block_realign(ppBlockTail);
            pInstr->pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
        }
    }
}


/**
 * Emits a kKmkCcExpInstr_CopyString.
 *
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   pchStr              The string to emit (ASSUMED presistent thru-out
 *                              the program life time).
 * @param   cchStr              The number of chars to copy. If zero, nothing
 *                              will be emitted.
 */
static void kmk_cc_exp_emit_copy_string(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr)
{
    if (cchStr > 0)
    {
        PKMKCCEXPCOPYSTRING pInstr = (PKMKCCEXPCOPYSTRING)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
        pInstr->Core.enmOpcode = kKmkCcExpInstr_CopyString;
        pInstr->cchCopy = cchStr;
        pInstr->pachSrc = pchStr;
    }
}


/**
 * String expansion compilation function common to both normal and sub programs.
 *
 * @returns 0 on success, non-zero on failure.
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   pchStr              The expression to compile.
 * @param   cchStr              The length of the expression to compile.
 */
static int kmk_cc_exp_compile_common(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr)
{
    /*
     * Process the string.
     */
    while (cchStr > 0)
    {
        /* Look for dollar sign, marks variable expansion or dollar-escape. */
        int         rc;
        const char *pchDollar = memchr(pchStr, '$', cchStr);
        if (pchDollar)
        {
            /*
             * Check for multiple dollar chars.
             */
            uint32_t offDollar = (uint32_t)(pchDollar - pchStr);
            uint32_t cDollars  = 1;
            while (   offDollar + cDollars < cchStr
                   && pchStr[offDollar + cDollars] == '$')
                cDollars++;

            /*
             * Emit a string copy for any preceeding stuff, including half of
             * the dollars we found (dollar escape: $$ -> $).
             * (kmk_cc_exp_emit_copy_string ignore zero length strings).
             */
            kmk_cc_exp_emit_copy_string(ppBlockTail, pchStr, offDollar + cDollars / 2);
            pchStr += offDollar + cDollars;
            cchStr -= offDollar + cDollars;

            /*
             * Odd number of dollar chars means there is a variable to expand
             * or function to call.
             */
            if (cDollars & 1)
            {
                if (cchStr > 0)
                {
                    char const chOpen = *pchStr;
                    if (chOpen == '(' || chOpen == '{')
                    {
                        /* There are several alternative ways of finding the ending
                           parenthesis / braces.

                           GNU make does one thing for functions and variable containing
                           any '$' chars before the first closing char.  While for
                           variables where a closing char comes before any '$' char, a
                           simplified approach is taken.  This means that for example:

                                Given VAR=var, the expressions "$(var())" and
                                "$($(VAR)())" would be expanded differently.
                                In the first case the variable "var(" would be
                                used and in the second "var()".

                           This code will not duplicate this weird behavior, but work
                           the same regardless of whether there is a '$' char before
                           the first closing char. */
                        make_function_ptr_t pfnFunction;
                        const char         *pszFunction;
                        unsigned char       cMaxArgs;
                        unsigned char       cMinArgs;
                        char                fExpandArgs;
                        char const          chClose   = chOpen == '(' ? ')' : '}';
                        char                ch        = 0;
                        uint32_t            cchName   = 0;
                        uint32_t            cDepth    = 1;
                        uint32_t            cMaxDepth = 1;
                        cDollars = 0;

                        pchStr++;
                        cchStr--;

                        /* First loop: Identify potential function calls and dynamic expansion. */
                        KMK_CC_ASSERT(!func_char_map[(unsigned char)chOpen]);
                        KMK_CC_ASSERT(!func_char_map[(unsigned char)chClose]);
                        KMK_CC_ASSERT(!func_char_map[(unsigned char)'$']);
                        while (cchName < cchStr)
                        {
                            ch = pchStr[cchName];
                            if (!func_char_map[(unsigned char)ch])
                                break;
                            cchName++;
                        }

                        if (   cchName >= MIN_FUNCTION_LENGTH
                            && cchName <= MAX_FUNCTION_LENGTH
                            && (ISBLANK(ch) || ch == chClose || cchName == cchStr)
                            && (pfnFunction = lookup_function_for_compiler(pchStr, cchName, &cMinArgs, &cMaxArgs,
                                                                           &fExpandArgs, &pszFunction)) != NULL)
                        {
                            /*
                             * It's a function invocation, we should count parameters while
                             * looking for the end.
                             * Note! We use cchName for the length of the argument list.
                             */
                            uint32_t cArgs = 1;
                            if (ch != chClose)
                            {
                                /* Skip leading spaces before the first arg. */
                                cchName++;
                                while (cchName < cchStr && ISBLANK(pchStr[cchName]))
                                    cchName++;

                                pchStr += cchName;
                                cchStr -= cchName;
                                cchName = 0;

                                while (cchName < cchStr)
                                {
                                    ch = pchStr[cchName];
                                    if (ch == ',')
                                    {
                                        if (cDepth == 1)
                                            cArgs++;
                                    }
                                    else if (ch == chClose)
                                    {
                                        if (!--cDepth)
                                            break;
                                    }
                                    else if (ch == chOpen)
                                    {
                                        if (++cDepth > cMaxDepth)
                                            cMaxDepth = cDepth;
                                    }
                                    else if (ch == '$')
                                        cDollars++;
                                    cchName++;
                                }
                            }
                            else
                            {
                                pchStr += cchName;
                                cchStr -= cchName;
                                cchName = 0;
                            }
                            if (cArgs < cMinArgs)
                            {
                                fatal(NULL, _("Function '%s' takes a minimum of %d arguments: %d given"),
                                      pszFunction, (int)cMinArgs, (int)cArgs);
                                return -1; /* not reached */
                            }
                            if (cDepth != 0)
                            {
                                fatal(NULL, chOpen == '('
                                      ? _("Missing closing parenthesis calling '%s'") : _("Missing closing braces calling '%s'"),
                                      pszFunction);
                                return -1; /* not reached */
                            }
                            if (cMaxDepth > 16 && fExpandArgs)
                            {
                                fatal(NULL, _("Too many levels of nested function arguments expansions: %s"), pszFunction);
                                return -1; /* not reached */
                            }
                            if (!fExpandArgs || cDollars == 0)
                                kmk_cc_exp_emit_plain_function(ppBlockTail, pszFunction, pchStr, cchName,
                                                               cArgs, chOpen, chClose, pfnFunction, cMaxArgs);
                            else
                            {
                                rc = kmk_cc_exp_emit_dyn_function(ppBlockTail, pszFunction, pchStr, cchName,
                                                                  cArgs, chOpen, chClose, pfnFunction, cMaxArgs);
                                if (rc != 0)
                                    return rc;
                            }
                        }
                        else
                        {
                            /*
                             * Variable, find the end while checking whether anything needs expanding.
                             */
                            if (ch == chClose)
                                cDepth = 0;
                            else if (cchName < cchStr)
                            {
                                if (ch != '$')
                                {
                                    /* Second loop: Look for things that needs expanding. */
                                    while (cchName < cchStr)
                                    {
                                        ch = pchStr[cchName];
                                        if (ch == chClose)
                                        {
                                            if (!--cDepth)
                                                break;
                                        }
                                        else if (ch == chOpen)
                                        {
                                            if (++cDepth > cMaxDepth)
                                                cMaxDepth = cDepth;
                                        }
                                        else if (ch == '$')
                                            break;
                                        cchName++;
                                    }
                                }
                                if (ch == '$')
                                {
                                    /* Third loop: Something needs expanding, just find the end. */
                                    cDollars = 1;
                                    cchName++;
                                    while (cchName < cchStr)
                                    {
                                        ch = pchStr[cchName];
                                        if (ch == chClose)
                                        {
                                            if (!--cDepth)
                                                break;
                                        }
                                        else if (ch == chOpen)
                                        {
                                            if (++cDepth > cMaxDepth)
                                                cMaxDepth = cDepth;
                                        }
                                        cchName++;
                                    }
                                }
                            }
                            if (cDepth > 0) /* After warning, we just assume they're all there. */
                                error(NULL, chOpen == '(' ? _("Missing closing parenthesis ") : _("Missing closing braces"));
                            if (cMaxDepth >= 16)
                            {
                                fatal(NULL, _("Too many levels of nested variable expansions: '%.*s'"), (int)cchName + 2, pchStr - 1);
                                return -1; /* not reached */
                            }
                            if (cDollars == 0)
                                kmk_cc_exp_emit_plain_variable_maybe_sr(ppBlockTail, pchStr, cchName);
                            else
                            {
                                rc = kmk_cc_exp_emit_dyn_variable(ppBlockTail, pchStr, cchName);
                                if (rc != 0)
                                    return rc;
                            }
                        }
                        pchStr += cchName + 1;
                        cchStr -= cchName + (cDepth == 0);
                    }
                    else
                    {
                        /* Single character variable name. */
                        kmk_cc_exp_emit_plain_variable_maybe_sr(ppBlockTail, pchStr, 1);
                        pchStr++;
                        cchStr--;
                    }
                }
                else
                {
                    error(NULL, _("Unexpected end of string after $"));
                    break;
                }
            }
        }
        else
        {
            /*
             * Nothing more to expand, the remainder is a simple string copy.
             */
            kmk_cc_exp_emit_copy_string(ppBlockTail, pchStr, cchStr);
            break;
        }
    }

    /*
     * Emit final instruction.
     */
    kmk_cc_exp_emit_return(ppBlockTail);
    return 0;
}


/**
 * Initializes string expansion program statistics.
 * @param   pStats              Pointer to the statistics structure to init.
 */
static void kmk_cc_exp_stats_init(PKMKCCEXPSTATS pStats)
{
    pStats->cchAvg = 0;
}


/**
 * Compiles a string expansion subprogram.
 *
 * The caller typically make a call to kmk_cc_block_get_next_ptr after this
 * function returns to figure out where to continue executing.
 *
 * @returns 0 on success, non-zero on failure.
 * @param   ppBlockTail         Pointer to the allocator tail pointer.
 * @param   pchStr              Pointer to the string to compile an expansion
 *                              program for (ASSUMED to be valid for the
 *                              lifetime of the program).
 * @param   cchStr              The length of the string to compile. Expected to
 *                              be at least on char long.
 * @param   pSubprog            The subprogram structure to initialize.
 */
static int kmk_cc_exp_compile_subprog(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr, PKMKCCEXPSUBPROG pSubprog)
{
    KMK_CC_ASSERT(cchStr > 0);
    pSubprog->pFirstInstr = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
    kmk_cc_exp_stats_init(&pSubprog->Stats);
    return kmk_cc_exp_compile_common(ppBlockTail, pchStr, cchStr);
}


/**
 * Compiles a string expansion program.
 *
 * @returns Pointer to the program on success, NULL on failure.
 * @param   pchStr              Pointer to the string to compile an expansion
 *                              program for (ASSUMED to be valid for the
 *                              lifetime of the program).
 * @param   cchStr              The length of the string to compile. Expected to
 *                              be at least on char long.
 */
static PKMKCCEXPPROG kmk_cc_exp_compile(const char *pchStr, uint32_t cchStr)
{
    /*
     * Estimate block size, allocate one and initialize it.
     */
    PKMKCCEXPPROG   pProg;
    PKMKCCBLOCK     pBlock;
    pProg = kmk_cc_block_alloc_first(&pBlock, sizeof(*pProg),
                                     (kmk_cc_count_dollars(pchStr, cchStr) + 4)  * 8);
    if (pProg)
    {
        pProg->pBlockTail   = pBlock;
        pProg->pFirstInstr  = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(pBlock);
        kmk_cc_exp_stats_init(&pProg->Stats);
        pProg->cRefs        = 1;
#ifdef KMK_CC_STRICT
        pProg->uInputHash   = kmk_cc_debug_string_hash_n(0, pchStr, cchStr);
#endif

        /*
         * Join forces with the subprogram compilation code.
         */
        if (kmk_cc_exp_compile_common(&pProg->pBlockTail, pchStr, cchStr) == 0)
        {
#ifdef KMK_CC_WITH_STATS
            pBlock = pProg->pBlockTail;
            if (!pBlock->pNext)
                g_cSingleBlockExpProgs++;
            else if (!pBlock->pNext->pNext)
                g_cTwoBlockExpProgs++;
            else
                g_cMultiBlockExpProgs++;
            for (; pBlock; pBlock = pBlock->pNext)
            {
                g_cBlocksAllocatedExpProgs++;
                g_cbAllocatedExpProgs += pBlock->cbBlock;
                g_cbUnusedMemExpProgs += pBlock->cbBlock - pBlock->offNext;
            }
#endif
            return pProg;
        }
        kmk_cc_block_free_list(pProg->pBlockTail);
    }
    return NULL;
}


/**
 * Updates the recursive_without_dollar member of a variable structure.
 *
 * This avoid compiling string expansion programs with only a CopyString
 * instruction.  By setting recursive_without_dollar to 1, code calling
 * kmk_cc_compile_variable_for_expand and kmk_exec_expand_to_var_buf will
 * instead treat start treating it as a simple variable, which is faster.
 *
 * @returns The updated recursive_without_dollar value.
 * @param   pVar        Pointer to the variable.
 */
static int kmk_cc_update_variable_recursive_without_dollar(struct variable *pVar)
{
    int fValue;
    KMK_CC_ASSERT(pVar->recursive_without_dollar == 0);

    if (memchr(pVar->value, '$', pVar->value_length))
        fValue = -1;
    else
        fValue = 1;
    pVar->recursive_without_dollar = fValue;

    return fValue;
}


/**
 * Compiles a variable for string expansion.
 *
 * @returns Pointer to the string expansion program on success, NULL if no
 *          program was created.
 * @param   pVar        Pointer to the variable.
 */
struct kmk_cc_expandprog *kmk_cc_compile_variable_for_expand(struct variable *pVar)
{
    KMK_CC_ASSERT(strlen(pVar->value) == pVar->value_length);
    KMK_CC_ASSERT(!pVar->expandprog);
    KMK_CC_ASSERT(pVar->recursive_without_dollar <= 0);

    if (   !pVar->expandprog
        && pVar->recursive)
    {
        if (   pVar->recursive_without_dollar < 0
            || (   pVar->recursive_without_dollar == 0
                && kmk_cc_update_variable_recursive_without_dollar(pVar) < 0) )
        {
            pVar->expandprog = kmk_cc_exp_compile(pVar->value, pVar->value_length);
            g_cVarForExpandCompilations++;
        }
    }
    return pVar->expandprog;
}


/**
 * String expansion execution worker for outputting a variable.
 *
 * @returns The new variable buffer position.
 * @param   pVar        The variable to reference.
 * @param   pchDst      The current variable buffer position.
 */
static char *kmk_exec_expand_worker_reference_variable(struct variable *pVar, char *pchDst)
{
    if (pVar->value_length > 0)
    {
        if (!pVar->recursive || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(pVar))
            pchDst = variable_buffer_output(pchDst, pVar->value, pVar->value_length);
        else
            pchDst = reference_recursive_variable(pchDst, pVar);
    }
    else if (pVar->append)
        pchDst = reference_recursive_variable(pchDst, pVar);
    return pchDst;
}


/**
 * Executes a stream string expansion instructions, outputting to the current
 * varaible buffer.
 *
 * @returns The new variable buffer position.
 * @param   pInstrCore      The instruction to start executing at.
 * @param   pchDst          The current variable buffer position.
 */
static char *kmk_exec_expand_instruction_stream_to_var_buf(PKMKCCEXPCORE pInstrCore, char *pchDst)
{
    for (;;)
    {
        switch (pInstrCore->enmOpcode)
        {
            case kKmkCcExpInstr_CopyString:
            {
                PKMKCCEXPCOPYSTRING pInstr = (PKMKCCEXPCOPYSTRING)pInstrCore;
                pchDst = variable_buffer_output(pchDst, pInstr->pachSrc, pInstr->cchCopy);

                pInstrCore = &(pInstr + 1)->Core;
                break;
            }

            case kKmkCcExpInstr_PlainVariable:
            {
                PKMKCCEXPPLAINVAR pInstr = (PKMKCCEXPPLAINVAR)pInstrCore;
                struct variable  *pVar = lookup_variable_strcached(pInstr->pszName);
                if (pVar)
                    pchDst = kmk_exec_expand_worker_reference_variable(pVar, pchDst);
                else
                    warn_undefined(pInstr->pszName, strcache2_get_len(&variable_strcache, pInstr->pszName));

                pInstrCore = &(pInstr + 1)->Core;
                break;
            }

            case kKmkCcExpInstr_DynamicVariable:
            {
                PKMKCCEXPDYNVAR  pInstr = (PKMKCCEXPDYNVAR)pInstrCore;
                struct variable *pVar;
                uint32_t         cchName;
                char            *pszName = kmk_exec_expand_subprog_to_tmp(&pInstr->Subprog, &cchName);
                char            *pszColon = (char *)memchr(pszName, ':', cchName);
                char            *pszEqual;
                if (   pszColon == NULL
                    || (pszEqual = (char *)memchr(pszColon + 1, '=', &pszName[cchName] - pszColon - 1)) == NULL
                    || pszEqual == pszColon + 1)
                {
                    pVar = lookup_variable(pszName, cchName);
                    if (pVar)
                        pchDst = kmk_exec_expand_worker_reference_variable(pVar, pchDst);
                    else
                        warn_undefined(pszName, cchName);
                }
                else if (pszColon != pszName)
                {
                    /*
                     * Oh, we have to do search and replace. How tedious.
                     * Since the variable name is a temporary buffer, we can transform
                     * the strings into proper search and replacement patterns directly.
                     */
                    pVar = lookup_variable(pszName, pszColon - pszName);
                    if (pVar)
                    {
                        char const *pszExpandedVarValue = pVar->recursive ? recursively_expand(pVar) : pVar->value;
                        char       *pszSearchPat  = pszColon + 1;
                        char       *pszReplacePat = pszEqual + 1;
                        const char *pchPctSearchPat;
                        const char *pchPctReplacePat;

                        *pszEqual = '\0';
                        pchPctSearchPat = find_percent(pszSearchPat);
                        pchPctReplacePat = find_percent(pszReplacePat);

                        if (!pchPctReplacePat)
                        {
                            if (pszReplacePat[-2] != '\0') /* On the offchance that a pct was unquoted by find_percent. */
                            {
                                memmove(pszName + 1, pszSearchPat, pszReplacePat - pszSearchPat);
                                if (pchPctSearchPat)
                                    pchPctSearchPat -= pszSearchPat - &pszName[1];
                                pszSearchPat = &pszName[1];
                            }
                            pchPctReplacePat = --pszReplacePat;
                            *pszReplacePat = '%';
                        }

                        if (!pchPctSearchPat)
                        {
                            pchPctSearchPat = --pszSearchPat;
                            *pszSearchPat = '%';
                        }

                        pchDst = patsubst_expand_pat(pchDst, pszExpandedVarValue,
                                                     pszSearchPat, pszReplacePat,
                                                     pchPctSearchPat, pchPctReplacePat);

                        if (pVar->recursive)
                            free((void *)pszExpandedVarValue);
                    }
                    else
                        warn_undefined(pszName, pszColon - pszName);
                }
                free(pszName);

                pInstrCore = pInstr->pNext;
                break;
            }


            case kKmkCcExpInstr_SearchAndReplacePlainVariable:
            {
                PKMKCCEXPSRPLAINVAR pInstr = (PKMKCCEXPSRPLAINVAR)pInstrCore;
                struct variable    *pVar = lookup_variable_strcached(pInstr->pszName);
                if (pVar)
                {
                    char const *pszExpandedVarValue = pVar->recursive ? recursively_expand(pVar) : pVar->value;
                    pchDst = patsubst_expand_pat(pchDst,
                                                 pszExpandedVarValue,
                                                 pInstr->pszSearchPattern,
                                                 pInstr->pszReplacePattern,
                                                 &pInstr->pszSearchPattern[pInstr->offPctSearchPattern],
                                                 &pInstr->pszReplacePattern[pInstr->offPctReplacePattern]);
                    if (pVar->recursive)
                        free((void *)pszExpandedVarValue);
                }
                else
                    warn_undefined(pInstr->pszName, strcache2_get_len(&variable_strcache, pInstr->pszName));

                pInstrCore = pInstr->pNext;
                break;
            }

            case kKmkCcExpInstr_PlainFunction:
            {
                PKMKCCEXPPLAINFUNC pInstr = (PKMKCCEXPPLAINFUNC)pInstrCore;
                uint32_t iArg;
                if (!pInstr->FnCore.fDirty)
                {
#ifdef KMK_CC_STRICT
                    uint32_t uCrcBefore = 0;
                    uint32_t uCrcAfter = 0;
                    iArg = pInstr->FnCore.cArgs;
                    while (iArg-- > 0)
                        uCrcBefore = kmk_cc_debug_string_hash(uCrcBefore, pInstr->apszArgs[iArg]);
#endif

                    pchDst = pInstr->FnCore.pfnFunction(pchDst, (char **)&pInstr->apszArgs[0], pInstr->FnCore.pszFuncName);

#ifdef KMK_CC_STRICT
                    iArg = pInstr->FnCore.cArgs;
                    while (iArg-- > 0)
                        uCrcAfter = kmk_cc_debug_string_hash(uCrcAfter, pInstr->apszArgs[iArg]);
                    KMK_CC_ASSERT(uCrcBefore == uCrcAfter);
#endif
                }
                else
                {
                    char **papszShadowArgs = xmalloc((pInstr->FnCore.cArgs * 2 + 1) * sizeof(papszShadowArgs[0]));
                    char **papszArgs = &papszShadowArgs[pInstr->FnCore.cArgs];

                    iArg = pInstr->FnCore.cArgs;
                    papszArgs[iArg] = NULL;
                    while (iArg-- > 0)
                        papszArgs[iArg] = papszShadowArgs[iArg] = xstrdup(pInstr->apszArgs[iArg]);

                    pchDst = pInstr->FnCore.pfnFunction(pchDst, (char **)&pInstr->apszArgs[0], pInstr->FnCore.pszFuncName);

                    iArg = pInstr->FnCore.cArgs;
                    while (iArg-- > 0)
                        free(papszShadowArgs[iArg]);
                    free(papszShadowArgs);
                }

                pInstrCore = pInstr->FnCore.pNext;
                break;
            }

            case kKmkCcExpInstr_DynamicFunction:
            {
                PKMKCCEXPDYNFUNC pInstr = (PKMKCCEXPDYNFUNC)pInstrCore;
                char           **papszArgsShadow = xmalloc( (pInstr->FnCore.cArgs * 2 + 1) * sizeof(char *));
                char           **papszArgs = &papszArgsShadow[pInstr->FnCore.cArgs];
                uint32_t         iArg;

                if (!pInstr->FnCore.fDirty)
                {
#ifdef KMK_CC_STRICT
                    uint32_t    uCrcBefore = 0;
                    uint32_t    uCrcAfter = 0;
#endif
                    iArg = pInstr->FnCore.cArgs;
                    papszArgs[iArg] = NULL;
                    while (iArg-- > 0)
                    {
                        char *pszArg;
                        if (pInstr->aArgs[iArg].fSubprog)
                            pszArg = kmk_exec_expand_subprog_to_tmp(&pInstr->aArgs[iArg].u.Subprog, NULL);
                        else
                            pszArg = (char *)pInstr->aArgs[iArg].u.Plain.psz;
                        papszArgsShadow[iArg] = pszArg;
                        papszArgs[iArg]       = pszArg;
#ifdef KMK_CC_STRICT
                        uCrcBefore = kmk_cc_debug_string_hash(uCrcBefore, pszArg);
#endif
                    }
                    pchDst = pInstr->FnCore.pfnFunction(pchDst, papszArgs, pInstr->FnCore.pszFuncName);

                    iArg = pInstr->FnCore.cArgs;
                    while (iArg-- > 0)
                    {
#ifdef KMK_CC_STRICT
                        KMK_CC_ASSERT(papszArgsShadow[iArg] == papszArgs[iArg]);
                        uCrcAfter = kmk_cc_debug_string_hash(uCrcAfter, papszArgsShadow[iArg]);
#endif
                        if (pInstr->aArgs[iArg].fSubprog)
                            free(papszArgsShadow[iArg]);
                    }
                    KMK_CC_ASSERT(uCrcBefore == uCrcAfter);
                }
                else
                {
                    iArg = pInstr->FnCore.cArgs;
                    papszArgs[iArg] = NULL;
                    while (iArg-- > 0)
                    {
                        char *pszArg;
                        if (pInstr->aArgs[iArg].fSubprog)
                            pszArg = kmk_exec_expand_subprog_to_tmp(&pInstr->aArgs[iArg].u.Subprog, NULL);
                        else
                            pszArg = xstrdup(pInstr->aArgs[iArg].u.Plain.psz);
                        papszArgsShadow[iArg] = pszArg;
                        papszArgs[iArg]       = pszArg;
                    }

                    pchDst = pInstr->FnCore.pfnFunction(pchDst, papszArgs, pInstr->FnCore.pszFuncName);

                    iArg = pInstr->FnCore.cArgs;
                    while (iArg-- > 0)
                        free(papszArgsShadow[iArg]);
                }
                free(papszArgsShadow);

                pInstrCore = pInstr->FnCore.pNext;
                break;
            }

            case kKmkCcExpInstr_Jump:
            {
                PKMKCCEXPJUMP pInstr = (PKMKCCEXPJUMP)pInstrCore;
                pInstrCore = pInstr->pNext;
                break;
            }

            case kKmkCcExpInstr_Return:
                return pchDst;

            default:
                fatal(NULL, _("Unknown string expansion opcode: %d (%#x)"),
                      (int)pInstrCore->enmOpcode, (int)pInstrCore->enmOpcode);
                return NULL;
        }
    }
}


/**
 * Updates the string expansion statistics.
 *
 * @param   pStats              The statistics structure to update.
 * @param   cchResult           The result lenght.
 */
void kmk_cc_exp_stats_update(PKMKCCEXPSTATS pStats, uint32_t cchResult)
{
    /*
     * The average is simplified and not an exact average for every
     * expansion that has taken place.
     */
    pStats->cchAvg = (pStats->cchAvg * 7 + cchResult) / 8;
}


/**
 * Execute a string expansion subprogram, outputting to a new heap buffer.
 *
 * @returns Pointer to the output buffer (hand to free when done).
 * @param   pSubprog          The subprogram to execute.
 * @param   pcchResult        Where to return the size of the result. Optional.
 */
static char *kmk_exec_expand_subprog_to_tmp(PKMKCCEXPSUBPROG pSubprog, uint32_t *pcchResult)
{
    char           *pchOldVarBuf;
    unsigned int    cbOldVarBuf;
    char           *pchDst;
    char           *pszResult;
    uint32_t        cchResult;

    /*
     * Temporarily replace the variable buffer while executing the instruction
     * stream for this subprogram.
     */
    pchDst = install_variable_buffer_with_hint(&pchOldVarBuf, &cbOldVarBuf,
                                               pSubprog->Stats.cchAvg ? pSubprog->Stats.cchAvg + 32 : 256);

    pchDst = kmk_exec_expand_instruction_stream_to_var_buf(pSubprog->pFirstInstr, pchDst);

    /* Ensure that it's terminated. */
    pchDst = variable_buffer_output(pchDst, "\0", 1) - 1;

    /* Grab the result buffer before restoring the previous one. */
    pszResult = variable_buffer;
    cchResult = (uint32_t)(pchDst - pszResult);
    if (pcchResult)
        *pcchResult = cchResult;
    kmk_cc_exp_stats_update(&pSubprog->Stats, cchResult);

    variable_buffer = pchOldVarBuf;
    variable_buffer_length = cbOldVarBuf;

    return pszResult;
}


/**
 * Execute a string expansion program, outputting to the current variable
 * buffer.
 *
 * @returns New variable buffer position.
 * @param   pProg               The program to execute.
 * @param   pchDst              The current varaible buffer position.
 */
static char *kmk_exec_expand_prog_to_var_buf(PKMKCCEXPPROG pProg, char *pchDst)
{
    uint32_t cchResult;
    uint32_t offStart = (uint32_t)(pchDst - variable_buffer);

    if (pProg->Stats.cchAvg >= variable_buffer_length - offStart)
        pchDst = ensure_variable_buffer_space(pchDst, offStart + pProg->Stats.cchAvg + 32);

    KMK_CC_ASSERT(pProg->cRefs > 0);
    pProg->cRefs++;

    pchDst = kmk_exec_expand_instruction_stream_to_var_buf(pProg->pFirstInstr, pchDst);

    pProg->cRefs--;
    KMK_CC_ASSERT(pProg->cRefs > 0);

    cchResult = (uint32_t)(pchDst - variable_buffer);
    KMK_CC_ASSERT(cchResult >= offStart);
    cchResult -= offStart;
    kmk_cc_exp_stats_update(&pProg->Stats, cchResult);
    g_cVarForExpandExecs++;

    return pchDst;
}


/**
 * Expands a variable into a variable buffer using its expandprog.
 *
 * @returns The new variable buffer position.
 * @param   pVar        Pointer to the variable.  Must have a program.
 * @param   pchDst      Pointer to the current variable buffer position.
 */
char *kmk_exec_expand_to_var_buf(struct variable *pVar, char *pchDst)
{
    KMK_CC_ASSERT(pVar->expandprog);
    KMK_CC_ASSERT(pVar->expandprog->uInputHash == kmk_cc_debug_string_hash(0, pVar->value));
    return kmk_exec_expand_prog_to_var_buf(pVar->expandprog, pchDst);
}





/*
 *
 * Makefile evaluation programs.
 * Makefile evaluation programs.
 * Makefile evaluation programs.
 *
 */

static size_t kmk_cc_eval_detect_eol_style(char *pchFirst, char *pchSecond, const char *pszContent, size_t cchContent)
{
    /* Look for LF first. */
    const char *pszTmp = (const char *)memchr(pszContent, '\n', cchContent);
    if (pszTmp)
    {
        /* CRLF? */
        if (pszTmp != pszContent && pszTmp[-1] == '\r')
        {
            *pchFirst = '\r';
            *pchSecond = '\n';
            return 2;
        }

        /* No, LF or LFCR. (pszContent is zero terminated, so no bounds checking necessary.) */
        *pchFirst = '\n';
        if (pszTmp[1] != '\r')
        {
            *pchSecond = 0;
            return 1;
        }
        *pchSecond = '\r';
        return 2;
    }

    /* Probably no EOLs here. */
    if (memchr(pszContent, '\r', cchContent) == NULL)
    {
        *pchSecond = *pchFirst = 0;
        return 0;
    }

    /* kind of unlikely */
    *pchFirst  = '\r';
    *pchSecond = 0;
    return 1;
}


#if 0
/**
 * Checks whether we've got an EOL escape sequence or not.
 *
 * @returns non-zero if escaped EOL, 0 if not (i.e. actual EOL).
 * @param   pszContent          The string pointer @a offEol is relative to.
 * @param   offEol              The offset of the first EOL char.
 */
static unsigned kmk_cc_eval_is_eol_escape_seq(const char *pszContent, size_t offEol)
{
    /* The caller has already checked out two backslashes. */
    size_t offFirstBackslash = offEol;
    KMK_CC_ASSERT(offFirstBackslash >= 2);
    offFirstBackslash -= 2;

    /* Find the first backslash. */
    while (offFirstBackslash > 0 && pszContent[offFirstBackslash - 1] == '\\')
        offFirstBackslash--;

    /* Odd number -> escaped EOL; Even number -> real EOL; */
    return (offEol - offFirstBackslash) & 1;
}
#endif



/**
 * Tokens (for KMKCCEVALWORD).
 */
typedef enum kmk_cc_eval_token
{
    /** Invalid token value 0. */
    kKmkCcEvalToken_Invalid = 0,

    /** Plain word. */
    kKmkCcEvalToken_WordPlain,
    /** Plain word with one or more escaped EOLs. (Currently not possible.) */
    kKmkCcEvalToken_WordPlainWithEscEol,
    /** Word that maybe in need of expanding. */
    kKmkCcEvalToken_WordWithDollar,
    /** Word that is in need of expanding and include one or more escped EOLs. */
    kKmkCcEvalToken_WordWithDollarAndEscEol,

    /** Recipe colon. */
    kKmkCcEvalToken_colon,
    /** Recipe double colon.   */
    kKmkCcEvalToken_double_colon,
    /** Recipe multi target plus. */
    kKmkCcEvalToken_plus,
    /** Recipe multi target plus-maybe (+|). */
    kKmkCcEvalToken_plus_maybe,
    /** Recipe semicolon. */
    kKmkCcEvalToken_semicolon,

    /** End of valid token values (not included). */
    kKmkCcEvalToken_End
} KMKCCEVALTOKEN;

/**
 * A tokenized word.
 */
typedef struct kmk_cc_eval_word
{
    /** The token word (lexeme).   */
    const char         *pchWord;
    /** The length of the word (lexeme). */
    uint32_t            cchWord;
    /** The token classification. */
    KMKCCEVALTOKEN      enmToken;
} KMKCCEVALWORD;
typedef KMKCCEVALWORD *PKMKCCEVALWORD;
typedef KMKCCEVALWORD const *PCKMKCCEVALWORD;


/**
 * Escaped end-of-line sequence in the current line.
 */
typedef struct KMKCCEVALESCEOL
{
    /** Offset at which the EOL escape sequence starts for a non-command line. */
    size_t              offEsc;
    /** Offset of the newline sequence. */
    size_t              offEol;
} KMKCCEVALESCEOL;
typedef KMKCCEVALESCEOL *PKMKCCEVALESCEOL;


/**
 * String copy segment.
 */
typedef struct KMKCCEVALSTRCPYSEG
{
    /** The start. */
    const char         *pchSrc;
    /** The number of chars to copy and whether to prepend space.
     * Negative values indicates that we should prepend a space. */
    ssize_t             cchSrcAndPrependSpace;
} KMKCCEVALSTRCPYSEG;
typedef KMKCCEVALSTRCPYSEG *PKMKCCEVALSTRCPYSEG;
typedef KMKCCEVALSTRCPYSEG const *PCKMKCCEVALSTRCPYSEG;


typedef struct KMKCCEVALCOMPILER
{
    /** Pointer to the KMKCCEVALPROG::pBlockTail member.  */
    PKMKCCBLOCK        *ppBlockTail;

    /** @name Line parsing state.
     * @{ */
    /** Offset of newline escape sequences in the current line.
     * This is only applicable if cEscEols is not zero.  */
    PKMKCCEVALESCEOL    paEscEols;
    /** The number of number of paEscEols entries we've allocated. */
    unsigned            cEscEolsAllocated;
    /** Number of escaped EOLs (line count - 1). */
    unsigned            cEscEols;
    /** The paEscEols entry corresponding to the current parsing location.
     * Still to be seen how accurate this can be made to be. */
    unsigned            iEscEol;

    /** The current line number for error handling / debugging (1-based). */
    unsigned            iLine;
    /** The start offset (into pchContent) of the current line. */
    size_t              offLine;
    /** Length of the current line, sans the final EOL and comments. */
    size_t              cchLine;
    /** Length of the current line, sans the final EOL but with comments. */
    size_t              cchLineWithComments;
    /** For 'define' only, the start offset of the next line.  Modified to the
     * line following 'endef'. */
    size_t              offNext;

    /** The first char in an EOL sequence.
     * We ASSUMES that this char won't appear in any other sequence in the file,
     * thus skipping matching any subsequent chars. */
    char                chFirstEol;
    /** The second char in an EOL sequence, if applicable. */
    char                chSecondEol;

    /** The length of the EOL sequence. */
    size_t              cchEolSeq;
    /** The minimum length of an esacped EOL sequence (cchEolSeq + 1). */
    size_t              cchEscEolSeq;

    /** String copy segments. */
    PKMKCCEVALSTRCPYSEG paStrCopySegs;
    /** The number of segments that has been prepared. */
    unsigned            cStrCopySegs;
    /** The number of segments we've allocated. */
    unsigned            cStrCopySegsAllocated;
    /** @} */


    /** @name Recipe state.
     * @{ */
    /** Set if we're working on a recipe. */
    PKMKCCEVALRECIPE    pRecipe;
    /** Set for ignoring recipes without targets (SunOS 4 Make). */
    uint8_t             fNoTargetRecipe;
    /** The command prefix character. */
    char                chCmdPrefix;
    /** @} */

    /** @name Tokenzied words.
     * @{ */
    unsigned            cWords;
    unsigned            cWordsAllocated;
    PKMKCCEVALWORD      paWords;
    /** @} */

    /** @name Conditionals.
     * @{ */
    /** Current conditional stack depth. */
    unsigned            cIfs;
    /** The conditional directive stack. */
    PKMKCCEVALIFCORE    apIfs[KMK_CC_EVAL_MAX_IF_DEPTH];
    /** @} */

    /** The program being compiled. */
    PKMKCCEVALPROG      pEvalProg;
    /** Pointer to the content. */
    const char         *pszContent;
    /** The amount of input to parse. */
    size_t              cchContent;
} KMKCCEVALCOMPILER;
typedef KMKCCEVALCOMPILER *PKMKCCEVALCOMPILER;


static void kmk_cc_eval_init_compiler(PKMKCCEVALCOMPILER pCompiler, PKMKCCEVALPROG pEvalProg, unsigned iLine,
                                      const char *pszContent, size_t cchContent)
{
    pCompiler->ppBlockTail      = &pEvalProg->pBlockTail;

    pCompiler->pRecipe          = NULL;
    pCompiler->fNoTargetRecipe  = 0;
    pCompiler->chCmdPrefix      = cmd_prefix;

    pCompiler->cWordsAllocated  = 0;
    pCompiler->paWords          = NULL;

    pCompiler->cEscEolsAllocated = 0;
    pCompiler->paEscEols        = NULL;
    pCompiler->iLine            = iLine;

    pCompiler->cStrCopySegsAllocated = 0;
    pCompiler->paStrCopySegs    = NULL;

    pCompiler->cIfs             = 0;

    pCompiler->pEvalProg        = pEvalProg;
    pCompiler->pszContent       = pszContent;
    pCompiler->cchContent       = cchContent;

    /* Detect EOL style. */
    pCompiler->cchEolSeq        = kmk_cc_eval_detect_eol_style(&pCompiler->chFirstEol, &pCompiler->chSecondEol,
                                                               pszContent, cchContent);
    pCompiler->cchEscEolSeq     = 1 + pCompiler->cchEolSeq;
}


static void kmk_cc_eval_delete_compiler(PKMKCCEVALCOMPILER pCompiler)
{
    if (pCompiler->paWords)
        free(pCompiler->paWords);
    if (pCompiler->paEscEols)
        free(pCompiler->paEscEols);
}


/**
 * Translates a makefile source pointer to a line number and offset.
 *
 * @returns Line number (1-based)
 * @param   pCompiler   The compiler state.
 * @param   pszWhere    There location to translate.
 * @param   piColumn    Where to return the line offset (1-based).
 */
static unsigned kmk_cc_eval_translate_location(PKMKCCEVALCOMPILER pCompiler, const char *pchWhere, unsigned *piColumn)
{
    unsigned iLine   = pCompiler->iLine;
    size_t   offLine = pCompiler->offLine;
    size_t   off     = pchWhere - pCompiler->pszContent;
    unsigned i       = 0;
    while (   i   < pCompiler->cEscEols
           && off > pCompiler->paEscEols[i].offEol)
    {
        offLine = pCompiler->paEscEols[i].offEol + 1 + pCompiler->cchEolSeq;
        iLine++;
        i++;
    }
    KMK_CC_ASSERT(off <= pCompiler->cchContent);
    if (piColumn)
        *piColumn = (unsigned)(off - offLine) + 1;
    return iLine;
}


static void KMK_CC_FN_NO_RETURN kmk_cc_eval_fatal(PKMKCCEVALCOMPILER pCompiler, const char *pchWhere, const char *pszMsg, ...)
{
    va_list  va;
    log_working_directory(1);

    /*
     * If we have a pointer location, use it to figure out the exact line and column.
     */
    if (pchWhere)
    {
        unsigned iColumn;
        unsigned iLine = kmk_cc_eval_translate_location(pCompiler, pchWhere, &iColumn);

        if (pCompiler->pEvalProg->pszVarName)
            fprintf(stderr, "%s:%u:%u: *** fatal parsing error in %s: ",
                    pCompiler->pEvalProg->pszFilename, iLine, iColumn, pCompiler->pEvalProg->pszVarName);
        else
            fprintf(stderr, "%s:%u:%u: *** fatal parsing error: ",
                    pCompiler->pEvalProg->pszFilename, iLine, iColumn);
    }
    else if (pCompiler->pEvalProg->pszVarName)
        fprintf(stderr, "%s:%u: *** fatal parsing error in %s: ",
                pCompiler->pEvalProg->pszFilename, pCompiler->iLine, pCompiler->pEvalProg->pszVarName);
    else
        fprintf(stderr, "%s:%u: *** fatal parsing error: ",
                pCompiler->pEvalProg->pszFilename, pCompiler->iLine);

    /*
     * Print the message and die.
     */
    va_start(va, pszMsg);
    vfprintf(stderr, pszMsg, va);
    va_end(va);
    fputs(".  Stop.\n", stderr);

    for (;;)
        die(2);
}


static KMK_CC_FN_NO_RETURN void
kmk_cc_eval_fatal_eol(PKMKCCEVALCOMPILER pCompiler, const char *pchEol, unsigned iLine, size_t offLine)
{
    pCompiler->iLine   = iLine;
    pCompiler->offLine = offLine;

    for (;;)
        kmk_cc_eval_fatal(pCompiler, pchEol, "Missing 2nd EOL character: found %#x instead of %#x\n",
                          pchEol, pCompiler->chSecondEol);
}


static void kmk_cc_eval_warn(PKMKCCEVALCOMPILER pCompiler, const char *pchWhere, const char *pszMsg, ...)
{
    va_list  va;

    log_working_directory(1);

    /*
     * If we have a pointer location, use it to figure out the exact line and column.
     */
    if (pchWhere)
    {
        unsigned iColumn;
        unsigned iLine = kmk_cc_eval_translate_location(pCompiler, pchWhere, &iColumn);

        if (pCompiler->pEvalProg->pszVarName)
            fprintf(stderr, "%s:%u:%u: *** warning in %s: ",
                    pCompiler->pEvalProg->pszFilename, iLine, iColumn, pCompiler->pEvalProg->pszVarName);
        else
            fprintf(stderr, "%s:%u:%u: *** warning: ",
                    pCompiler->pEvalProg->pszFilename, iLine, iColumn);
    }
    else if (pCompiler->pEvalProg->pszVarName)
        fprintf(stderr, "%s:%u: *** warning in %s: ",
                pCompiler->pEvalProg->pszFilename, pCompiler->iLine, pCompiler->pEvalProg->pszVarName);
    else
        fprintf(stderr, "%s:%u: *** warning: ",
                pCompiler->pEvalProg->pszFilename, pCompiler->iLine);

    /*
     * Print the message.
     */
    va_start(va, pszMsg);
    vfprintf(stderr, pszMsg, va);
    va_end(va);
    fputs(".\n", stderr);
}


/**
 * Compiles a string expansion subprogram.
 *
 * @param   pCompiler   The compiler state.
 * @param   pszExpr     The expression to compile.
 * @param   cchExpr     The length of the expression.
 * @param   pSubprog    The subprogram to compile.
 */
static void kmk_cc_eval_compile_string_exp_subprog(PKMKCCEVALCOMPILER pCompiler, const char *pszExpr, size_t cchExpr,
                                                   PKMKCCEXPSUBPROG pSubprog)
{
    int rc = kmk_cc_exp_compile_subprog(pCompiler->ppBlockTail, pszExpr, cchExpr, pSubprog);
    if (rc == 0)
        return;
    kmk_cc_eval_fatal(pCompiler, NULL, "String expansion compile error");
}


/**
 * Initializes a subprogam or plain operand structure.
 *
 * @param   pCompiler   The compiler state.
 * @param   pOperand    The subprogram or plain structure to init.
 * @param   pszString   The string.
 * @param   cchString   The length of the string.
 * @param   fPlain      Whether it's plain or not.  If not, we'll compile it.
 */
static void kmk_cc_eval_init_subprogram_or_plain(PKMKCCEVALCOMPILER pCompiler, PKMKCCEXPSUBPROGORPLAIN pOperand,
                                                 const char *pszString, size_t cchString, int fPlain)
{
    pOperand->fPlainIsInVarStrCache  = 0;
    pOperand->bUser                  = 0;
    pOperand->bUser2                 = 0;
    pOperand->fSubprog               = fPlain;
    if (fPlain)
    {
        pOperand->u.Plain.cch = cchString;
        pOperand->u.Plain.psz = pszString;
    }
    else
        kmk_cc_eval_compile_string_exp_subprog(pCompiler, pszString, cchString, &pOperand->u.Subprog);
}

/**
 * Initializes an array of subprogram-or-plain (spp) operands from a word array.
 *
 * The words will be duplicated and the caller must therefore call
 * kmk_cc_block_realign() when done (it's not done here as the caller may
 * initialize several string operands and we don't want any unnecessary
 * fragmentation).
 *
 * @param   pCompiler   The compiler state.
 * @param   cWords      The number of words to copy.
 * @param   paSrc       The source words.
 * @param   paDst       The destination subprogram-or-plain array.
 */
static void kmk_cc_eval_init_spp_array_from_duplicated_words(PKMKCCEVALCOMPILER pCompiler, unsigned cWords,
                                                             PKMKCCEVALWORD paSrc, PKMKCCEXPSUBPROGORPLAIN paDst)
{
    unsigned i;
    for (i = 0; i < cWords; i++)
    {
        const char *pszCopy = kmk_cc_block_strdup(pCompiler->ppBlockTail, paSrc[i].pchWord, paSrc[i].cchWord);
        paDst[i].fPlainIsInVarStrCache  = 0;
        paDst[i].bUser                  = 0;
        paDst[i].bUser2                 = 0;
        if (paSrc[i].enmToken == kKmkCcEvalToken_WordWithDollar)
        {
            paDst[i].fSubprog    = 1;
            kmk_cc_block_realign(pCompiler->ppBlockTail);
            kmk_cc_eval_compile_string_exp_subprog(pCompiler, pszCopy, paSrc[i].cchWord, &paDst[i].u.Subprog);
        }
        else
        {
            paDst[i].fSubprog    = 0;
            paDst[i].u.Plain.cch = paSrc[i].cchWord;
            paDst[i].u.Plain.psz = pszCopy;
        }
        KMK_CC_EVAL_DPRINTF(("  %s\n", pszCopy));
    }
}



/** @name KMK_CC_WORD_COMP_CONST_XXX - Optimal(/insane) constant work matching.
 * @{
 */
#if (defined(KBUILD_ARCH_X86) || defined(KBUILD_ARCH_AMD64)) /* Unaligned access is reasonably cheap. */ \
 && !defined(GCC_ADDRESS_SANITIZER)
# define KMK_CC_WORD_COMP_CONST_2(a_pchLine, a_pszWord) \
        (   *(uint16_t const *)(a_pchLine)     == *(uint16_t const *)(a_pszWord) )
# define KMK_CC_WORD_COMP_CONST_3(a_pchLine, a_pszWord) \
        (   *(uint16_t const *)(a_pchLine)     == *(uint16_t const *)(a_pszWord) \
         && (a_pchLine)[2]                     == (a_pszWord)[2] )
# define KMK_CC_WORD_COMP_CONST_4(a_pchLine, a_pszWord) \
        (   *(uint32_t const *)(a_pchLine)     == *(uint32_t const *)(a_pszWord) )
# define KMK_CC_WORD_COMP_CONST_5(a_pchLine, a_pszWord) \
        (   *(uint32_t const *)(a_pchLine)     == *(uint32_t const *)(a_pszWord) \
         && (a_pchLine)[4]                     == (a_pszWord)[4] )
# define KMK_CC_WORD_COMP_CONST_6(a_pchLine, a_pszWord) \
        (   *(uint32_t const *)(a_pchLine)     == *(uint32_t const *)(a_pszWord) \
         && ((uint16_t const *)(a_pchLine))[2] == ((uint16_t const *)(a_pszWord))[2] )
# define KMK_CC_WORD_COMP_CONST_7(a_pchLine, a_pszWord) \
        (   *(uint32_t const *)(a_pchLine)     == *(uint32_t const *)(a_pszWord) \
         && ((uint16_t const *)(a_pchLine))[2] == ((uint16_t const *)(a_pszWord))[2] \
         && (a_pchLine)[6]                     == (a_pszWord)[6] )
# define KMK_CC_WORD_COMP_CONST_8(a_pchLine, a_pszWord) \
        (   *(uint64_t const *)(a_pchLine)     == *(uint64_t const *)(a_pszWord) )
# define KMK_CC_WORD_COMP_CONST_10(a_pchLine, a_pszWord) \
        (   *(uint64_t const *)(a_pchLine)     == *(uint64_t const *)(a_pszWord) \
         && ((uint16_t const *)(a_pchLine))[4] == ((uint16_t const *)(a_pszWord))[4] )
# define KMK_CC_WORD_COMP_CONST_16(a_pchLine, a_pszWord) \
        (   *(uint64_t const *)(a_pchLine)     == *(uint64_t const *)(a_pszWord) \
         && ((uint64_t const *)(a_pchLine))[1] == ((uint64_t const *)(a_pszWord))[1] )
#else
# define KMK_CC_WORD_COMP_CONST_2(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] )
# define KMK_CC_WORD_COMP_CONST_3(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] \
         && (a_pchLine)[2] == (a_pszWord)[2] )
# define KMK_CC_WORD_COMP_CONST_4(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] \
         && (a_pchLine)[2] == (a_pszWord)[2] \
         && (a_pchLine)[3] == (a_pszWord)[3] )
# define KMK_CC_WORD_COMP_CONST_5(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] \
         && (a_pchLine)[2] == (a_pszWord)[2] \
         && (a_pchLine)[3] == (a_pszWord)[3] \
         && (a_pchLine)[4] == (a_pszWord)[4] )
# define KMK_CC_WORD_COMP_CONST_6(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] \
         && (a_pchLine)[2] == (a_pszWord)[2] \
         && (a_pchLine)[3] == (a_pszWord)[3] \
         && (a_pchLine)[4] == (a_pszWord)[4] \
         && (a_pchLine)[5] == (a_pszWord)[5] )
# define KMK_CC_WORD_COMP_CONST_7(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] \
         && (a_pchLine)[2] == (a_pszWord)[2] \
         && (a_pchLine)[3] == (a_pszWord)[3] \
         && (a_pchLine)[4] == (a_pszWord)[4] \
         && (a_pchLine)[5] == (a_pszWord)[5] \
         && (a_pchLine)[6] == (a_pszWord)[6] )
# define KMK_CC_WORD_COMP_CONST_8(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] \
         && (a_pchLine)[2] == (a_pszWord)[2] \
         && (a_pchLine)[3] == (a_pszWord)[3] \
         && (a_pchLine)[4] == (a_pszWord)[4] \
         && (a_pchLine)[5] == (a_pszWord)[5] \
         && (a_pchLine)[6] == (a_pszWord)[6] \
         && (a_pchLine)[7] == (a_pszWord)[7] )
# define KMK_CC_WORD_COMP_CONST_10(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] \
         && (a_pchLine)[2] == (a_pszWord)[2] \
         && (a_pchLine)[3] == (a_pszWord)[3] \
         && (a_pchLine)[4] == (a_pszWord)[4] \
         && (a_pchLine)[5] == (a_pszWord)[5] \
         && (a_pchLine)[6] == (a_pszWord)[6] \
         && (a_pchLine)[7] == (a_pszWord)[7] \
         && (a_pchLine)[8] == (a_pszWord)[8] \
         && (a_pchLine)[9] == (a_pszWord)[9] )
# define KMK_CC_WORD_COMP_CONST_16(a_pchLine, a_pszWord) \
        (   (a_pchLine)[0] == (a_pszWord)[0] \
         && (a_pchLine)[1] == (a_pszWord)[1] \
         && (a_pchLine)[2] == (a_pszWord)[2] \
         && (a_pchLine)[3] == (a_pszWord)[3] \
         && (a_pchLine)[4] == (a_pszWord)[4] \
         && (a_pchLine)[5] == (a_pszWord)[5] \
         && (a_pchLine)[6] == (a_pszWord)[6] \
         && (a_pchLine)[7] == (a_pszWord)[7] \
         && (a_pchLine)[8] == (a_pszWord)[8] \
         && (a_pchLine)[9] == (a_pszWord)[9] \
         && (a_pchLine)[10] == (a_pszWord)[10] \
         && (a_pchLine)[11] == (a_pszWord)[11] \
         && (a_pchLine)[12] == (a_pszWord)[12] \
         && (a_pchLine)[13] == (a_pszWord)[13] \
         && (a_pchLine)[14] == (a_pszWord)[14] \
         && (a_pchLine)[15] == (a_pszWord)[15])
#endif

/** See if the given string match a constant string. */
#define KMK_CC_STRCMP_CONST(a_pchLeft, a_cchLeft, a_pszConst, a_cchConst) \
    (   (a_cchLeft) == (a_cchConst) \
     && KMK_CC_WORD_COMP_CONST_##a_cchConst(a_pchLeft, a_pszConst) )

/** See if a starting of a given length starts with a constant word. */
#define KMK_CC_EVAL_WORD_COMP_IS_EOL(a_pCompiler, a_pchLine, a_cchLine) \
    (   (a_cchLine) == 0 \
     || KMK_CC_EVAL_IS_SPACE((a_pchLine)[0]) \
     || ((a_pchLine)[0] == '\\' && (a_pchLine)[1] == (a_pCompiler)->chFirstEol) ) \

/** See if a starting of a given length starts with a constant word. */
#define KMK_CC_EVAL_WORD_COMP_CONST(a_pCompiler, a_pchLine, a_cchLine, a_pszWord, a_cchWord) \
    (    (a_cchLine) >= (a_cchWord) \
      && (   (a_cchLine) == (a_cchWord) \
          || KMK_CC_EVAL_IS_SPACE((a_pchLine)[a_cchWord]) \
          || ((a_pchLine)[a_cchWord] == '\\' && (a_pchLine)[(a_cchWord) + 1] == (a_pCompiler)->chFirstEol) ) \
      && KMK_CC_WORD_COMP_CONST_##a_cchWord(a_pchLine, a_pszWord) )
/** @} */


/**
 * Checks if a_ch is a space after a word.
 *
 * Since there is always a terminating zero, the user can safely access a char
 * beyond @a a_cchLeft.  However, that byte isn't necessarily a zero terminator
 * character, so we have to check @a a_cchLeft whether we're at the end of the
 * parsing input string.
 *
 * @returns true / false.
 * @param   a_pCompiler     The compiler instance data.
 * @param   a_ch            The character to inspect.
 * @param   a_ch2           The character following it, in case of escaped EOL.
 * @param   a_cchLeft       The number of chars left to parse (from @a a_ch).
 */
#define KMK_CC_EVAL_IS_SPACE_AFTER_WORD(a_pCompiler, a_ch, a_ch2, a_cchLeft) \
    (   a_cchLeft == 0 \
     || KMK_CC_EVAL_IS_SPACE(a_ch) \
     || ((a_ch) == '\\' && (a_ch2) == (a_pCompiler)->chFirstEol) )


/**
 * Common path for space skipping worker functions when escaped EOLs may be
 * involed.
 *
 * @returns Points to the first non-space character or end of input.
 * @param   pchWord         The current position. There is some kind of char
 * @param   cchLeft         The current number of chars left to parse in the
 *                          current line.
 * @param   pcchLeft        Where to store the updated @a cchLeft value.
 * @param   pCompiler       The compiler instance data.
 */
static const char *kmk_cc_eval_skip_spaces_with_esc_eol(const char *pchWord, size_t cchLeft, size_t *pcchLeft,
                                                        PKMKCCEVALCOMPILER pCompiler)
{
    /*
     * Skip further spaces. We unrolls 4 loops here.
     * ASSUMES cchEscEolSeq is either 2 or 3!
     */
    KMK_CC_ASSERT(pCompiler->cchEscEolSeq == 2 || pCompiler->cchEscEolSeq == 3);
    KMK_CC_ASSERT(pCompiler->iEscEol < pCompiler->cEscEols);
    while (cchLeft >= 4)
    {
        /* First char. */
        char ch = pchWord[0];
        if (KMK_CC_EVAL_IS_SPACE(ch))
        { /* maybe likely */ }
        else if (   ch == '\\'
                 && pchWord[1] == pCompiler->chFirstEol)
        {
            pchWord += pCompiler->cchEscEolSeq;
            cchLeft -= pCompiler->cchEscEolSeq;
            pCompiler->iEscEol++;
            continue;
        }
        else
        {
            *pcchLeft = cchLeft;
            return pchWord;
        }

        /* Second char. */
        ch = pchWord[1];
        if (KMK_CC_EVAL_IS_SPACE(ch))
        { /* maybe likely */ }
        else if (   ch == '\\'
                 && pchWord[2] == pCompiler->chFirstEol)
        {
            pchWord += 1 + pCompiler->cchEscEolSeq;
            cchLeft -= 1 + pCompiler->cchEscEolSeq;
            pCompiler->iEscEol++;
            continue;
        }
        else
        {
            *pcchLeft = cchLeft - 1;
            return pchWord + 1;
        }

        /* Third char. */
        ch = pchWord[2];
        if (KMK_CC_EVAL_IS_SPACE(ch))
        { /* maybe likely */ }
        else if (   ch == '\\'
                 && pchWord[3] == pCompiler->chFirstEol
                 && cchLeft >= 2 + pCompiler->cchEscEolSeq)
        {
            pchWord += 2 + pCompiler->cchEscEolSeq;
            cchLeft -= 2 + pCompiler->cchEscEolSeq;
            pCompiler->iEscEol++;
            continue;
        }
        else
        {
            *pcchLeft = cchLeft - 2;
            return pchWord + 2;
        }

        /* Third char. */
        ch = pchWord[3];
        if (KMK_CC_EVAL_IS_SPACE(ch))
        {
            pchWord += 4;
            cchLeft -= 4;
        }
        else if (   ch == '\\'
                 && cchLeft >= 3 + pCompiler->cchEscEolSeq
                 && pchWord[4] == pCompiler->chFirstEol)
        {
            pchWord += 3 + pCompiler->cchEscEolSeq;
            cchLeft -= 3 + pCompiler->cchEscEolSeq;
            pCompiler->iEscEol++;
        }
        else
        {
            *pcchLeft = cchLeft - 3;
            return pchWord + 3;
        }
    }

    /*
     * Simple loop for the final three chars.
     */
    while (cchLeft > 0)
    {
        /* First char. */
        char ch = *pchWord;
        if (KMK_CC_EVAL_IS_SPACE(ch))
        {
            pchWord += 1;
            cchLeft -= 1;
        }
        else if (   ch == '\\'
                 && cchLeft > pCompiler->cchEolSeq
                 && pchWord[1] == pCompiler->chFirstEol)
        {
            pchWord += pCompiler->cchEscEolSeq;
            cchLeft -= pCompiler->cchEscEolSeq;
            pCompiler->iEscEol++;
        }
        else
            break;
    }

    *pcchLeft = cchLeft;
    return pchWord;
}


/**
 * Common path for space skipping worker functions when no escaped EOLs need
 * considering.
 *
 * @returns Points to the first non-space character or end of input.
 * @param   pchWord         The current position. There is some kind of char
 * @param   cchLeft         The current number of chars left to parse in the
 *                          current line.
 * @param   pcchLeft        Where to store the updated @a cchLeft value.
 * @param   pCompiler       The compiler instance data.
 */
static const char *kmk_cc_eval_skip_spaces_without_esc_eol(const char *pchWord, size_t cchLeft, size_t *pcchLeft,
                                                           PKMKCCEVALCOMPILER pCompiler)
{
    /*
     * 4x loop unroll.
     */
    while (cchLeft >= 4)
    {
        if (KMK_CC_EVAL_IS_SPACE(pchWord[0]))
        {
            if (KMK_CC_EVAL_IS_SPACE(pchWord[1]))
            {
                if (KMK_CC_EVAL_IS_SPACE(pchWord[2]))
                {
                    if (KMK_CC_EVAL_IS_SPACE(pchWord[3]))
                    {
                        pchWord += 4;
                        cchLeft -= 4;
                    }
                    else
                    {
                        *pcchLeft = cchLeft - 3;
                        return pchWord + 3;
                    }
                }
                else
                {
                    *pcchLeft = cchLeft - 2;
                    return pchWord + 2;
                }
            }
            else
            {
                *pcchLeft = cchLeft - 1;
                return pchWord + 1;
            }
        }
        else
        {
            *pcchLeft = cchLeft;
            return pchWord;
        }
    }

    /*
     * The last 3. Not entirely sure if this yield good code.
     */
    switch (cchLeft & 3)
    {
        case 3:
            if (!KMK_CC_EVAL_IS_SPACE(*pchWord))
                break;
            pchWord++;
            cchLeft--;
        case 2:
            if (!KMK_CC_EVAL_IS_SPACE(*pchWord))
                break;
            pchWord++;
            cchLeft--;
        case 1:
            if (!KMK_CC_EVAL_IS_SPACE(*pchWord))
                break;
            pchWord++;
            cchLeft--;
        case 0:
            break;
    }

    *pcchLeft = cchLeft;
    return pchWord;
}


/**
 * Used to skip spaces after a word.
 *
 * We ASSUME that the first char is a space or that we've reached the end of the
 * string (a_cchLeft == 0).
 *
 * @param   a_pCompiler     The compiler instance data.
 * @param   a_pchWord       The current input position, this will be moved to
 *                          the start of the next word or end of the input.
 * @param   a_cchLeft       The number of chars left to parse.  This will be
 *                          updated.
 */
#define KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(a_pCompiler, a_pchWord, a_cchLeft) \
    do { \
        /* Skip the first char which is known to be a space, end of line or end of input. */ \
        if ((a_cchLeft) > 0) \
        { \
            char const chSkipBlanksFirst = *(a_pchWord); \
            KMK_CC_ASSERT(KMK_CC_EVAL_IS_SPACE_AFTER_WORD(a_pCompiler, chSkipBlanksFirst, (a_pchWord)[1], a_cchLeft)); \
            if (chSkipBlanksFirst != '\\') \
            { \
                (a_pchWord) += 1; \
                (a_cchLeft) -= 1; \
                \
                /* Another space or escaped EOL? Then there are probably more then, so call worker function. */ \
                if ((a_cchLeft) > 0) \
                { \
                    char const chSkipBlanksSecond = *(a_pchWord); \
                    if (KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(chSkipBlanksSecond)) \
                        (a_pchWord) = kmk_cc_eval_skip_spaces_after_word_slow(a_pchWord, &(a_cchLeft), \
                                                                              chSkipBlanksSecond, a_pCompiler); \
                } \
            } \
            else /* escape sequences can be complicated. */ \
                (a_pchWord) = kmk_cc_eval_skip_spaces_after_word_slow(a_pchWord, &(a_cchLeft), \
                                                                      chSkipBlanksFirst, a_pCompiler); \
        } \
    } while (0)

/**
 * The slow path of KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD.
 *
 * This is called to handles escaped EOL sequences, as these can involve
 * multiple backslashes and therefore doesn't led themselves well to inlined
 * code.
 *
 * The other case this is used for is to handle more than once space, since it's
 * likely that when there are two there might be more.  No point in inlining
 * that, better do some loop unrolling instead.
 *
 * @returns Points to the first non-space character or end of input.
 * @param   pchWord             The current position. There is some kind of char
 * @param   pcchLeft            Pointer to the cchLeft variable, this is both
 *                              input and output.
 * @param   ch                  The current character.
 * @param   pCompiler           The compiler instance data.
 */
static const char *kmk_cc_eval_skip_spaces_after_word_slow(const char *pchWord, size_t *pcchLeft, char ch,
                                                           PKMKCCEVALCOMPILER pCompiler)
{
    size_t cchLeft = *pcchLeft;

    /*
     * It's all very simple when we don't have to consider escaped EOLs.
     */
    if (pCompiler->iEscEol >= pCompiler->cEscEols)
    {
        if (ch != '\\')
        {
            pchWord += 1;
            cchLeft -= 1;
        }
        else
            return pchWord;
        return kmk_cc_eval_skip_spaces_without_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
    }

    /*
     * Skip the pending space or EOL found by the caller.  We need to
     * confirm the EOL.
     *
     * Note! We only need to care about simple backslash+EOL sequences here
     *       since we're either at the end of a validated word, or we've already
     *       skipped one space.  In the former case, someone else has already
     *       validated the escape esequence, in the latter case multiple
     *       backslashes would indicate a new word that that we should return.
     */
    if (ch != '\\')
    {
        pchWord += 1;
        cchLeft -= 1;
    }
    else if (   cchLeft >= pCompiler->cchEscEolSeq
             && pchWord[1] == pCompiler->chFirstEol)
    {
        KMK_CC_ASSERT(pCompiler->cchEolSeq == 1 || pchWord[2] == pCompiler->chSecondEol);
        pchWord += pCompiler->cchEscEolSeq;
        cchLeft -= pCompiler->cchEscEolSeq;
        pCompiler->iEscEol++;

        if (pCompiler->iEscEol < pCompiler->cEscEols)
        { /* likely */ }
        else return kmk_cc_eval_skip_spaces_without_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
    }
    else
        return pchWord;
    return kmk_cc_eval_skip_spaces_with_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
}


/**
 * Skip zero or more spaces.
 *
 * This macro deals with a single space, if there are more or we're hittin some
 * possible escaped EOL sequence, work is deferred to a worker function.
 *
 * @param   a_pCompiler The compiler state.
 * @param   a_pchWord   The current input position. Advanced past spaces.
 * @param   a_cchLeft   The amount of input left to parse. Will be updated.
 */
#define KMK_CC_EVAL_SKIP_SPACES(a_pCompiler, a_pchWord, a_cchLeft) \
    do { \
        if ((a_cchLeft) > 0) \
        { \
            char chSkipSpaces = *(a_pchWord); \
            if (KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(chSkipSpaces)) \
            { \
                if (chSkipSpaces != '\\') \
                { \
                    (a_pchWord) += 1; \
                    (a_cchLeft) -= 1; \
                    chSkipSpaces = *(a_pchWord); \
                    if (KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(chSkipSpaces)) \
                        (a_pchWord) = kmk_cc_eval_skip_spaces_slow(a_pchWord, &(a_cchLeft), chSkipSpaces, a_pCompiler); \
                } \
                else \
                    (a_pchWord) = kmk_cc_eval_skip_spaces_slow(a_pchWord, &(a_cchLeft), chSkipSpaces, a_pCompiler); \
            } \
        } \
    } while (0)


/**
 * Worker for KMK_CC_EVAL_SKIP_SPACES.
 *
 * @returns Points to the first non-space character or end of input.
 * @param   pchWord             The current position. There is some kind of char
 * @param   pcchLeft            Pointer to the cchLeft variable, this is both
 *                              input and output.
 * @param   ch                  The current character.
 * @param   pCompiler           The compiler instance data.
 */
static const char *kmk_cc_eval_skip_spaces_slow(const char *pchWord, size_t *pcchLeft, char ch, PKMKCCEVALCOMPILER pCompiler)
{
    size_t cchLeft = *pcchLeft;
#ifdef KMK_CC_STRICT
    size_t offWordCcStrict = pchWord - pCompiler->pszContent;
#endif
    KMK_CC_ASSERT(cchLeft > 0);
    KMK_CC_ASSERT(cchLeft <= pCompiler->cchLine);
    KMK_CC_ASSERT(*pchWord == ch);
    KMK_CC_ASSERT(KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(ch));
    KMK_CC_ASSERT(offWordCcStrict >= pCompiler->offLine);
    KMK_CC_ASSERT(offWordCcStrict < pCompiler->offLine + pCompiler->cchLine);
    KMK_CC_ASSERT(   pCompiler->iEscEol >= pCompiler->cEscEols
                  || offWordCcStrict <= pCompiler->paEscEols[pCompiler->iEscEol].offEsc);
    KMK_CC_ASSERT(   pCompiler->iEscEol >= pCompiler->cEscEols
                  || pCompiler->iEscEol == 0
                  || offWordCcStrict >= pCompiler->paEscEols[pCompiler->iEscEol - 1].offEol + pCompiler->cchEolSeq);

    /*
     * If we don't need to consider escaped EOLs, things are much much simpler.
     */
    if (pCompiler->iEscEol >= pCompiler->cEscEols)
    {
        if (ch != '\\')
        {
            pchWord++;
            cchLeft--;
        }
        else
            return pchWord;
        return kmk_cc_eval_skip_spaces_without_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
    }

    /*
     * Possible escaped EOL complications.
     */
    if (ch != '\\')
    {
        pchWord++;
        cchLeft--;
    }
    else
    {
        size_t          cchSkip;
        size_t          offWord;
        unsigned        iEscEol = pCompiler->iEscEol;
        if (iEscEol >= pCompiler->cEscEols)
            return pchWord;

        offWord = pchWord - pCompiler->pszContent;
        if (offWord < pCompiler->paEscEols[iEscEol].offEsc)
            return pchWord;
        KMK_CC_ASSERT(offWord == pCompiler->paEscEols[iEscEol].offEsc);

        cchSkip  = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq - offWord;
        pchWord += cchSkip;
        cchLeft -= cchSkip;
        pCompiler->iEscEol = ++iEscEol;

        if (iEscEol < pCompiler->cEscEols)
        { /* likely */ }
        else return kmk_cc_eval_skip_spaces_without_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
    }
    return kmk_cc_eval_skip_spaces_with_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
}


#if 0 /* unused - probably forever. */
/**
 * Skips to the end of a variable name.
 *
 * This may advance pCompiler->iEscEol.
 *
 * @returns Pointer to the first char after the variable name.
 * @param   pCompiler   The compiler state.
 * @param   pchWord     The current position. Must be at the start of the
 *                      variable name.
 * @param   cchLeft     The number of chars left to parse in the current line.
 * @param   pcchLeft    The to store the updated count of characters left to
 *                      parse.
 * @param   pfPlain     Where to store the plain variable name indicator.
 *                      Returns 0 if plain, and 1 if there are variable
 *                      references in it.
 */
static const char *kmk_cc_eval_skip_var_name(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft,
                                             size_t *pcchLeft, int *pfPlain)
{
    const char * const  pszContent = pCompiler->pszContent;
    size_t              off        = pchWord - pszContent;
    size_t const        offLineEnd = off + cchLeft;
    int                 fPlain     = 1;
    unsigned            iEscEol    = pCompiler->iEscEol;

    /* Check our expectations. */
    KMK_CC_ASSERT(cchLeft);
    KMK_CC_ASSERT(!KMK_CC_EVAL_IS_SPACE(*pchWord));
    KMK_CC_ASSERT(iEscEol <= pCompiler->cEscEols);
    KMK_CC_ASSERT(   iEscEol >= pCompiler->cEscEols
                  || off < pCompiler->paEscEols[iEscEol].offEol);
    KMK_CC_ASSERT(off >= (iEscEol == 0 ? pCompiler->offLine : pCompiler->paEscEols[iEscEol - 1].offEol + pCompiler->cchEolSeq));

    /*
     * The outer loop parses plain text.  Variable expansion ($) is handled
     * by the inner loop.
     */
    while (off < offLineEnd)
    {
        char ch = pszContent[off];
        if (!KMK_CC_EVAL_IS_SPACE_DOLLAR_OR_SLASH(ch))
            off++;
        else if (KMK_CC_EVAL_IS_SPACE(ch))
            break;
        else if (ch == '$')
        {
            off++;
            if (off < offLineEnd)
            {
                char const chOpen = pszContent[off];
                if (chOpen == '(' || chOpen == '{')
                {
                    /*
                     * Got a $(VAR) or ${VAR} to deal with here.  This may
                     * include nested variable references and span multiple
                     * lines (at least for function calls).
                     *
                     * We scan forward till we've found the corresponding
                     * closing parenthesis, considering any open parentheses
                     * of the same kind as worth counting, even if there are
                     * no dollar preceeding them, just like GNU make does.
                     */
                    size_t const offStart = off - 1;
                    char const   chClose  = chOpen == '(' ? ')' : '}';
                    unsigned     cOpen    = 1;
                    off++;
                    for (;;)
                    {
                        if (off < offLineEnd)
                        {
                            ch = pszContent[off];
                            if (!(KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch)))
                                off++;
                            else
                            {
                                off++;
                                if (ch == chClose)
                                {
                                    if (--cOpen == 0)
                                        break;
                                }
                                else if (ch == chOpen)
                                    cOpen++;
                                else if (   ch == '\\'
                                         && iEscEol < pCompiler->cEscEols
                                         && off == pCompiler->paEscEols[iEscEol].offEsc)
                                {
                                    off = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq;
                                    pCompiler->iEscEol = ++iEscEol;
                                }
                            }
                        }
                        else if (cOpen == 1)
                            kmk_cc_eval_fatal(pCompiler, &pszContent[offStart],
                                              "Variable reference is missing '%c'", chClose);
                        else
                            kmk_cc_eval_fatal(pCompiler, &pszContent[offStart],
                                              "%u variable references are missing '%c'", cOpen, chClose);
                    }
                }
                /* Single char variable name. */
                else if (!KMK_CC_EVAL_IS_SPACE(chOpen))
                {  /* likely */ }
                else
                    kmk_cc_eval_fatal(pCompiler, &pszContent[off], "Expected variable name after '$', not end of line");
            }
            else
                kmk_cc_eval_fatal(pCompiler, &pszContent[off], "Expected variable name after '$', not end of line");
            fPlain = 0;
        }
        /* Deal with potential escaped EOL. */
        else if (   ch != '\\'
                 || iEscEol >= pCompiler->cEscEols
                 || off != pCompiler->paEscEols[iEscEol].offEsc )
            off++;
        else
            break;
    }

    *pcchLeft = offLineEnd - off;
    *pfPlain  = fPlain;
    return &pszContent[off];
}
#endif /* unused */


#if 0  /* unused atm */
/**
 * Prepares for copying a command line.
 *
 * The current version of this code will not modify any of the paEscEols
 * entries, unlike our kmk_cc_eval_prep_normal_line sibling function.
 *
 * @returns The number of chars that will be copied by
 *          kmk_cc_eval_copy_prepped_command_line().
 * @param   pCompiler   The compiler instance data.
 * @param   pchLeft     Pointer to the first char to copy from the current line.
 *                      This does not have to the start of a word.
 * @param   cchLeft     The number of chars left on the current line starting at
 *                      @a pchLeft.
 */
static size_t kmk_cc_eval_prep_command_line(PKMKCCEVALCOMPILER pCompiler, const char * const pchLeft, size_t cchLeft)
{
    size_t          cchRet;
    unsigned        iEscEol  = pCompiler->iEscEol;
    unsigned const  cEscEols = pCompiler->cEscEols;

    KMK_CC_ASSERT(cchLeft > 0);
    KMK_CC_ASSERT(iEscEol <= cEscEols);

    if (iEscEol >= cEscEols)
    {
        /*
         * No escaped EOLs left, dead simple.
         */
        cchRet = cchLeft;
    }
    else
    {
        /*
         * Compared to the normal prepping of a line, this is actually
         * really simple.  We need to account for two kind of conversions:
         *      - One leading tab is skipped after escaped EOL.
         *      - Convert EOL to LF.
         */
        const char * const  pszContent = pCompiler->pszContent;
        size_t       const  cchEolSeq  = pCompiler->cchEolSeq;

#ifdef KMK_CC_STRICT
        size_t const offLeft   = pchLeft - pszContent;
        KMK_CC_ASSERT(offLeft + cchLeft <= pCompiler->offLine + pCompiler->cchLine);
        KMK_CC_ASSERT(offLeft + cchLeft <= pCompiler->cchContent);
        KMK_CC_ASSERT(offLeft <  pCompiler->paEscEols[iEscEol].offEsc);
        KMK_CC_ASSERT(offLeft >= (iEscEol ? pCompiler->paEscEols[cEscEols - 1].offEol + pCompiler->cchEolSeq : pCompiler->offLine));
#endif

        cchRet = cchLeft;
        if (cchEolSeq > 1)
            cchRet -= (cchEolSeq - 1) * cEscEols;
        do
        {
            if (pszContent[pCompiler->paEscEols[cchEolSeq].offEol])
                cchRet--;
            iEscEol++;
        } while (iEscEol < cEscEols);
    }
    return cchRet;
}


/**
 * Copies a command line to the buffer @a pszDst points to.
 *
 * Must only be used immediately after kmk_cc_eval_prep_command_line().
 *
 * @returns
 * @param   pCompiler   The compiler instance data.
 * @param   pchLeft     Pointer to the first char to copy from the current line.
 *                      This does not have to the start of a word.
 * @param   cchPrepped  The return value of kmk_cc_eval_prep_command_line().
 * @param   pszDst      The destination buffer, must be at least @a cchPrepped
 *                      plus one (terminator) char big.
 */
static void kmk_cc_eval_copy_prepped_command_line(PKMKCCEVALCOMPILER pCompiler, const char *pchLeft,
                                                  size_t cchPrepped, char *pszDst)
{
    unsigned        iEscEol  = pCompiler->iEscEol;
    unsigned const  cEscEols = pCompiler->cEscEols;
    if (iEscEol >= cEscEols)
    {
        /* Single line. */
        memcpy(pszDst, pchLeft, cchPrepped);
        pszDst[cchPrepped] = '\0';
    }
    else
    {
        /* Multiple lines with normalized EOL and maybe one stripped leading TAB. */
        char * const        pszDstStart = pszDst;
        const char * const  pszContent  = pCompiler->pszContent;
        size_t const        cchEolSeq   = pCompiler->cchEolSeq;
        size_t              offLeft     = pchLeft - pCompiler->pszContent;
        size_t              cchCopy;

        do
        {
            size_t offEol = pCompiler->paEscEols[iEscEol].offEsc;
            cchCopy = offEol - offLeft;
            KMK_CC_ASSERT(offEol >= offLeft);

            memcpy(pszDst, &pszContent[offLeft], cchCopy);
            pszDst += cchCopy;
            *pszDst += '\n';

            offLeft = offEol + cchEolSeq;
            if (pszContent[offLeft] == '\t')
                offLeft++;
        } while (iEscEol < cEscEols);

        cchCopy = cchPrepped - (pszDst - pszDstStart);
        KMK_CC_ASSERT(cchCopy <= cchPrepped);
        memcpy(pszDst, &pszContent[offLeft], cchCopy);
        pszDst += cchCopy;

        *pszDst = '\0';
        KMK_CC_ASSERT(pszDst == &pszDstStart[cchPrepped]);
    }
}
#endif /* unused atm */


static size_t kmk_cc_eval_parse_var_exp(PKMKCCEVALCOMPILER pCompiler, const char *pch, size_t cchLeft, size_t off)
{
    off++;
    if (off < cchLeft)
    {
        char const chOpen = pch[++off];
        if (chOpen == '(' || chOpen == '{')
        {
            /*
             * Got a $(VAR) or ${VAR} to deal with here.  This may include nested
             * variable references and span multiple lines (at least for function
             * calls).
             *
             * We scan forward till we've found the corresponding closing
             * parenthesis, considering any open parentheses of the same kind as
             * worth counting, even if there are no dollar preceeding them, just
             * like GNU make does.
             */
            size_t const offStart = off - 1;
            char const   chClose  = chOpen == '(' ? ')' : '}';
            unsigned     cOpen    = 1;
            off++;
            for (;;)
            {
                if (off < cchLeft)
                {
                    char ch = pch[off];
                    if (!(KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch)))
                        off++;
                    else
                    {
                        off++;
                        if (ch == chClose)
                        {
                            if (--cOpen == 0)
                                break;
                        }
                        else if (ch == chOpen)
                            cOpen++;
                        else if (   ch == '\\'
                                 && pCompiler->iEscEol < pCompiler->cEscEols
                                 &&    (size_t)(&pch[off] - pCompiler->pszContent)
                                    == pCompiler->paEscEols[pCompiler->iEscEol].offEsc)
                        {
                            off += pCompiler->paEscEols[pCompiler->iEscEol].offEol
                                 - pCompiler->paEscEols[pCompiler->iEscEol].offEsc
                                 + pCompiler->cchEolSeq;
                            pCompiler->iEscEol++;
                        }
                    }
                }
                else if (cOpen == 1)
                    kmk_cc_eval_fatal(pCompiler, &pch[offStart], "Variable reference is missing '%c'", chClose);
                else
                    kmk_cc_eval_fatal(pCompiler, &pch[offStart],
                                      "%u variable references are missing '%c'", cOpen, chClose);
            }
        }
        /* Single char variable name. */
        else if (!KMK_CC_EVAL_IS_SPACE(chOpen))
        {  /* likely */ }
        else
            kmk_cc_eval_fatal(pCompiler, &pch[off], "Expected variable name after '$', not space ");
    }
    else
        kmk_cc_eval_fatal(pCompiler, &pch[off], "Expected variable name after '$', end of line");
    return off;
}

/**
 * Helper for ensuring that we've got sufficient number of words allocated.
 */
#define KMK_CC_EVAL_ENSURE_WORDS(a_pCompiler, a_cRequiredWords) \
    do { \
        if ((a_cRequiredWords) < (a_pCompiler)->cWordsAllocated) \
        { /* likely */ } \
        else \
        { \
            unsigned cEnsureWords = ((a_cRequiredWords) + 3 /*15*/) & ~(unsigned)3/*15*/; \
            KMK_CC_ASSERT((a_cRequiredWords) < 0x8000); \
            (a_pCompiler)->paWords = (PKMKCCEVALWORD)xrealloc((a_pCompiler)->paWords, \
                                                              cEnsureWords * sizeof((a_pCompiler)->paWords)[0]); \
        } \
    } while (0)


/**
 * Word parser helper function for dealing with dollars, simple variant that
 * doesn't need to take multiple lines into account.
 *
 * @returns New word length placing us after the
 * @param   pCompiler   The compiler state.
 * @param   cchWord     Offset of the dollar into pchWord.
 * @param   pchWord     The word we're currently parsing.
 * @param   cchLeft     How much we've got left to parse.
 */
K_INLINE size_t kmk_cc_eval_parse_along_dollar_simple(PKMKCCEVALCOMPILER pCompiler, size_t cchWord,
                                                      const char *pchWord, size_t cchLeft)
{
    const size_t cchStart = cchWord;
    cchWord++;
    if (cchWord < cchLeft)
    {
        /*
         * Got a $(VAR) or ${VAR} to deal with here.  This may include nested variable
         * references and span multiple lines (at least for function calls).
         *
         * We scan forward till we've found the corresponding closing parenthesis,
         * considering any open parentheses of the same kind as worth counting, even
         * if there are no dollar preceeding them, just like GNU make does.
         *
         * We leave the other parenthesis type to the expansion compiler to deal with.
         */
        unsigned   cOpens = 1;
        char const chOpen = pchWord[cchWord++];
        if (chOpen == '(')
        {
            for (;;)
            {
                if (cchWord < cchLeft)
                {
                    char const ch = pchWord[cchWord++];
                    if (!KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch))
                    { /* likely */ }
                    else if (ch == ')')
                    {
                        if (--cOpens == 0)
                            return cchWord;
                    }
                    else if (ch == '(')
                       cOpens++;
                }
                else
                    break;
            }
        }
        else if (chOpen == '{')
        {
            for (;;)
            {
                if (cchWord < cchLeft)
                {
                    char const ch = pchWord[cchWord++];
                    if (!KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch))
                    { /* likely */ }
                    else if (ch == '}')
                    {
                        if (--cOpens == 0)
                            return cchWord;
                    }
                    else if (ch == '{')
                       cOpens++;
                }
                else
                    break;
            }
        }
        else
            return cchWord;

        /* Unterminated. */
        if (cOpens == 1)
            kmk_cc_eval_fatal(pCompiler, &pchWord[cchStart],
                              "Variable reference is missing '%c'", chOpen == '(' ? ')' : '}');
        else
            kmk_cc_eval_fatal(pCompiler, &pchWord[cchStart],
                              "%u variable references are missing '%c'", cOpens, chOpen == '(' ? ')' : '}');
    }
    else
        kmk_cc_eval_warn(pCompiler, &pchWord[cchWord - 1], "found '$' at end of line");
    return cchWord;
}


/**
 * Word parser helper function for dealing with dollars, complicated variant
 * that takes escaped EOLs into account.
 *
 * @returns New word length placing us after the
 * @param   pCompiler   The compiler state.
 * @param   cchWord     Offset of the dollar into pchWord.
 * @param   pchWord     The word we're currently parsing.
 * @param   cchLeft     How much we've got left to parse.
 */
static size_t kmk_cc_eval_parse_along_dollar_esc_eol(PKMKCCEVALCOMPILER pCompiler, size_t cchWord,
                                                     const char *pchWord, size_t cchLeft)
{
    const size_t cchStart = cchWord;
    cchWord++;
    if (cchWord < cchLeft)
    {
        /*
         * Got a $(VAR) or ${VAR} to deal with here.  This may include nested variable
         * references and span multiple lines (at least for function calls).
         *
         * We scan forward till we've found the corresponding closing parenthesis,
         * considering any open parentheses of the same kind as worth counting, even
         * if there are no dollar preceeding them, just like GNU make does.
         *
         * We leave the other parenthesis type to the expansion compiler to deal with.
         */
        unsigned   cOpens = 1;
        char const chOpen = pchWord[cchWord++];
        if (chOpen == '(')
        {
            for (;;)
            {
                if (cchWord < cchLeft)
                {
                    char const ch = pchWord[cchWord++];
                    if (!KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch))
                    { /* likely */ }
                    else if (ch == ')')
                    {
                        if (--cOpens == 0)
                            return cchWord;
                    }
                    else if (ch == '(')
                       cOpens++;
                    else if (ch == '\\')
                    {
                        unsigned const iEscEol = pCompiler->iEscEol;
                        if (   iEscEol < pCompiler->cEscEols
                            && (size_t)(&pchWord[cchWord] - pCompiler->pszContent) == pCompiler->paEscEols[iEscEol].offEsc)
                        {
                            cchWord += pCompiler->paEscEols[iEscEol].offEol
                                     - pCompiler->paEscEols[iEscEol].offEsc
                                     + pCompiler->cchEolSeq;
                            pCompiler->iEscEol = iEscEol + 1;
                        }
                    }
                }
                else
                    break;
            }
        }
        else if (chOpen == '{')
        {
            for (;;)
            {
                if (cchWord < cchLeft)
                {
                    char const ch = pchWord[cchWord++];
                    if (!KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch))
                    { /* likely */ }
                    else if (ch == '}')
                    {
                        if (--cOpens == 0)
                            return cchWord;
                    }
                    else if (ch == '{')
                       cOpens++;
                    else if (ch == '\\')
                    {
                        unsigned const iEscEol = pCompiler->iEscEol;
                        if (   iEscEol < pCompiler->cEscEols
                            && (size_t)(&pchWord[cchWord] - pCompiler->pszContent) == pCompiler->paEscEols[iEscEol].offEsc)
                        {
                            cchWord += pCompiler->paEscEols[iEscEol].offEol
                                     - pCompiler->paEscEols[iEscEol].offEsc
                                     + pCompiler->cchEolSeq;
                            pCompiler->iEscEol = iEscEol + 1;
                        }
                    }
                }
                else
                    break;
            }
        }
        else
            return cchWord;

        /* Unterminated. */
        if (cOpens == 1)
            kmk_cc_eval_fatal(pCompiler, &pchWord[cchStart],
                              "Variable reference is missing '%c'", chOpen == '(' ? ')' : '}');
        else
            kmk_cc_eval_fatal(pCompiler, &pchWord[cchStart],
                              "%u variable references are missing '%c'", cOpens, chOpen == '(' ? ')' : '}');
    }
    else
        kmk_cc_eval_warn(pCompiler, &pchWord[cchWord - 1], "found '$' at end of line");
    return cchWord;
}


/**
 * Parses the remainder of the line into simple words.
 *
 * The resulting words are classified as either kKmkCcEvalToken_WordPlain,
 * kKmkCcEvalToken_WordWithDollar, or kKmkCcEvalToken_WordWithDollarAndEscEol.
 *
 * @returns Number of words.
 * @param   pCompiler   The compiler state.
 * @param   pchWord     Where to start, we expect this to be at a word.
 * @param   cchLeft     The number of chars left to parse on this line.
 *                      This is expected to be non-zero.
 */
static unsigned kmk_cc_eval_parse_words(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    unsigned iEscEol  = pCompiler->iEscEol;
    unsigned cEscEols = pCompiler->cEscEols;
    unsigned cWords   = 0;

    /* Precoditions. */
    KMK_CC_ASSERT(cchLeft > 0);
    KMK_CC_ASSERT(!KMK_CC_EVAL_IS_SPACE(*pchWord));

    /*
     * If we don't have to deal with escaped EOLs, the find-end-of word search
     * becomes a little bit simpler.  Since this function will be used a lot
     * for simple lines with single words, this could maybe save a nano second
     * or two.
     */
    if (iEscEol >= cEscEols)
    {
        do
        {
            size_t          cchSkipAfter = 0;
            size_t          cchWord      = 0;
            KMKCCEVALTOKEN  enmToken     = kKmkCcEvalToken_WordPlain;

            /* Find the end of the current word. */
            while (cchWord < cchLeft)
            {
                char ch = pchWord[cchWord];
                if (!KMK_CC_EVAL_IS_SPACE_OR_DOLLAR(ch))
                    cchWord++;
                else if (ch == '$')
                {
#ifdef XXXX
                    cchWord = kmk_cc_eval_parse_var_exp(pCompiler, pchWord, cchLeft, cchWord);
                    enmToken = kKmkCcEvalToken_WordWithDollar;
#else
                    enmToken = kKmkCcEvalToken_WordWithDollar;
                    cchWord = kmk_cc_eval_parse_along_dollar_simple(pCompiler, cchWord, pchWord, cchLeft);
#endif
                }
                else
                    break;
            }

            /* Add the word. */
            KMK_CC_EVAL_ENSURE_WORDS(pCompiler, cWords + 1);
            pCompiler->paWords[cWords].pchWord            = pchWord;
            pCompiler->paWords[cWords].cchWord            = cchWord;
            pCompiler->paWords[cWords].enmToken           = enmToken;
            cWords++;

            /* Skip the work and any trailing blanks. */
            cchWord += cchSkipAfter;
            pchWord += cchWord;
            cchLeft -= cchWord;
            KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
        } while (cchLeft > 0);
    }
    /*
     * Have to deal with escaped EOLs.
     */
    else
    {
        const char *pszContent = pCompiler->pszContent;
        do
        {
            size_t          cchSkipAfter = 0;
            size_t          cchWord      = 0;
            KMKCCEVALTOKEN  enmToken     = kKmkCcEvalToken_WordPlain;

            /* Find the end of the current word. */
            while (cchWord < cchLeft)
            {
                char ch = pchWord[cchWord];
                if (!KMK_CC_EVAL_IS_SPACE_DOLLAR_OR_SLASH(ch))
                    cchWord++;
                else if (ch == '$')
#ifdef XXXX
                {
                    const unsigned iEscEolBefore = pCompiler->iEscEol;
                    cchWord = kmk_cc_eval_parse_var_exp(pCompiler, pchWord, cchLeft, cchWord);
                    enmToken = pCompiler->iEscEol == iEscEolBefore
                             ? kKmkCcEvalToken_WordWithDollar : kKmkCcEvalToken_WordWithDollarAndEscEol;
                }
#else
                {
                    enmToken = kKmkCcEvalToken_WordWithDollar;
                    cchWord = kmk_cc_eval_parse_along_dollar_esc_eol(pCompiler, cchWord, pchWord, cchLeft);
                }
#endif
                else if (ch != '\\')
                    break;
                else if ((size_t)(&pchWord[cchWord] - pszContent) != pCompiler->paEscEols[iEscEol].offEsc)
                    cchWord++;
                else
                {
                    cchSkipAfter = pCompiler->paEscEols[iEscEol].offEol - pCompiler->paEscEols[iEscEol].offEsc
                                 + pCompiler->cchEolSeq;
                    iEscEol++;
                    break;
                }
            }

            /* Add the word. */
            KMK_CC_EVAL_ENSURE_WORDS(pCompiler, cWords + 1);
            pCompiler->paWords[cWords].pchWord  = pchWord;
            pCompiler->paWords[cWords].cchWord  = cchWord;
            pCompiler->paWords[cWords].enmToken = enmToken;
            cWords++;

            /* Skip the work and any trailing blanks. */
            cchWord += cchSkipAfter;
            pchWord += cchWord;
            cchLeft -= cchWord;
            KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
        } while (cchLeft > 0);
    }
    pCompiler->cWords = cWords;
    return cWords;
}


/**
 * Parses the remainder of the line into target words.
 *
 * The resulting words are classified as either kKmkCcEvalToken_WordPlain or
 * kKmkCcEvalToken_WordWithDollar.
 *
 * @returns Number of words.
 * @param   pCompiler   The compiler state.
 * @param   pchWord     Where to start, we expect this to be at a word.
 * @param   cchLeft     The number of chars left to parse on this line.
 *                      This is expected to be non-zero.
 */
static unsigned kmk_cc_eval_parse_recipe_words(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    unsigned iEscEol  = pCompiler->iEscEol;
    unsigned cEscEols = pCompiler->cEscEols;
    unsigned cWords   = 0;

    /* Precoditions. */
    KMK_CC_ASSERT(cchLeft > 0);
    KMK_CC_ASSERT(!KMK_CC_EVAL_IS_SPACE(*pchWord));

    /*
     * If we don't have to deal with escaped EOLs, the find-end-of word search
     * becomes a little bit simpler.  Since this function will be used a lot
     * for simple lines with single words, this could maybe save a nano second
     * or two.
     */
    if (iEscEol >= cEscEols)
    {
        do
        {
            size_t          cchSkipAfter = 0;
            size_t          cchWord      = 0;
            KMKCCEVALTOKEN  enmToken     = kKmkCcEvalToken_WordPlain;

            /* Find the end of the current word. */
            while (cchWord < cchLeft)
            {
                char ch = pchWord[cchWord];
                if (!KMK_CC_EVAL_IS_SPACE_OR_DOLLAR(ch))
                    cchWord++;
                else if (ch == '$')
                {
                    enmToken = kKmkCcEvalToken_WordWithDollar;
                    cchWord = kmk_cc_eval_parse_along_dollar_simple(pCompiler, cchWord, pchWord, cchLeft);
                }
                else
                    break;
            }

            /* Add the word. */
            KMK_CC_EVAL_ENSURE_WORDS(pCompiler, cWords + 1);
            pCompiler->paWords[cWords].pchWord  = pchWord;
            pCompiler->paWords[cWords].cchWord  = cchWord;
            pCompiler->paWords[cWords].enmToken = enmToken;
            cWords++;

            /* Skip the work and any trailing blanks. */
            cchWord += cchSkipAfter;
            pchWord += cchWord;
            cchLeft -= cchWord;
            KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
        } while (cchLeft > 0);
    }
    /*
     * Have to deal with escaped EOLs.
     */
    else
    {
        const char *pszContent = pCompiler->pszContent;
        do
        {
            size_t          cchSkipAfter = 0;
            size_t          cchWord      = 0;
            KMKCCEVALTOKEN  enmToken     = kKmkCcEvalToken_WordPlain;

            /* Find the end of the current word. */
            while (cchWord < cchLeft)
            {
                char ch = pchWord[cchWord];
                if (!KMK_CC_EVAL_IS_SPACE_DOLLAR_OR_SLASH(ch))
                    cchWord++;
                else if (ch == '$')
                {
                    enmToken = kKmkCcEvalToken_WordWithDollar;
                    cchWord = kmk_cc_eval_parse_along_dollar_esc_eol(pCompiler, cchWord, pchWord, cchLeft);
                }
                else if (ch != '\\')
                    break;
                else if ((size_t)(&pchWord[cchWord] - pszContent) != pCompiler->paEscEols[iEscEol].offEsc)
                    cchWord++;
                else
                {
                    cchSkipAfter = pCompiler->paEscEols[iEscEol].offEol - pCompiler->paEscEols[iEscEol].offEsc
                                 + pCompiler->cchEolSeq;
                    iEscEol++;
                    break;
                }
            }

            /* Add the word. */
            KMK_CC_EVAL_ENSURE_WORDS(pCompiler, cWords + 1);
            pCompiler->paWords[cWords].pchWord  = pchWord;
            pCompiler->paWords[cWords].cchWord  = cchWord;
            pCompiler->paWords[cWords].enmToken = enmToken;
            cWords++;

            /* Skip the work and any trailing blanks. */
            cchWord += cchSkipAfter;
            pchWord += cchWord;
            cchLeft -= cchWord;
            KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
        } while (cchLeft > 0);
    }
    pCompiler->cWords = cWords;
    return cWords;
}




/**
 * Gather string from segments and optional space insertion trick.
 *
 * @param   pszDst          The destination buffer.
 * @param   paSegs          The source segments.
 * @param   cSegs           The number of segments.
 * @param   cchDstPrepped   The size of pszDst, excluding the terminator.
 */
static void kmk_cc_eval_strcpyv(char *pszDst, PCKMKCCEVALSTRCPYSEG paSegs, unsigned cSegs, size_t cchDstPrepped)
{
    const char *pszDstStart = pszDst;
    unsigned    iSeg = 0;
    while (iSeg < cSegs)
    {
        size_t cchToCopy;
        if (paSegs[iSeg].cchSrcAndPrependSpace >= 0)
            cchToCopy = paSegs[iSeg].cchSrcAndPrependSpace;
        else
        {
            cchToCopy = -paSegs[iSeg].cchSrcAndPrependSpace;
            *pszDst++ = ' ';
        }

        memcpy(pszDst, paSegs[iSeg].pchSrc, cchToCopy);
        pszDst += cchToCopy;

        iSeg++;
    }
    *pszDst = '\0';
    KMK_CC_ASSERT(pszDst == &pszDstStart[cchDstPrepped]); K_NOREF(pszDstStart); K_NOREF(cchDstPrepped);
}


/**
 * Allocate a byte buffer and ocpy the prepared string segments into it.
 *
 * The caller must call kmk_cc_block_realign!
 *
 * @returns Pointer to the duplicated string.
 * @param   pCompiler       The compiler instance data.
 * @param   cchPrepped      The length of the prepped string segments.
 */
static char *kmk_cc_eval_strdup_prepped(PKMKCCEVALCOMPILER pCompiler, size_t cchPrepped)
{
    char *pszCopy = kmk_cc_block_byte_alloc(pCompiler->ppBlockTail, cchPrepped + 1);
    kmk_cc_eval_strcpyv(pszCopy, pCompiler->paStrCopySegs, pCompiler->cStrCopySegs, cchPrepped);
    return pszCopy;
}


/**
 * Strip trailing spaces from prepped copy
 *
 * @param   paSegs          The segments to strip trailing chars from.
 * @param   pcSegs          The number of segments (in/out).
 * @param   pcchDstPrepped  The total number of chars prepped (in/out).
 */
static void kmk_cc_eval_strip_right_v(PKMKCCEVALSTRCPYSEG paSegs, unsigned *pcSegs, size_t *pcchDstPrepped)
{
    /*
     * Work our way thru the segments, from the end obviously.
     */
    size_t   cchDstPrepped = *pcchDstPrepped;
    unsigned cSegs         = *pcSegs;
    while (cSegs > 0)
    {
        unsigned    iSeg   = cSegs - 1;
        const char *pszSrc = paSegs[iSeg].pchSrc;
        size_t      cchSrc = paSegs[iSeg].cchSrcAndPrependSpace >= 0
                           ? paSegs[iSeg].cchSrcAndPrependSpace : -paSegs[iSeg].cchSrcAndPrependSpace;
        if (cchSrc)
        {
            /*
             * Check for trailing spaces.
             */
            size_t cchSrcOrg;
            if (!KMK_CC_EVAL_IS_SPACE(pszSrc[cchSrc - 1]))
            {
                /* Special case: No trailing spaces at all. No need to update
                                 input/output variables. */
                if (cSegs == *pcSegs)
                    return;
                break;
            }

            /* Skip the rest of the trailing spaces. */
            cchSrcOrg = cchSrc;
            do
                cchSrc--;
            while (cchSrc > 0 && KMK_CC_EVAL_IS_SPACE(pszSrc[cchSrc - 1]));

            if (cchSrc > 0)
            {
                /*
                 * There are non-space chars in this segment. So, update the
                 * segment and total char count and we're done.
                 */
                cchDstPrepped -= cchSrcOrg - cchSrc;
                if (paSegs[iSeg].cchSrcAndPrependSpace < 0)
                    paSegs[iSeg].cchSrcAndPrependSpace = -(ssize_t)cchSrc;
                else
                    paSegs[iSeg].cchSrcAndPrependSpace = cchSrc;
                break;
            }

            /*
             * Skip the whole segment.
             */
            cchDstPrepped -= cchSrcOrg + (paSegs[iSeg].cchSrcAndPrependSpace < 0);
        }
        cSegs--;
    }
    *pcchDstPrepped = cchDstPrepped;
    *pcSegs         = cSegs;
}

/**
 * Helper for ensuring that we've got sufficient number of string copy segments.
 */
#define KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(a_pCompiler, a_cRequiredSegs) \
    do { \
        if ((a_cRequiredSegs) < (a_pCompiler)->cStrCopySegsAllocated) \
        { /* likely */ } \
        else \
        { \
            unsigned cEnsureSegs = ((a_cRequiredSegs) + 3 /*15*/) & ~(unsigned)3/*15*/; \
            KMK_CC_ASSERT((a_cRequiredSegs) < 0x8000); \
            (a_pCompiler)->paStrCopySegs = (PKMKCCEVALSTRCPYSEG)xmalloc(cEnsureSegs * sizeof((a_pCompiler)->paStrCopySegs)[0]); \
        } \
    } while (0)


/**
 * Prepares for copying a normal line, extended version.
 *
 * This does not assume that we start on a word, it can handle any starting
 * character.  It can also prepare partial copies.
 *
 * In addition to the returned information, this will store instruction in
 * paEscEols for the following kmk_cc_eval_strcpyv() call.
 *
 * This will advance pCompiler->iEscEol, so that it's possible to use the common
 * macros and helpers for parsing what comes afterwards.
 *
 * @returns The number of chars that will be copied by kmk_cc_eval_strcpyv().
 * @param   pCompiler               The compiler instance data.
 * @param   pchWord                 Pointer to the first char to copy from the
 *                                  current line. This must be the start of a
 *                                  word.
 * @param   cchLeft                 The number of chars left on the current line
 *                                  starting at @a pchWord.
 */
static size_t kmk_cc_eval_prep_normal_line_ex(PKMKCCEVALCOMPILER pCompiler, const char * const pchWord, size_t cchLeft)
{
    size_t          cchRet;
    unsigned        iEscEol  = pCompiler->iEscEol;
    unsigned const  cEscEols = pCompiler->cEscEols;

    KMK_CC_ASSERT(iEscEol <= cEscEols);

    if (cchLeft > 0)
    {
        /*
         * If there are no escaped EOLs left, just copy exactly
         * what was passed in.
         */
        if (iEscEol >= cEscEols)
        {
            KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, 1);
            pCompiler->cStrCopySegs = 1;
            pCompiler->paStrCopySegs[0].pchSrc = pchWord;
            pCompiler->paStrCopySegs[0].cchSrcAndPrependSpace = cchRet = cchLeft;
        }
        /*
         * Ok, we have to deal with escaped EOLs and do the proper
         * replacement of escaped newlines with space.  The deal is that we
         * collaps all whitespace before and after one or more newlines into a
         * single space.  (FreeBSD make does this differently, by the by.)
         */
        else
        {
            const char * const  pszContent    = pCompiler->pszContent;
            size_t              offWord       = pchWord - pCompiler->pszContent;
            size_t const        offLineEnd    = offWord + cchLeft;             /* Note! Not necessarily end of line.*/
            size_t              offEsc;
            size_t              fPendingSpace = 0;
            unsigned            cSegs         = 0;
            size_t              cchSeg;

            /* Go nuts checking our preconditions here. */
            KMK_CC_ASSERT(offWord >= pCompiler->offLine);
            KMK_CC_ASSERT(offWord + cchLeft <= pCompiler->offLine + pCompiler->cchLine);
            KMK_CC_ASSERT(offWord + cchLeft <= pCompiler->cchContent);
            KMK_CC_ASSERT(offWord <= pCompiler->paEscEols[iEscEol].offEsc);
            KMK_CC_ASSERT(offWord >= (iEscEol ? pCompiler->paEscEols[cEscEols - 1].offEol + pCompiler->cchEolSeq
                                              : pCompiler->offLine));
            KMK_CC_ASSERT(offWord <  offLineEnd);

            /* Make sure we've got more than enough segments to fill in. */
            KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cEscEols - iEscEol + 2);

            /*
             * All but the last line.
             */
            cchRet = 0;
            do
            {
                KMK_CC_ASSERT(offWord < offLineEnd);
                offEsc = pCompiler->paEscEols[iEscEol].offEsc;
                if (offWord < offEsc)
                {
                    /* Strip trailing spaces. */
                    while (offEsc > offWord && KMK_CC_EVAL_IS_SPACE(pszContent[offEsc - 1]))
                        offEsc--;
                    cchSeg = offEsc - offWord;
                    if (cchSeg)
                    {
                        /* Add segment. */
                        pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offWord];
                        if (offEsc < offLineEnd)
                        {
                            pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
                                                                                  ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
                            cchRet       += cchSeg + fPendingSpace;
                            cSegs        += 1;
                            fPendingSpace = 1;
                        }
                        else
                        {
                            cchSeg = offLineEnd - offWord;
                            pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
                                                                                  ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
                            pCompiler->cStrCopySegs = cSegs + 1;
                            pCompiler->iEscEol      = iEscEol;
                            return cchRet + cchSeg + fPendingSpace;
                        }
                    }
                }
                else
                    KMK_CC_ASSERT(offWord == offEsc);

                /* Next line. */
                offWord = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq;
                iEscEol++;

                /* Strip leading spaces. */
                while (offWord < offLineEnd && KMK_CC_EVAL_IS_SPACE(pszContent[offWord]))
                    offWord++;
                if (offWord >= offLineEnd)
                {
                    pCompiler->cStrCopySegs = cSegs;
                    pCompiler->iEscEol      = iEscEol;
                    return cchRet;
                }
            } while (iEscEol < cEscEols);

            /*
             * The last line.
             */
            cchSeg      = offLineEnd - offWord;
            cchRet     += cchSeg;
            pCompiler->paStrCopySegs[cSegs].pchSrc                = &pszContent[offWord];
            pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
                                                                  ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
            pCompiler->cStrCopySegs = cSegs + 1;
            pCompiler->iEscEol      = iEscEol;
        }
    }
    /*
     * Odd case: Nothing to copy.
     */
    else
    {
        cchRet = 0;
        pCompiler->cStrCopySegs = 0;
    }
    return cchRet;
}


/**
 * Prepares for copying a normal line, from the given position all the way to
 * the end.
 *
 * In addition to the returned information, this will store instruction in
 * paStrCopySegs and cSTrCopySeg for the following kmk_cc_eval_strcpyv() call.
 *
 * @returns The number of chars that will be copied by kmk_cc_eval_strcpyv().
 * @param   pCompiler               The compiler instance data.
 * @param   pchWord                 Pointer to the first char to copy from the
 *                                  current line. This must be the start of a
 *                                  word.
 * @param   cchLeft                 The number of chars left on the current line
 *                                  starting at @a pchWord.
 */
static size_t kmk_cc_eval_prep_normal_line(PKMKCCEVALCOMPILER pCompiler, const char * const pchWord, size_t cchLeft)
{
    size_t          cchRet;
    unsigned        iEscEol  = pCompiler->iEscEol;
    unsigned const  cEscEols = pCompiler->cEscEols;

    KMK_CC_ASSERT(cchLeft > 0);
    KMK_CC_ASSERT(!KMK_CC_EVAL_IS_SPACE(*pchWord)); /* The fact that we're standing at a word, is exploited below. */
    KMK_CC_ASSERT(iEscEol <= cEscEols);

    /*
     * If there are no escaped EOLs left, just copy what was specified,
     * optionally sans any trailing spaces.
     */
    if (iEscEol >= cEscEols)
    {
        cchRet = cchLeft;

        KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, 1);
        pCompiler->cStrCopySegs = 1;
        pCompiler->paStrCopySegs[0].pchSrc = pchWord;
        pCompiler->paStrCopySegs[0].cchSrcAndPrependSpace = cchRet;
    }
    /*
     * Ok, we have to deal with escaped EOLs and do the proper
     * replacement of escaped newlines with space.  The deal is that we
     * collaps all whitespace before and after one or more newlines into a
     * single space.  (FreeBSD make does this differently, by the by.)
     */
    else
    {
        const char *pszContent = pCompiler->pszContent;
        size_t      offWord    = pchWord - pCompiler->pszContent;
        size_t      offEsc;
        size_t      fPendingSpace;
        size_t      cchSeg;
        unsigned    cSegs      = 0;

        /* Go nuts checking our preconditions here. */
        KMK_CC_ASSERT(offWord >= pCompiler->offLine);
        KMK_CC_ASSERT(offWord + cchLeft <= pCompiler->offLine + pCompiler->cchLine);
        KMK_CC_ASSERT(offWord + cchLeft <= pCompiler->cchContent);
        KMK_CC_ASSERT(offWord <  pCompiler->paEscEols[iEscEol].offEsc);
        KMK_CC_ASSERT(offWord >= (iEscEol ? pCompiler->paEscEols[iEscEol - 1].offEol + pCompiler->cchEolSeq : pCompiler->offLine));

        /* Make sure we've got more than enough segments to fill in. */
        KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cEscEols - iEscEol + 2);

        /*
         * First line - We're at the start of a word, so no left stripping needed.
         */
        offEsc = pCompiler->paEscEols[iEscEol].offEsc;
        KMK_CC_ASSERT(offEsc > offWord);
        while (KMK_CC_EVAL_IS_SPACE(pszContent[offEsc - 1]))
            offEsc--;
        KMK_CC_ASSERT(offEsc > offWord);

        fPendingSpace = 1;
        cchRet        = offEsc - offWord;
        pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = cchRet;
        pCompiler->paStrCopySegs[cSegs].pchSrc                = pchWord;
        cSegs++;

        offWord = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq;
        iEscEol++;

        /*
         * All but the last line.
         */
        while (iEscEol < cEscEols)
        {
            offEsc = pCompiler->paEscEols[iEscEol].offEsc;

            /* Strip leading spaces. */
            while (offWord < offEsc && KMK_CC_EVAL_IS_SPACE(pszContent[offWord]))
                offWord++;

            if (offWord < offEsc)
            {
                /* Strip trailing spaces. */
                while (KMK_CC_EVAL_IS_SPACE(pszContent[offEsc - 1]))
                    offEsc--;
                cchSeg = offEsc - offWord;
                pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
                cchRet += cchSeg + fPendingSpace;
                pCompiler->paStrCopySegs[cSegs].pchSrc                = &pszContent[offWord];
                cSegs  += 1;
                fPendingSpace = 1;
            }

            /* Next. */
            offWord = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq;
            iEscEol++;
        }

        /*
         * Final line. We must calculate the end of line offset our selves here.
         */
        offEsc = &pchWord[cchLeft] - pszContent;
        while (offWord < offEsc && KMK_CC_EVAL_IS_SPACE(pszContent[offWord]))
            offWord++;

        if (offWord < offEsc)
        {
            cchSeg = offEsc - offWord;
            pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
            cchRet += cchSeg + fPendingSpace;
            pCompiler->paStrCopySegs[cSegs].pchSrc                = &pszContent[offWord];
            cSegs  += 1;
        }

        pCompiler->cStrCopySegs = cSegs;
    }
    return cchRet;
}


/**
 * Common worker for all kmk_cc_eval_do_if*() functions.
 *
 * @param   pCompiler   The compiler state.
 * @param   pIfCore     The new IF statement.
 * @param   fInElse     Set if this is an 'else if' (rather than just 'if').
 */
static void kmk_cc_eval_do_if_core(PKMKCCEVALCOMPILER pCompiler, PKMKCCEVALIFCORE pIfCore, int fInElse)
{
    unsigned iIf = pCompiler->cIfs;
    if (!fInElse)
    {
        /* Push an IF statement. */
        if (iIf < KMK_CC_EVAL_MAX_IF_DEPTH)
        {
            pCompiler->cIfs = iIf + 1;
            pCompiler->apIfs[iIf] = pIfCore;
            pIfCore->pPrevCond = NULL;
        }
        else
            kmk_cc_eval_fatal(pCompiler, NULL, "Too deep IF nesting");
    }
    else if (iIf > 0)
    {
        /* Link an IF statement. */
        iIf--;
        pIfCore->pPrevCond    = pCompiler->apIfs[iIf];
        pCompiler->apIfs[iIf] = pIfCore;
    }
    else
        kmk_cc_eval_fatal(pCompiler, NULL, "'else if' without 'if'");
    pIfCore->pNextTrue    = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
    pIfCore->pNextFalse   = NULL; /* This is set by else or endif. */
    pIfCore->pTrueEndJump = NULL; /* This is set by else or endif. */
}


/**
 * Deals with 'if expr' and 'else if expr' statements.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after 'if'.
 * @param   cchLeft     The number of chars left to parse on this line.
 * @param   fInElse     Set if this is an 'else if' (rather than just 'if').
 */
static int kmk_cc_eval_do_if(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, int fInElse)
{
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (cchLeft)
    {
        PKMKCCEVALIFEXPR pInstr;
        size_t           cchExpr = kmk_cc_eval_prep_normal_line(pCompiler, pchWord, cchLeft);
        kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &cchExpr);

        pInstr = (PKMKCCEVALIFEXPR)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, KMKCCEVALIFEXPR_SIZE(cchExpr));
        kmk_cc_eval_strcpyv(pInstr->szExpr, pCompiler->paStrCopySegs, pCompiler->cStrCopySegs, cchExpr);
        pInstr->cchExpr = cchExpr;
        pInstr->IfCore.Core.enmOpcode = kKmkCcEvalInstr_if;
        pInstr->IfCore.Core.iLine     = pCompiler->iLine;
        kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);
    }
    else
        kmk_cc_eval_fatal(pCompiler, pchWord, "Expected expression after 'if' directive");
    return 1;
}


/**
 * Deals with 'ifdef var', 'ifndef var', 'else ifdef var' and 'else ifndef var'
 * statements.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler       The compiler state.
 * @param   pchWord         First char after 'if[n]def'.
 * @param   cchLeft         The number of chars left to parse on this line.
 * @param   fInElse         Set if this is an 'else if' (rather than just 'if').
 * @param   fPositiveStmt   Set if 'ifdef', clear if 'ifndef'.
 */
static int kmk_cc_eval_do_ifdef(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, int fInElse, int fPositiveStmt)
{
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (cchLeft)
    {
        /*
         * Skip to the end of the variable name.
         * GNU make just does normal word parsing, so lets do that too.
         */
        unsigned const  iSavedEscEol = pCompiler->iEscEol;
        unsigned        cWords = kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft);
        if (cWords == 0)
        {
            PCKMKCCEVALWORD pWord = pCompiler->paWords;
            if (pWord->enmToken == kKmkCcEvalToken_WordPlain)
            {
                PKMKCCEVALIFDEFPLAIN pInstr;
                pInstr = (PKMKCCEVALIFDEFPLAIN)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
                pInstr->IfCore.Core.enmOpcode = fPositiveStmt ? kKmkCcEvalInstr_ifdef_plain : kKmkCcEvalInstr_ifndef_plain;
                pInstr->IfCore.Core.iLine     = pCompiler->iLine;
                pInstr->pszName = strcache2_add(&variable_strcache, pWord->pchWord, pWord->cchWord);
                kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);
            }
            else
            {
                PKMKCCEVALIFDEFDYNAMIC  pInstr;
                size_t                  cchCopy;
                char const             *pszCopy;

                pInstr = (PKMKCCEVALIFDEFDYNAMIC)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));

                /** @todo Make the subprogram embed necessary strings. */
                if (pWord->enmToken != kKmkCcEvalToken_WordWithDollar)
                {
                    pszCopy = kmk_cc_block_strdup(pCompiler->ppBlockTail, pWord->pchWord, pWord->cchWord);
                    cchCopy = pWord->cchWord;
                }
                else
                {
                    KMK_CC_ASSERT(pWord->enmToken == kKmkCcEvalToken_WordWithDollarAndEscEol);
                    pCompiler->iEscEol = iSavedEscEol;
                    cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pWord->pchWord, cchLeft);
                    pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, cchCopy);
                }
                kmk_cc_block_realign(pCompiler->ppBlockTail);

                pInstr->IfCore.Core.enmOpcode = fPositiveStmt ? kKmkCcEvalInstr_ifdef_dynamic : kKmkCcEvalInstr_ifndef_dynamic;
                pInstr->IfCore.Core.iLine     = pCompiler->iLine;
                pInstr->uPadding              = 0;
                kmk_cc_eval_compile_string_exp_subprog(pCompiler, pszCopy, cchCopy, &pInstr->NameSubprog);

                kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);
            }
        }
        else
        {
            KMK_CC_ASSERT(cWords > 1);
            kmk_cc_eval_fatal(pCompiler, pCompiler->paWords[1].pchWord,
                              "Bogus stuff after 'if%sdef' variable name", fPositiveStmt ? "" : "n");
        }
    }
    else
        kmk_cc_eval_fatal(pCompiler, pchWord, "Expected expression after 'if' directive");
    return 1;
}


/**
 * Deals with 'ifeq (a,b)', 'ifeq "a" "b"', 'ifneq (a,b)', 'ifneq "a" "b"',
 * 'else ifeq (a,b)', 'else ifeq "a" "b"', 'else ifneq (a,b)' and
 * 'else ifneq "a" "b"' statements.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler       The compiler state.
 * @param   pchWord         First char after 'if[n]eq'.
 * @param   cchLeft         The number of chars left to parse on this line.
 * @param   fInElse         Set if this is an 'else if' (rather than just 'if').
 * @param   fPositiveStmt   Set if 'ifeq', clear if 'ifneq'.
 */
static int kmk_cc_eval_do_ifeq(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, int fInElse, int fPositiveStmt)
{
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (cchLeft)
    {
        /*
         * There are two forms:
         *
         *   ifeq (string1, string2)
         *   ifeq "string1" 'string2'
         *
         */
        const char * const  pchEnd = &pchWord[cchLeft];
        PKMKCCEVALIFEQ      pInstr = (PKMKCCEVALIFEQ)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));

        struct
        {
            char           *pszCopy;
            size_t          cchCopy;
            int             fPlain;
        } Left, Right;

        char ch = *pchWord;
        if (ch == '(')
        {
            int      cCounts;
            size_t   off;

            /*
             * The left side ends with a comma.  We respect parentheses, but
             * not curly brackets.
             */

            /* Skip the parenthesis. */
            pchWord++;
            cchLeft--;

            /* Find the comma, checking for non-plainness. */
            cCounts = 0;
            Left.fPlain = 1;
            for (off = 0; off < cchLeft; off++)
            {
                ch = pchWord[off];
                if (!KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(ch))
                { /* likely */ }
                else if (ch == '$')
                    Left.fPlain = 0;
                else if (ch == '(')
                    cCounts++;
                else if (ch == ')')
                    cCounts--; /** @todo warn if it goes negative. */
                else if (ch == ',' && cCounts == 0)
                    break;
                else
                    KMK_CC_ASSERT(cCounts > 0);
            }
            if (ch == ',' && cCounts == 0) { /* likely */ }
            else kmk_cc_eval_fatal(pCompiler, &pchWord[off], "Expected ',' before end of line");

            /* Copy out the string. */
            Left.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, off);
            kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &Left.cchCopy);
            Left.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Left.cchCopy);

            /* Skip past the comma and any following spaces. */
            pchWord += off + 1;
            cchLeft -= off + 1;
            if (   cchLeft /** @todo replace with straight 'isspace' that takes escaped EOLs into account. */
                && KMK_CC_EVAL_IS_SPACE_AFTER_WORD(pCompiler, pchWord[0], pchWord[1], cchLeft))
                KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);

            /*
             * Ditto for the right side, only it ends with a closing parenthesis.
             */
            cCounts = 1;
            Right.fPlain = 1;
            for (off = 0; off < cchLeft; off++)
            {
                ch = pchWord[off];
                if (!KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(ch))
                { /* likely */ }
                else if (ch == '$')
                    Right.fPlain = 0;
                else if (ch == '(')
                    cCounts++;
                else if (ch == ')')
                {
                    if (--cCounts == 0)
                        break;
                }
                else
                    KMK_CC_ASSERT(cCounts > 0 || ch == ',');
            }
            if (ch == ')' && cCounts == 0) { /* likely */ }
            else kmk_cc_eval_fatal(pCompiler, &pchWord[off], "Expected ')' before end of line");

            /* Copy out the string. */
            Right.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, off);
            kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &Right.cchCopy);
            Right.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Right.cchCopy);

            /* Skip past the parenthesis. */
            pchWord += off + 1;
            cchLeft -= off + 1;
        }
        else if (ch == '"' || ch == '\'')
        {
            const char *pchTmp;

            /*
             * Quoted left side.
             */
            /* Skip leading quote. */
            pchWord++;
            cchLeft--;

            /* Locate the end quote. */
            pchTmp = (const char *)memchr(pchWord, ch, cchLeft);
            if (pchTmp) { /* likely */ }
            else kmk_cc_eval_fatal(pCompiler, pchWord - 1, "Unbalanced quote in first if%seq string", fPositiveStmt ? "" : "n");

            Left.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, pchTmp - pchWord);
            Left.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Left.cchCopy);
            Left.fPlain  = memchr(Left.pszCopy, '$', Left.cchCopy) == NULL;

            /* skip end quote */
            pchWord  = pchTmp + 1;
            cchLeft  = pchEnd - pchWord;

            /* Skip anything inbetween the left and right hand side (not mandatory). */
            if (   cchLeft /** @todo replace with straight 'isspace' that takes escaped EOLs into account. */
                && KMK_CC_EVAL_IS_SPACE_AFTER_WORD(pCompiler, pchWord[0], pchWord[1], cchLeft))
                KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);

            /*
             * Quoted right side.
             */
            if (   cchLeft > 0
                && ( (ch = *pchWord) != '"' || ch == '\'') )
            {
                /* Skip leading quote. */
                pchWord++;
                cchLeft--;

                /* Locate the end quote. */
                pchTmp = (const char *)memchr(pchWord, ch, cchLeft);
                if (pchTmp) { /* likely */ }
                else kmk_cc_eval_fatal(pCompiler, pchWord - 1, "Unbalanced quote in second if%seq string", fPositiveStmt ? "" : "n");

                Right.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, pchTmp - pchWord);
                Right.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Right.cchCopy);
                Right.fPlain  = memchr(Right.pszCopy, '$', Right.cchCopy) == NULL;

                /* skip end quote */
                pchWord  = pchTmp + 1;
                cchLeft  = pchEnd - pchWord;
            }
            else
                kmk_cc_eval_fatal(pCompiler, pchWord, "Expected a second quoted string for 'if%seq'",
                                  fPositiveStmt ? "" : "n");
        }
        else
            kmk_cc_eval_fatal(pCompiler, pchWord, "Expected parentheses or quoted string after 'if%seq'",
                              fPositiveStmt ? "" : "n");
        kmk_cc_block_realign(pCompiler->ppBlockTail);

        /*
         * Initialize the instruction.
         */
        pInstr->IfCore.Core.enmOpcode = fPositiveStmt ? kKmkCcEvalInstr_ifeq : kKmkCcEvalInstr_ifneq;
        pInstr->IfCore.Core.iLine     = pCompiler->iLine;
        kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Left, Left.pszCopy, Left.cchCopy, Left.fPlain);
        kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Right, Right.pszCopy, Right.cchCopy, Right.fPlain);
        kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);

        /*
         * Make sure there is nothing following the variable name.
         */
        KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
        if (cchLeft)
            kmk_cc_eval_fatal(pCompiler, pchWord, "Bogus stuff after 'if%sdef' variable name", fPositiveStmt ? "" : "n");
    }
    else
        kmk_cc_eval_fatal(pCompiler, pchWord, "Expected expression after 'if' directive");
    return 1;
}


/**
 * Deals with 'if1of (set-a,set-b)', 'ifn1of (set-a,set-b)',
 * 'else if1of (set-a,set-b)' and 'else ifn1of (set-a,set-b)' statements.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler       The compiler state.
 * @param   pchWord         First char after 'if[n]1of'.
 * @param   cchLeft         The number of chars left to parse on this line.
 * @param   fInElse         Set if this is an 'else if' (rather than just 'if').
 * @param   fPositiveStmt   Set if 'if1of', clear if 'ifn1of'.
 */
static int kmk_cc_eval_do_if1of(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, int fInElse, int fPositiveStmt)
{
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (cchLeft)
    {
        /*
         * This code is (currently) very similar to kmk_cc_eval_do_ifeq.
         * However, we may want to add hashing optimizations of plain text,
         * and we don't want to support the quoted form as it is not necessary
         * and may interfere with support for quoted words later on.
         */
        PKMKCCEVALIF1OF     pInstr = (PKMKCCEVALIF1OF)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));

        struct
        {
            char           *pszCopy;
            size_t          cchCopy;
            int             fPlain;
        } Left, Right;

        char ch = *pchWord;
        if (ch == '(')
        {
            int      cCounts;
            size_t   off;

            /*
             * The left side ends with a comma.  We respect parentheses, but
             * not curly brackets.
             */

            /* Skip the parenthesis. */
            pchWord++;
            cchLeft--;

            /* Find the comma, checking for non-plainness. */
            cCounts = 0;
            Left.fPlain = 1;
            for (off = 0; off < cchLeft; off++)
            {
                ch = pchWord[off];
                if (!KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(ch))
                { /* likely */ }
                else if (ch == '$')
                    Left.fPlain = 0;
                else if (ch == '(')
                    cCounts++;
                else if (ch == ')')
                    cCounts--; /** @todo warn if it goes negative. */
                else if (ch == ',' && cCounts == 0)
                    break;
                else
                    KMK_CC_ASSERT(cCounts > 0);
            }
            if (ch == ',' && cCounts == 0) { /* likely */ }
            else kmk_cc_eval_fatal(pCompiler, &pchWord[off], "Expected ',' before end of line");

            /* Copy out the string. */
            Left.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, off);
            kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &Left.cchCopy);
            Left.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Left.cchCopy);

            /* Skip past the comma and any following spaces. */
            pchWord += off + 1;
            cchLeft -= off + 1;
            if (   cchLeft /** @todo replace with straight 'isspace' that takes escaped EOLs into account. */
                && KMK_CC_EVAL_IS_SPACE_AFTER_WORD(pCompiler, pchWord[0], pchWord[1], cchLeft))
                KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);

            /*
             * Ditto for the right side, only it ends with a closing parenthesis.
             */
            cCounts = 1;
            Right.fPlain = 1;
            for (off = 0; off < cchLeft; off++)
            {
                ch = pchWord[off];
                if (!KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(ch))
                { /* likely */ }
                else if (ch == '$')
                    Right.fPlain = 0;
                else if (ch == '(')
                    cCounts++;
                else if (ch == ')')
                {
                    if (--cCounts == 0)
                        break;
                }
                else
                    KMK_CC_ASSERT(cCounts > 0 || ch == ',');
            }
            if (ch == ')' && cCounts == 0) { /* likely */ }
            else kmk_cc_eval_fatal(pCompiler, &pchWord[off], "Expected ')' before end of line");

            /* Copy out the string. */
            Right.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, off);
            kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &Right.cchCopy);
            Right.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Right.cchCopy);

            /* Skip past the parenthesis. */
            pchWord += off + 1;
            cchLeft -= off + 1;
        }
        else
            kmk_cc_eval_fatal(pCompiler, pchWord, "Expected parentheses after 'if%s1of'", fPositiveStmt ? "" : "n");
        kmk_cc_block_realign(pCompiler->ppBlockTail);

        /*
         * Initialize the instruction.
         */
        pInstr->IfCore.Core.enmOpcode = fPositiveStmt ? kKmkCcEvalInstr_if1of : kKmkCcEvalInstr_ifn1of;
        pInstr->IfCore.Core.iLine     = pCompiler->iLine;
        kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Left, Left.pszCopy, Left.cchCopy, Left.fPlain);
        kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Right, Right.pszCopy, Right.cchCopy, Right.fPlain);
        kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);

        /*
         * Make sure there is nothing following the variable name.
         */
        KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
        if (cchLeft)
            kmk_cc_eval_fatal(pCompiler, pchWord, "Bogus stuff after 'if%s1of' variable name", fPositiveStmt ? "" : "n");
    }
    else
        kmk_cc_eval_fatal(pCompiler, pchWord, "Expected expression after 'if' directive");
    return 1;
}


/**
 * Deals with 'else' and 'else ifxxx' statements.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after 'define'.
 * @param   cchLeft     The number of chars left to parse on this line.
 */
static int kmk_cc_eval_do_else(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    /*
     * There must be an 'if' on the stack.
     */
    unsigned iIf = pCompiler->cIfs;
    if (iIf > 0)
    {
        PKMKCCEVALIFCORE pIfCore = pCompiler->apIfs[--iIf];
        if (!pIfCore->pTrueEndJump)
        {
            /* Emit a jump instruction that will take us from the 'True' block to the 'endif'. */
            PKMKCCEVALJUMP  pInstr = (PKMKCCEVALJUMP)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
            pInstr->Core.enmOpcode = kKmkCcEvalInstr_jump;
            pInstr->Core.iLine     = pCompiler->iLine;
            pInstr->pNext          = NULL;
            pIfCore->pTrueEndJump  = pInstr;

            /* The next instruction is the first in the 'False' block of the current 'if'.
               Should this be an 'else if', this will be the 'if' instruction emitted below. */
            pIfCore->pNextFalse    = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
        }
        else if (iIf == 0)
            kmk_cc_eval_fatal(pCompiler, pchWord, "2nd 'else' for 'if' at line %u", pIfCore->Core.iLine);
        else
            kmk_cc_eval_fatal(pCompiler, pchWord, "2nd 'else' in a row - missing 'endif' for 'if' at line %u?",
                              pIfCore->Core.iLine);
    }
    else
        kmk_cc_eval_fatal(pCompiler, pchWord, "'else' without 'if'");

    /*
     * Check for 'else ifxxx'. There can be nothing else following an else.
     */
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (cchLeft)
    {
        if (   cchLeft > 2
            && KMK_CC_WORD_COMP_CONST_2(pchWord, "if"))
        {
            pchWord += 2;
            cchLeft -= 2;

            if (KMK_CC_EVAL_WORD_COMP_IS_EOL(pCompiler, pchWord, cchLeft))
                return kmk_cc_eval_do_if(pCompiler, pchWord, cchLeft, 1 /* in else */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "eq", 2))
                return kmk_cc_eval_do_ifeq( pCompiler, pchWord + 2, cchLeft - 2, 1 /* in else */, 1 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "def", 3))
                return kmk_cc_eval_do_ifdef(pCompiler, pchWord + 3, cchLeft - 3, 1 /* in else */, 1 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "neq", 3))
                return kmk_cc_eval_do_ifeq( pCompiler, pchWord + 3, cchLeft - 3, 1 /* in else */, 0 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "1of", 3))
                return kmk_cc_eval_do_if1of(pCompiler, pchWord + 3, cchLeft - 3, 1 /* in else */, 1 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "ndef", 4))
                return kmk_cc_eval_do_ifdef(pCompiler, pchWord + 4, cchLeft - 4, 1 /* in else */, 0 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "n1of", 4))
                return kmk_cc_eval_do_if1of(pCompiler, pchWord + 4, cchLeft - 4, 1 /* in else */, 0 /* positive */);

            pchWord -= 2;
            cchLeft += 2;
        }
        kmk_cc_eval_fatal(pCompiler, pchWord, "Bogus stuff after 'else'");
    }

    return 1;
}


/**
 * Deals with the 'endif' statement.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after 'define'.
 * @param   cchLeft     The number of chars left to parse on this line.
 */
static int kmk_cc_eval_do_endif(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    /*
     * There must be an 'if' on the stack.  We'll POP it.
     */
    unsigned iIf = pCompiler->cIfs;
    if (iIf > 0)
    {
        PKMKCCEVALCORE   pNextInstr;
        PKMKCCEVALIFCORE pIfCore = pCompiler->apIfs[--iIf];
        pCompiler->cIfs = iIf; /* POP! */

        /* Update the jump targets for all IFs at this level. */
        pNextInstr = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
        do
        {
            if (pIfCore->pTrueEndJump)
            {
                /* Make the true block jump here, to the 'endif'. The false block is already here. */
                pIfCore->pTrueEndJump->pNext = pNextInstr;
                KMK_CC_ASSERT(pIfCore->pNextFalse);
            }
            else
            {
                /* No 'else'. The false-case jump here, to the 'endif'. */
                KMK_CC_ASSERT(!pIfCore->pNextFalse);
                pIfCore->pNextFalse = pNextInstr;
            }

            pIfCore = pIfCore->pPrevCond;
        } while (pIfCore);
    }
    else
        kmk_cc_eval_fatal(pCompiler, pchWord, "'endif' without 'if'");

    /*
     * There shouldn't be anything trailing an 'endif'.
     */
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (!cchLeft) { /* likely */ }
    else kmk_cc_eval_fatal(pCompiler, pchWord, "Bogus stuff after 'else'");

    return 1;
}


/**
 * Parses a 'include file...', 'sinclude file...', '-include file...',
 * 'includedep file...', 'includedep-queue file...' and
 * 'includedep-flush file...'
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after the include directive.
 * @param   cchLeft     The number of chars left to parse on this line.
 * @param   enmOpcode   The opcode for the include directive we're parsing.
 */
static int kmk_cc_eval_do_include(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, KMKCCEVALINSTR enmOpcode)
{
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (cchLeft)
    {
        /*
         * Split what's left up into words.
         */
/** @todo GNU make supports escape sequences for spaces here (they confusingly refers to this as quoting).  So, it's possible
 * to include C:/Program\ Files/kBuild/footer.kmk if we wanted to.  It my intention to add support for double and/or single
 * quoted files names to offer an alternative way of addressing this. */
        unsigned cWords = kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft);
        KMK_CC_EVAL_DPRINTF(("%s: cWords=%d\n", g_apszEvalInstrNms[enmOpcode], cWords));
        if (cWords)
        {
            PKMKCCEVALINCLUDE pInstr = (PKMKCCEVALINCLUDE)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail,
                                                                                  KMKCCEVALINCLUDE_SIZE(cWords));
            pInstr->Core.enmOpcode = enmOpcode;
            pInstr->Core.iLine     = pCompiler->iLine;
            pInstr->cFiles         = cWords;
            kmk_cc_eval_init_spp_array_from_duplicated_words(pCompiler, cWords, pCompiler->paWords, pInstr->aFiles);
            kmk_cc_block_realign(pCompiler->ppBlockTail);
        }
        else
            KMK_CC_ASSERT(0);
    }
    else
        KMK_CC_EVAL_DPRINTF(("%s: include without args\n", g_apszEvalInstrNms[enmOpcode]));
    return 1;
}


static int kmk_cc_eval_do_vpath(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    kmk_cc_eval_fatal(pCompiler, NULL, "vpath directive is not implemented\n");
    return 1;
}


/**
 * Called when any previous recipe must have ended and can be finalized.
 *
 * This occurs when encountering an assignement, a new recipe or the end of the
 * complication unit.
 *
 * @param   pCompiler   The compiler state.
 */
static void kmk_cc_eval_end_of_recipe(PKMKCCEVALCOMPILER pCompiler, const char *pchWord)
{
    if (pCompiler->pRecipe)
    {
        /** @todo do stuff here. */
        kmk_cc_eval_fatal(pCompiler, pchWord, "end-of-recipe handling not implemented yet");
    }
}


static void kmk_cc_eval_handle_command(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    kmk_cc_eval_fatal(pCompiler, pchWord, "command handling not implemented yet");
}


/**
 * Pick up the recipe parsing at the colon.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord0    The first word.
 * @param   cchWord0    The length of the first word.
 * @param   enmToken0   The classification of the first word.
 * @param   pchColon    The colon.
 * @param   cchLeft     How much is left, starting at the colon.
 */
static int kmk_cc_eval_handle_recipe_cont_colon(PKMKCCEVALCOMPILER pCompiler, const char *pchWord0, size_t cchWord0,
                                                KMKCCEVALTOKEN enmToken0, const char *pchColon, size_t cchLeft)
{
    kmk_cc_eval_fatal(pCompiler, pchWord0, "recipe handling not implemented yet (#1)");
    return 1;
}


/**
 * Pick up the recipe parsing at the 2nd word (after it was determined not to be
 * an assignment operator after all).
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord0    The first word.
 * @param   cchWord0    The length of the first word.
 * @param   pchWord     Where to continue parsing.
 * @param   cchLeft     How much is left to parse.
 */
static int kmk_cc_eval_handle_recipe_cont_2nd_word(PKMKCCEVALCOMPILER pCompiler, const char *pchWord0, size_t cchWord0,
                                                   const char *pchWord, size_t cchLeft)
{
//    const char *pchColon = memchr()

    kmk_cc_eval_fatal(pCompiler, pchWord, "recipe handling not implemented yet (#2)");
    return 1;
}


static void kmk_cc_eval_handle_recipe(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    const char *pszColon = NULL;//memchr(pchWord, cchLeft);
    if (pszColon)
    {


        kmk_cc_eval_fatal(pCompiler, pchWord, "recipe handling not implemented yet (#3)");
    }
    else if (pchWord[0] == '$')
    {

    }
    else
        kmk_cc_eval_fatal(pCompiler, pchWord,
                          "Not variable assignment. Could be a complicated indirect recipe definition, however that's currently not supported.");

}



/**
 * Common worker for handling export (non-assign), undefine and unexport.
 *
 * For instructions using the KMKCCEVALVARIABLES structure.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First non-space chare after the keyword.
 * @param   cchLeft     The number of chars left to parse on this line.
 * @param   fQualifiers The qualifiers.
 */
static int kmk_cc_eval_do_with_variable_list(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft,
                                             KMKCCEVALINSTR enmOpcode, unsigned fQualifiers)
{
    if (cchLeft)
    {
        /*
         * Parse the variable name list.  GNU make is using normal word
         * handling here, so we can share code with the include directives.
         */
        unsigned cWords = kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft);
#ifdef KMK_CC_EVAL_LOGGING_ENABLED
        unsigned iWord;
        KMK_CC_EVAL_DPRINTF(("%s: cWords=%d\n", g_apszEvalInstrNms[enmOpcode], cWords));
        for (iWord = 0; iWord < cWords; iWord++)
            KMK_CC_EVAL_DPRINTF(("  word[%u]: len=%#05x t=%d '%*.*s'\n", iWord, (int)pCompiler->paWords[iWord].cchWord,
                                 (int)pCompiler->paWords[iWord].enmToken, (int)pCompiler->paWords[iWord].cchWord,
                                 (int)pCompiler->paWords[iWord].cchWord, pCompiler->paWords[iWord].pchWord));
#endif
        if (cWords)
        {
            PKMKCCEVALVARIABLES pInstr = (PKMKCCEVALVARIABLES)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail,
                                                                                      KMKCCEVALVARIABLES_SIZE(cWords));
            pInstr->Core.enmOpcode = enmOpcode;
            pInstr->Core.iLine     = pCompiler->iLine;
            pInstr->cVars          = cWords;
            kmk_cc_eval_init_spp_array_from_duplicated_words(pCompiler, cWords, pCompiler->paWords, pInstr->aVars);
            kmk_cc_block_realign(pCompiler->ppBlockTail);
        }
        else
            KMK_CC_ASSERT(0);
    }
    /* else: NOP */
    return 1;
}


/**
 * Parses a '[qualifiers] undefine variable [..]' expression.
 *
 * A 'undefine' directive is final, any qualifiers must preceed it.  So, we just
 * have to extract the variable names now.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after 'define'.
 * @param   cchLeft     The number of chars left to parse on this line.
 * @param   fQualifiers The qualifiers.
 */
static int kmk_cc_eval_do_var_undefine(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, unsigned fQualifiers)
{
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (!cchLeft)
        kmk_cc_eval_fatal(pCompiler, pchWord, "undefine requires a variable name");

    /** @todo GNU make doesn't actually do the list thing for undefine, it seems
     *        to assume everything after it is a single variable...  Going with
     *        simple common code for now. */
    return kmk_cc_eval_do_with_variable_list(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_undefine, fQualifiers);
}


/**
 * Parses a '[qualifiers] unexport variable [..]' expression.
 *
 * A 'unexport' directive is final, any qualifiers must preceed it.  So, we just
 * have to extract the variable names now.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after 'define'.
 * @param   cchLeft     The number of chars left to parse on this line.
 * @param   fQualifiers The qualifiers.
 */
static int kmk_cc_eval_do_var_unexport(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, unsigned fQualifiers)
{
    PKMKCCEVALCORE pInstr;

    /*
     * Join paths with undefine and export, unless it's an unexport all directive.
     */
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (cchLeft)
        return kmk_cc_eval_do_with_variable_list(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_unexport, fQualifiers);

    /*
     * We're unexporting all variables.
     */
    pInstr = kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
    pInstr->enmOpcode = kKmkCcEvalInstr_unexport_all;
    pInstr->iLine     = pCompiler->iLine;
    return 1;
}


/**
 * Parse the value of a 'define' at pCompiler->offNext.
 *
 * This will update 'offNext' to the start of the line following the 'endef'
 * matching the 'define' of the value.
 *
 * The value is prepared for kmk_cc_eval_strcpyv.
 *
 * @returns The value length that we've prepared for copying.
 * @param   pCompiler           The compiler state.
 * @param   pfPlainValue        Where to return whether this is a plain value or
 *                              one needing expansion.
 */
static size_t kmk_cc_eval_parse_define_value(PKMKCCEVALCOMPILER pCompiler, int *pfPlainValue)
{
    /*
     * Now we need to find the matching 'endef', we support nested ones.
     *
     * We look for the lines starting with 'endef' and 'define', like GNU
     * make does even if we really should also be checking for variable
     * qualifiers too.
     *
     * As we go on looking, we prepare the value in paStrCopySegs.
     *
     * Note! We duplicate code/logic from the top level compile loop here.
     */
    const char * const  pszContent   = pCompiler->pszContent;
    size_t              cchContent   = pCompiler->cchContent;
    int const           chFirstEol   = pCompiler->chFirstEol;
    size_t const        cchEolSeq    = pCompiler->cchEolSeq;

    unsigned            cNestings    = 1;
    size_t              offNext      = pCompiler->offNext;
    unsigned            iLine        = pCompiler->iLine;

    unsigned            cSegs        = 0;
    size_t              cchValue     = 0;
    int                 fPlainValue  = 1;

    for (;;)
    {
        /*
         * Find end of line, preparing to copy it.
         */
        if (offNext < cchContent)
        {
            unsigned const  cSegsAtStartOfLine  = cSegs;
            size_t const    cchValueStartOfLine = cchValue;
            size_t          offFirstWord        = offNext;
            const char     *pchLine             = &pszContent[offNext];
            size_t          cchLine;
            const char     *pchTmp;

            pCompiler->cEscEols = 0;
            pCompiler->iEscEol  = 0;

            /* Add newline if necessary and make sure we've got a segment handy. */
            KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cSegs + 2);
            if (cSegs)
            {
                cchValue++;
                pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = 1;
                pCompiler->paStrCopySegs[cSegs].pchSrc = "\n";
                cSegs++;
            }

            /* Simple case: No escaped EOL, nor the end of the input. */
            pchTmp = (const char *)memchr(&pszContent[offNext], chFirstEol, cchContent - offNext);
            if (   pchTmp
                && (   &pszContent[offNext] == pchTmp
                    || pchTmp[-1] != '\\'))
            {
                if (   cchEolSeq == 1
                    || pchTmp[1] == pCompiler->chSecondEol)
                {
                    offNext = pchTmp - pszContent;
                    cchLine = pchTmp - pchLine;

                    cchValue += cchLine;
                    pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = cchLine;
                    pCompiler->paStrCopySegs[cSegs].pchSrc = pchLine;
                    cSegs++;

                    while (offFirstWord < offNext && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
                        offFirstWord++;

                    offNext += cchEolSeq;
                }
                else
                    kmk_cc_eval_fatal_eol(pCompiler, pchTmp, iLine, offNext);
            }
            /* The complicated, less common cases. */
            else
            {
                size_t fPendingSpace = 0;
                for (;;)
                {
                    /* Find the first non-space char on this line.  We always need it. */
                    size_t offThisFirstWord = offNext;
                    size_t offEol = pchTmp ? pchTmp - pszContent : cchContent;
                    if (offFirstWord == offNext)
                    {
                        while (offFirstWord < offEol && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
                            offFirstWord++;
                        if (pCompiler->cEscEols > 0)
                            offThisFirstWord = offFirstWord;
                    }
                    else
                        while (offThisFirstWord < offEol && KMK_CC_EVAL_IS_SPACE(pszContent[offThisFirstWord]))
                            offThisFirstWord++;

                    /* We normally need one, so just make sure once. */
                    KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cSegs + 1);

                    if (pchTmp)
                    {
                        if (   cchEolSeq == 1
                            || pchTmp[1] == pCompiler->chSecondEol)
                        {
                            size_t const offThis = offNext;
                            size_t offEsc;
                            int fDone;
                            offNext = pchTmp - pszContent;

                            /* Is it an escape sequence? */
                            if (   !offNext
                                || pchTmp[-1] != '\\')
                                fDone = 1;
                            else if (offNext < 2 || pchTmp[-2] != '\\')
                            {
                                offEsc = offNext - 1;
                                fDone = 0;
                            }
                            else
                            {
                                /* Count how many backslashes there are. Must be odd number to be an escape
                                   sequence.  Normally we keep half of them, except for command lines.  */
                                size_t cSlashes = 2;
                                while (offNext >= cSlashes && pchTmp[0 - cSlashes] == '\\')
                                    cSlashes--;
                                fDone = !(cSlashes & 1);
                                offEsc = offNext - (cSlashes >> 1);
                            }

                            /* Anything to copy? */
/** @todo fixme tomorrow! */
                            cchLine = offThisFirstWord - offNext;
                            if (cchLine)
                            {
                                pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
                                                                                      ? -(ssize_t)cchLine : (ssize_t)cchLine;
                                pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offThisFirstWord];
                                cSegs++;
                            }

                            if (fDone)
                            {
                                cchLine = &pszContent[offNext] - pchLine;
                                offNext += cchEolSeq;
                                break;
                            }

                            /* Record it. */
                            if (pCompiler->cEscEols < pCompiler->cEscEolsAllocated) { /* likely */ }
                            else
                            {
                                KMK_CC_ASSERT(pCompiler->cEscEols == pCompiler->cEscEolsAllocated);
                                pCompiler->cEscEolsAllocated = pCompiler->cEscEolsAllocated
                                                             ? pCompiler->cEscEolsAllocated * 2 : 2;
                                pCompiler->paEscEols = (PKMKCCEVALESCEOL)xrealloc(pCompiler->paEscEols,
                                                                                    pCompiler->cEscEolsAllocated
                                                                                  * sizeof(pCompiler->paEscEols[0]));
                            }
                            pCompiler->paEscEols[pCompiler->cEscEols].offEsc = offEsc;
                            pCompiler->paEscEols[pCompiler->cEscEols].offEol = offNext;
                            pCompiler->cEscEols++;

                            /* Anything to copy? */
                            cchLine = offThisFirstWord - offNext;
                            if (cchLine)
                            {
                            }

                            /* Advance. */
                            offNext += cchEolSeq;
                            if (offFirstWord == offEsc)
                            {
                                offFirstWord = offNext;
                                pCompiler->iEscEol++;
                            }
                        }
                        else
                            kmk_cc_eval_fatal_eol(pCompiler, pchTmp, pCompiler->iLine, off);
                    }
                    else
                    {
                        /* End of input. Happens only once per compilation, nothing to optimize for. */

                        if (offFirstWord == offNext)
                            while (offFirstWord < cchContent && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
                                offFirstWord++;

                        KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cSegs + 2);
                        if (cSegs == cSegsAtStartOfLine)
                        {
                            /* No escaped EOLs. */
                            cchLine = &pszContent[cchContent] - pchLine;
                            cchValue += cchLine;
                            pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = cchLine;
                            pCompiler->paStrCopySegs[cSegs].pchSrc = pchLine;
                            cSegs++;
                        }
                        else
                        {
                            if (offFirstWordThisLine < cchContent)
                            {
                                cchLine = cchContent - offFirstWordThisLine;
                                cchValue += cchLine;
                                pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
                                                                                      ? -(ssize_t)cchLine : (ssize_t)cchLine;
                                pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offFirstWordThisLine];
                                cSegs++;
                            }
                            cchLine = &pszContent[cchContent] - pchLine;
                        }
                        offNext = cchContent;
                        break;
                    }
                    pchTmp = (const char *)memchr(&pszContent[offNext], chFirstEol, cchContent - offNext);
                }
            }
            KMK_CC_ASSERT(offNext       <= cchContent);
            KMK_CC_ASSERT(offNext       >= off + cchLine);
            KMK_CC_ASSERT(off + cchLine <= cchContent && cchLine <= cchContent);
            KMK_CC_ASSERT(offFirstWord  <= off + cchLine);
            KMK_CC_ASSERT(offFirstWord  >= off);
            KMK_CC_ASSERT(pszContent[offFirstWord] != ' ' && pszContent[offFirstWord] != '\t');

            KMK_CC_EVAL_DPRINTF(("#%03u: %*.*s\n", pCompiler->iLine, (int)cchLine, (int)cchLine, &pszContent[off]));

            /*
             * Look for 'endef' and 'define' directives.
             */
            cchLine -= offFirstWord - off;
            if (   cchLine >= 5 /* shortest word is 5 chars ('endef', 'local') */
                && pchLine[0] != pCompiler->chCmdPrefix)
            {
                pchTmp = &pszContent[offFirstWord];
                if (!KMK_CC_EVAL_IS_1ST_IN_VARIABLE_KEYWORD(*pchTmp))
                { /* Kind of likely (and saves one indent). */ }
                else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "endef", 5))
                {
                    cNestings--;
                    if (cNestings == 0)
                    {
                        cchValue = cchValueStartOfLine;
                        cSegs    = cSegsAtStartOfLine;
                        break;
                    }
                }
                else
                    for (;;)
                    {
                        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "define", 6))
                        {
                            cNestings++;
                            break;
                        }

                        /* GNU make doesn't do this, but I think it makes sense. */
                        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "local", 5))
                        {
                            pchTmp  += 5;
                            cchLine -= 5;
                        }
                        else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "export", 6))
                        {
                            pchTmp  += 6;
                            cchLine -= 6;
                        }
                        else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "private", 7))
                        {
                            pchTmp  += 7;
                            cchLine -= 7;
                        }
                        else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "override", 8))
                        {
                            pchTmp  += 8;
                            cchLine -= 8;
                        }
                        else
                            break;
                        KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchTmp, cchLine);
                    }
            }

            /*
             * Advance to the next line.
             */
            iLine += pCompiler->cEscEols + 1;
        }
        else
            kmk_cc_eval_fatal(pCompiler, NULL, )
    }

    /*
     * Update globals and return values.
     */
    pCompiler->offNext      = offNext;
    pCompiler->iLine        = iLine;
    pCompiler->cStrCopySegs = cSegs;

    *pfPlainValue = fPlainValue;
    return cchValue;
}

/**
 * Parses a 'define variable' expression.
 *
 * A 'define' directive is final, any qualifiers must preceed it.  So, we just
 * have to extract the variable name now, well and find the corresponding
 * 'endef'.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after 'define'.
 * @param   cchLeft     The number of chars left to parse on this line.
 * @param   fQualifiers The qualifiers.
 */
static int kmk_cc_eval_do_var_define(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, unsigned fQualifiers)
{
    /*
     * Now comes the variable name.  It may optionally be followed by an
     * assignment operator to indicate what kind of variable is being defined.
     */
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    unsigned cWords = cchLeft ? kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft) : 0;
    if (cWords >= 1)
    {
        /*
         * Check for variable assignment operator.  Kind of tedious...
         */
        KMKCCEVALINSTR  enmOpcode;
        PKMKCCEVALWORD  pVarWord = pCompiler->paWords;
        if (   cWords == 1
            && (   pVarWord->cchWord == 0
                || pVarWord->pchWord[pVarWord->cchWord - 1] != '='))
            enmOpcode = kKmkCcEvalInstr_define_recursive; /* very likely */
        else if (   pVarWord->cchWord > 0
                 && pVarWord->pchWord[pVarWord->cchWord - 1] == '=')
        {
            if (pVarWord->cchWord == 1)
                enmOpcode = kKmkCcEvalInstr_define_recursive;
            else
            {
                char chPenultimate = pVarWord->pchWord[pVarWord->cchWord - 2];
                if (chPenultimate == '?')
                    enmOpcode = kKmkCcEvalInstr_define_if_new;
                else if (chPenultimate == ':')
                    enmOpcode = kKmkCcEvalInstr_assign_simple;
                else if (chPenultimate == '+')
                    enmOpcode = kKmkCcEvalInstr_assign_append;
                else if (chPenultimate == '<')
                    enmOpcode = kKmkCcEvalInstr_assign_prepend;
                else
                    enmOpcode = kKmkCcEvalInstr_define_recursive;
            }
            pVarWord->cchWord -= enmOpcode == kKmkCcEvalInstr_define_recursive ? 1 : 2;
            if (cWords > 1)
                kmk_cc_eval_fatal(pCompiler, pCompiler->paWords[1].pchWord,
                                  "Bogus stuff after 'define' variable name and assignment operator");
        }
        else
        {
            PCKMKCCEVALWORD pOpWord = &pCompiler->paWords[1];
            KMK_CC_ASSERT(cWords > 1);
            if (   pOpWord->cchWord == 1
                && pOpWord->pchWord[0] == '=')
                enmOpcode = kKmkCcEvalInstr_define_recursive;
            else if (   pOpWord->cchWord == 2
                     && pOpWord->pchWord[1] == '=')
            {
                char chFirst = pVarWord->pchWord[0];
                if (chFirst == '?')
                    enmOpcode = kKmkCcEvalInstr_define_if_new;
                else if (chFirst == ':')
                    enmOpcode = kKmkCcEvalInstr_assign_simple;
                else if (chFirst == '+')
                    enmOpcode = kKmkCcEvalInstr_assign_append;
                else if (chFirst == '<')
                    enmOpcode = kKmkCcEvalInstr_assign_prepend;
                else
                    kmk_cc_eval_fatal(pCompiler, pOpWord->pchWord, "Bogus stuff after 'define' variable name");
            }
            else
                kmk_cc_eval_fatal(pCompiler, pOpWord->pchWord, "Bogus stuff after 'define' variable name");
            if (cWords > 2)
                kmk_cc_eval_fatal(pCompiler, pCompiler->paWords[2].pchWord,
                                  "Bogus stuff after 'define' variable name and assignment operator");
        }

        /*
         * The variable name must not be empty.
         */
        if (pVarWord->cchWord)
        {
            int const           fPlainVarNm = pVarWord->enmToken == kKmkCcEvalToken_WordPlain;
            const char *        pchVarNm    = pVarWord->pchWord;
            size_t              cchVarNm    = pVarWord->cchWord;
            PKMKCCEVALASSIGN    pInstr;
            size_t              cchValue;
            const char         *pszValue;
            int                 fPlainValue;

            if (   enmOpcode == kKmkCcEvalInstr_define_recursive
                || enmOpcode == kKmkCcEvalInstr_define_if_new)
            {
                PKMKCCEVALASSIGNDEF pInstrDef;
                pInstrDef = (PKMKCCEVALASSIGNDEF)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstrDef));
                pInstr = &pInstrDef->AssignCore;
                pInstrDef->pEvalProg = NULL; /** @todo consider this later at some point, need some trial and error approach. */
            }
            else
                pInstr = (PKMKCCEVALASSIGN)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));

            pInstr->Core.enmOpcode = enmOpcode;
            pInstr->Core.iLine     = pCompiler->iLine;
            pInstr->fExport        = (fQualifiers & KMK_CC_EVAL_QUALIFIER_EXPORT)   != 0;
            pInstr->fOverride      = (fQualifiers & KMK_CC_EVAL_QUALIFIER_OVERRIDE) != 0;
            pInstr->fPrivate       = (fQualifiers & KMK_CC_EVAL_QUALIFIER_PRIVATE)  != 0;
            pInstr->fLocal         = (fQualifiers & KMK_CC_EVAL_QUALIFIER_LOCAL)    != 0;

            cchValue = kmk_cc_eval_parse_define_value(pCompiler, &fPlainValue);
            pszValue = kmk_cc_eval_strdup_prepped(pCompiler, cchValue);
            if (fPlainVarNm)
                pchVarNm = strcache2_add(&variable_strcache, pchVarNm, cchVarNm);
            else
            {
/** @todo fix work copying. */
//                pCompiler->iEscEol = iEscEolVarNm;
                cchVarNm = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchVarNm, cchVarNm);
                pchVarNm = kmk_cc_eval_strdup_prepped(pCompiler, cchVarNm);
            }
            kmk_cc_block_realign(pCompiler->ppBlockTail);
            KMK_CC_EVAL_DPRINTF(("%s: define '%s'\n%s\nendef\n", g_apszEvalInstrNms[enmOpcode], pchVarNm, pszValue));

            kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Variable, pchVarNm, cchVarNm, fPlainVarNm);
            kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Value, pszValue, cchValue, fPlainValue);

            pInstr->pNext = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
        }
        else
            kmk_cc_eval_fatal(pCompiler, pchWord, "Empty variable name after 'define'");
    }
    else
        kmk_cc_eval_fatal(pCompiler, pchWord, "Expected variable name after 'define'");
    return 1;
}


/**
 * Emits a 'expand(-and-eval)' instruction.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchSubprog  The subprogram that needs expanding.
 * @param   cchSubprog  The length of the subprogram.
 * @param   iEscEol     The escaped EOL index corresponding to pchSubprog.
 */
static int kmk_cc_eval_emit_expand(PKMKCCEVALCOMPILER pCompiler, const char *pchSubprog, size_t cchSubprog, unsigned iEscEolVarNm)
{
    /*
     * We're unexporting all variables.
     */
    size_t           cchCopy;
    char            *pszCopy;
    PKMKCCEVALEXPAND pInstr;

    pCompiler->iEscEol = iEscEolVarNm;
    cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchSubprog, cchSubprog);
    pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, cchCopy);
    kmk_cc_block_realign(pCompiler->ppBlockTail);

    pInstr = (PKMKCCEVALEXPAND)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
    pInstr->Core.enmOpcode = kKmkCcEvalInstr_expand;
    pInstr->Core.iLine     = pCompiler->iLine;
    pInstr->uPadding       = 0;
    /** @todo Make the subprogram embed necessary strings. */
    kmk_cc_eval_compile_string_exp_subprog(pCompiler, pszCopy, cchCopy, &pInstr->Subprog);

    return 1;
}


static int kmk_cc_eval_handle_assignment_or_recipe(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft,
                                                   unsigned fQualifiers)
{
    /*
     * We're currently at a word which may or may not be a variable name
     * followed by an assignment operator, alternatively it must be a recipe.
     * We need to figure this out and deal with it in the most efficient
     * manner as this is a very common occurence.
     */
    unsigned const  iEscEolVarNm = pCompiler->iEscEol;
    int             fPlainVarNm  = 1;
    const char     *pchVarNm     = pchWord;
    size_t          cchVarNm;
    size_t          cch = 0;
    size_t          cchSubprog = 0;
    char            ch;

    /*
     * The variable name.  Complicate by there being no requirement of a space
     * preceeding the assignment operator, as well as that the variable name
     * may include variable references with spaces (function++) in them.
     */
    for (;;)
    {
        if (cch < cchLeft)
        { /*likely*/ }
        else
        {
            /* Single word, join paths with word + whitespace. */
            KMK_CC_ASSERT(cch == cchLeft);
            cchVarNm = cch;
            pchWord += cch;
            cchLeft -= cch;
            break;
        }

        ch = pchWord[cch];
        if (!KMK_CC_EVAL_IS_SPACE_DOLLAR_SLASH_OR_ASSIGN(ch))
            cch++;
        /* Space? */
        else if (KMK_CC_EVAL_IS_SPACE(ch))
        {
            cchVarNm = cch;
            pchWord += cch;
            cchLeft -= cch;
            KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
            break;
        }
        /* Variable expansion may contain spaces, so handle specially. */
        else if (ch == '$')
        {
            size_t const offStart = cch;
            cch = kmk_cc_eval_parse_var_exp(pCompiler, pchWord, cchLeft, cch);
            cchSubprog += cch - offStart;
            fPlainVarNm = 0;
        }
        /* Check out potential recipe, simple assignment or DOS drive letter separator. */
        else if (ch == ':')
        {
            if (   cch + 1 < cchLeft
                && pchWord[cch + 1] != '=')
            {
                cchVarNm = cch;
                pchWord += cch;
                cchLeft -= cch;
                break;
            }
#ifdef HAVE_DOS_PATHS
            /* Don't confuse the first colon in:
                    C:/Windows/System32/Kernel32.dll: C:/Windows/System32/NtDll.dll
               for a recipe, it is only the second one which counts. */
            if (   cch == 1
                && isalpha((unsigned char)pchWord[0]))
                cch++;
            else
#endif
            if (!fQualifiers)
                return kmk_cc_eval_handle_recipe_cont_colon(pCompiler, pchWord, cch,
                                                            fPlainVarNm
                                                            ? kKmkCcEvalToken_WordPlain : kKmkCcEvalToken_WordWithDollar,
                                                            pchWord + cch, cchLeft - cch);
            /** @todo we may have words already preparsed here so restarting is easy...  */
            return 0; /* retry with preceeding keywords */
        }
        /* Check out assignment operator. */
        else if (ch == '=')
        {
            if (cch > 0)
            {
                char chPrev = pchWord[cch - 1];
                if (chPrev == ':' || chPrev == '+' || chPrev == '?' || chPrev == '<')
                    cch--;
                cchVarNm = cch;
                pchWord += cch;
                cchLeft -= cch;
                break;
            }
            kmk_cc_eval_fatal(pCompiler, pchWord, "Empty variable name.");
        }
        /* Check out potential escaped EOL sequence. */
        else if (ch == '\\')
        {
            unsigned const iEscEol = pCompiler->iEscEol;
            if (iEscEol >= pCompiler->cEscEols)
                cch++;
            else
            {
                size_t offCur = &pchWord[cch] - pCompiler->pszContent;
                if (offCur < pCompiler->paEscEols[iEscEol].offEol)
                    cch++;
                else
                {
                    cchVarNm = cch;
                    KMK_CC_ASSERT(offCur == pCompiler->paEscEols[iEscEol].offEol);
                    cch = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq - offCur;
                    pCompiler->iEscEol = iEscEol + 1;
                    pchWord += cch;
                    cchLeft -= cch;
                    KMK_CC_EVAL_SKIP_SPACES(pCompiler, pchWord, cchLeft);
                    break;
                }
            }
        }
        else
            KMK_CC_ASSERT(0);
    }

    /*
     * Check for assignment operator.
     */
    if (cchLeft)
    {
        size_t              cchValue;
        PKMKCCEVALASSIGN    pInstr;
        KMKCCEVALINSTR      enmOpcode;
        int                 fPlainValue;
        char               *pszValue;

        ch = *pchWord;
        if (ch == '=')
        {
            enmOpcode = kKmkCcEvalInstr_assign_recursive;
            pchWord++;
            cchLeft--;
        }
        else if (cchLeft >= 2 && pchWord[1] == '=')
        {
            if (ch == ':')
                enmOpcode = kKmkCcEvalInstr_assign_simple;
            else if (ch == '+')
                enmOpcode = kKmkCcEvalInstr_assign_append;
            else if (ch == '<')
                enmOpcode = kKmkCcEvalInstr_assign_prepend;
            else if (ch == '?')
                enmOpcode = kKmkCcEvalInstr_assign_if_new;
            else if (!fQualifiers)
                return kmk_cc_eval_handle_recipe_cont_2nd_word(pCompiler, pchVarNm, cchVarNm, pchWord, cchLeft);
            else
                return 0; /* retry without preceding keywords */
            pchWord += 2;
            cchLeft -= 2;
        }
        else if (!fQualifiers)
            return kmk_cc_eval_handle_recipe_cont_2nd_word(pCompiler, pchVarNm, cchVarNm, pchWord, cchLeft);
        else
            return 0; /* retry without preceding keywords */

        /*
         * Skip leading spaces, if any and prep the value for copying.
         */
        KMK_CC_EVAL_SKIP_SPACES(pCompiler, pchWord, cchLeft);
        cchValue    = kmk_cc_eval_prep_normal_line(pCompiler, pchWord, cchLeft);
        fPlainValue = memchr(pchWord, '$', cchLeft) == NULL;

        /*
         * Emit the instruction.
         */
        kmk_cc_eval_end_of_recipe(pCompiler, pchWord);

        pInstr = (PKMKCCEVALASSIGN)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
        pInstr->Core.enmOpcode = enmOpcode;
        pInstr->Core.iLine     = pCompiler->iLine;
        pInstr->fExport        = (fQualifiers & KMK_CC_EVAL_QUALIFIER_EXPORT)   != 0;
        pInstr->fOverride      = (fQualifiers & KMK_CC_EVAL_QUALIFIER_OVERRIDE) != 0;
        pInstr->fPrivate       = (fQualifiers & KMK_CC_EVAL_QUALIFIER_PRIVATE)  != 0;
        pInstr->fLocal         = (fQualifiers & KMK_CC_EVAL_QUALIFIER_LOCAL)    != 0;

        /* We copy the value before messing around with the variable name since
           we have to do more iEolEsc saves & restores the other way around. */
        pszValue = kmk_cc_eval_strdup_prepped(pCompiler, cchValue);
        if (fPlainVarNm)
            pchVarNm = strcache2_add(&variable_strcache, pchVarNm, cchVarNm);
        else
        {
            pCompiler->iEscEol = iEscEolVarNm;
            cchVarNm = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchVarNm, cchVarNm);
            pchVarNm = kmk_cc_eval_strdup_prepped(pCompiler, cchVarNm);
        }
        kmk_cc_block_realign(pCompiler->ppBlockTail);
        KMK_CC_EVAL_DPRINTF(("%s: '%s' '%s'\n", g_apszEvalInstrNms[enmOpcode], pchVarNm, pszValue));

        kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Variable, pchVarNm, cchVarNm, fPlainVarNm);
        kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Value, pszValue, cchValue, fPlainValue);

        pInstr->pNext = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
    }
    /*
     * This could be one or more function calls.
     */
    else if (!fPlainVarNm && cchVarNm == cchSubprog && fQualifiers == 0)
        return kmk_cc_eval_emit_expand(pCompiler, pchVarNm, cchVarNm, iEscEolVarNm);
    else if (!fPlainVarNm)
        kmk_cc_eval_fatal(pCompiler, pchWord,
                          "Not variable assignment. Could be a complicated indirect recipe definition, however that's currently not supported.");
    else
        kmk_cc_eval_fatal(pCompiler, pchWord, "Neither recipe nor variable assignment");
    return 1;
}


/**
 * Parses a 'local [override] variable = value', 'local define variable', and
 * 'local undefine variable [...]' expressions.
 *
 * The 'local' directive must be first and it does not permit any qualifiers at
 * the moment.  Should any be added later, they will have to come after 'local'.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after 'local'.
 * @param   cchLeft     The number of chars left to parse on this line.
 * @param   fQualifiers The qualifiers.
 */
static int kmk_cc_eval_do_var_local(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
    if (cchLeft)
    {
        /*
         * Check for 'local define' and 'local undefine'
         */
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "define", 6))   /* final */
            return kmk_cc_eval_do_var_define(pCompiler, pchWord + 6, cchLeft + 6, KMK_CC_EVAL_QUALIFIER_LOCAL);
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "undefine", 8))   /* final */
            return kmk_cc_eval_do_var_undefine(pCompiler, pchWord + 8, cchLeft + 8, KMK_CC_EVAL_QUALIFIER_LOCAL);

        /*
         * Simpler to just join paths with the rest here, even if we could
         * probably optimize the parsing a little if we liked.
         */
        return kmk_cc_eval_handle_assignment_or_recipe(pCompiler, pchWord, cchLeft, KMK_CC_EVAL_QUALIFIER_LOCAL);
    }
    kmk_cc_eval_fatal(pCompiler, pchWord, "Expected variable name, assignment operator and value after 'local'");
    return 1;
}


/**
 * We've found one variable qualification keyword, now continue parsing and see
 * if this is some kind of variable assignment expression or not.
 *
 * @returns 1 if variable assignment, 0 if not.
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after the first qualifier.
 * @param   cchLeft     The number of chars left to parse on this line.
 * @param   fQualifiers The qualifier.
 */
static int kmk_cc_eval_try_handle_var_with_keywords(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft,
                                                    unsigned fQualifiers)
{
    for (;;)
    {
        KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
        if (cchLeft)
        {
            char ch = *pchWord;
            if (KMK_CC_EVAL_IS_1ST_IN_VARIABLE_KEYWORD(ch))
            {
                if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "define", 6))   /* final */
                    return kmk_cc_eval_do_var_define(pCompiler, pchWord + 6, cchLeft - 6, fQualifiers);

                if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "undefine", 8)) /* final */
                    return kmk_cc_eval_do_var_undefine(pCompiler, pchWord + 8, cchLeft -86, fQualifiers);

                if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "unexport", 8)) /* final */
                    return kmk_cc_eval_do_var_unexport(pCompiler, pchWord + 8, cchLeft - 8, fQualifiers);

                if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "export", 6))
                {
                    if (!(fQualifiers & KMK_CC_EVAL_QUALIFIER_EXPORT))
                        fQualifiers |= KMK_CC_EVAL_QUALIFIER_EXPORT;
                    else
                        kmk_cc_eval_warn(pCompiler, pchWord, "'export' qualifier repeated");
                    pchWord += 6;
                    cchLeft -= 6;
                    continue;
                }

                if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "override", 8))
                {
                    if (!(fQualifiers & KMK_CC_EVAL_QUALIFIER_OVERRIDE))
                        fQualifiers |= KMK_CC_EVAL_QUALIFIER_OVERRIDE;
                    else
                        kmk_cc_eval_warn(pCompiler, pchWord, "'override' qualifier repeated");
                    pchWord += 8;
                    cchLeft -= 8;
                    continue;
                }

                if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "private", 7))
                {
                    if (!(fQualifiers & KMK_CC_EVAL_QUALIFIER_PRIVATE))
                        fQualifiers |= KMK_CC_EVAL_QUALIFIER_PRIVATE;
                    else
                        kmk_cc_eval_warn(pCompiler, pchWord, "'private' qualifier repeated");
                    pchWord += 7;
                    cchLeft -= 7;
                    continue;
                }
            }

            /*
             * Not a keyword, likely variable name followed by an assignment
             * operator and a value.  Do a rough check for the assignment operator
             * and join paths with the unqualified assignment handling code.
             */
            {
                const char *pchEqual = (const char *)memchr(pchWord, '=', cchLeft);
                if (pchEqual)
                    return kmk_cc_eval_handle_assignment_or_recipe(pCompiler, pchWord, cchLeft, fQualifiers);
            }
            return 0;
        }
        else
            kmk_cc_eval_fatal(pCompiler, NULL,
                              "Expected assignment operator or variable directive after variable qualifier(s)\n");
    }
}


/**
 * Parses 'export [variable]' and 'export [qualifiers] variable = value'
 * expressions.
 *
 * When we find the 'export' directive at the start of a line, we need to
 * continue parsing with till we can tell the difference between the two forms.
 *
 * @returns 1 to indicate we've handled a keyword (see
 *          kmk_cc_eval_try_handle_keyword).
 * @param   pCompiler   The compiler state.
 * @param   pchWord     First char after 'define'.
 * @param   cchLeft     The number of chars left to parse on this line.
 */
static int kmk_cc_eval_handle_var_export(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
{
    KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);

    if (cchLeft)
    {
        unsigned iSavedEscEol;
        unsigned cWords;

        /*
         * We need to figure out whether this is an assignment or a export statement,
         * in the latter case join paths with 'export' and 'undefine'.
         */
        const char *pchEqual = (const char *)memchr(pchWord, '=', cchLeft);
        if (!pchEqual)
            return kmk_cc_eval_do_with_variable_list(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_export, 0 /*fQualifiers*/);

        /*
         * Found an '=', could be an assignment.  Let's take the easy way out
         * and just parse the whole statement into words like we would do if
         * it wasn't an assignment, and then check the words out for
         * assignment keywords and operators.
         */
        iSavedEscEol = pCompiler->iEscEol;
        cWords       = kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft);
        if (cWords)
        {
            PKMKCCEVALVARIABLES pInstr;
            PKMKCCEVALWORD pWord = pCompiler->paWords;
            unsigned       iWord = 0;
            while (iWord < cWords)
            {
                /* Trailing assignment operator or terminal assignment directive ('undefine'
                   and 'unexport' makes no sense here but GNU make ignores that). */
                if (   (   pWord->cchWord > 1
                        && pWord->pchWord[pWord->cchWord - 1] == '=')
                    || KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "define", 6)
                    || KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "undefine", 8)
                    || KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "unexport", 8) )
                {
                    pCompiler->iEscEol = iSavedEscEol;
                    return kmk_cc_eval_try_handle_var_with_keywords(pCompiler, pchWord, cchLeft, KMK_CC_EVAL_QUALIFIER_EXPORT);
                }

                /* If not a variable assignment qualifier, it must be a variable name
                   followed by an assignment operator. */
                if (iWord + 1 < cWords)
                {
                    if (   !KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "export", 6)
                        && !KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "private", 7)
                        && !KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "override", 8))
                    {
                        pWord++;
                        if (   pWord->cchWord > 0
                            && (   pWord->pchWord[0] == '='
                                || (   pWord->cchWord > 1
                                    && pWord->pchWord[1] == '='
                                    && (   pWord->pchWord[0] == ':'
                                        || pWord->pchWord[0] == '+'
                                        || pWord->pchWord[0] == '?'
                                        || pWord->pchWord[0] == '<') ) ) )
                        {
                            pCompiler->iEscEol = iSavedEscEol;
                            return kmk_cc_eval_try_handle_var_with_keywords(pCompiler, pchWord, cchLeft,
                                                                            KMK_CC_EVAL_QUALIFIER_EXPORT);
                        }
                        break;
                    }
                }
                else
                    break;
                /* next */
                pWord++;
                iWord++;
            }

            /*
             * It's not an assignment.
             * (This is the same as kmk_cc_eval_do_with_variable_list does.)
             */
            pInstr = (PKMKCCEVALVARIABLES)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, KMKCCEVALVARIABLES_SIZE(cWords));
            pInstr->Core.enmOpcode = kKmkCcEvalInstr_export;
            pInstr->Core.iLine     = pCompiler->iLine;
            pInstr->cVars          = cWords;
            kmk_cc_eval_init_spp_array_from_duplicated_words(pCompiler, cWords, pCompiler->paWords, pInstr->aVars);
            kmk_cc_block_realign(pCompiler->ppBlockTail);
        }
        else
            KMK_CC_ASSERT(0);
    }
    else
    {
        /*
         * We're exporting all variables.
         */
        PKMKCCEVALCORE pInstr = kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
        pInstr->enmOpcode = kKmkCcEvalInstr_export_all;
        pInstr->iLine     = pCompiler->iLine;
    }
    return 1;
}


/**
 * When entering this function we know that the first two character in the first
 * word both independently occurs in keywords.
 *
 * @returns 1 if make directive or qualified variable assignment, 0 if neither.
 * @param   pCompiler   The compiler state.
 * @param   ch          The first char.
 * @param   pchWord     Pointer to the first word.
 * @param   cchLeft     Number of characters left to parse starting at
 *                      @a cchLeft.
 */
int kmk_cc_eval_try_handle_keyword(PKMKCCEVALCOMPILER pCompiler, char ch, const char *pchWord, size_t cchLeft)
{
    unsigned iSavedEscEol = pCompiler->iEscEol;

    KMK_CC_ASSERT(cchLeft >= 2);
    KMK_CC_ASSERT(ch == pchWord[0]);
    KMK_CC_ASSERT(KMK_CC_EVAL_IS_1ST_IN_KEYWORD(pchWord[0]));
    KMK_CC_ASSERT(KMK_CC_EVAL_IS_2ND_IN_KEYWORD(pchWord[1]));

    /*
     * If it's potentially a variable related keyword, check that out first.
     */
    if (KMK_CC_EVAL_IS_1ST_IN_VARIABLE_KEYWORD(ch))
    {
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "local", 5))
            return kmk_cc_eval_do_var_local(pCompiler, pchWord + 5, cchLeft - 5);
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "define", 6))
            return kmk_cc_eval_do_var_define(pCompiler, pchWord + 6, cchLeft - 6, 0);
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "export", 6))
            return kmk_cc_eval_handle_var_export(pCompiler, pchWord + 6, cchLeft - 6);
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "undefine", 8))
            return kmk_cc_eval_do_var_undefine(pCompiler, pchWord + 8, cchLeft - 8, 0);
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "unexport", 8))
            return kmk_cc_eval_do_var_unexport(pCompiler, pchWord + 8, cchLeft - 8, 0);
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "override", 8))
        {
            if (kmk_cc_eval_try_handle_var_with_keywords(pCompiler, pchWord + 8, cchLeft - 8, KMK_CC_EVAL_QUALIFIER_OVERRIDE))
                return 1;
            pCompiler->iEscEol = iSavedEscEol;
        }
        else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "private", 7))
        {
            if (kmk_cc_eval_try_handle_var_with_keywords(pCompiler, pchWord + 7, cchLeft - 7, KMK_CC_EVAL_QUALIFIER_PRIVATE))
                return 1;
            pCompiler->iEscEol = iSavedEscEol;
        }
    }

    /*
     * Check out the other keywords.
     */
    if (ch == 'i') /* Lots of directives starting with 'i'. */
    {
        char ch2 = pchWord[1];
        pchWord += 2;
        cchLeft -= 2;

        /* 'if...' */
        if (ch2 == 'f')
        {
            if (KMK_CC_EVAL_WORD_COMP_IS_EOL(pCompiler, pchWord, cchLeft))
                return kmk_cc_eval_do_if(pCompiler, pchWord, cchLeft, 0 /* in else */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "eq", 2))
                return kmk_cc_eval_do_ifeq( pCompiler, pchWord + 2, cchLeft - 2, 0 /* in else */, 1 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "def", 3))
                return kmk_cc_eval_do_ifdef(pCompiler, pchWord + 3, cchLeft - 3, 0 /* in else */, 1 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "neq", 3))
                return kmk_cc_eval_do_ifeq( pCompiler, pchWord + 3, cchLeft - 3, 0 /* in else */, 0 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "1of", 3))
                return kmk_cc_eval_do_if1of(pCompiler, pchWord + 3, cchLeft - 3, 0 /* in else */, 1 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "ndef", 4))
                return kmk_cc_eval_do_ifdef(pCompiler, pchWord + 4, cchLeft - 4, 0 /* in else */, 0 /* positive */);

            if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "n1of", 4))
                return kmk_cc_eval_do_if1of(pCompiler, pchWord + 4, cchLeft - 4, 0 /* in else */, 0 /* positive */);
        }
        /* include... */
        else if (ch2 == 'n' && cchLeft >= 5 && KMK_CC_WORD_COMP_CONST_5(pchWord, "clude") ) /* 'in...' */
        {
            pchWord += 5;
            cchLeft -= 5;
            if (KMK_CC_EVAL_WORD_COMP_IS_EOL(pCompiler, pchWord, cchLeft))
                return kmk_cc_eval_do_include(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_include);
            if (cchLeft >= 3 && KMK_CC_WORD_COMP_CONST_3(pchWord, "dep"))
            {
                pchWord += 3;
                cchLeft -= 3;
                if (KMK_CC_EVAL_WORD_COMP_IS_EOL(pCompiler, pchWord, cchLeft))
                    return kmk_cc_eval_do_include(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_includedep);
                if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "-queue", 6))
                    return kmk_cc_eval_do_include(pCompiler, pchWord + 6, cchLeft - 6, kKmkCcEvalInstr_includedep_queue);
                if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "-flush", 6))
                    return kmk_cc_eval_do_include(pCompiler, pchWord + 6, cchLeft - 6, kKmkCcEvalInstr_includedep_flush);
            }
        }
    }
    else if (ch == 'e') /* A few directives starts with 'e'. */
    {
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "else", 4))
            return kmk_cc_eval_do_else(pCompiler, pchWord + 4, cchLeft - 4);
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "endif", 5))
            return kmk_cc_eval_do_endif(pCompiler, pchWord + 5, cchLeft - 5);
        /* export and endef are handled elsewhere, though stray endef's may end up here... */
        KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "export", 6));

    }
    else /* the rest. */
    {
        if (   KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "sinclude", 8)
            || KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "-include", 8))
            return kmk_cc_eval_do_include(pCompiler, pchWord + 8, cchLeft - 8, kKmkCcEvalInstr_include_silent);
        if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "vpath", 5))
            return kmk_cc_eval_do_vpath(pCompiler, pchWord + 5, cchLeft - 5);

        KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "local", 5));
        KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "define", 6));
        KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "private", 7));
        KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "override", 8));
        KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "unexport", 8));
        KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "undefine", 8));
    }

    pCompiler->iEscEol = iSavedEscEol;
    return 0;
}




static int kmk_cc_eval_compile_worker(PKMKCCEVALPROG pEvalProg, const char *pszContent, size_t cchContent, unsigned iLine)
{
    const char *pchTmp;

    /*
     * Compiler state.
     */
    KMKCCEVALCOMPILER   Compiler;
    kmk_cc_eval_init_compiler(&Compiler, pEvalProg, iLine, pszContent, cchContent);
    KMK_CC_EVAL_DPRINTF(("\nkmk_cc_eval_compile_worker - begin (%s/%s/%d)\n", pEvalProg->pszFilename, pEvalProg->pszVarName, iLine));

    {
        /*
         * Line state.
         */
        size_t              cchLine;                                /* The length of the current line (w/o comments). */
        size_t              offNext = 0;                            /* The offset of the next line. */
        size_t              off     = 0;                            /* The offset into pszContent of the current line. */

        /* Try for some register/whatever optimzations. */
        int const           chFirstEol = Compiler.chFirstEol;
        size_t const        cchEolSeq  = Compiler.cchEolSeq;

        /*
         * Process input lines.
         *
         * The code here concerns itself with getting the next line in an efficient
         * manner, very basic classification and trying out corresponding handlers.
         * The real work is done in the handlers.
         */
        while (offNext < cchContent)
        {
            size_t offFirstWord;

            /*
             * Find the end of the next line.
             */
            KMK_CC_ASSERT(off == offNext);

            /* Simple case: No escaped EOL, nor the end of the input. */
            pchTmp = (const char *)memchr(&pszContent[offNext], chFirstEol, cchContent - offNext);
            if (   pchTmp
                && (   &pszContent[offNext] == pchTmp
                    || pchTmp[-1] != '\\') )
            {
                if (   cchEolSeq == 1
                    || pchTmp[1] == Compiler.chSecondEol)
                {
                    /* Frequent: Blank line. */
                    if (&pszContent[offNext] == pchTmp)
                    {
                        KMK_CC_EVAL_DPRINTF(("#%03u: <empty>\n", Compiler.iLine));
                        Compiler.iLine++;
                        off = offNext += cchEolSeq;
                        continue;
                    }
                    if (pszContent[offNext] == '#')
                    {
                        KMK_CC_EVAL_DPRINTF(("#%03u: <comment-col-0>\n", Compiler.iLine));
                        Compiler.iLine++;
                        offNext = pchTmp - pszContent;
                        off = offNext += cchEolSeq;
                        continue;
                    }

                    offNext  = pchTmp - pszContent;
                    cchLine  = offNext - off;

                    offFirstWord = off;
                    while (offFirstWord < offNext && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
                        offFirstWord++;

                    offNext += cchEolSeq;
                    Compiler.cEscEols = 0;
                    Compiler.iEscEol  = 0;
                }
                else
                    kmk_cc_eval_fatal_eol(&Compiler, pchTmp, Compiler.iLine, off);
            }
            /* The complicated, less common cases. */
            else
            {
                Compiler.cEscEols = 0;
                Compiler.iEscEol  = 0;
                offFirstWord = offNext;
                for (;;)
                {
                    if (pchTmp)
                    {
                        if (   cchEolSeq == 1
                            || pchTmp[1] == Compiler.chSecondEol)
                        {
                            size_t offEsc;
                            if (offFirstWord != offNext)
                                offNext = pchTmp - pszContent;
                            else
                            {
                                offNext = pchTmp - pszContent;
                                while (offFirstWord < offNext && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
                                    offFirstWord++;
                            }


                            /* Is it an escape sequence? */
                            if (   !offNext
                                || pchTmp[-1] != '\\')
                            {
                                cchLine  = offNext - off;
                                offNext += cchEolSeq;
                                break;
                            }
                            if (offNext < 2 || pchTmp[-2] != '\\')
                                offEsc = offNext - 1;
                            else
                            {
                                /* Count how many backslashes there are. Must be odd number to be an escape
                                   sequence.  Normally we keep half of them, except for command lines.  */
                                size_t cSlashes = 2;
                                while (offNext >= cSlashes && pchTmp[0 - cSlashes] == '\\')
                                    cSlashes--;
                                if (!(cSlashes & 1))
                                {
                                    cchLine  = offNext - off;
                                    offNext += cchEolSeq;
                                    break;
                                }
                                offEsc = offNext - (cSlashes >> 1);
                            }

                            /* Record it. */
                            if (Compiler.cEscEols < Compiler.cEscEolsAllocated) { /* likely */ }
                            else
                            {
                                KMK_CC_ASSERT(Compiler.cEscEols == Compiler.cEscEolsAllocated);
                                Compiler.cEscEolsAllocated = Compiler.cEscEolsAllocated
                                                           ? Compiler.cEscEolsAllocated * 2 : 2;
                                Compiler.paEscEols = (PKMKCCEVALESCEOL)xrealloc(Compiler.paEscEols,
                                                                                  Compiler.cEscEolsAllocated
                                                                                * sizeof(Compiler.paEscEols[0]));
                            }
                            Compiler.paEscEols[Compiler.cEscEols].offEsc = offEsc;
                            Compiler.paEscEols[Compiler.cEscEols].offEol = offNext;
                            Compiler.cEscEols++;

                            /* Advance. */
                            offNext += cchEolSeq;
                            if (offFirstWord == offEsc)
                            {
                                offFirstWord = offNext;
                                Compiler.iEscEol++;
                            }
                        }
                        else
                            kmk_cc_eval_fatal_eol(&Compiler, pchTmp, Compiler.iLine, off);
                    }
                    else
                    {
                        /* End of input. Happens only once per compilation, nothing to optimize for. */
                        if (offFirstWord == offNext)
                            while (offFirstWord < cchContent && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
                                offFirstWord++;
                        offNext = cchContent;
                        cchLine = cchContent - off;
                        break;
                    }
                    pchTmp = (const char *)memchr(&pszContent[offNext], chFirstEol, cchContent - offNext);
                }
            }
            KMK_CC_ASSERT(offNext       <= cchContent);
            KMK_CC_ASSERT(offNext       >= off + cchLine);
            KMK_CC_ASSERT(off + cchLine <= cchContent && cchLine <= cchContent);
            KMK_CC_ASSERT(offFirstWord  <= off + cchLine);
            KMK_CC_ASSERT(offFirstWord  >= off);
            KMK_CC_ASSERT(pszContent[offFirstWord] != ' ' && pszContent[offFirstWord] != '\t');

            KMK_CC_EVAL_DPRINTF(("#%03u: %*.*s\n", Compiler.iLine, (int)cchLine, (int)cchLine, &pszContent[off]));

            /*
             * Skip blank lines.
             */
            if (offFirstWord < off + cchLine)
            {
                /*
                 * Command? Ignore command prefix if no open recipe (SunOS 4 behavior).
                 */
                if (   pszContent[off] == Compiler.chCmdPrefix
                    && (Compiler.pRecipe || Compiler.fNoTargetRecipe))
                {
                    if (!Compiler.fNoTargetRecipe)
                        kmk_cc_eval_handle_command(&Compiler, &pszContent[off], cchLine);
                }
                /*
                 * Since it's not a command line, we can now skip comment lines
                 * even with a tab indentation.  If it's not a comment line, we
                 * tentatively strip any trailing comment.
                 */
                else if (pszContent[offFirstWord] != '#')
                {
                    const char *pchWord = &pszContent[offFirstWord];
                    size_t      cchLeft = off + cchLine - offFirstWord;
                    char        ch;

                    Compiler.cchLineWithComments = cchLine;
                    pchTmp = (const char *)memchr(pchWord, '#', cchLeft);
                    if (pchTmp)
                    {
                        cchLeft = pchTmp - pchWord;
                        cchLine = pchTmp - &pszContent[off];
                    }
                    Compiler.cchLine = cchLine; /** @todo only used by assertions. */
                    Compiler.offLine = off;     /** @todo only used by fatal errors. */

#ifdef KMK_CC_STRICT
                    Compiler.cWords = 0x424242;
#endif

                    /*
                     * If not a directive or variable qualifier, it's either a variable
                     * assignment or a recipe.
                     */
                    ch = *pchWord;
                    if (   !KMK_CC_EVAL_IS_1ST_IN_KEYWORD(ch)
                        || !KMK_CC_EVAL_IS_2ND_IN_KEYWORD(pchWord[1]))
                    {
                        if (memchr(pchWord, '=', cchLeft))
                            kmk_cc_eval_handle_assignment_or_recipe(&Compiler, pchWord, cchLeft, 0 /*fQualifiers*/);
                        else
                            kmk_cc_eval_handle_recipe(&Compiler, pchWord, cchLeft);
                    }
                    else
                    {
                        /* Possible directive or variable qualifier. */
                        Compiler.offNext = offNext;
                        if (kmk_cc_eval_try_handle_keyword(&Compiler, ch, pchWord, cchLeft))
                            offNext = Compiler.offNext;
                        /* No, that wasn't it... */
                        else if (memchr(pchWord, '=', cchLeft))
                            kmk_cc_eval_handle_assignment_or_recipe(&Compiler, pchWord, cchLeft, 0 /*fQualifiers*/);
                        else
                            kmk_cc_eval_handle_recipe(&Compiler, pchTmp, pchWord, cchLeft);
                    }
                }
            }

            /*
             * Advance to the next line.
             */
            off             = offNext;
            Compiler.iLine += Compiler.cEscEols + 1;
        }
    }

    /*
     * Check whether
     */

    kmk_cc_eval_delete_compiler(&Compiler);
    KMK_CC_EVAL_DPRINTF(("kmk_cc_eval_compile_worker - done (%s/%s)\n\n", pEvalProg->pszFilename, pEvalProg->pszVarName));
    return 0;
}



static PKMKCCEVALPROG kmk_cc_eval_compile(const char *pszContent, size_t cchContent,
                                          const char *pszFilename, unsigned iLine, const char *pszVarName)
{
    /*
     * Estimate block size, allocate one and initialize it.
     */
    PKMKCCEVALPROG  pEvalProg;
    PKMKCCBLOCK     pBlock;
    pEvalProg = kmk_cc_block_alloc_first(&pBlock, sizeof(*pEvalProg), cchContent / 32); /** @todo adjust */
    if (pEvalProg)
    {
        pEvalProg->pBlockTail   = pBlock;
        pEvalProg->pFirstInstr  = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(pBlock);
        pEvalProg->pszFilename  = pszFilename ? pszFilename : "<unknown>";
        pEvalProg->pszVarName   = pszVarName;
        pEvalProg->cRefs        = 1;
#ifdef KMK_CC_STRICT
        pEvalProg->uInputHash   = kmk_cc_debug_string_hash_n(0, pszContent, cchContent);
#endif

        /*
         * Do the actual compiling.
         */
#ifdef CONFIG_WITH_EVAL_COMPILER
        if (kmk_cc_eval_compile_worker(pEvalProg, pszContent, cchContent, iLine) == 0)
#else
        if (0)
#endif
        {
#ifdef KMK_CC_WITH_STATS
            pBlock = pEvalProg->pBlockTail;
            if (!pBlock->pNext)
                g_cSingleBlockEvalProgs++;
            else if (!pBlock->pNext->pNext)
                g_cTwoBlockEvalProgs++;
            else
                g_cMultiBlockEvalProgs++;
            for (; pBlock; pBlock = pBlock->pNext)
            {
                g_cBlocksAllocatedEvalProgs++;
                g_cbAllocatedEvalProgs += pBlock->cbBlock;
                g_cbUnusedMemEvalProgs += pBlock->cbBlock - pBlock->offNext;
            }
#endif
            return pEvalProg;
        }
        kmk_cc_block_free_list(pEvalProg->pBlockTail);
    }
    return NULL;
}


/**
 * Compiles a variable direct evaluation as is, setting v->evalprog on success.
 *
 * @returns Pointer to the program on success, NULL if no program was created.
 * @param   pVar        Pointer to the variable.
 */
struct kmk_cc_evalprog   *kmk_cc_compile_variable_for_eval(struct variable *pVar)
{
    PKMKCCEVALPROG pEvalProg = pVar->evalprog;
    if (!pEvalProg)
    {
#ifdef CONFIG_WITH_EVAL_COMPILER
        pEvalProg = kmk_cc_eval_compile(pVar->value, pVar->value_length,
                                        pVar->fileinfo.filenm, pVar->fileinfo.lineno, pVar->name);
        pVar->evalprog = pEvalProg;
#endif
        g_cVarForEvalCompilations++;
    }
    return pEvalProg;
}


/**
 * Compiles a makefile for
 *
 * @returns Pointer to the program on success, NULL if no program was created.
 * @param   pVar        Pointer to the variable.
 */
struct kmk_cc_evalprog   *kmk_cc_compile_file_for_eval(FILE *pFile, const char *pszFilename)
{
    PKMKCCEVALPROG  pEvalProg;

    /*
     * Read the entire file into a zero terminate memory buffer.
     */
    size_t          cchContent = 0;
    char           *pszContent = NULL;
    struct stat st;
    if (!fstat(fileno(pFile), &st))
    {
        if (   st.st_size > (off_t)KMK_CC_EVAL_MAX_COMPILE_SIZE
            && st.st_size < 0)
            fatal(NULL, _("Makefile too large to compile: %ld bytes (%#lx) - max %uMB"),
                  (long)st.st_size, (long)st.st_size, KMK_CC_EVAL_MAX_COMPILE_SIZE / 1024 / 1024);
        cchContent = (size_t)st.st_size;
        pszContent = (char *)xmalloc(cchContent + 1);

        cchContent = fread(pszContent, 1, cchContent, pFile);
        if (ferror(pFile))
            fatal(NULL, _("Read error: %s"), strerror(errno));
    }
    else
    {
        size_t cbAllocated = 2048;
        do
        {
            cbAllocated *= 2;
            if (cbAllocated > KMK_CC_EVAL_MAX_COMPILE_SIZE)
                fatal(NULL, _("Makefile too large to compile: max %uMB"), KMK_CC_EVAL_MAX_COMPILE_SIZE / 1024 / 1024);
            pszContent = (char *)xrealloc(pszContent, cbAllocated);
            cchContent += fread(&pszContent[cchContent], 1, cbAllocated - 1 - cchContent, pFile);
            if (ferror(pFile))
                fatal(NULL, _("Read error: %s"), strerror(errno));
        } while (!feof(pFile));
    }
    pszContent[cchContent] = '\0';

    /*
     * Call common function to do the compilation.
     */
    pEvalProg = kmk_cc_eval_compile(pszContent, cchContent, pszFilename, 1, NULL /*pszVarName*/);
    g_cFileForEvalCompilations++;

    free(pszContent);
    if (!pEvalProg)
        fseek(pFile, 0, SEEK_SET);
    return pEvalProg;
}


/**
 * Equivalent of eval_buffer, only it's using the evalprog of the variable.
 *
 * @param   pVar        Pointer to the variable. Must have a program.
 */
void kmk_exec_eval_variable(struct variable *pVar)
{
    KMK_CC_ASSERT(pVar->evalprog);
    assert(0);
}


/**
 * Worker for eval_makefile.
 *
 * @param   pEvalProg   The program pointer.
 */
void kmk_exec_eval_file(struct kmk_cc_evalprog *pEvalProg)
{
    KMK_CC_ASSERT(pEvalProg);
    assert(0);

}



/*
 *
 * Program destruction hooks.
 * Program destruction hooks.
 * Program destruction hooks.
 *
 */


/**
 * Called when a variable with expandprog or/and evalprog changes.
 *
 * @param   pVar        Pointer to the variable.
 */
void  kmk_cc_variable_changed(struct variable *pVar)
{
    PKMKCCEXPPROG pProg = pVar->expandprog;

    KMK_CC_ASSERT(pVar->evalprog || pProg);

    if (pVar->evalprog)
    {
        kmk_cc_block_free_list(pVar->evalprog->pBlockTail);
        pVar->evalprog = NULL;
    }

    if (pProg)
    {
        if (pProg->cRefs == 1)
            kmk_cc_block_free_list(pProg->pBlockTail);
        else
            fatal(NULL, _("Modifying a variable (%s) while its expansion program is running is not supported"), pVar->name);
        pVar->expandprog = NULL;
    }
}


/**
 * Called when a variable with expandprog or/and evalprog is deleted.
 *
 * @param   pVar        Pointer to the variable.
 */
void  kmk_cc_variable_deleted(struct variable *pVar)
{
    PKMKCCEXPPROG pProg = pVar->expandprog;

    KMK_CC_ASSERT(pVar->evalprog || pProg);

    if (pVar->evalprog)
    {
        kmk_cc_block_free_list(pVar->evalprog->pBlockTail);
        pVar->evalprog = NULL;
    }

    if (pProg)
    {
        if (pProg->cRefs == 1)
            kmk_cc_block_free_list(pProg->pBlockTail);
        else
            fatal(NULL, _("Deleting a variable (%s) while its expansion program is running is not supported"), pVar->name);
        pVar->expandprog = NULL;
    }
}







#endif /* CONFIG_WITH_COMPILER */

