#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 * * 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 * */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include "makeint.h" #include "dep.h" #include "variable.h" #include "rule.h" #include "debug.h" #include "hash.h" #include #ifdef HAVE_STDINT_H # include #endif #include #include #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: \n", Compiler.iLine)); Compiler.iLine++; off = offNext += cchEolSeq; continue; } if (pszContent[offNext] == '#') { KMK_CC_EVAL_DPRINTF(("#%03u: \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 : ""; 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 */