diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
commit | 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch) | |
tree | 63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/kmk/kmk_cc_exec.c | |
parent | Initial commit. (diff) | |
download | kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip |
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/kmk/kmk_cc_exec.c')
-rw-r--r-- | src/kmk/kmk_cc_exec.c | 7613 |
1 files changed, 7613 insertions, 0 deletions
diff --git a/src/kmk/kmk_cc_exec.c b/src/kmk/kmk_cc_exec.c new file mode 100644 index 0000000..6f60477 --- /dev/null +++ b/src/kmk/kmk_cc_exec.c @@ -0,0 +1,7613 @@ +#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 */ + |