/* $Id: kbuild-object.c 3141 2018-03-14 21:58:32Z bird $ */
/** @file
 * kBuild objects.
 */

/*
 * Copyright (c) 2011-2014 knut st. osmundsen <bird-kBuild-spamx@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/>
 *
 */

/* No GNU coding style here! */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include "makeint.h"
#include "filedef.h"
#include "variable.h"
#include "dep.h"
#include "debug.h"
#include "kbuild.h"

#include <assert.h>
#include <stdarg.h>


/*******************************************************************************
*   Defined Constants And Macros                                               *
*******************************************************************************/
#define WORD_IS(a_pszWord, a_cchWord, a_szWord2) \
        (  (a_cchWord) == sizeof(a_szWord2) - 1 && memcmp((a_pszWord), a_szWord2, sizeof(a_szWord2) - 1) == 0)


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/** kBuild object type.  */
enum kBuildType
{
    kBuildType_Invalid,
    kBuildType_Target,
    kBuildType_Template,
    kBuildType_Tool,
    kBuildType_Sdk,
    kBuildType_Unit
};

enum kBuildSeverity
{
    kBuildSeverity_Warning,
    kBuildSeverity_Error,
    kBuildSeverity_Fatal
};


/**
 * kBuild object data.
 */
struct kbuild_object
{
    /** The object type. */
    enum kBuildType             enmType;
    /** Object name length.  */
    size_t                      cchName;
    /** The bare name of the define. */
    char                       *pszName;
    /** The file location where this define was declared. */
    floc                        FileLoc;

    /** Pointer to the next element in the global list. */
    struct kbuild_object       *pGlobalNext;

    /** The variable set associated with this define. */
    struct variable_set_list   *pVariables;

    /** The parent name, NULL if none. */
    char                       *pszParent;
    /** The length of the parent name. */
    size_t                      cchParent;
    /** Pointer to the parent.  Resolved lazily, so it can be NULL even if we
     * have a parent. */
    struct kbuild_object       *pParent;

    /** The template, NULL if none.  Only applicable to targets.  Only covers the
     * primary template, not target or type specific templates.
     * @todo not sure if this is really necessary. */
    char const                 *pszTemplate;

    /** The variable prefix. */
    char                       *pszVarPrefix;
    /** The length of the variable prefix. */
    size_t                      cchVarPrefix;
};


/**
 * The data we stack during eval.
 */
struct kbuild_eval_data
{
    /** Pointer to the element below us on the stack. */
    struct kbuild_eval_data    *pStackDown;
    /** Pointer to the object. */
    struct kbuild_object       *pObj;
    /** The saved current variable set, for restoring in kBuild-endef. */
    struct variable_set_list   *pVariablesSaved;
};



/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** Linked list (LIFO) of kBuild defines.
 * @todo use a hash! */
static struct kbuild_object    *g_pHeadKbObjs = NULL;
/** Stack of kBuild evalutation contexts.
 * This is for dealing with potential recursive kBuild object definition,
 * generally believed to only happen via $(eval ) or include similar. */
struct kbuild_eval_data        *g_pTopKbEvalData = NULL;

/** Cached variable name '_TEMPLATE'.  */
static const char              *g_pszVarNmTemplate = NULL;

/** Zero if compatibility mode is disabled, non-zero if enabled.
 * If explicitily enabled, the value will be greater than 1. */
int                             g_fKbObjCompMode = 1;


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
static struct kbuild_object *
resolve_kbuild_object_parent(struct kbuild_object *pObj, int fQuiet);
static struct kbuild_object *
get_kbuild_object_parent(struct kbuild_object *pObj, enum kBuildSeverity enmSeverity);

static struct kbuild_object *
parse_kbuild_object_variable_accessor(const char *pchExpr, size_t cchExpr,
                                      enum kBuildSeverity enmSeverity, const floc *pFileLoc,
                                      const char **ppchVarNm, size_t *pcchVarNm, enum kBuildType *penmType);


/**
 * Initializes the kBuild object stuff.
 *
 * Requires the variable_cache to be initialized.
 */
void init_kbuild_object(void)
{
    g_pszVarNmTemplate = strcache2_add(&variable_strcache, STRING_SIZE_TUPLE("_TEMPLATE"));
}


/**
 * Reports a problem with dynamic severity level.
 *
 * @param   enmSeverity         The severity level.
 * @param   pFileLoc            The file location.
 * @param   pszFormat           The format string.
 * @param   ...                 Arguments for the format string.
 */
static void kbuild_report_problem(enum kBuildSeverity enmSeverity, const floc *pFileLoc,
                                  const char *pszFormat, ...)
{
    char    szBuf[8192];
    va_list va;

    va_start(va, pszFormat);
#ifdef _MSC_VER
    _vsnprintf(szBuf, sizeof(szBuf), pszFormat, va);
#else
    vsnprintf(szBuf, sizeof(szBuf), pszFormat, va);
#endif
    va_end(va);

    switch (enmSeverity)
    {
        case kBuildSeverity_Warning:
            OS(message, 0, "%s", szBuf);
            break;
        case kBuildSeverity_Error:
            OS(error, pFileLoc, "%s", szBuf);
            break;
        default:
        case kBuildSeverity_Fatal:
            OS(fatal, pFileLoc, "%s", szBuf);
            break;
    }
}


static const char *
eval_kbuild_type_to_string(enum kBuildType enmType)
{
    switch (enmType)
    {
        case kBuildType_Target:      return "target";
        case kBuildType_Template:    return "template";
        case kBuildType_Tool:        return "tool";
        case kBuildType_Sdk:         return "sdk";
        case kBuildType_Unit:        return "unit";
        default:
        case kBuildType_Invalid:     return "invalid";
    }
}

/**
 * Gets the length of the string representation of the given type.
 *
 * @returns The string length.
 * @param   enmType             The kBuild object type in question.
 */
static unsigned
eval_kbuild_type_to_string_length(enum kBuildType enmType)
{
    switch (enmType)
    {
        case kBuildType_Target:      return sizeof("target") - 1;
        case kBuildType_Template:    return sizeof("template") - 1;
        case kBuildType_Tool:        return sizeof("tool") - 1;
        case kBuildType_Sdk:         return sizeof("sdk") - 1;
        case kBuildType_Unit:        return sizeof("unit") - 1;
        default:
        case kBuildType_Invalid:     return sizeof("invalid") - 1;
    }
}

/**
 * Converts a string into an kBuild object type.
 *
 * @returns The type on success, kBuildType_Invalid on failure.
 * @param   pchWord             The pchWord.  Not necessarily zero terminated.
 * @param   cchWord             The length of the word.
 */
static enum kBuildType
eval_kbuild_type_from_string(const char *pchWord, size_t cchWord)
{
    if (cchWord >= 3)
    {
        if (*pchWord == 't')
        {
            if (WORD_IS(pchWord, cchWord, "target"))
                return kBuildType_Target;
            if (WORD_IS(pchWord, cchWord, "template"))
                return kBuildType_Template;
            if (WORD_IS(pchWord, cchWord, "tool"))
                return kBuildType_Tool;
        }
        else
        {
            if (WORD_IS(pchWord, cchWord, "sdk"))
                return kBuildType_Sdk;
            if (WORD_IS(pchWord, cchWord, "unit"))
                return kBuildType_Unit;
        }
    }

    return kBuildType_Invalid;
}



#if 0 /* unused */
/**
 * Helper function for caching variable name strings.
 *
 * @returns The string cache variable name.
 * @param   pszName             The variable name.
 * @param   ppszCache           Cache variable, static or global.  Initialize to
 *                              NULL.
 */
static const char *
kbuild_variable_name(const char *pszName, const char **ppszCache)
{
    const char *pszRet = *ppszCache;
    if (!pszRet)
        *ppszCache = pszRet = strcache2_add(&variable_strcache, pszName, strlen(pszName));
    return pszRet;
}
#endif

static struct kbuild_object *
lookup_kbuild_object(enum kBuildType enmType, const char *pchName, size_t cchName)
{
    /* Linear lookup for now. */
    struct kbuild_object *pCur = g_pHeadKbObjs;
    while (pCur)
    {
        if (   pCur->enmType == enmType
            && pCur->cchName == cchName
            && !memcmp(pCur->pszName, pchName, cchName))
            return pCur;
        pCur = pCur->pGlobalNext;
    }
    return NULL;
}


/** @name Defining and modifying variables
 * @{
 */

/**
 * Checks if the variable name is valid.
 *
 * @returns 1 if valid, 0 if not.
 * @param   pchName             The variable name.
 * @param   cchName             The length of the variable name.
 */
static int
is_valid_kbuild_object_variable_name(const char *pchName, size_t cchName)
{
    if (cchName > 0)
    {
        if (!memchr(pchName, '[', cchName))
        {
            /** @todo more? */
            return 1;
        }
    }
    return 0;
}

static const char *
kbuild_replace_special_accessors(const char *pchValue, size_t *pcchValue, int *pfDuplicateValue,
                                 const floc *pFileLoc)
{
    size_t      cchValue    = *pcchValue;
    size_t      cbAllocated = *pfDuplicateValue ? 0 : cchValue + 1;

    /*
     * Loop thru each potential special accessor occurance in the string.
     *
     * Unfortunately, we don't have a strnstr function in the C library, so
     * we'll using memchr and doing a few more rounds in this loop.
     */
    size_t  cchLeft  = cchValue;
    char   *pchLeft  = (char *)pchValue;
    for (;;)
    {
        int     fSuper;
        char   *pch = (char *)memchr(pchLeft, '$', cchLeft);
        if (!pch)
            break;

        pch++;
        cchLeft -= pch - pchLeft;
        pchLeft  = pch;

        /* [@self] is the shorter, quit if there isn't enough room for even it. */
        if (cchLeft < sizeof("([@self]") - 1)
            break;

        /* We don't care how many dollars there are in front of a special accessor. */
        if (*pchLeft == '$')
        {
            do
            {
                cchLeft--;
                pchLeft++;
            } while (cchLeft >= sizeof("([@self]") - 1 && *pchLeft == '$');
            if (cchLeft < sizeof("([@self]") - 1)
                break;
        }

        /* Is it a special accessor? */
        if (   pchLeft[2] != '@'
            || pchLeft[1] != '['
            || pchLeft[0] != '(')
            continue;
        pchLeft += 2;
        cchLeft -= 2;
        if (!memcmp(pchLeft, STRING_SIZE_TUPLE("@self]")))
            fSuper = 0;
        else if (   cchLeft >= sizeof("@super]")
                 && !memcmp(pchLeft, STRING_SIZE_TUPLE("@super]")))
            fSuper = 1;
        else
            continue;

        /*
         * We've got something to replace. First figure what with and then
         * resize the value buffer.
         */
        if (g_pTopKbEvalData)
        {
            struct kbuild_object   *pObj       = g_pTopKbEvalData->pObj;
            size_t const            cchSpecial = fSuper ? sizeof("@super") - 1 : sizeof("@self") - 1;
            size_t                  cchName;
            size_t                  cchType;
            long                    cchDelta;
            const char             *pszName;

            if (fSuper)
            {
                pObj = get_kbuild_object_parent(pObj, kBuildSeverity_Error);
                if (!pObj)
                    continue;
            }
            pszName = pObj->pszName;
            cchName = pObj->cchName;
            cchType = eval_kbuild_type_to_string_length(pObj->enmType);
            cchDelta = cchType + 1 + cchName - cchSpecial;

            if (cchValue + cchDelta >= cbAllocated)
            {
                size_t  offLeft = pchLeft - pchValue;
                char   *pszNewValue;

                cbAllocated = cchValue + cchDelta + 1;
                if (cchValue < 1024)
                    cbAllocated = (cbAllocated + 31) & ~(size_t)31;
                else
                    cbAllocated = (cbAllocated + 255) & ~(size_t)255;
                pszNewValue = (char *)xmalloc(cbAllocated);

                memcpy(pszNewValue, pchValue, offLeft);
                memcpy(pszNewValue + offLeft + cchSpecial + cchDelta,
                       pchLeft + cchSpecial,
                       cchLeft - cchSpecial + 1);

                if (*pfDuplicateValue == 0)
                    free((char *)pchValue);
                else
                    *pfDuplicateValue = 0;

                pchValue = pszNewValue;
                pchLeft  = pszNewValue + offLeft;
            }
            else
            {
                assert(*pfDuplicateValue == 0);
                memmove(pchLeft + cchSpecial + cchDelta,
                        pchLeft + cchSpecial,
                        cchLeft - cchSpecial + 1);
            }

            cchLeft  += cchDelta;
            cchValue += cchDelta;
            *pcchValue = cchValue;

            memcpy(pchLeft, eval_kbuild_type_to_string(pObj->enmType), cchType);
            pchLeft += cchType;
            *pchLeft++ = '@';
            memcpy(pchLeft, pszName, cchName);
            pchLeft += cchName;
            cchLeft -= cchType + 1 + cchName;
        }
        else
            error(pFileLoc, 20, _("The '$([%.*s...' accessor can only be used in the context of a kBuild object"),
                  (int)MIN(cchLeft, 20), pchLeft);
    }

    return pchValue;
}

static struct variable *
define_kbuild_object_variable_cached(struct kbuild_object *pObj, const char *pszName,
                                     const char *pchValue, size_t cchValue,
                                     int fDuplicateValue, enum variable_origin enmOrigin,
                                     int fRecursive, int fNoSpecialAccessors, const floc *pFileLoc)
{
    struct variable *pVar;
    size_t cchName = strcache2_get_len(&variable_strcache, pszName);

    if (fRecursive && !fNoSpecialAccessors)
        pchValue = kbuild_replace_special_accessors(pchValue, &cchValue, &fDuplicateValue, pFileLoc);

    pVar = define_variable_in_set(pszName, cchName,
                                  pchValue, cchValue, fDuplicateValue,
                                  enmOrigin, fRecursive,
                                  pObj->pVariables->set,
                                  pFileLoc);

    /* Single underscore prefixed variables gets a global alias. */
    if (   pszName[0] == '_'
        && pszName[1] != '_'
        && g_fKbObjCompMode)
    {
        struct variable *pAlias;
        size_t           cchPrefixed = pObj->cchVarPrefix + cchName;
        char            *pszPrefixed = xmalloc(cchPrefixed + 1);
        memcpy(pszPrefixed, pObj->pszVarPrefix, pObj->cchVarPrefix);
        memcpy(&pszPrefixed[pObj->cchVarPrefix], pszName, cchName);
        pszPrefixed[cchPrefixed] = '\0';

        pAlias = define_variable_alias_in_set(pszPrefixed, cchPrefixed, pVar, enmOrigin,
                                              &global_variable_set, pFileLoc);
        if (!pAlias->alias)
            OS(error, pFileLoc, _("Error defining alias '%s'"), pszPrefixed);
    }

    return pVar;
}

#if 0
struct variable *
define_kbuild_object_variable(struct kbuild_object *pObj, const char *pchName, size_t cchName,
                              const char *pchValue, size_t cchValue,
                              int fDuplicateValue, enum variable_origin enmOrigin,
                              int fRecursive, const floc *pFileLoc)
{
    return define_kbuild_object_variable_cached(pObj, strcache2_add(&variable_strcache, pchName, cchName),
                                                pchValue, cchValue,
                                                fDuplicateValue, enmOrigin,
                                                fRecursive, pFileLoc);
}
#endif

/**
 * Try define a kBuild object variable via a possible accessor
 * ([type@object]var).
 *
 * @returns Pointer to the defined variable on success.
 * @retval  VAR_NOT_KBUILD_ACCESSOR if it isn't an accessor.
 *
 * @param   pchName         The variable name, not cached.
 * @param   cchName         The variable name length.  This will not be ~0U.
 * @param   pszValue        The variable value.  If @a fDuplicateValue is clear,
 *                          this should be assigned as the actual variable
 *                          value, otherwise it will be duplicated.  In the
 *                          latter case it might not be properly null
 *                          terminated.
 * @param   cchValue        The value length.
 * @param   fDuplicateValue Whether @a pszValue need to be duplicated on the
 *                          heap or is already there.
 * @param   enmOrigin       The variable origin.
 * @param   fRecursive      Whether it's a recursive variable.
 * @param   pFileLoc        The location of the variable definition.
 */
struct variable *
try_define_kbuild_object_variable_via_accessor(const char *pchName, size_t cchName,
                                               const char *pszValue, size_t cchValue, int fDuplicateValue,
                                               enum variable_origin enmOrigin, int fRecursive,
                                               floc const *pFileLoc)
{
    struct kbuild_object   *pObj;
    const char             *pchVarNm;
    size_t                  cchVarNm;

    pObj = parse_kbuild_object_variable_accessor(pchName, cchName, kBuildSeverity_Fatal, pFileLoc,
                                                 &pchVarNm, &cchVarNm, NULL);
    if (pObj != KOBJ_NOT_KBUILD_ACCESSOR)
    {
        assert(pObj != NULL);
        if (!is_valid_kbuild_object_variable_name(pchVarNm, cchVarNm))
            fatal(pFileLoc, cchVarNm + cchName, _("Invalid kBuild object variable name: '%.*s' ('%.*s')"),
                  (int)cchVarNm, pchVarNm, (int)cchName, pchName);
        return define_kbuild_object_variable_cached(pObj, strcache2_add(&variable_strcache, pchVarNm, cchVarNm),
                                                    pszValue, cchValue, fDuplicateValue, enmOrigin, fRecursive,
                                                    0 /*fNoSpecialAccessors*/, pFileLoc);
    }

    return VAR_NOT_KBUILD_ACCESSOR;
}

/**
 * Define a kBuild object variable in the topmost kBuild object.
 *
 * This won't be an variable accessor.
 *
 * @returns Pointer to the defined variable on success.
 *
 * @param   pchName         The variable name, not cached.
 * @param   cchName         The variable name length.  This will not be ~0U.
 * @param   pszValue        The variable value.  If @a fDuplicateValue is clear,
 *                          this should be assigned as the actual variable
 *                          value, otherwise it will be duplicated.  In the
 *                          latter case it might not be properly null
 *                          terminated.
 * @param   cchValue        The value length.
 * @param   fDuplicateValue Whether @a pszValue need to be duplicated on the
 *                          heap or is already there.
 * @param   enmOrigin       The variable origin.
 * @param   fRecursive      Whether it's a recursive variable.
 * @param   pFileLoc        The location of the variable definition.
 */
struct variable *
define_kbuild_object_variable_in_top_obj(const char *pchName, size_t cchName,
                                         const char *pszValue, size_t cchValue, int fDuplicateValue,
                                         enum variable_origin enmOrigin, int fRecursive,
                                         floc const *pFileLoc)
{
    assert(g_pTopKbEvalData != NULL);

    if (!is_valid_kbuild_object_variable_name(pchName, cchName))
        fatal(pFileLoc, cchName, _("Invalid kBuild object variable name: '%.*s'"), (int)cchName, pchName);

    return define_kbuild_object_variable_cached(g_pTopKbEvalData->pObj, strcache2_add(&variable_strcache, pchName, cchName),
                                                pszValue, cchValue, fDuplicateValue, enmOrigin, fRecursive,
                                                0 /*fNoSpecialAccessors*/, pFileLoc);
}

/**
 * Implements appending and prepending to a kBuild object variable.
 *
 * The variable is either accessed thru an accessor or by the topmost kBuild
 * object.
 *
 * @returns Pointer to the defined variable on success.
 *
 * @param   pchName         The variable name, not cached.
 * @param   cchName         The variable name length.  This will not be ~0U.
 * @param   pszValue        The variable value. Must be duplicated.
 * @param   cchValue        The value length.
 * @param   fSimpleValue    Whether we've already figured that it's a simple
 *                          value.  This is for optimizing appending/prepending
 *                          to an existing simple value variable.
 * @param   enmOrigin       The variable origin.
 * @param   fAppend         Append if set, prepend if clear.
 * @param   pFileLoc        The location of the variable definition.
 */
struct variable *
kbuild_object_variable_pre_append(const char *pchName, size_t cchName,
                                  const char *pchValue, size_t cchValue, int fSimpleValue,
                                  enum variable_origin enmOrigin, int fAppend,
                                  const floc *pFileLoc)
{
    struct kbuild_object   *pObj;
    struct variable         VarKey;

    /*
     * Resolve the relevant kBuild object first.
     */
    if (cchName > 3 && pchName[0] == '[')
    {
        const char *pchVarNm;
        size_t      cchVarNm;
        pObj = parse_kbuild_object_variable_accessor(pchName, cchName, kBuildSeverity_Fatal, pFileLoc,
                                                     &pchVarNm, &cchVarNm, NULL);
        if (pObj != KOBJ_NOT_KBUILD_ACCESSOR)
        {
            pchName = pchVarNm;
            cchName = cchVarNm;
        }
        else
            pObj = g_pTopKbEvalData->pObj;
    }
    else
        pObj = g_pTopKbEvalData->pObj;

    /*
     * Make sure the variable name is valid.  Raise fatal error if not.
     */
    if (!is_valid_kbuild_object_variable_name(pchName, cchName))
        fatal(pFileLoc, cchName, _("Invalid kBuild object variable name: '%.*s'"), (int)cchName, pchName);

    /*
     * Get the cached name and look it up in the object's variables.
     */
    VarKey.name = strcache2_lookup(&variable_strcache, pchName, cchName);
    if (VarKey.name)
    {
        struct variable *pVar;

        VarKey.length = cchName;
        pVar = (struct variable *)hash_find_item_strcached(&pObj->pVariables->set->table, &VarKey);
        if (pVar)
        {
            /* Append/prepend to existing variable. */
            int fDuplicateValue = 1;
            if (pVar->recursive && !fSimpleValue)
                pchValue = kbuild_replace_special_accessors(pchValue, &cchValue, &fDuplicateValue, pFileLoc);

            pVar = do_variable_definition_append(pFileLoc, pVar, pchValue, cchValue, fSimpleValue, enmOrigin, fAppend);

            if (fDuplicateValue == 0)
                free((char *)pchValue);
            return pVar;
        }

        /*
         * Not found. Check ancestors if the 'override' directive isn't applied.
         */
        if (pObj->pszParent && enmOrigin != o_override)
        {
            struct kbuild_object *pParent = pObj;
            for (;;)
            {
                pParent = resolve_kbuild_object_parent(pParent, 0 /*fQuiet*/);
                if (!pParent)
                    break;

                pVar = (struct variable *)hash_find_item_strcached(&pParent->pVariables->set->table, &VarKey);
                if (pVar)
                {
                    if (pVar->value_length != ~0U)
                        assert(pVar->value_length == strlen(pVar->value));
                    else
                        pVar->value_length = strlen(pVar->value);

                    /*
                     * Combine the two values and define the variable in the
                     * specified child object.  We must disregard 'origin' a
                     * little here, so we must do the gritty stuff our selves.
                     */
                    if (   pVar->recursive
                        || fSimpleValue
                        || !cchValue
                        || memchr(pchValue, '$', cchValue) == NULL )
                    {
                        int     fDuplicateValue = 1;
                        size_t  cchNewValue;
                        char   *pszNewValue;
                        char   *pszTmp;

                        /* Just join up the two values. */
                        if (pVar->recursive && !fSimpleValue)
                            pchValue = kbuild_replace_special_accessors(pchValue, &cchValue, &fDuplicateValue, pFileLoc);
                        if (pVar->value_length == 0)
                        {
                            cchNewValue = cchValue;
                            pszNewValue = xstrndup(pchValue, cchValue);
                        }
                        else if (!cchValue)
                        {
                            cchNewValue = pVar->value_length;
                            pszNewValue = xmalloc(cchNewValue + 1);
                            memcpy(pszNewValue, pVar->value, cchNewValue + 1);
                        }
                        else
                        {
                            cchNewValue = pVar->value_length + 1 + cchValue;
                            pszNewValue = xmalloc(cchNewValue + 1);
                            if (fAppend)
                            {
                                memcpy(pszNewValue, pVar->value, pVar->value_length);
                                pszTmp = pszNewValue + pVar->value_length;
                                *pszTmp++ = ' ';
                                memcpy(pszTmp, pchValue, cchValue);
                                pszTmp[cchValue] = '\0';
                            }
                            else
                            {
                                memcpy(pszNewValue, pchValue, cchValue);
                                pszTmp = pszNewValue + cchValue;
                                *pszTmp++ = ' ';
                                memcpy(pszNewValue, pVar->value, pVar->value_length);
                                pszTmp[pVar->value_length] = '\0';
                            }
                        }

                        /* Define the new variable in the child. */
                        pVar = define_kbuild_object_variable_cached(pObj, VarKey.name,
                                                                    pszNewValue, cchNewValue, 0 /*fDuplicateValue*/,
                                                                    enmOrigin, pVar->recursive, 1 /*fNoSpecialAccessors*/,
                                                                    pFileLoc);
                        if (fDuplicateValue == 0)
                            free((char *)pchValue);
                    }
                    else
                    {
                        /* Lazy bird: Copy the variable from the ancestor and
                                      then do a normal append/prepend on it. */
                        pVar = define_kbuild_object_variable_cached(pObj, VarKey.name,
                                                                    pVar->value, pVar->value_length, 1 /*fDuplicateValue*/,
                                                                    enmOrigin, pVar->recursive, 1 /*fNoSpecialAccessors*/,
                                                                    pFileLoc);
                        append_expanded_string_to_variable(pVar, pchValue, cchValue, fAppend);
                    }
                    return pVar;
                }
            }
        }
    }
    else
        VarKey.name = strcache2_add(&variable_strcache, pchName, cchName);

    /* Variable not found. */
    return define_kbuild_object_variable_cached(pObj, VarKey.name,
                                                pchValue, cchValue, 1 /*fDuplicateValue*/, enmOrigin,
                                                1 /*fRecursive */, 0 /*fNoSpecialAccessors*/, pFileLoc);
}

/** @} */


static char *
allocate_expanded_next_token(const char **ppszCursor, const char *pszEos, size_t *pcchToken, int fStrip)
{
    unsigned int cchToken;
    char *pszToken = find_next_token_eos(ppszCursor, pszEos, &cchToken);
    if (pszToken)
    {
        pszToken = allocated_variable_expand_2(pszToken, cchToken, &cchToken);
        if (pszToken)
        {
            if (fStrip)
            {
                unsigned int off = 0;
                while (MY_IS_BLANK(pszToken[off]))
                    off++;
                if (off)
                {
                    cchToken -= off;
                    memmove(pszToken, &pszToken[off], cchToken + 1);
                }

                while (cchToken > 0 && MY_IS_BLANK(pszToken[cchToken - 1]))
                    pszToken[--cchToken] = '\0';
            }

            assert(cchToken == strlen(pszToken));
            if (pcchToken)
                *pcchToken = cchToken;
            return pszToken;
        }
    }

    if (pcchToken)
        *pcchToken = 0;
    return NULL;
}

static struct kbuild_object *
resolve_kbuild_object_parent(struct kbuild_object *pObj, int fQuiet)
{
    if (   !pObj->pParent
        && pObj->pszParent)
    {
        struct kbuild_object *pCur = g_pHeadKbObjs;
        while (pCur)
        {
            if (   pCur->enmType == pObj->enmType
                && !strcmp(pCur->pszName, pObj->pszParent))
            {
                if (    pCur->pszParent
                    &&  (   pCur->pParent == pObj
                         || !strcmp(pCur->pszParent, pObj->pszName)) )
                    OSS(fatal, &pObj->FileLoc, _("'%s' and '%s' are both trying to be each other children..."),
                        pObj->pszName, pCur->pszName);

                pObj->pParent = pCur;
                pObj->pVariables->next = pObj->pVariables;
                return pCur;
            }

            pCur = pCur->pGlobalNext;
        }

        /* Not found. */
        if (!fQuiet)
            OSS(error, &pObj->FileLoc, _("Could not locate parent '%s' of '%s'"), pObj->pszParent, pObj->pszName);
    }
    return pObj->pParent;
}

/**
 * Get the parent of the given object, it is expected to have one.
 *
 * @returns Pointer to the parent. NULL if we survive failure.
 * @param   pObj                The kBuild object.
 * @param   enmSeverity         The severity of a missing parent.
 */
static struct kbuild_object *
get_kbuild_object_parent(struct kbuild_object *pObj, enum kBuildSeverity enmSeverity)
{
    struct kbuild_object *pParent = pObj->pParent;
    if (pParent)
        return pParent;

    pParent = resolve_kbuild_object_parent(pObj, 1 /*fQuiet - complain below */);
    if (pParent)
        return pParent;

    if (pObj->pszParent)
        kbuild_report_problem(enmSeverity, &pObj->FileLoc,
                              _("Could not local parent '%s' for kBuild object '%s'"),
                              pObj->pszParent, pObj->pszName);
    else
        kbuild_report_problem(enmSeverity, &pObj->FileLoc,
                              _("kBuild object '%s' has no parent ([@super])"),
                              pObj->pszName);
    return NULL;
}

static int
eval_kbuild_define_xxxx(struct kbuild_eval_data **ppData, const floc *pFileLoc,
                        const char *pszLine, const char *pszEos, int fIgnoring, enum kBuildType enmType)
{
    unsigned int            cch;
    char                    ch;
    char                   *psz;
    const char             *pszPrefix;
    struct kbuild_object   *pObj;
    struct kbuild_eval_data *pData;

    if (fIgnoring)
        return 0;

    /*
     * Create a new kBuild object.
     */
    pObj = xmalloc(sizeof(*pObj));
    pObj->enmType           = enmType;
    pObj->pszName           = NULL;
    pObj->cchName           = 0;
    pObj->FileLoc           = *pFileLoc;

    pObj->pGlobalNext       = g_pHeadKbObjs;
    g_pHeadKbObjs           = pObj;

    pObj->pVariables        = create_new_variable_set();

    pObj->pszParent         = NULL;
    pObj->cchParent         = 0;
    pObj->pParent           = NULL;

    pObj->pszTemplate       = NULL;

    pObj->pszVarPrefix      = NULL;
    pObj->cchVarPrefix      = 0;

    /*
     * The first word is the name.
     */
    pObj->pszName = allocate_expanded_next_token(&pszLine, pszEos, &pObj->cchName, 1 /*strip*/);
    if (!pObj->pszName || !*pObj->pszName)
        O(fatal, pFileLoc, _("The kBuild define requires a name"));

    psz = pObj->pszName;
    while ((ch = *psz++) != '\0')
        if (!isgraph(ch))
        {
            OSS(error, pFileLoc, _("The 'kBuild-define-%s' name '%s' contains one or more invalid characters"),
                eval_kbuild_type_to_string(enmType), pObj->pszName);
            break;
        }

    /*
     * Calc the variable prefix.
     */
    switch (enmType)
    {
        case kBuildType_Target:      pszPrefix = ""; break;
        case kBuildType_Template:    pszPrefix = "TEMPLATE_"; break;
        case kBuildType_Tool:        pszPrefix = "TOOL_"; break;
        case kBuildType_Sdk:         pszPrefix = "SDK_"; break;
        case kBuildType_Unit:        pszPrefix = "UNIT_"; break;
        default:
            ON(fatal, pFileLoc, _("enmType=%d"), enmType);
            return -1;
    }
    cch = strlen(pszPrefix);
    pObj->cchVarPrefix = cch + pObj->cchName;
    pObj->pszVarPrefix = xmalloc(pObj->cchVarPrefix + 1);
    memcpy(pObj->pszVarPrefix, pszPrefix, cch);
    memcpy(&pObj->pszVarPrefix[cch], pObj->pszName, pObj->cchName);

    /*
     * Parse subsequent words.
     */
    psz = find_next_token_eos(&pszLine, pszEos, &cch);
    while (psz)
    {
        if (WORD_IS(psz, cch, "extending"))
        {
            /* Inheritance directive. */
            if (pObj->pszParent != NULL)
                O(fatal, pFileLoc, _("'extending' can only occure once"));
            pObj->pszParent = allocate_expanded_next_token(&pszLine, pszEos, &pObj->cchParent, 1 /*strip*/);
            if (!pObj->pszParent || !*pObj->pszParent)
                O(fatal, pFileLoc, _("'extending' requires a parent name"));
        }
        else if (WORD_IS(psz, cch, "using"))
        {
            char   *pszTemplate;
            size_t  cchTemplate;

            /* Template directive. */
            if (enmType != kBuildType_Target)
                O(fatal, pFileLoc, _("'using <template>' can only be used with 'kBuild-define-target'"));
            if (pObj->pszTemplate != NULL)
                O(fatal, pFileLoc, _("'using' can only occure once"));

            pszTemplate = allocate_expanded_next_token(&pszLine, pszEos, &cchTemplate, 1 /*fStrip*/);
            if (!pszTemplate || !*pszTemplate)
                O(fatal, pFileLoc, _("'using' requires a template name"));

            define_kbuild_object_variable_cached(pObj, g_pszVarNmTemplate, pszTemplate, cchTemplate,
                                                 0 /*fDuplicateValue*/, o_default, 0 /*fRecursive*/,
                                                 1 /*fNoSpecialAccessors*/, pFileLoc);

        }
        else
            fatal(pFileLoc, cch, _("Don't know what '%.*s' means"), (int)cch, psz);

        /* next token */
        psz = find_next_token_eos(&pszLine, pszEos, &cch);
    }

    /*
     * Try resolve the parent.
     */
    resolve_kbuild_object_parent(pObj, 1 /*fQuiet*/);

    /*
     * Create an eval stack entry and change the current variable set.
     */
    pData = xmalloc(sizeof(*pData));
    pData->pObj             = pObj;
    pData->pVariablesSaved  = current_variable_set_list;
    current_variable_set_list = pObj->pVariables;

    pData->pStackDown       = *ppData;
    *ppData                 = pData;
    g_pTopKbEvalData        = pData;

    return 0;
}

static int
eval_kbuild_endef_xxxx(struct kbuild_eval_data **ppData, const floc *pFileLoc,
                       const char *pszLine, const char *pszEos, int fIgnoring, enum kBuildType enmType)
{
    struct kbuild_eval_data *pData;
    struct kbuild_object    *pObj;
    size_t                   cchName;
    char                    *pszName;

    if (fIgnoring)
        return 0;

    /*
     * Is there something to pop?
     */
    pData = *ppData;
    if (!pData)
    {
        OSS(error, pFileLoc, _("kBuild-endef-%s is missing kBuild-define-%s"),
            eval_kbuild_type_to_string(enmType), eval_kbuild_type_to_string(enmType));
        return 0;
    }

    /*
     * ... and does it have a matching kind?
     */
    pObj = pData->pObj;
    if (pObj->enmType != enmType)
        OSSS(error, pFileLoc, _("'kBuild-endef-%s' does not match 'kBuild-define-%s %s'"),
             eval_kbuild_type_to_string(enmType), eval_kbuild_type_to_string(pObj->enmType), pObj->pszName);

    /*
     * The endef-kbuild may optionally be followed by the target name.
     * It should match the name given to the kBuild-define.
     */
    pszName = allocate_expanded_next_token(&pszLine, pszEos, &cchName, 1 /*fStrip*/);
    if (pszName)
    {
        if (   cchName != pObj->cchName
            || strcmp(pszName, pObj->pszName))
            OSSSS(error, pFileLoc, _("'kBuild-endef-%s %s' does not match 'kBuild-define-%s %s'"),
                  eval_kbuild_type_to_string(enmType), pszName,
                  eval_kbuild_type_to_string(pObj->enmType), pObj->pszName);
        free(pszName);
    }

    /*
     * Pop a define off the stack.
     */
    assert(pData == g_pTopKbEvalData);
    *ppData = g_pTopKbEvalData = pData->pStackDown;
    pData->pStackDown      = NULL;
    current_variable_set_list = pData->pVariablesSaved;
    pData->pVariablesSaved = NULL;
    free(pData);

    return 0;
}

int eval_kbuild_read_hook(struct kbuild_eval_data **kdata, const floc *flocp,
                          const char *pchWord, size_t cchWord, const char *line, const char *eos, int ignoring)
{
    enum kBuildType enmType;

    /*
     * Skip the 'kBuild-' prefix that the caller already matched.
     */
    assert(memcmp(pchWord, "kBuild-", sizeof("kBuild-") - 1) == 0);
    pchWord += sizeof("kBuild-") - 1;
    cchWord -= sizeof("kBuild-") - 1;

    /*
     * String switch.
     */
    if (   cchWord >= sizeof("define-") - 1
        && strneq(pchWord, "define-", sizeof("define-") - 1))
    {
        enmType = eval_kbuild_type_from_string(pchWord + sizeof("define-") - 1, cchWord - sizeof("define-") + 1);
        if (enmType != kBuildType_Invalid)
            return eval_kbuild_define_xxxx(kdata, flocp, line, eos, ignoring, enmType);
    }
    else if (   cchWord >= sizeof("endef-") - 1
             && strneq(pchWord, "endef-", sizeof("endef-") - 1))
    {
        enmType = eval_kbuild_type_from_string(pchWord + sizeof("endif-") - 1, cchWord - sizeof("endif-") + 1);
        if (enmType != kBuildType_Invalid)
            return eval_kbuild_endef_xxxx(kdata, flocp, line, eos, ignoring, enmType);
    }
    else if (WORD_IS(pchWord, cchWord, "endef"))
    {
        /* Terminate whatever definition is on top. */

    }

    /*
     * Everything that is prefixed with 'kBuild-' is reserved for language
     * extensions, at least until legacy assignments/whatever turns up.
     */
    error(flocp, cchWord, _("Unknown syntax 'kBuild-%.*s'"), (int)cchWord, pchWord);
    return 0;
}


/** @name kBuild object variable accessor related functions
 * @{
 */

/**
 * Checks if the given name is an object variable accessor.
 *
 * @returns 1 if it is, 0 if it isn't.
 * @param   pchName             The potential kBuild variable accessor
 *                              expression.
 * @param   cchName             Length of the expression.
 */
int is_kbuild_object_variable_accessor(const char *pchName, size_t cchName)
{
    char const *pchTmp;

    /* See lookup_kbuild_object_variable for the rules. */
    if (cchName >= 1+1+1+1 && *pchName == '[')
    {
        pchName++;
        cchName--;

        pchTmp = memchr(pchName, '@', cchName);
        if (pchTmp)
        {
            cchName -= pchTmp + 1 - pchName;
            pchName  = pchTmp + 1;
            pchTmp = memchr(pchName, ']', cchName);
            if (pchTmp)
            {
                cchName -= pchTmp + 1 - pchName;
                if (cchName > 0)
                    return 1;
            }
        }
    }
    return 0;
}

/**
 * Parses a kBuild object variable accessor, resolving the object.
 *
 * @returns Pointer to the variable if found.
 * @retval  NULL if the object (or type) couldn't be resolved.
 * @retval  KOBJ_NOT_KBUILD_ACCESSOR if no a kBuild variable accessor.
 *
 * @param   pchExpr             The kBuild variable accessor expression.
 * @param   cchExpr             Length of the expression.
 * @param   enmSeverity         The minimum severity level for errors.
 * @param   pFileLoc            The file location any errors should be reported
 *                              at. Optional.
 * @param   ppchVarNm           Where to return the pointer to the start of the
 *                              variable name within the string @a pchExpr
 *                              points to. Mandatory.
 * @param   pcchVarNm           Where to return the length of the variable name.
 *                              Mandatory.
 * @param   penmType            Where to return the object type. Optional.
 */
static struct kbuild_object *
parse_kbuild_object_variable_accessor(const char *pchExpr, size_t cchExpr,
                                      enum kBuildSeverity enmSeverity, const floc *pFileLoc,
                                      const char **ppchVarNm, size_t *pcchVarNm, enum kBuildType *penmType)
{
    const char * const pchOrgExpr = pchExpr;
    size_t       const cchOrgExpr = cchExpr;
    char const        *pchTmp;

    /*
     * To accept this as an kBuild accessor, we require:
     *   1. Open bracket.
     *   2. At sign separating the type from the name.
     *   3. Closing bracket.
     *   4. At least one character following it.
     */
    if (cchExpr >= 1+1+1+1 && *pchExpr == '[')
    {
        pchExpr++;
        cchExpr--;

        pchTmp = memchr(pchExpr, '@', cchExpr);
        if (pchTmp)
        {
            const char  * const pchType = pchExpr;
            size_t        const cchType = pchTmp - pchExpr;

            cchExpr -= cchType + 1;
            pchExpr  = pchTmp + 1;
            pchTmp = memchr(pchExpr, ']', cchExpr);
            if (pchTmp)
            {
                const char * const pchObjName = pchExpr;
                size_t       const cchObjName = pchTmp - pchExpr;

                cchExpr -= cchObjName + 1;
                pchExpr  = pchTmp + 1;
                if (cchExpr > 0)
                {

                    /*
                     * It's an kBuild define variable accessor, alright.
                     */
                    *pcchVarNm = cchExpr;
                    *ppchVarNm = pchExpr;

                    /* Deal with known special accessors: [@self]VAR, [@super]VAR. */
                    if (cchType == 0)
                    {
                        int fSuper;

                        if (WORD_IS(pchObjName, cchObjName, "self"))
                            fSuper = 0;
                        else if (WORD_IS(pchObjName, cchObjName, "super"))
                            fSuper = 1;
                        else
                        {
                            kbuild_report_problem(MAX(enmSeverity, kBuildSeverity_Error), pFileLoc,
                                                  _("Invalid special kBuild object accessor: '%.*s'"),
                                                  (int)cchOrgExpr, pchOrgExpr);
                            if (penmType)
                                *penmType = kBuildType_Invalid;
                            return NULL;
                        }
                        if (g_pTopKbEvalData)
                        {
                            struct kbuild_object *pObj = g_pTopKbEvalData->pObj;
                            struct kbuild_object *pParent;

                            if (penmType)
                                *penmType = pObj->enmType;

                            if (!fSuper)
                                return pObj;

                            pParent = get_kbuild_object_parent(pObj, MAX(enmSeverity, kBuildSeverity_Error));
                            if (pParent)
                                return pParent;
                        }
                        else
                            kbuild_report_problem(MAX(enmSeverity, kBuildSeverity_Error), pFileLoc,
                                                  _("The '%.*s' accessor can only be used in the context of a kBuild object"),
                                                  (int)cchOrgExpr, pchOrgExpr);
                        if (penmType)
                            *penmType = kBuildType_Invalid;
                    }
                    else
                    {
                        /* Genric accessor. Check the type and look up the object. */
                        enum kBuildType enmType = eval_kbuild_type_from_string(pchType, cchType);
                        if (penmType)
                            *penmType = enmType;
                        if (enmType != kBuildType_Invalid)
                        {
                            struct kbuild_object *pObj = lookup_kbuild_object(enmType, pchObjName, cchObjName);
                            if (pObj)
                                return pObj;

                            /* failed. */
                            kbuild_report_problem(enmSeverity, pFileLoc,
                                                  _("kBuild object '%s' not found in kBuild variable accessor '%.*s'"),
                                                  (int)cchObjName, pchObjName, (int)cchOrgExpr, pchOrgExpr);
                        }
                        else
                            kbuild_report_problem(MAX(enmSeverity, kBuildSeverity_Error), pFileLoc,
                                                  _("Invalid type '%.*s' specified in kBuild variable accessor '%.*s'"),
                                                  (int)cchType, pchType, (int)cchOrgExpr, pchOrgExpr);
                    }
                    return NULL;
                }
            }
        }
    }

    *ppchVarNm = NULL;
    *pcchVarNm = 0;
    if (penmType)
        *penmType = kBuildType_Invalid;
    return KOBJ_NOT_KBUILD_ACCESSOR;
}

/**
 * Looks up a variable in a kBuild object.
 *
 * The caller has done minimal matching, i.e. starting square brackets and
 * minimum length.  We do the rest here.
 *
 * @returns Pointer to the variable if found.
 * @retval  NULL if not found.
 * @retval  VAR_NOT_KBUILD_ACCESSOR if no a kBuild variable accessor.
 *
 * @param   pchName             The kBuild variable accessor expression.
 * @param   cchName             Length of the expression.
 */
struct variable *
lookup_kbuild_object_variable_accessor(const char *pchName, size_t cchName)
{
    /*const char * const     pchOrgName = pchName;*/
    /*size_t       const     cchOrgName = cchName;*/
    const char *           pchVarNm;
    size_t                 cchVarNm;
    struct kbuild_object  *pObj;

    pObj = parse_kbuild_object_variable_accessor(pchName, cchName, kBuildSeverity_Warning, NULL, &pchVarNm, &cchVarNm, NULL);
    if (pObj != KOBJ_NOT_KBUILD_ACCESSOR)
    {
        if (pObj)
        {
            /*
             * Do the variable lookup.
             */
            const char *pszCachedName = strcache2_lookup(&variable_strcache, pchVarNm, cchVarNm);
            if (pszCachedName)
            {
                struct variable  VarKey;
                struct variable *pVar;
                VarKey.name   = pszCachedName;
                VarKey.length = cchName;

                pVar = (struct variable *)hash_find_item_strcached(&pObj->pVariables->set->table, &VarKey);
                if (pVar)
                    return pVar;

                /*
                 * Not found, check ancestors if any.
                 */
                if (pObj->pszParent || pObj->pszTemplate)
                {
                    struct kbuild_object *pParent = pObj;
                    for (;;)
                    {
                        pParent = resolve_kbuild_object_parent(pParent, 0 /*fQuiet*/);
                        if (!pParent)
                            break;
                        pVar = (struct variable *)hash_find_item_strcached(&pParent->pVariables->set->table, &VarKey);
                        if (pVar)
                            return pVar;
                    }
                }
            }
        }

        /* Not found one way or the other. */
        return NULL;
    }

    /* Not a kBuild object variable accessor. */
    return VAR_NOT_KBUILD_ACCESSOR;
}

/** @} */

void print_kbuild_data_base(void)
{
    struct kbuild_object *pCur;

    puts(_("\n# kBuild defines"));

    for (pCur = g_pHeadKbObjs; pCur; pCur = pCur->pGlobalNext)
    {
        printf("\nkBuild-define-%s %s",
               eval_kbuild_type_to_string(pCur->enmType), pCur->pszName);
        if (pCur->pszParent)
            printf(" extending %s", pCur->pszParent);
        if (pCur->pszTemplate)
            printf(" using %s", pCur->pszTemplate);
        putchar('\n');

        print_variable_set(pCur->pVariables->set, "", 0);

        printf("kBuild-endef-%s  %s\n",
               eval_kbuild_type_to_string(pCur->enmType), pCur->pszName);
    }
    /** @todo hash stats. */
}

void print_kbuild_define_stats(void)
{
    /* later when hashing stuff */
}

