aboutsummaryrefslogtreecommitdiff
path: root/contrib/bmake/var.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/bmake/var.c')
-rw-r--r--contrib/bmake/var.c2469
1 files changed, 1239 insertions, 1230 deletions
diff --git a/contrib/bmake/var.c b/contrib/bmake/var.c
index 6e5148bba968..54d6ef57c497 100644
--- a/contrib/bmake/var.c
+++ b/contrib/bmake/var.c
@@ -1,4 +1,4 @@
-/* $NetBSD: var.c,v 1.934 2021/06/21 08:40:44 rillig Exp $ */
+/* $NetBSD: var.c,v 1.1101 2024/03/01 17:53:30 rillig Exp $ */
/*
* Copyright (c) 1988, 1989, 1990, 1993
@@ -98,12 +98,11 @@
* Var_Value Return the unexpanded value of a variable, or NULL if
* the variable is undefined.
*
- * Var_Subst Substitute all variable expressions in a string.
+ * Var_Subst Substitute all expressions in a string.
*
- * Var_Parse Parse a variable expression such as ${VAR:Mpattern}.
+ * Var_Parse Parse an expression such as ${VAR:Mpattern}.
*
* Var_Delete
- * Var_DeleteExpand
* Delete a variable.
*
* Var_ReexportVars
@@ -126,16 +125,17 @@
#include <sys/stat.h>
#include <sys/types.h>
-#ifndef NO_REGEX
-#include <regex.h>
-#endif
#include "make.h"
#include <errno.h>
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
-#elif defined(HAVE_STDINT_H)
+#endif
+#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_LIMITS_H
@@ -148,7 +148,7 @@
#include "metachar.h"
/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */
-MAKE_RCSID("$NetBSD: var.c,v 1.934 2021/06/21 08:40:44 rillig Exp $");
+MAKE_RCSID("$NetBSD: var.c,v 1.1101 2024/03/01 17:53:30 rillig Exp $");
/*
* Variables are defined using one of the VAR=value assignments. Their
@@ -161,12 +161,13 @@ MAKE_RCSID("$NetBSD: var.c,v 1.934 2021/06/21 08:40:44 rillig Exp $");
* Scope variables are stored in a GNode.scope. The only way to undefine
* a scope variable is using the .undef directive. In particular, it must
* not be possible to undefine a variable during the evaluation of an
- * expression, or Var.name might point nowhere.
+ * expression, or Var.name might point nowhere. (There is another,
+ * unintended way to undefine a scope variable, see varmod-loop-delete.mk.)
*
- * Environment variables are temporary. They are returned by VarFind, and
- * after using them, they must be freed using VarFreeEnv.
+ * Environment variables are short-lived. They are returned by VarFind, and
+ * after using them, they must be freed using VarFreeShortLived.
*
- * Undefined variables occur during evaluation of variable expressions such
+ * Undefined variables occur during evaluation of expressions such
* as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers.
*/
typedef struct Var {
@@ -181,51 +182,55 @@ typedef struct Var {
Buffer val;
/* The variable came from the command line. */
- bool fromCmd: 1;
+ bool fromCmd:1;
/*
- * The variable comes from the environment.
+ * The variable is short-lived.
* These variables are not registered in any GNode, therefore they
- * must be freed as soon as they are not used anymore.
+ * must be freed after use.
*/
- bool fromEnv: 1;
+ bool shortLived:1;
+
+ /*
+ * The variable comes from the environment.
+ * Appending to its value moves the variable to the global scope.
+ */
+ bool fromEnvironment:1;
/*
* The variable value cannot be changed anymore, and the variable
* cannot be deleted. Any attempts to do so are silently ignored,
* they are logged with -dv though.
+ * Use .[NO]READONLY: to adjust.
*
* See VAR_SET_READONLY.
*/
- bool readOnly: 1;
+ bool readOnly:1;
/*
- * The variable's value is currently being used by Var_Parse or
- * Var_Subst. This marker is used to avoid endless recursion.
- */
- bool inUse: 1;
+ * The variable is currently being accessed by Var_Parse or Var_Subst.
+ * This temporary marker is used to avoid endless recursion.
+ */
+ bool inUse:1;
/*
* The variable is exported to the environment, to be used by child
* processes.
*/
- bool exported: 1;
+ bool exported:1;
/*
* At the point where this variable was exported, it contained an
* unresolved reference to another variable. Before any child
- * process is started, it needs to be exported again, in the hope
- * that the referenced variable can then be resolved.
+ * process is started, it needs to be actually exported, resolving
+ * the referenced variable just in time.
*/
- bool reexport: 1;
+ bool reexport:1;
} Var;
/*
* Exporting variables is expensive and may leak memory, so skip it if we
* can.
- *
- * To avoid this, it might be worth encapsulating the environment variables
- * in a separate data structure called EnvVars.
*/
typedef enum VarExportedMode {
VAR_EXPORTED_NONE,
@@ -250,10 +255,10 @@ typedef enum UnexportWhat {
/* Flags for pattern matching in the :S and :C modifiers */
typedef struct PatternFlags {
- bool subGlobal: 1; /* 'g': replace as often as possible */
- bool subOnce: 1; /* '1': replace only once */
- bool anchorStart: 1; /* '^': match only at start of word */
- bool anchorEnd: 1; /* '$': match only at end of word */
+ bool subGlobal:1; /* 'g': replace as often as possible */
+ bool subOnce:1; /* '1': replace only once */
+ bool anchorStart:1; /* '^': match only at start of word */
+ bool anchorEnd:1; /* '$': match only at end of word */
} PatternFlags;
/* SepBuf builds a string from words interleaved with separators. */
@@ -265,10 +270,7 @@ typedef struct SepBuf {
} SepBuf;
-/*
- * This lets us tell if we have replaced the original environ
- * (which we cannot free).
- */
+/* Whether we have replaced the original environ (which we cannot free). */
char **savedEnv = NULL;
/*
@@ -312,12 +314,11 @@ static bool save_dollars = false;
* override variables from SCOPE_GLOBAL.
*
* There is no scope for environment variables, these are generated on-the-fly
- * whenever they are referenced. If there were such a scope, each change to
- * environment variables would have to be reflected in that scope, which may
- * be simpler or more complex than the current implementation.
+ * whenever they are referenced.
*
* Each target has its own scope, containing the 7 target-local variables
- * .TARGET, .ALLSRC, etc. No other variables are in these scopes.
+ * .TARGET, .ALLSRC, etc. Variables set on dependency lines also go in
+ * this scope.
*/
GNode *SCOPE_CMDLINE;
@@ -326,8 +327,9 @@ GNode *SCOPE_INTERNAL;
static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE;
-static const char *VarEvalMode_Name[] = {
+static const char VarEvalMode_Name[][32] = {
"parse-only",
+ "parse-balanced",
"eval",
"eval-defined",
"eval-keep-dollar",
@@ -337,7 +339,8 @@ static const char *VarEvalMode_Name[] = {
static Var *
-VarNew(FStr name, const char *value, bool fromEnv, bool readOnly)
+VarNew(FStr name, const char *value,
+ bool shortLived, bool fromEnvironment, bool readOnly)
{
size_t value_len = strlen(value);
Var *var = bmake_malloc(sizeof *var);
@@ -345,7 +348,8 @@ VarNew(FStr name, const char *value, bool fromEnv, bool readOnly)
Buf_InitSize(&var->val, value_len + 1);
Buf_AddBytes(&var->val, value, value_len);
var->fromCmd = false;
- var->fromEnv = fromEnv;
+ var->shortLived = shortLived;
+ var->fromEnvironment = fromEnvironment;
var->readOnly = readOnly;
var->inUse = false;
var->exported = false;
@@ -375,11 +379,11 @@ CanonicalVarname(Substring name)
if (Substring_Equals(name, ".TARGET"))
return Substring_InitStr(TARGET);
+ /* GNU make has an additional alias $^ == ${.ALLSRC}. */
+
if (Substring_Equals(name, ".SHELL") && shellPath == NULL)
Shell_Init();
- /* GNU make has an additional alias $^ == ${.ALLSRC}. */
-
return name;
}
@@ -399,8 +403,8 @@ GNode_FindVar(GNode *scope, Substring varname, unsigned int hash)
*
* Results:
* The found variable, or NULL if the variable does not exist.
- * If the variable is an environment variable, it must be freed using
- * VarFreeEnv after use.
+ * If the variable is short-lived (such as environment variables), it
+ * must be freed using VarFreeShortLived after use.
*/
static Var *
VarFindSubstring(Substring name, GNode *scope, bool elsewhere)
@@ -431,15 +435,10 @@ VarFindSubstring(Substring name, GNode *scope, bool elsewhere)
FStr envName;
const char *envValue;
- /*
- * TODO: try setting an environment variable with the empty
- * name, which should be technically possible, just to see
- * how make reacts. All .for loops should be broken then.
- */
envName = Substring_Str(name);
envValue = getenv(envName.str);
if (envValue != NULL)
- return VarNew(envName, envValue, true, false);
+ return VarNew(envName, envValue, true, true, false);
FStr_Done(&envName);
if (opts.checkEnvFirst && scope != SCOPE_GLOBAL) {
@@ -456,18 +455,17 @@ VarFindSubstring(Substring name, GNode *scope, bool elsewhere)
return var;
}
-/* TODO: Replace these calls with VarFindSubstring, as far as possible. */
static Var *
VarFind(const char *name, GNode *scope, bool elsewhere)
{
return VarFindSubstring(Substring_InitStr(name), scope, elsewhere);
}
-/* If the variable is an environment variable, free it, including its value. */
+/* If the variable is short-lived, free it, including its value. */
static void
-VarFreeEnv(Var *v)
+VarFreeShortLived(Var *v)
{
- if (!v->fromEnv)
+ if (!v->shortLived)
return;
FStr_Done(&v->name);
@@ -475,15 +473,26 @@ VarFreeEnv(Var *v)
free(v);
}
+static const char *
+ValueDescription(const char *value)
+{
+ if (value[0] == '\0')
+ return "# (empty)";
+ if (ch_isspace(value[strlen(value) - 1]))
+ return "# (ends with space)";
+ return "";
+}
+
/* Add a new variable of the given name and value to the given scope. */
static Var *
VarAdd(const char *name, const char *value, GNode *scope, VarSetFlags flags)
{
HashEntry *he = HashTable_CreateEntry(&scope->vars, name, NULL);
Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), value,
- false, (flags & VAR_SET_READONLY) != 0);
+ false, false, (flags & VAR_SET_READONLY) != 0);
HashEntry_Set(he, v);
- DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, value);
+ DEBUG4(VAR, "%s: %s = %s%s\n",
+ scope->name, name, value, ValueDescription(value));
return v;
}
@@ -498,16 +507,30 @@ Var_Delete(GNode *scope, const char *varname)
Var *v;
if (he == NULL) {
- DEBUG2(VAR, "%s:delete %s (not found)\n", scope->name, varname);
+ DEBUG2(VAR, "%s: ignoring delete '%s' as it is not found\n",
+ scope->name, varname);
return;
}
- DEBUG2(VAR, "%s:delete %s\n", scope->name, varname);
v = he->value;
+ if (v->readOnly) {
+ DEBUG2(VAR, "%s: ignoring delete '%s' as it is read-only\n",
+ scope->name, varname);
+ return;
+ }
+ if (v->inUse) {
+ Parse_Error(PARSE_FATAL,
+ "Cannot delete variable \"%s\" while it is used",
+ v->name.str);
+ return;
+ }
+
+ DEBUG2(VAR, "%s: delete %s\n", scope->name, varname);
if (v->exported)
unsetenv(v->name.str);
- if (strcmp(v->name.str, MAKE_EXPORTED) == 0)
+ if (strcmp(v->name.str, ".MAKE.EXPORTED") == 0)
var_exportedVars = VAR_EXPORTED_NONE;
+
assert(v->name.freeIt == NULL);
HashTable_DeleteEntry(&scope->vars, he);
Buf_Done(&v->val);
@@ -515,34 +538,12 @@ Var_Delete(GNode *scope, const char *varname)
}
/*
- * Remove a variable from a scope, freeing all related memory as well.
- * The variable name is expanded once.
- */
-void
-Var_DeleteExpand(GNode *scope, const char *name)
-{
- FStr varname = FStr_InitRefer(name);
-
- if (strchr(varname.str, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(varname.str, SCOPE_GLOBAL, VARE_WANTRES,
- &expanded);
- /* TODO: handle errors */
- varname = FStr_InitOwn(expanded);
- }
-
- Var_Delete(scope, varname.str);
- FStr_Done(&varname);
-}
-
-/*
* Undefine one or more variables from the global scope.
* The argument is expanded exactly once and then split into words.
*/
void
Var_Undef(const char *arg)
{
- VarParseResult vpr;
char *expanded;
Words varnames;
size_t i;
@@ -553,8 +554,9 @@ Var_Undef(const char *arg)
return;
}
- vpr = Var_Subst(arg, SCOPE_GLOBAL, VARE_WANTRES, &expanded);
- if (vpr != VPR_OK) {
+ expanded = Var_Subst(arg, SCOPE_GLOBAL, VARE_WANTRES);
+ if (expanded == var_Error) {
+ /* TODO: Make this part of the code reachable. */
Parse_Error(PARSE_FATAL,
"Error in variable names to be undefined");
return;
@@ -599,7 +601,7 @@ MayExport(const char *name)
}
static bool
-ExportVarEnv(Var *v)
+ExportVarEnv(Var *v, GNode *scope)
{
const char *name = v->name.str;
char *val = v->val.data;
@@ -614,17 +616,18 @@ ExportVarEnv(Var *v)
return true;
}
- if (v->inUse) {
- /*
- * We recursed while exporting in a child.
- * This isn't going to end well, just skip it.
- */
- return false;
- }
+ if (v->inUse)
+ return false; /* see EMPTY_SHELL in directive-export.mk */
/* XXX: name is injected without escaping it */
expr = str_concat3("${", name, "}");
- (void)Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES, &val);
+ val = Var_Subst(expr, scope, VARE_WANTRES);
+ if (scope != SCOPE_GLOBAL) {
+ /* we will need to re-export the global version */
+ v = VarFind(name, SCOPE_GLOBAL, false);
+ if (v != NULL)
+ v->exported = false;
+ }
/* TODO: handle errors */
setenv(name, val, 1);
free(val);
@@ -668,22 +671,24 @@ ExportVarLiteral(Var *v)
/*
* Mark a single variable to be exported later for subprocesses.
*
- * Internal variables (those starting with '.') are not exported.
+ * Internal variables are not exported.
*/
static bool
-ExportVar(const char *name, VarExportMode mode)
+ExportVar(const char *name, GNode *scope, VarExportMode mode)
{
Var *v;
if (!MayExport(name))
return false;
- v = VarFind(name, SCOPE_GLOBAL, false);
+ v = VarFind(name, scope, false);
+ if (v == NULL && scope != SCOPE_GLOBAL)
+ v = VarFind(name, SCOPE_GLOBAL, false);
if (v == NULL)
return false;
if (mode == VEM_ENV)
- return ExportVarEnv(v);
+ return ExportVarEnv(v, scope);
else if (mode == VEM_PLAIN)
return ExportVarPlain(v);
else
@@ -695,7 +700,7 @@ ExportVar(const char *name, VarExportMode mode)
* re-exported.
*/
void
-Var_ReexportVars(void)
+Var_ReexportVars(GNode *scope)
{
char *xvarnames;
@@ -705,9 +710,9 @@ Var_ReexportVars(void)
* We allow the makefiles to update MAKELEVEL and ensure
* children see a correctly incremented value.
*/
- char tmp[21];
- snprintf(tmp, sizeof tmp, "%d", makelevel + 1);
- setenv(MAKE_LEVEL_ENV, tmp, 1);
+ char level_buf[21];
+ snprintf(level_buf, sizeof level_buf, "%d", makelevel + 1);
+ setenv(MAKE_LEVEL_ENV, level_buf, 1);
if (var_exportedVars == VAR_EXPORTED_NONE)
return;
@@ -719,20 +724,20 @@ Var_ReexportVars(void)
HashIter_Init(&hi, &SCOPE_GLOBAL->vars);
while (HashIter_Next(&hi) != NULL) {
Var *var = hi.entry->value;
- ExportVar(var->name.str, VEM_ENV);
+ ExportVar(var->name.str, scope, VEM_ENV);
}
return;
}
- (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", SCOPE_GLOBAL, VARE_WANTRES,
- &xvarnames);
+ xvarnames = Var_Subst("${.MAKE.EXPORTED:O:u}", SCOPE_GLOBAL,
+ VARE_WANTRES);
/* TODO: handle errors */
if (xvarnames[0] != '\0') {
Words varnames = Str_Words(xvarnames, false);
size_t i;
for (i = 0; i < varnames.len; i++)
- ExportVar(varnames.words[i], VEM_ENV);
+ ExportVar(varnames.words[i], scope, VEM_ENV);
Words_Free(varnames);
}
free(xvarnames);
@@ -750,14 +755,14 @@ ExportVars(const char *varnames, bool isExport, VarExportMode mode)
for (i = 0; i < words.len; i++) {
const char *varname = words.words[i];
- if (!ExportVar(varname, mode))
+ if (!ExportVar(varname, SCOPE_GLOBAL, mode))
continue;
if (var_exportedVars == VAR_EXPORTED_NONE)
var_exportedVars = VAR_EXPORTED_SOME;
if (isExport && mode == VEM_PLAIN)
- Global_Append(MAKE_EXPORTED, varname);
+ Global_Append(".MAKE.EXPORTED", varname);
}
Words_Free(words);
}
@@ -765,9 +770,7 @@ ExportVars(const char *varnames, bool isExport, VarExportMode mode)
static void
ExportVarsExpand(const char *uvarnames, bool isExport, VarExportMode mode)
{
- char *xvarnames;
-
- (void)Var_Subst(uvarnames, SCOPE_GLOBAL, VARE_WANTRES, &xvarnames);
+ char *xvarnames = Var_Subst(uvarnames, SCOPE_GLOBAL, VARE_WANTRES);
/* TODO: handle errors */
ExportVars(xvarnames, isExport, mode);
free(xvarnames);
@@ -792,15 +795,13 @@ Var_ExportVars(const char *varnames)
}
-extern char **environ;
-
static void
ClearEnv(void)
{
- const char *cp;
+ const char *level;
char **newenv;
- cp = getenv(MAKE_LEVEL_ENV); /* we should preserve this */
+ level = getenv(MAKE_LEVEL_ENV); /* we should preserve this */
if (environ == savedEnv) {
/* we have been here before! */
newenv = bmake_realloc(environ, 2 * sizeof(char *));
@@ -816,8 +817,8 @@ ClearEnv(void)
environ = savedEnv = newenv;
newenv[0] = NULL;
newenv[1] = NULL;
- if (cp != NULL && *cp != '\0')
- setenv(MAKE_LEVEL_ENV, cp, 1);
+ if (level != NULL && *level != '\0')
+ setenv(MAKE_LEVEL_ENV, level, 1);
}
static void
@@ -843,10 +844,8 @@ GetVarnamesToUnexport(bool isEnv, const char *arg,
}
if (what != UNEXPORT_NAMED) {
- char *expanded;
- /* Using .MAKE.EXPORTED */
- (void)Var_Subst("${" MAKE_EXPORTED ":O:u}", SCOPE_GLOBAL,
- VARE_WANTRES, &expanded);
+ char *expanded = Var_Subst("${.MAKE.EXPORTED:O:u}",
+ SCOPE_GLOBAL, VARE_WANTRES);
/* TODO: handle errors */
varnames = FStr_InitOwn(expanded);
}
@@ -856,15 +855,17 @@ GetVarnamesToUnexport(bool isEnv, const char *arg,
}
static void
-UnexportVar(const char *varname, UnexportWhat what)
+UnexportVar(Substring varname, UnexportWhat what)
{
- Var *v = VarFind(varname, SCOPE_GLOBAL, false);
+ Var *v = VarFindSubstring(varname, SCOPE_GLOBAL, false);
if (v == NULL) {
- DEBUG1(VAR, "Not unexporting \"%s\" (not found)\n", varname);
+ DEBUG2(VAR, "Not unexporting \"%.*s\" (not found)\n",
+ (int)Substring_Length(varname), varname.start);
return;
}
- DEBUG1(VAR, "Unexporting \"%s\"\n", varname);
+ DEBUG2(VAR, "Unexporting \"%.*s\"\n",
+ (int)Substring_Length(varname), varname.start);
if (what != UNEXPORT_ENV && v->exported && !v->reexport)
unsetenv(v->name.str);
v->exported = false;
@@ -873,13 +874,12 @@ UnexportVar(const char *varname, UnexportWhat what)
if (what == UNEXPORT_NAMED) {
/* Remove the variable names from .MAKE.EXPORTED. */
/* XXX: v->name is injected without escaping it */
- char *expr = str_concat3("${" MAKE_EXPORTED ":N",
- v->name.str, "}");
- char *cp;
- (void)Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES, &cp);
+ char *expr = str_concat3(
+ "${.MAKE.EXPORTED:N", v->name.str, "}");
+ char *filtered = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES);
/* TODO: handle errors */
- Global_Set(MAKE_EXPORTED, cp);
- free(cp);
+ Global_Set(".MAKE.EXPORTED", filtered);
+ free(filtered);
free(expr);
}
}
@@ -888,27 +888,21 @@ static void
UnexportVars(FStr *varnames, UnexportWhat what)
{
size_t i;
- Words words;
+ SubstringWords words;
if (what == UNEXPORT_ENV)
ClearEnv();
- words = Str_Words(varnames->str, false);
- for (i = 0; i < words.len; i++) {
- const char *varname = words.words[i];
- UnexportVar(varname, what);
- }
- Words_Free(words);
+ words = Substring_Words(varnames->str, false);
+ for (i = 0; i < words.len; i++)
+ UnexportVar(words.words[i], what);
+ SubstringWords_Free(words);
if (what != UNEXPORT_NAMED)
- Global_Delete(MAKE_EXPORTED);
+ Global_Delete(".MAKE.EXPORTED");
}
-/*
- * This is called when .unexport[-env] is seen.
- *
- * str must have the form "unexport[-env] varname...".
- */
+/* Handle the .unexport and .unexport-env directives. */
void
Var_UnExport(bool isEnv, const char *arg)
{
@@ -920,32 +914,6 @@ Var_UnExport(bool isEnv, const char *arg)
FStr_Done(&varnames);
}
-/*
- * When there is a variable of the same name in the command line scope, the
- * global variable would not be visible anywhere. Therefore there is no
- * point in setting it at all.
- *
- * See 'scope == SCOPE_CMDLINE' in Var_SetWithFlags.
- */
-static bool
-ExistsInCmdline(const char *name, const char *val)
-{
- Var *v;
-
- v = VarFind(name, SCOPE_CMDLINE, false);
- if (v == NULL)
- return false;
-
- if (v->fromCmd) {
- DEBUG3(VAR, "%s: %s = %s ignored!\n",
- SCOPE_GLOBAL->name, name, val);
- return true;
- }
-
- VarFreeEnv(v);
- return false;
-}
-
/* Set the variable to the value; the name is not expanded. */
void
Var_SetWithFlags(GNode *scope, const char *name, const char *val,
@@ -955,12 +923,24 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val,
assert(val != NULL);
if (name[0] == '\0') {
- DEBUG0(VAR, "SetVar: variable name is empty - ignored\n");
+ DEBUG3(VAR,
+ "%s: ignoring '%s = %s' as the variable name is empty\n",
+ scope->name, name, val);
return;
}
- if (scope == SCOPE_GLOBAL && ExistsInCmdline(name, val))
+ if (scope == SCOPE_GLOBAL
+ && VarFind(name, SCOPE_CMDLINE, false) != NULL) {
+ /*
+ * The global variable would not be visible anywhere.
+ * Therefore, there is no point in setting it at all.
+ */
+ DEBUG3(VAR,
+ "%s: ignoring '%s = %s' "
+ "due to a command line variable of the same name\n",
+ scope->name, name, val);
return;
+ }
/*
* Only look for a variable in the given scope since anything set
@@ -979,77 +959,63 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val,
*/
Var_Delete(SCOPE_GLOBAL, name);
}
+ if (strcmp(name, ".SUFFIXES") == 0) {
+ /* special: treat as read-only */
+ DEBUG3(VAR,
+ "%s: ignoring '%s = %s' as it is read-only\n",
+ scope->name, name, val);
+ return;
+ }
v = VarAdd(name, val, scope, flags);
} else {
if (v->readOnly && !(flags & VAR_SET_READONLY)) {
- DEBUG3(VAR, "%s: %s = %s ignored (read-only)\n",
+ DEBUG3(VAR,
+ "%s: ignoring '%s = %s' as it is read-only\n",
scope->name, name, val);
return;
}
- Buf_Empty(&v->val);
+ Buf_Clear(&v->val);
Buf_AddStr(&v->val, val);
- DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, val);
+ DEBUG4(VAR, "%s: %s = %s%s\n",
+ scope->name, name, val, ValueDescription(val));
if (v->exported)
- ExportVar(name, VEM_PLAIN);
+ ExportVar(name, scope, VEM_PLAIN);
}
- /*
- * Any variables given on the command line are automatically exported
- * to the environment (as per POSIX standard), except for internals.
- */
- if (scope == SCOPE_CMDLINE && !(flags & VAR_SET_NO_EXPORT) &&
- name[0] != '.') {
+ if (scope == SCOPE_CMDLINE) {
v->fromCmd = true;
/*
- * If requested, don't export these in the environment
- * individually. We still put them in MAKEOVERRIDES so
- * that the command-line settings continue to override
- * Makefile settings.
+ * Any variables given on the command line are automatically
+ * exported to the environment (as per POSIX standard), except
+ * for internals.
*/
- if (!opts.varNoExportEnv)
- setenv(name, val, 1);
- /* XXX: What about .MAKE.EXPORTED? */
- /* XXX: Why not just mark the variable for needing export,
- * as in ExportVarPlain? */
+ if (!(flags & VAR_SET_NO_EXPORT) && name[0] != '.') {
- Global_Append(MAKEOVERRIDES, name);
+ /*
+ * If requested, don't export these in the
+ * environment individually. We still put
+ * them in .MAKEOVERRIDES so that the
+ * command-line settings continue to override
+ * Makefile settings.
+ */
+ if (!opts.varNoExportEnv)
+ setenv(name, val, 1);
+ /* XXX: What about .MAKE.EXPORTED? */
+ /*
+ * XXX: Why not just mark the variable for
+ * needing export, as in ExportVarPlain?
+ */
+ Global_Append(".MAKEOVERRIDES", name);
+ }
}
if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0)
save_dollars = ParseBoolean(val, save_dollars);
if (v != NULL)
- VarFreeEnv(v);
-}
-
-/* See Var_Set for documentation. */
-void
-Var_SetExpandWithFlags(GNode *scope, const char *name, const char *val,
- VarSetFlags flags)
-{
- const char *unexpanded_name = name;
- FStr varname = FStr_InitRefer(name);
-
- assert(val != NULL);
-
- if (strchr(varname.str, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(varname.str, scope, VARE_WANTRES, &expanded);
- /* TODO: handle errors */
- varname = FStr_InitOwn(expanded);
- }
-
- if (varname.str[0] == '\0') {
- DEBUG2(VAR,
- "Var_SetExpand: variable name \"%s\" expands "
- "to empty string, with value \"%s\" - ignored\n",
- unexpanded_name, val);
- } else
- Var_SetWithFlags(scope, varname.str, val, flags);
-
- FStr_Done(&varname);
+ VarFreeShortLived(v);
}
void
@@ -1059,20 +1025,27 @@ Var_Set(GNode *scope, const char *name, const char *val)
}
/*
- * Set the variable name to the value val in the given scope.
- *
- * If the variable doesn't yet exist, it is created.
- * Otherwise the new value overwrites and replaces the old value.
- *
- * Input:
- * name name of the variable to set, is expanded once
- * val value to give to the variable
- * scope scope in which to set it
+ * In the scope, expand the variable name once, then create the variable or
+ * replace its value.
*/
void
Var_SetExpand(GNode *scope, const char *name, const char *val)
{
- Var_SetExpandWithFlags(scope, name, val, VAR_SET_NONE);
+ FStr varname = FStr_InitRefer(name);
+
+ assert(val != NULL);
+
+ Var_Expand(&varname, scope, VARE_WANTRES);
+
+ if (varname.str[0] == '\0') {
+ DEBUG4(VAR,
+ "%s: ignoring '%s = %s' "
+ "as the variable name '%s' expands to empty\n",
+ scope->name, varname.str, val, name);
+ } else
+ Var_SetWithFlags(scope, varname.str, val, VAR_SET_NONE);
+
+ FStr_Done(&varname);
}
void
@@ -1082,15 +1055,15 @@ Global_Set(const char *name, const char *value)
}
void
-Global_SetExpand(const char *name, const char *value)
+Global_Delete(const char *name)
{
- Var_SetExpand(SCOPE_GLOBAL, name, value);
+ Var_Delete(SCOPE_GLOBAL, name);
}
void
-Global_Delete(const char *name)
+Global_Set_ReadOnly(const char *name, const char *value)
{
- Var_Delete(SCOPE_GLOBAL, name);
+ Var_SetWithFlags(SCOPE_GLOBAL, name, value, VAR_SET_READONLY);
}
/*
@@ -1109,51 +1082,34 @@ Var_Append(GNode *scope, const char *name, const char *val)
if (v == NULL) {
Var_SetWithFlags(scope, name, val, VAR_SET_NONE);
} else if (v->readOnly) {
- DEBUG1(VAR, "Ignoring append to %s since it is read-only\n",
- name);
+ DEBUG3(VAR, "%s: ignoring '%s += %s' as it is read-only\n",
+ scope->name, name, val);
} else if (scope == SCOPE_CMDLINE || !v->fromCmd) {
Buf_AddByte(&v->val, ' ');
Buf_AddStr(&v->val, val);
DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, v->val.data);
- if (v->fromEnv) {
- /*
- * If the original variable came from the environment,
- * we have to install it in the global scope (we
- * could place it in the environment, but then we
- * should provide a way to export other variables...)
- */
- v->fromEnv = false;
- /*
- * This is the only place where a variable is
- * created whose v->name is not the same as
- * scope->vars->key.
- */
- HashTable_Set(&scope->vars, name, v);
+ if (v->fromEnvironment) {
+ /* See VarAdd. */
+ HashEntry *he =
+ HashTable_CreateEntry(&scope->vars, name, NULL);
+ HashEntry_Set(he, v);
+ FStr_Done(&v->name);
+ v->name = FStr_InitRefer(/* aliased to */ he->key);
+ v->shortLived = false;
+ v->fromEnvironment = false;
}
}
}
/*
- * The variable of the given name has the given value appended to it in the
- * given scope.
- *
- * If the variable doesn't exist, it is created. Otherwise the strings are
- * concatenated, with a space in between.
+ * In the scope, expand the variable name once. If the variable exists in the
+ * scope, add a space and the value, otherwise set the variable to the value.
*
- * Input:
- * name name of the variable to modify, is expanded once
- * val string to append to it
- * scope scope in which this should occur
- *
- * Notes:
- * Only if the variable is being sought in the global scope is the
- * environment searched.
- * XXX: Knows its calling circumstances in that if called with scope
- * an actual target, it will only search that scope since only
- * a local variable could be being appended to. This is actually
- * a big win and must be tolerated.
+ * Appending to an environment variable only works in the global scope, that
+ * is, for variable assignments in makefiles, but not inside conditions or the
+ * commands of a target.
*/
void
Var_AppendExpand(GNode *scope, const char *name, const char *val)
@@ -1162,22 +1118,14 @@ Var_AppendExpand(GNode *scope, const char *name, const char *val)
assert(val != NULL);
- if (strchr(name, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(name, scope, VARE_WANTRES, &expanded);
- /* TODO: handle errors */
- xname = FStr_InitOwn(expanded);
- if (expanded[0] == '\0') {
- DEBUG2(VAR,
- "Var_AppendExpand: variable name \"%s\" expands "
- "to empty string, with value \"%s\" - ignored\n",
- name, val);
- FStr_Done(&xname);
- return;
- }
- }
-
- Var_Append(scope, xname.str, val);
+ Var_Expand(&xname, scope, VARE_WANTRES);
+ if (xname.str != name && xname.str[0] == '\0')
+ DEBUG4(VAR,
+ "%s: ignoring '%s += %s' "
+ "as the variable name '%s' expands to empty\n",
+ scope->name, xname.str, val, name);
+ else
+ Var_Append(scope, xname.str, val);
FStr_Done(&xname);
}
@@ -1195,7 +1143,7 @@ Var_Exists(GNode *scope, const char *name)
if (v == NULL)
return false;
- VarFreeEnv(v);
+ VarFreeShortLived(v);
return true;
}
@@ -1204,8 +1152,8 @@ Var_Exists(GNode *scope, const char *name)
* fallback scopes.
*
* Input:
- * name Variable to find, is expanded once
- * scope Scope in which to start search
+ * scope scope in which to start search
+ * name name of the variable to find, is expanded once
*/
bool
Var_ExistsExpand(GNode *scope, const char *name)
@@ -1213,13 +1161,7 @@ Var_ExistsExpand(GNode *scope, const char *name)
FStr varname = FStr_InitRefer(name);
bool exists;
- if (strchr(varname.str, '$') != NULL) {
- char *expanded;
- (void)Var_Subst(varname.str, scope, VARE_WANTRES, &expanded);
- /* TODO: handle errors */
- varname = FStr_InitOwn(expanded);
- }
-
+ Var_Expand(&varname, scope, VARE_WANTRES);
exists = Var_Exists(scope, varname.str);
FStr_Done(&varname);
return exists;
@@ -1227,11 +1169,11 @@ Var_ExistsExpand(GNode *scope, const char *name)
/*
* Return the unexpanded value of the given variable in the given scope,
- * or the usual scopes.
+ * falling back to the command, global and environment scopes, in this order,
+ * but see the -e option.
*
* Input:
- * name name to find, is not expanded any further
- * scope scope in which to search for it
+ * name the name to find, is not expanded any further
*
* Results:
* The value if the variable exists, NULL if it doesn't.
@@ -1246,16 +1188,31 @@ Var_Value(GNode *scope, const char *name)
if (v == NULL)
return FStr_InitRefer(NULL);
- if (!v->fromEnv)
+ if (!v->shortLived)
return FStr_InitRefer(v->val.data);
- /* Since environment variables are short-lived, free it now. */
- FStr_Done(&v->name);
- value = Buf_DoneData(&v->val);
- free(v);
+ value = v->val.data;
+ v->val.data = NULL;
+ VarFreeShortLived(v);
+
return FStr_InitOwn(value);
}
+/* Set or clear the read-only attribute of the variable if it exists. */
+void
+Var_ReadOnly(const char *name, bool bf)
+{
+ Var *v;
+
+ v = VarFind(name, SCOPE_GLOBAL, false);
+ if (v == NULL) {
+ DEBUG1(VAR, "Var_ReadOnly: %s not found\n", name);
+ return;
+ }
+ v->readOnly = bf;
+ DEBUG2(VAR, "Var_ReadOnly: %s %s\n", name, bf ? "true" : "false");
+}
+
/*
* Return the unexpanded variable value from this node, without trying to look
* up the variable in any other scope.
@@ -1331,7 +1288,7 @@ SepBuf_AddBytes(SepBuf *buf, const char *mem, size_t mem_size)
}
static void
-SepBuf_AddBytesBetween(SepBuf *buf, const char *start, const char *end)
+SepBuf_AddRange(SepBuf *buf, const char *start, const char *end)
{
SepBuf_AddBytes(buf, start, (size_t)(end - start));
}
@@ -1345,7 +1302,7 @@ SepBuf_AddStr(SepBuf *buf, const char *str)
static void
SepBuf_AddSubstring(SepBuf *buf, Substring sub)
{
- SepBuf_AddBytesBetween(buf, sub.start, sub.end);
+ SepBuf_AddRange(buf, sub.start, sub.end);
}
static char *
@@ -1356,7 +1313,7 @@ SepBuf_DoneData(SepBuf *buf)
/*
- * This callback for ModifyWords gets a single word from a variable expression
+ * This callback for ModifyWords gets a single word from an expression
* and typically adds a modification of this word to the buffer. It may also
* do nothing or add several words.
*
@@ -1370,10 +1327,6 @@ SepBuf_DoneData(SepBuf *buf)
typedef void (*ModifyWordProc)(Substring word, SepBuf *buf, void *data);
-/*
- * Callback for ModifyWords to implement the :H modifier.
- * Add the dirname of the given word to the buffer.
- */
/*ARGSUSED*/
static void
ModifyWord_Head(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
@@ -1381,10 +1334,6 @@ ModifyWord_Head(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
SepBuf_AddSubstring(buf, Substring_Dirname(word));
}
-/*
- * Callback for ModifyWords to implement the :T modifier.
- * Add the basename of the given word to the buffer.
- */
/*ARGSUSED*/
static void
ModifyWord_Tail(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
@@ -1392,63 +1341,26 @@ ModifyWord_Tail(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
SepBuf_AddSubstring(buf, Substring_Basename(word));
}
-/*
- * Callback for ModifyWords to implement the :E modifier.
- * Add the filename suffix of the given word to the buffer, if it exists.
- */
/*ARGSUSED*/
static void
ModifyWord_Suffix(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
{
- const char *lastDot = Substring_LastIndex(word, '.');
+ const char *lastDot = Substring_FindLast(word, '.');
if (lastDot != NULL)
- SepBuf_AddBytesBetween(buf, lastDot + 1, word.end);
+ SepBuf_AddRange(buf, lastDot + 1, word.end);
}
-/*
- * Callback for ModifyWords to implement the :R modifier.
- * Add the filename without extension of the given word to the buffer.
- */
/*ARGSUSED*/
static void
ModifyWord_Root(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED)
{
const char *lastDot, *end;
- lastDot = Substring_LastIndex(word, '.');
+ lastDot = Substring_FindLast(word, '.');
end = lastDot != NULL ? lastDot : word.end;
- SepBuf_AddBytesBetween(buf, word.start, end);
-}
-
-/*
- * Callback for ModifyWords to implement the :M modifier.
- * Place the word in the buffer if it matches the given pattern.
- */
-static void
-ModifyWord_Match(Substring word, SepBuf *buf, void *data)
-{
- const char *pattern = data;
-
- assert(word.end[0] == '\0'); /* assume null-terminated word */
- if (Str_Match(word.start, pattern))
- SepBuf_AddSubstring(buf, word);
-}
-
-/*
- * Callback for ModifyWords to implement the :N modifier.
- * Place the word in the buffer if it doesn't match the given pattern.
- */
-static void
-ModifyWord_NoMatch(Substring word, SepBuf *buf, void *data)
-{
- const char *pattern = data;
-
- assert(word.end[0] == '\0'); /* assume null-terminated word */
- if (!Str_Match(word.start, pattern))
- SepBuf_AddSubstring(buf, word);
+ SepBuf_AddRange(buf, word.start, end);
}
-#ifdef SYSVVARSUB
struct ModifyWord_SysVSubstArgs {
GNode *scope;
Substring lhsPrefix;
@@ -1457,55 +1369,37 @@ struct ModifyWord_SysVSubstArgs {
const char *rhs;
};
-/* Callback for ModifyWords to implement the :%.from=%.to modifier. */
static void
ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data)
{
const struct ModifyWord_SysVSubstArgs *args = data;
FStr rhs;
- char *rhsExp;
const char *percent;
if (Substring_IsEmpty(word))
return;
- if (!Substring_HasPrefix(word, args->lhsPrefix))
- goto no_match;
- if (!Substring_HasSuffix(word, args->lhsSuffix))
- goto no_match;
+ if (!Substring_HasPrefix(word, args->lhsPrefix) ||
+ !Substring_HasSuffix(word, args->lhsSuffix)) {
+ SepBuf_AddSubstring(buf, word);
+ return;
+ }
rhs = FStr_InitRefer(args->rhs);
- if (strchr(rhs.str, '$') != NULL) {
- (void)Var_Subst(args->rhs, args->scope, VARE_WANTRES, &rhsExp);
- /* TODO: handle errors */
- rhs = FStr_InitOwn(rhsExp);
- }
+ Var_Expand(&rhs, args->scope, VARE_WANTRES);
percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL;
if (percent != NULL)
- SepBuf_AddBytesBetween(buf, rhs.str, percent);
+ SepBuf_AddRange(buf, rhs.str, percent);
if (percent != NULL || !args->lhsPercent)
- SepBuf_AddBytesBetween(buf,
+ SepBuf_AddRange(buf,
word.start + Substring_Length(args->lhsPrefix),
word.end - Substring_Length(args->lhsSuffix));
SepBuf_AddStr(buf, percent != NULL ? percent + 1 : rhs.str);
FStr_Done(&rhs);
- return;
-
-no_match:
- SepBuf_AddSubstring(buf, word);
}
-#endif
-
-
-struct ModifyWord_SubstArgs {
- Substring lhs;
- Substring rhs;
- PatternFlags pflags;
- bool matched;
-};
static const char *
Substring_Find(Substring haystack, Substring needle)
@@ -1520,19 +1414,21 @@ Substring_Find(Substring haystack, Substring needle)
return NULL;
}
-/*
- * Callback for ModifyWords to implement the :S,from,to, modifier.
- * Perform a string substitution on the given word.
- */
+struct ModifyWord_SubstArgs {
+ Substring lhs;
+ Substring rhs;
+ PatternFlags pflags;
+ bool matched;
+};
+
static void
ModifyWord_Subst(Substring word, SepBuf *buf, void *data)
{
struct ModifyWord_SubstArgs *args = data;
size_t wordLen, lhsLen;
- const char *wordEnd, *match;
+ const char *match;
wordLen = Substring_Length(word);
- wordEnd = word.end;
if (args->pflags.subOnce && args->matched)
goto nosub;
@@ -1547,7 +1443,7 @@ ModifyWord_Subst(Substring word, SepBuf *buf, void *data)
/* :S,^prefix,replacement, or :S,^whole$,replacement, */
SepBuf_AddSubstring(buf, args->rhs);
- SepBuf_AddBytesBetween(buf, word.start + lhsLen, wordEnd);
+ SepBuf_AddRange(buf, word.start + lhsLen, word.end);
args->matched = true;
return;
}
@@ -1555,11 +1451,11 @@ ModifyWord_Subst(Substring word, SepBuf *buf, void *data)
if (args->pflags.anchorEnd) {
if (wordLen < lhsLen)
goto nosub;
- if (memcmp(wordEnd - lhsLen, args->lhs.start, lhsLen) != 0)
+ if (memcmp(word.end - lhsLen, args->lhs.start, lhsLen) != 0)
goto nosub;
/* :S,suffix$,replacement, */
- SepBuf_AddBytesBetween(buf, word.start, wordEnd - lhsLen);
+ SepBuf_AddRange(buf, word.start, word.end - lhsLen);
SepBuf_AddSubstring(buf, args->rhs);
args->matched = true;
return;
@@ -1570,7 +1466,7 @@ ModifyWord_Subst(Substring word, SepBuf *buf, void *data)
/* unanchored case, may match more than once */
while ((match = Substring_Find(word, args->lhs)) != NULL) {
- SepBuf_AddBytesBetween(buf, word.start, match);
+ SepBuf_AddRange(buf, word.start, match);
SepBuf_AddSubstring(buf, args->rhs);
args->matched = true;
word.start = match + lhsLen;
@@ -1581,10 +1477,10 @@ nosub:
SepBuf_AddSubstring(buf, word);
}
-#ifndef NO_REGEX
+#ifdef HAVE_REGEX_H
/* Print the error caused by a regcomp or regexec call. */
static void
-VarREError(int reerr, const regex_t *pat, const char *str)
+RegexError(int reerr, const regex_t *pat, const char *str)
{
size_t errlen = regerror(reerr, pat, NULL, 0);
char *errbuf = bmake_malloc(errlen);
@@ -1593,109 +1489,104 @@ VarREError(int reerr, const regex_t *pat, const char *str)
free(errbuf);
}
+/* In the modifier ':C', replace a backreference from \0 to \9. */
+static void
+RegexReplaceBackref(char ref, SepBuf *buf, const char *wp,
+ const regmatch_t *m, size_t nsub)
+{
+ unsigned int n = (unsigned)ref - '0';
+
+ if (n >= nsub)
+ Error("No subexpression \\%u", n);
+ else if (m[n].rm_so == -1) {
+ if (opts.strict)
+ Error("No match for subexpression \\%u", n);
+ } else {
+ SepBuf_AddRange(buf,
+ wp + (size_t)m[n].rm_so,
+ wp + (size_t)m[n].rm_eo);
+ }
+}
+
+/*
+ * The regular expression matches the word; now add the replacement to the
+ * buffer, taking back-references from 'wp'.
+ */
+static void
+RegexReplace(Substring replace, SepBuf *buf, const char *wp,
+ const regmatch_t *m, size_t nsub)
+{
+ const char *rp;
+
+ for (rp = replace.start; rp != replace.end; rp++) {
+ if (*rp == '\\' && rp + 1 != replace.end &&
+ (rp[1] == '&' || rp[1] == '\\'))
+ SepBuf_AddBytes(buf, ++rp, 1);
+ else if (*rp == '\\' && rp + 1 != replace.end &&
+ ch_isdigit(rp[1]))
+ RegexReplaceBackref(*++rp, buf, wp, m, nsub);
+ else if (*rp == '&') {
+ SepBuf_AddRange(buf,
+ wp + (size_t)m[0].rm_so,
+ wp + (size_t)m[0].rm_eo);
+ } else
+ SepBuf_AddBytes(buf, rp, 1);
+ }
+}
+
struct ModifyWord_SubstRegexArgs {
regex_t re;
size_t nsub;
- const char *replace;
+ Substring replace;
PatternFlags pflags;
bool matched;
};
-/*
- * Callback for ModifyWords to implement the :C/from/to/ modifier.
- * Perform a regex substitution on the given word.
- */
static void
ModifyWord_SubstRegex(Substring word, SepBuf *buf, void *data)
{
struct ModifyWord_SubstRegexArgs *args = data;
int xrv;
const char *wp;
- const char *rp;
int flags = 0;
regmatch_t m[10];
assert(word.end[0] == '\0'); /* assume null-terminated word */
wp = word.start;
if (args->pflags.subOnce && args->matched)
- goto nosub;
+ goto no_match;
-tryagain:
+again:
xrv = regexec(&args->re, wp, args->nsub, m, flags);
+ if (xrv == 0)
+ goto ok;
+ if (xrv != REG_NOMATCH)
+ RegexError(xrv, &args->re, "Unexpected regex error");
+no_match:
+ SepBuf_AddRange(buf, wp, word.end);
+ return;
- switch (xrv) {
- case 0:
- args->matched = true;
- SepBuf_AddBytes(buf, wp, (size_t)m[0].rm_so);
-
- /*
- * Replacement of regular expressions is not specified by
- * POSIX, therefore implement it here.
- */
-
- for (rp = args->replace; *rp != '\0'; rp++) {
- if (*rp == '\\' && (rp[1] == '&' || rp[1] == '\\')) {
- SepBuf_AddBytes(buf, rp + 1, 1);
- rp++;
- continue;
- }
-
- if (*rp == '&') {
- SepBuf_AddBytesBetween(buf,
- wp + m[0].rm_so, wp + m[0].rm_eo);
- continue;
- }
+ok:
+ args->matched = true;
+ SepBuf_AddBytes(buf, wp, (size_t)m[0].rm_so);
- if (*rp != '\\' || !ch_isdigit(rp[1])) {
- SepBuf_AddBytes(buf, rp, 1);
- continue;
- }
+ RegexReplace(args->replace, buf, wp, m, args->nsub);
- { /* \0 to \9 backreference */
- size_t n = (size_t)(rp[1] - '0');
- rp++;
-
- if (n >= args->nsub) {
- Error("No subexpression \\%u",
- (unsigned)n);
- } else if (m[n].rm_so == -1) {
- if (opts.strict) {
- Error(
- "No match for subexpression \\%u",
- (unsigned)n);
- }
- } else {
- SepBuf_AddBytesBetween(buf,
- wp + m[n].rm_so, wp + m[n].rm_eo);
- }
- }
- }
-
- wp += m[0].rm_eo;
- if (args->pflags.subGlobal) {
- flags |= REG_NOTBOL;
- if (m[0].rm_so == 0 && m[0].rm_eo == 0) {
- SepBuf_AddBytes(buf, wp, 1);
- wp++;
- }
- if (*wp != '\0')
- goto tryagain;
+ wp += (size_t)m[0].rm_eo;
+ if (args->pflags.subGlobal) {
+ flags |= REG_NOTBOL;
+ if (m[0].rm_so == 0 && m[0].rm_eo == 0 && *wp != '\0') {
+ SepBuf_AddBytes(buf, wp, 1);
+ wp++;
}
if (*wp != '\0')
- SepBuf_AddStr(buf, wp);
- break;
- default:
- VarREError(xrv, &args->re, "Unexpected regex error");
- /* FALLTHROUGH */
- case REG_NOMATCH:
- nosub:
- SepBuf_AddStr(buf, wp);
- break;
+ goto again;
}
+ if (*wp != '\0')
+ SepBuf_AddStr(buf, wp);
}
#endif
-
struct ModifyWord_LoopArgs {
GNode *scope;
const char *var; /* name of the temporary variable */
@@ -1703,7 +1594,6 @@ struct ModifyWord_LoopArgs {
VarEvalMode emode;
};
-/* Callback for ModifyWords to implement the :@var@...@ modifier of ODE make. */
static void
ModifyWord_Loop(Substring word, SepBuf *buf, void *data)
{
@@ -1717,13 +1607,11 @@ ModifyWord_Loop(Substring word, SepBuf *buf, void *data)
assert(word.end[0] == '\0'); /* assume null-terminated word */
Var_SetWithFlags(args->scope, args->var, word.start,
VAR_SET_NO_EXPORT);
- (void)Var_Subst(args->body, args->scope, args->emode, &s);
+ s = Var_Subst(args->body, args->scope, args->emode);
/* TODO: handle errors */
- assert(word.end[0] == '\0'); /* assume null-terminated word */
- DEBUG4(VAR, "ModifyWord_Loop: "
- "in \"%s\", replace \"%s\" with \"%s\" to \"%s\"\n",
- word.start, args->var, args->body, s);
+ DEBUG2(VAR, "ModifyWord_Loop: expand \"%s\" to \"%s\"\n",
+ args->body, s);
if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n'))
buf->needSep = false;
@@ -1740,7 +1628,7 @@ static char *
VarSelectWords(const char *str, int first, int last,
char sep, bool oneBigWord)
{
- Words words;
+ SubstringWords words;
int len, start, end, step;
int i;
@@ -1748,29 +1636,22 @@ VarSelectWords(const char *str, int first, int last,
SepBuf_Init(&buf, sep);
if (oneBigWord) {
- /* fake what Str_Words() would do if there were only one word */
+ /* fake what Substring_Words() would do */
words.len = 1;
- words.words = bmake_malloc(
- (words.len + 1) * sizeof(words.words[0]));
- words.freeIt = bmake_strdup(str);
- words.words[0] = words.freeIt;
- words.words[1] = NULL;
+ words.words = bmake_malloc(sizeof(words.words[0]));
+ words.freeIt = NULL;
+ words.words[0] = Substring_InitStr(str); /* no need to copy */
} else {
- words = Str_Words(str, false);
+ words = Substring_Words(str, false);
}
- /*
- * Now sanitize the given range. If first or last are negative,
- * convert them to the positive equivalents (-1 gets converted to len,
- * -2 gets converted to (len - 1), etc.).
- */
+ /* Convert -1 to len, -2 to (len - 1), etc. */
len = (int)words.len;
if (first < 0)
first += len + 1;
if (last < 0)
last += len + 1;
- /* We avoid scanning more of the list than we need to. */
if (first > last) {
start = (first > len ? len : first) - 1;
end = last < 1 ? 0 : last - 1;
@@ -1782,20 +1663,16 @@ VarSelectWords(const char *str, int first, int last,
}
for (i = start; (step < 0) == (i >= end); i += step) {
- SepBuf_AddStr(&buf, words.words[i]);
+ SepBuf_AddSubstring(&buf, words.words[i]);
SepBuf_Sep(&buf);
}
- Words_Free(words);
+ SubstringWords_Free(words);
return SepBuf_DoneData(&buf);
}
-/*
- * Callback for ModifyWords to implement the :tA modifier.
- * Replace each word with the result of realpath() if successful.
- */
/*ARGSUSED*/
static void
ModifyWord_Realpath(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED)
@@ -1814,7 +1691,7 @@ ModifyWord_Realpath(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED)
static char *
-Words_JoinFree(Words words)
+SubstringWords_JoinFree(SubstringWords words)
{
Buffer buf;
size_t i;
@@ -1823,13 +1700,15 @@ Words_JoinFree(Words words)
for (i = 0; i < words.len; i++) {
if (i != 0) {
- /* XXX: Use ch->sep instead of ' ', for consistency. */
+ /*
+ * XXX: Use ch->sep instead of ' ', for consistency.
+ */
Buf_AddByte(&buf, ' ');
}
- Buf_AddStr(&buf, words.words[i]);
+ Buf_AddRange(&buf, words.words[i].start, words.words[i].end);
}
- Words_Free(words);
+ SubstringWords_Free(words);
return Buf_DoneData(&buf);
}
@@ -1840,7 +1719,7 @@ Words_JoinFree(Words words)
* If quoteDollar is set, also quote and double any '$' characters.
*/
static void
-VarQuote(const char *str, bool quoteDollar, LazyBuf *buf)
+QuoteShell(const char *str, bool quoteDollar, LazyBuf *buf)
{
const char *p;
@@ -1853,7 +1732,7 @@ VarQuote(const char *str, bool quoteDollar, LazyBuf *buf)
LazyBuf_AddStr(buf, newline);
continue;
}
- if (ch_isspace(*p) || is_shell_metachar((unsigned char)*p))
+ if (ch_isspace(*p) || ch_is_shell_meta(*p))
LazyBuf_Add(buf, '\\');
LazyBuf_Add(buf, *p);
if (quoteDollar && *p == '$')
@@ -1866,7 +1745,7 @@ VarQuote(const char *str, bool quoteDollar, LazyBuf *buf)
* algorithm. Output is encoded as 8 hex digits, in Little Endian order.
*/
static char *
-VarHash(const char *str)
+Hash(const char *str)
{
static const char hexdigits[16] = "0123456789abcdef";
const unsigned char *ustr = (const unsigned char *)str;
@@ -1926,15 +1805,28 @@ VarHash(const char *str)
}
static char *
-VarStrftime(const char *fmt, bool zulu, time_t tim)
+FormatTime(const char *fmt, time_t t, bool gmt)
{
char buf[BUFSIZ];
- if (tim == 0)
- time(&tim);
+ if (t == 0)
+ time(&t);
if (*fmt == '\0')
fmt = "%c";
- strftime(buf, sizeof buf, fmt, zulu ? gmtime(&tim) : localtime(&tim));
+ if (gmt && strchr(fmt, 's') != NULL) {
+ /* strftime "%s" only works with localtime, not with gmtime. */
+ const char *prev_tz_env = getenv("TZ");
+ char *prev_tz = prev_tz_env != NULL
+ ? bmake_strdup(prev_tz_env) : NULL;
+ setenv("TZ", "UTC", 1);
+ strftime(buf, sizeof buf, fmt, localtime(&t));
+ if (prev_tz != NULL) {
+ setenv("TZ", prev_tz, 1);
+ free(prev_tz);
+ } else
+ unsetenv("TZ");
+ } else
+ strftime(buf, sizeof buf, fmt, (gmt ? gmtime : localtime)(&t));
buf[sizeof buf - 1] = '\0';
return bmake_strdup(buf);
@@ -1956,78 +1848,71 @@ VarStrftime(const char *fmt, bool zulu, time_t tim)
* XXX: As of 2020-11-15, some modifiers such as :S, :C, :P, :L do not
* need to be followed by a ':' or endc; this was an unintended mistake.
*
- * If parsing fails because of a missing delimiter (as in the :S, :C or :@
- * modifiers), return AMR_CLEANUP.
+ * If parsing fails because of a missing delimiter after a modifier part (as
+ * in the :S, :C or :@ modifiers), return AMR_CLEANUP.
*
* If parsing fails because the modifier is unknown, return AMR_UNKNOWN to
- * try the SysV modifier ${VAR:from=to} as fallback. This should only be
+ * try the SysV modifier ':from=to' as fallback. This should only be
* done as long as there have been no side effects from evaluating nested
* variables, to avoid evaluating them more than once. In this case, the
* parsing position may or may not be updated. (XXX: Why not? The original
* parsing position is well-known in ApplyModifiers.)
*
* If parsing fails and the SysV modifier ${VAR:from=to} should not be used
- * as a fallback, either issue an error message using Error or Parse_Error
- * and then return AMR_CLEANUP, or return AMR_BAD for the default error
- * message. Both of these return values will stop processing the variable
- * expression. (XXX: As of 2020-08-23, evaluation of the whole string
- * continues nevertheless after skipping a few bytes, which essentially is
- * undefined behavior. Not in the sense of C, but still the resulting string
- * is garbage.)
+ * as a fallback, issue an error message using Parse_Error (preferred over
+ * Error) and then return AMR_CLEANUP, which stops processing the expression.
+ * (XXX: As of 2020-08-23, evaluation of the string continues nevertheless
+ * after skipping a few bytes, which results in garbage.)
*
* Evaluating the modifier
*
* After parsing, the modifier is evaluated. The side effects from evaluating
- * nested variable expressions in the modifier text often already happen
+ * nested expressions in the modifier text often already happen
* during parsing though. For most modifiers this doesn't matter since their
- * only noticeable effect is that the update the value of the expression.
+ * only noticeable effect is that they update the value of the expression.
* Some modifiers such as ':sh' or '::=' have noticeable side effects though.
*
- * Evaluating the modifier usually takes the current value of the variable
- * expression from ch->expr->value, or the variable name from ch->var->name
- * and stores the result back in expr->value via Expr_SetValueOwn or
+ * Evaluating the modifier usually takes the current value of the
+ * expression from ch->expr->value, or the variable name from ch->var->name,
+ * and stores the result back in ch->expr->value via Expr_SetValueOwn or
* Expr_SetValueRefer.
*
- * If evaluating fails (as of 2020-08-23), an error message is printed using
- * Error. This function has no side-effects, it really just prints the error
- * message. Processing the expression continues as if everything were ok.
- * XXX: This should be fixed by adding proper error handling to Var_Subst,
- * Var_Parse, ApplyModifiers and ModifyWords.
- *
- * Housekeeping
+ * If evaluating fails, the fallback error message "Bad modifier" is printed
+ * using Error. This function has no side effects, it really just prints the
+ * error message, continuing as if nothing had happened. TODO: This should be
+ * fixed by adding proper error handling to Var_Subst, Var_Parse,
+ * ApplyModifiers and ModifyWords.
*
* Some modifiers such as :D and :U turn undefined expressions into defined
- * expressions (see Expr_Define).
- *
- * Some modifiers need to free some memory.
+ * expressions using Expr_Define.
*/
typedef enum ExprDefined {
- /* The variable expression is based on a regular, defined variable. */
+ /* The expression is based on a regular, defined variable. */
DEF_REGULAR,
- /* The variable expression is based on an undefined variable. */
+ /* The expression is based on an undefined variable. */
DEF_UNDEF,
/*
- * The variable expression started as an undefined expression, but one
+ * The expression started as an undefined expression, but one
* of the modifiers (such as ':D' or ':U') has turned the expression
* from undefined to defined.
*/
DEF_DEFINED
} ExprDefined;
-static const char *const ExprDefined_Name[] = {
+static const char ExprDefined_Name[][10] = {
"regular",
"undefined",
"defined"
};
#if __STDC_VERSION__ >= 199901L
-#define const_member const
+#define const_member const
#else
-#define const_member /* no const possible */
+#define const_member /* no const possible */
#endif
-/* A variable expression such as $@ or ${VAR:Mpattern:Q}. */
+/* An expression based on a variable, such as $@ or ${VAR:Mpattern:Q}. */
typedef struct Expr {
const char *name;
FStr value;
@@ -2052,14 +1937,11 @@ typedef struct Expr {
* Chain 2 ends at the ':' between ${IND1} and ${IND2}.
* Chain 3 starts with all modifiers from ${IND2}.
* Chain 3 ends at the ':' after ${IND2}.
- * Chain 1 continues with the the 2 modifiers ':O' and ':u'.
+ * Chain 1 continues with the 2 modifiers ':O' and ':u'.
* Chain 1 ends at the final '}' of the expression.
*
* After such a chain ends, its properties no longer have any effect.
*
- * It may or may not have been intended that 'defined' has scope Expr while
- * 'sep' and 'oneBigWord' have smaller scope.
- *
* See varmod-indirect.mk.
*/
typedef struct ModChain {
@@ -2068,10 +1950,10 @@ typedef struct ModChain {
char const_member startc;
/* '\0' or '}' or ')' */
char const_member endc;
- /* Word separator in expansions (see the :ts modifier). */
+ /* Separator when joining words (see the :ts modifier). */
char sep;
/*
- * True if some modifiers that otherwise split the variable value
+ * Whether some modifiers that otherwise split the variable value
* into words, like :S and :C, treat the variable value as a single
* big word, possibly containing spaces.
*/
@@ -2085,6 +1967,18 @@ Expr_Define(Expr *expr)
expr->defined = DEF_DEFINED;
}
+static const char *
+Expr_Str(const Expr *expr)
+{
+ return expr->value.str;
+}
+
+static SubstringWords
+Expr_Words(const Expr *expr)
+{
+ return Substring_Words(Expr_Str(expr), false);
+}
+
static void
Expr_SetValue(Expr *expr, FStr value)
{
@@ -2120,7 +2014,7 @@ ModChain_ShouldEval(const ModChain *ch)
typedef enum ApplyModifierResult {
/* Continue parsing */
AMR_OK,
- /* Not a match, try other modifiers as well. */
+ /* Not a match, try the ':from=to' modifier as well. */
AMR_UNKNOWN,
/* Error out with "Bad modifier" message. */
AMR_BAD,
@@ -2143,120 +2037,119 @@ IsEscapedModifierPart(const char *p, char delim,
return p[1] == '&' && subst != NULL;
}
+/*
+ * In a part of a modifier, parse a subexpression and evaluate it.
+ */
+static void
+ParseModifierPartExpr(const char **pp, LazyBuf *part, const ModChain *ch,
+ VarEvalMode emode)
+{
+ const char *p = *pp;
+ FStr nested_val = Var_Parse(&p, ch->expr->scope,
+ VarEvalMode_WithoutKeepDollar(emode));
+ /* TODO: handle errors */
+ if (VarEvalMode_ShouldEval(emode))
+ LazyBuf_AddStr(part, nested_val.str);
+ else
+ LazyBuf_AddSubstring(part, Substring_Init(*pp, p));
+ FStr_Done(&nested_val);
+ *pp = p;
+}
+
+/*
+ * In a part of a modifier, parse some text that looks like a subexpression.
+ * If the text starts with '$(', any '(' and ')' must be balanced.
+ * If the text starts with '${', any '{' and '}' must be balanced.
+ * If the text starts with '$', that '$' is copied verbatim, it is not parsed
+ * as a short-name expression.
+ */
+static void
+ParseModifierPartBalanced(const char **pp, LazyBuf *part)
+{
+ const char *p = *pp;
+
+ if (p[1] == '(' || p[1] == '{') {
+ char startc = p[1];
+ int endc = startc == '(' ? ')' : '}';
+ int depth = 1;
+
+ for (p += 2; *p != '\0' && depth > 0; p++) {
+ if (p[-1] != '\\') {
+ if (*p == startc)
+ depth++;
+ if (*p == endc)
+ depth--;
+ }
+ }
+ LazyBuf_AddSubstring(part, Substring_Init(*pp, p));
+ *pp = p;
+ } else {
+ LazyBuf_Add(part, *p);
+ *pp = p + 1;
+ }
+}
+
/* See ParseModifierPart for the documentation. */
-static VarParseResult
+static bool
ParseModifierPartSubst(
const char **pp,
+ /* If true, parse up to but excluding the next ':' or ch->endc. */
+ bool whole,
char delim,
VarEvalMode emode,
ModChain *ch,
LazyBuf *part,
- /* For the first part of the modifier ':S', set anchorEnd if the last
- * character of the pattern is a $. */
+ /*
+ * For the first part of the ':S' modifier, set anchorEnd if the last
+ * character of the pattern is a $.
+ */
PatternFlags *out_pflags,
- /* For the second part of the :S modifier, allow ampersands to be
- * escaped and replace unescaped ampersands with subst->lhs. */
+ /*
+ * For the second part of the ':S' modifier, allow ampersands to be
+ * escaped and replace unescaped ampersands with subst->lhs.
+ */
struct ModifyWord_SubstArgs *subst
)
{
const char *p;
+ char end1, end2;
p = *pp;
LazyBuf_Init(part, p);
- /*
- * Skim through until the matching delimiter is found; pick up
- * variable expressions on the way.
- */
- while (*p != '\0' && *p != delim) {
- const char *varstart;
-
+ end1 = whole ? ':' : delim;
+ end2 = whole ? ch->endc : delim;
+ while (*p != '\0' && *p != end1 && *p != end2) {
if (IsEscapedModifierPart(p, delim, subst)) {
LazyBuf_Add(part, p[1]);
p += 2;
- continue;
- }
-
- if (*p != '$') { /* Unescaped, simple text */
+ } else if (*p != '$') { /* Unescaped, simple text */
if (subst != NULL && *p == '&')
LazyBuf_AddSubstring(part, subst->lhs);
else
LazyBuf_Add(part, *p);
p++;
- continue;
- }
-
- if (p[1] == delim) { /* Unescaped $ at end of pattern */
+ } else if (p[1] == delim) { /* Unescaped '$' at end */
if (out_pflags != NULL)
out_pflags->anchorEnd = true;
else
LazyBuf_Add(part, *p);
p++;
- continue;
- }
-
- if (VarEvalMode_ShouldEval(emode)) {
- /* Nested variable, evaluated */
- const char *nested_p = p;
- FStr nested_val;
-
- (void)Var_Parse(&nested_p, ch->expr->scope,
- VarEvalMode_WithoutKeepDollar(emode), &nested_val);
- /* TODO: handle errors */
- LazyBuf_AddStr(part, nested_val.str);
- FStr_Done(&nested_val);
- p += nested_p - p;
- continue;
- }
-
- /*
- * XXX: This whole block is very similar to Var_Parse without
- * VARE_WANTRES. There may be subtle edge cases
- * though that are not yet covered in the unit tests and that
- * are parsed differently, depending on whether they are
- * evaluated or not.
- *
- * This subtle difference is not documented in the manual
- * page, neither is the difference between parsing :D and
- * :M documented. No code should ever depend on these
- * details, but who knows.
- */
-
- varstart = p; /* Nested variable, only parsed */
- if (p[1] == '(' || p[1] == '{') {
- /*
- * Find the end of this variable reference
- * and suck it in without further ado.
- * It will be interpreted later.
- */
- char startc = p[1];
- int endc = startc == '(' ? ')' : '}';
- int depth = 1;
-
- for (p += 2; *p != '\0' && depth > 0; p++) {
- if (p[-1] != '\\') {
- if (*p == startc)
- depth++;
- if (*p == endc)
- depth--;
- }
- }
- LazyBuf_AddBytesBetween(part, varstart, p);
- } else {
- LazyBuf_Add(part, *varstart);
- p++;
- }
+ } else if (emode == VARE_PARSE_BALANCED)
+ ParseModifierPartBalanced(&p, part);
+ else
+ ParseModifierPartExpr(&p, part, ch, emode);
}
- if (*p != delim) {
- *pp = p;
+ *pp = p;
+ if (*p != end1 && *p != end2) {
Error("Unfinished modifier for \"%s\" ('%c' missing)",
- ch->expr->name, delim);
+ ch->expr->name, end2);
LazyBuf_Done(part);
- return VPR_ERR;
+ return false;
}
-
- *pp = p + 1;
+ if (!whole)
+ (*pp)++;
{
Substring sub = LazyBuf_Get(part);
@@ -2264,7 +2157,7 @@ ParseModifierPartSubst(
(int)Substring_Length(sub), sub.start);
}
- return VPR_OK;
+ return true;
}
/*
@@ -2273,29 +2166,30 @@ ParseModifierPartSubst(
* including the next unescaped delimiter. The delimiter, as well as the
* backslash or the dollar, can be escaped with a backslash.
*
- * Return VPR_OK if parsing succeeded, together with the parsed (and possibly
+ * Return true if parsing succeeded, together with the parsed (and possibly
* expanded) part. In that case, pp points right after the delimiter. The
* delimiter is not included in the part though.
*/
-static VarParseResult
+static bool
ParseModifierPart(
/* The parsing position, updated upon return */
const char **pp,
/* Parsing stops at this delimiter */
char delim,
- /* Mode for evaluating nested variables. */
+ /* Mode for evaluating nested expressions. */
VarEvalMode emode,
ModChain *ch,
LazyBuf *part
)
{
- return ParseModifierPartSubst(pp, delim, emode, ch, part, NULL, NULL);
+ return ParseModifierPartSubst(pp, false, delim, emode, ch, part,
+ NULL, NULL);
}
MAKE_INLINE bool
IsDelimiter(char c, const ModChain *ch)
{
- return c == ':' || c == ch->endc;
+ return c == ':' || c == ch->endc || c == '\0';
}
/* Test whether mod starts with modname, followed by a delimiter. */
@@ -2388,12 +2282,15 @@ ModifyWords(ModChain *ch,
bool oneBigWord)
{
Expr *expr = ch->expr;
- const char *val = expr->value.str;
+ const char *val = Expr_Str(expr);
SepBuf result;
SubstringWords words;
size_t i;
Substring word;
+ if (!ModChain_ShouldEval(ch))
+ return;
+
if (oneBigWord) {
SepBuf_Init(&result, ch->sep);
/* XXX: performance: Substring_InitStr calls strlen */
@@ -2404,8 +2301,8 @@ ModifyWords(ModChain *ch,
words = Substring_Words(val, false);
- DEBUG2(VAR, "ModifyWords: split \"%s\" into %u words\n",
- val, (unsigned)words.len);
+ DEBUG3(VAR, "ModifyWords: split \"%s\" into %u %s\n",
+ val, (unsigned)words.len, words.len != 1 ? "words" : "word");
SepBuf_Init(&result, ch->sep);
for (i = 0; i < words.len; i++) {
@@ -2427,28 +2324,25 @@ ApplyModifier_Loop(const char **pp, ModChain *ch)
Expr *expr = ch->expr;
struct ModifyWord_LoopArgs args;
char prev_sep;
- VarParseResult res;
LazyBuf tvarBuf, strBuf;
FStr tvar, str;
args.scope = expr->scope;
(*pp)++; /* Skip the first '@' */
- res = ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &tvarBuf);
- if (res != VPR_OK)
+ if (!ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &tvarBuf))
return AMR_CLEANUP;
tvar = LazyBuf_DoneGet(&tvarBuf);
args.var = tvar.str;
if (strchr(args.var, '$') != NULL) {
Parse_Error(PARSE_FATAL,
"In the :@ modifier of \"%s\", the variable name \"%s\" "
- "must not contain a dollar.",
+ "must not contain a dollar",
expr->name, args.var);
return AMR_CLEANUP;
}
- res = ParseModifierPart(pp, '@', VARE_PARSE_ONLY, ch, &strBuf);
- if (res != VPR_OK)
+ if (!ParseModifierPart(pp, '@', VARE_PARSE_BALANCED, ch, &strBuf))
return AMR_CLEANUP;
str = LazyBuf_DoneGet(&strBuf);
args.body = str.str;
@@ -2470,62 +2364,66 @@ done:
return AMR_OK;
}
-/* :Ddefined or :Uundefined */
-static ApplyModifierResult
-ApplyModifier_Defined(const char **pp, ModChain *ch)
+static void
+ParseModifier_Defined(const char **pp, ModChain *ch, bool shouldEval,
+ LazyBuf *buf)
{
- Expr *expr = ch->expr;
- LazyBuf buf;
const char *p;
- VarEvalMode emode = VARE_PARSE_ONLY;
- if (Expr_ShouldEval(expr))
- if ((**pp == 'D') == (expr->defined == DEF_REGULAR))
- emode = expr->emode;
-
p = *pp + 1;
- LazyBuf_Init(&buf, p);
- while (!IsDelimiter(*p, ch) && *p != '\0') {
+ LazyBuf_Init(buf, p);
+ while (!IsDelimiter(*p, ch)) {
- /* XXX: This code is similar to the one in Var_Parse.
- * See if the code can be merged.
- * See also ApplyModifier_Match and ParseModifierPart. */
+ /*
+ * XXX: This code is similar to the one in Var_Parse. See if
+ * the code can be merged. See also ParseModifier_Match and
+ * ParseModifierPart.
+ */
- /* Escaped delimiter or other special character */
- /* See Buf_AddEscaped in for.c. */
+ /* See Buf_AddEscaped in for.c for the counterpart. */
if (*p == '\\') {
char c = p[1];
- if (IsDelimiter(c, ch) || c == '$' || c == '\\') {
- LazyBuf_Add(&buf, c);
+ if ((IsDelimiter(c, ch) && c != '\0') ||
+ c == '$' || c == '\\') {
+ if (shouldEval)
+ LazyBuf_Add(buf, c);
p += 2;
continue;
}
}
- /* Nested variable expression */
if (*p == '$') {
- FStr nested_val;
-
- (void)Var_Parse(&p, expr->scope, emode, &nested_val);
+ FStr val = Var_Parse(&p, ch->expr->scope,
+ shouldEval ? ch->expr->emode : VARE_PARSE_ONLY);
/* TODO: handle errors */
- if (Expr_ShouldEval(expr))
- LazyBuf_AddStr(&buf, nested_val.str);
- FStr_Done(&nested_val);
+ if (shouldEval)
+ LazyBuf_AddStr(buf, val.str);
+ FStr_Done(&val);
continue;
}
- /* Ordinary text */
- LazyBuf_Add(&buf, *p);
+ if (shouldEval)
+ LazyBuf_Add(buf, *p);
p++;
}
*pp = p;
+}
- Expr_Define(expr);
+/* :Ddefined or :Uundefined */
+static ApplyModifierResult
+ApplyModifier_Defined(const char **pp, ModChain *ch)
+{
+ Expr *expr = ch->expr;
+ LazyBuf buf;
+ bool shouldEval =
+ Expr_ShouldEval(expr) &&
+ (**pp == 'D') == (expr->defined == DEF_REGULAR);
- if (VarEvalMode_ShouldEval(emode))
+ ParseModifier_Defined(pp, ch, shouldEval, &buf);
+
+ Expr_Define(expr);
+ if (shouldEval)
Expr_SetValue(expr, Substring_Str(LazyBuf_Get(&buf)));
- else
- LazyBuf_Done(&buf);
return AMR_OK;
}
@@ -2565,62 +2463,48 @@ TryParseTime(const char **pp, time_t *out_time)
return true;
}
-/* :gmtime */
+/* :gmtime and :localtime */
static ApplyModifierResult
-ApplyModifier_Gmtime(const char **pp, ModChain *ch)
+ApplyModifier_Time(const char **pp, ModChain *ch)
{
- time_t utc;
-
+ Expr *expr;
+ time_t t;
+ const char *args;
const char *mod = *pp;
- if (!ModMatchEq(mod, "gmtime", ch))
- return AMR_UNKNOWN;
-
- if (mod[6] == '=') {
- const char *p = mod + 7;
- if (!TryParseTime(&p, &utc)) {
- Parse_Error(PARSE_FATAL,
- "Invalid time value: %s", mod + 7);
- return AMR_CLEANUP;
- }
- *pp = p;
- } else {
- utc = 0;
- *pp = mod + 6;
- }
-
- if (ModChain_ShouldEval(ch))
- Expr_SetValueOwn(ch->expr,
- VarStrftime(ch->expr->value.str, true, utc));
-
- return AMR_OK;
-}
+ bool gmt = mod[0] == 'g';
-/* :localtime */
-static ApplyModifierResult
-ApplyModifier_Localtime(const char **pp, ModChain *ch)
-{
- time_t utc;
-
- const char *mod = *pp;
- if (!ModMatchEq(mod, "localtime", ch))
+ if (!ModMatchEq(mod, gmt ? "gmtime" : "localtime", ch))
return AMR_UNKNOWN;
+ args = mod + (gmt ? 6 : 9);
- if (mod[9] == '=') {
- const char *p = mod + 10;
- if (!TryParseTime(&p, &utc)) {
- Parse_Error(PARSE_FATAL,
- "Invalid time value: %s", mod + 10);
+ if (args[0] == '=') {
+ const char *p = args + 1;
+ LazyBuf buf;
+ if (!ParseModifierPartSubst(&p, true, '\0', ch->expr->emode,
+ ch, &buf, NULL, NULL))
return AMR_CLEANUP;
- }
+ if (ModChain_ShouldEval(ch)) {
+ Substring arg = LazyBuf_Get(&buf);
+ const char *arg_p = arg.start;
+ if (!TryParseTime(&arg_p, &t) || arg_p != arg.end) {
+ Parse_Error(PARSE_FATAL,
+ "Invalid time value \"%.*s\"",
+ (int)Substring_Length(arg), arg.start);
+ LazyBuf_Done(&buf);
+ return AMR_CLEANUP;
+ }
+ } else
+ t = 0;
+ LazyBuf_Done(&buf);
*pp = p;
} else {
- utc = 0;
- *pp = mod + 9;
+ t = 0;
+ *pp = args;
}
- if (ModChain_ShouldEval(ch))
- Expr_SetValueOwn(ch->expr,
- VarStrftime(ch->expr->value.str, false, utc));
+ expr = ch->expr;
+ if (Expr_ShouldEval(expr))
+ Expr_SetValueOwn(expr, FormatTime(Expr_Str(expr), t, gmt));
return AMR_OK;
}
@@ -2634,7 +2518,7 @@ ApplyModifier_Hash(const char **pp, ModChain *ch)
*pp += 4;
if (ModChain_ShouldEval(ch))
- Expr_SetValueOwn(ch->expr, VarHash(ch->expr->value.str));
+ Expr_SetValueOwn(ch->expr, Hash(Expr_Str(ch->expr)));
return AMR_OK;
}
@@ -2649,17 +2533,17 @@ ApplyModifier_Path(const char **pp, ModChain *ch)
(*pp)++;
- if (!ModChain_ShouldEval(ch))
+ if (!Expr_ShouldEval(expr))
return AMR_OK;
Expr_Define(expr);
gn = Targ_FindNode(expr->name);
- if (gn == NULL || gn->type & OP_NOPATH) {
+ if (gn == NULL || gn->type & OP_NOPATH)
path = NULL;
- } else if (gn->path != NULL) {
+ else if (gn->path != NULL)
path = bmake_strdup(gn->path);
- } else {
+ else {
SearchPath *searchPath = Suff_FindPath(gn);
path = Dir_FindFile(expr->name, searchPath);
}
@@ -2675,25 +2559,26 @@ static ApplyModifierResult
ApplyModifier_ShellCommand(const char **pp, ModChain *ch)
{
Expr *expr = ch->expr;
- const char *errfmt;
- VarParseResult res;
LazyBuf cmdBuf;
FStr cmd;
(*pp)++;
- res = ParseModifierPart(pp, '!', expr->emode, ch, &cmdBuf);
- if (res != VPR_OK)
+ if (!ParseModifierPart(pp, '!', expr->emode, ch, &cmdBuf))
return AMR_CLEANUP;
cmd = LazyBuf_DoneGet(&cmdBuf);
-
- errfmt = NULL;
- if (Expr_ShouldEval(expr))
- Expr_SetValueOwn(expr, Cmd_Exec(cmd.str, &errfmt));
- else
+ if (Expr_ShouldEval(expr)) {
+ char *output, *error;
+ output = Cmd_Exec(cmd.str, &error);
+ Expr_SetValueOwn(expr, output);
+ if (error != NULL) {
+ /* XXX: why still return AMR_OK? */
+ Error("%s", error);
+ free(error);
+ }
+ } else
Expr_SetValueRefer(expr, "");
- if (errfmt != NULL)
- Error(errfmt, cmd.str); /* XXX: why still return AMR_OK? */
+
FStr_Done(&cmd);
Expr_Define(expr);
@@ -2733,16 +2618,18 @@ ApplyModifier_Range(const char **pp, ModChain *ch)
return AMR_OK;
if (n == 0) {
- Words words = Str_Words(ch->expr->value.str, false);
+ SubstringWords words = Expr_Words(ch->expr);
n = words.len;
- Words_Free(words);
+ SubstringWords_Free(words);
}
Buf_Init(&buf);
for (i = 0; i < n; i++) {
if (i != 0) {
- /* XXX: Use ch->sep instead of ' ', for consistency. */
+ /*
+ * XXX: Use ch->sep instead of ' ', for consistency.
+ */
Buf_AddByte(&buf, ' ');
}
Buf_AddInt(&buf, 1 + (int)i);
@@ -2753,9 +2640,8 @@ ApplyModifier_Range(const char **pp, ModChain *ch)
}
/* Parse a ':M' or ':N' modifier. */
-static void
-ParseModifier_Match(const char **pp, const ModChain *ch,
- char **out_pattern)
+static char *
+ParseModifier_Match(const char **pp, const ModChain *ch)
{
const char *mod = *pp;
Expr *expr = ch->expr;
@@ -2774,10 +2660,10 @@ ParseModifier_Match(const char **pp, const ModChain *ch,
* See if the code can be merged.
* See also ApplyModifier_Defined.
*/
- int nest = 0;
+ int depth = 0;
const char *p;
- for (p = mod + 1; *p != '\0' && !(*p == ':' && nest == 0); p++) {
- if (*p == '\\' &&
+ for (p = mod + 1; *p != '\0' && !(*p == ':' && depth == 0); p++) {
+ if (*p == '\\' && p[1] != '\0' &&
(IsDelimiter(p[1], ch) || p[1] == ch->startc)) {
if (!needSubst)
copy = true;
@@ -2787,10 +2673,10 @@ ParseModifier_Match(const char **pp, const ModChain *ch,
if (*p == '$')
needSubst = true;
if (*p == '(' || *p == '{')
- nest++;
+ depth++;
if (*p == ')' || *p == '}') {
- nest--;
- if (nest < 0)
+ depth--;
+ if (depth < 0)
break;
}
}
@@ -2819,35 +2705,134 @@ ParseModifier_Match(const char **pp, const ModChain *ch,
if (needSubst) {
char *old_pattern = pattern;
- (void)Var_Subst(pattern, expr->scope, expr->emode, &pattern);
+ /*
+ * XXX: Contrary to ParseModifierPart, a dollar in a ':M' or
+ * ':N' modifier must be escaped as '$$', not as '\$'.
+ */
+ pattern = Var_Subst(pattern, expr->scope, expr->emode);
/* TODO: handle errors */
free(old_pattern);
}
DEBUG2(VAR, "Pattern for ':%c' is \"%s\"\n", mod[0], pattern);
- *out_pattern = pattern;
+ return pattern;
+}
+
+struct ModifyWord_MatchArgs {
+ const char *pattern;
+ bool neg;
+ bool error_reported;
+};
+
+static void
+ModifyWord_Match(Substring word, SepBuf *buf, void *data)
+{
+ struct ModifyWord_MatchArgs *args = data;
+ StrMatchResult res;
+ assert(word.end[0] == '\0'); /* assume null-terminated word */
+ res = Str_Match(word.start, args->pattern);
+ if (res.error != NULL && !args->error_reported) {
+ args->error_reported = true;
+ Parse_Error(PARSE_WARNING,
+ "%s in pattern '%s' of modifier '%s'",
+ res.error, args->pattern, args->neg ? ":N" : ":M");
+ }
+ if (res.matched != args->neg)
+ SepBuf_AddSubstring(buf, word);
}
/* :Mpattern or :Npattern */
static ApplyModifierResult
ApplyModifier_Match(const char **pp, ModChain *ch)
{
- const char mod = **pp;
+ char mod = **pp;
char *pattern;
- ParseModifier_Match(pp, ch, &pattern);
+ pattern = ParseModifier_Match(pp, ch);
if (ModChain_ShouldEval(ch)) {
- ModifyWordProc modifyWord =
- mod == 'M' ? ModifyWord_Match : ModifyWord_NoMatch;
- ModifyWords(ch, modifyWord, pattern, ch->oneBigWord);
+ struct ModifyWord_MatchArgs args;
+ args.pattern = pattern;
+ args.neg = mod == 'N';
+ args.error_reported = false;
+ ModifyWords(ch, ModifyWord_Match, &args, ch->oneBigWord);
}
free(pattern);
return AMR_OK;
}
+struct ModifyWord_MtimeArgs {
+ bool error;
+ bool use_fallback;
+ ApplyModifierResult rc;
+ time_t fallback;
+};
+
+static void
+ModifyWord_Mtime(Substring word, SepBuf *buf, void *data)
+{
+ struct ModifyWord_MtimeArgs *args = data;
+ struct stat st;
+ char tbuf[21];
+
+ if (Substring_IsEmpty(word))
+ return;
+ assert(word.end[0] == '\0'); /* assume null-terminated word */
+ if (stat(word.start, &st) < 0) {
+ if (args->error) {
+ Parse_Error(PARSE_FATAL,
+ "Cannot determine mtime for '%s': %s",
+ word.start, strerror(errno));
+ args->rc = AMR_CLEANUP;
+ return;
+ }
+ if (args->use_fallback)
+ st.st_mtime = args->fallback;
+ else
+ time(&st.st_mtime);
+ }
+ snprintf(tbuf, sizeof(tbuf), "%u", (unsigned)st.st_mtime);
+ SepBuf_AddStr(buf, tbuf);
+}
+
+/* :mtime */
+static ApplyModifierResult
+ApplyModifier_Mtime(const char **pp, ModChain *ch)
+{
+ const char *p, *mod = *pp;
+ struct ModifyWord_MtimeArgs args;
+
+ if (!ModMatchEq(mod, "mtime", ch))
+ return AMR_UNKNOWN;
+ *pp += 5;
+ p = *pp;
+ args.error = false;
+ args.use_fallback = p[0] == '=';
+ args.rc = AMR_OK;
+ if (args.use_fallback) {
+ p++;
+ if (TryParseTime(&p, &args.fallback)) {
+ } else if (strncmp(p, "error", 5) == 0) {
+ p += 5;
+ args.error = true;
+ } else
+ goto invalid_argument;
+ if (!IsDelimiter(*p, ch))
+ goto invalid_argument;
+ *pp = p;
+ }
+ ModifyWords(ch, ModifyWord_Mtime, &args, ch->oneBigWord);
+ return args.rc;
+
+invalid_argument:
+ Parse_Error(PARSE_FATAL,
+ "Invalid argument '%.*s' for modifier ':mtime'",
+ (int)strcspn(*pp + 1, ":{}()"), *pp + 1);
+ return AMR_CLEANUP;
+}
+
static void
ParsePatternFlags(const char **pp, PatternFlags *pflags, bool *oneBigWord)
{
@@ -2876,7 +2861,6 @@ ApplyModifier_Subst(const char **pp, ModChain *ch)
{
struct ModifyWord_SubstArgs args;
bool oneBigWord;
- VarParseResult res;
LazyBuf lhsBuf, rhsBuf;
char delim = (*pp)[1];
@@ -2896,15 +2880,13 @@ ApplyModifier_Subst(const char **pp, ModChain *ch)
(*pp)++;
}
- res = ParseModifierPartSubst(pp, delim, ch->expr->emode, ch, &lhsBuf,
- &args.pflags, NULL);
- if (res != VPR_OK)
+ if (!ParseModifierPartSubst(pp,
+ false, delim, ch->expr->emode, ch, &lhsBuf, &args.pflags, NULL))
return AMR_CLEANUP;
args.lhs = LazyBuf_Get(&lhsBuf);
- res = ParseModifierPartSubst(pp, delim, ch->expr->emode, ch, &rhsBuf,
- NULL, &args);
- if (res != VPR_OK) {
+ if (!ParseModifierPartSubst(pp,
+ false, delim, ch->expr->emode, ch, &rhsBuf, NULL, &args)) {
LazyBuf_Done(&lhsBuf);
return AMR_CLEANUP;
}
@@ -2920,7 +2902,7 @@ ApplyModifier_Subst(const char **pp, ModChain *ch)
return AMR_OK;
}
-#ifndef NO_REGEX
+#ifdef HAVE_REGEX_H
/* :C,from,to, */
static ApplyModifierResult
@@ -2929,9 +2911,8 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
struct ModifyWord_SubstRegexArgs args;
bool oneBigWord;
int error;
- VarParseResult res;
LazyBuf reBuf, replaceBuf;
- FStr re, replace;
+ FStr re;
char delim = (*pp)[1];
if (delim == '\0') {
@@ -2942,34 +2923,28 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
*pp += 2;
- res = ParseModifierPart(pp, delim, ch->expr->emode, ch, &reBuf);
- if (res != VPR_OK)
+ if (!ParseModifierPart(pp, delim, ch->expr->emode, ch, &reBuf))
return AMR_CLEANUP;
re = LazyBuf_DoneGet(&reBuf);
- res = ParseModifierPart(pp, delim, ch->expr->emode, ch, &replaceBuf);
- if (res != VPR_OK) {
+ if (!ParseModifierPart(pp, delim, ch->expr->emode, ch, &replaceBuf)) {
FStr_Done(&re);
return AMR_CLEANUP;
}
- replace = LazyBuf_DoneGet(&replaceBuf);
- args.replace = replace.str;
+ args.replace = LazyBuf_Get(&replaceBuf);
args.pflags = PatternFlags_None();
args.matched = false;
oneBigWord = ch->oneBigWord;
ParsePatternFlags(pp, &args.pflags, &oneBigWord);
- if (!ModChain_ShouldEval(ch)) {
- FStr_Done(&replace);
- FStr_Done(&re);
- return AMR_OK;
- }
+ if (!ModChain_ShouldEval(ch))
+ goto done;
error = regcomp(&args.re, re.str, REG_EXTENDED);
if (error != 0) {
- VarREError(error, &args.re, "Regex compilation error");
- FStr_Done(&replace);
+ RegexError(error, &args.re, "Regex compilation error");
+ LazyBuf_Done(&replaceBuf);
FStr_Done(&re);
return AMR_CLEANUP;
}
@@ -2981,7 +2956,8 @@ ApplyModifier_Regex(const char **pp, ModChain *ch)
ModifyWords(ch, ModifyWord_SubstRegex, &args, oneBigWord);
regfree(&args.re);
- FStr_Done(&replace);
+done:
+ LazyBuf_Done(&replaceBuf);
FStr_Done(&re);
return AMR_OK;
}
@@ -3003,7 +2979,7 @@ ApplyModifier_Quote(const char **pp, ModChain *ch)
if (!ModChain_ShouldEval(ch))
return AMR_OK;
- VarQuote(ch->expr->value.str, quoteDollar, &buf);
+ QuoteShell(Expr_Str(ch->expr), quoteDollar, &buf);
if (buf.data != NULL)
Expr_SetValue(ch->expr, LazyBuf_DoneGet(&buf));
else
@@ -3026,8 +3002,8 @@ ApplyModifier_ToSep(const char **pp, ModChain *ch)
const char *sep = *pp + 2;
/*
- * Even in parse-only mode, proceed as normal since there is
- * neither any observable side effect nor a performance penalty.
+ * Even in parse-only mode, apply the side effects, since the side
+ * effects are neither observable nor is there a performance penalty.
* Checking for wantRes for every single piece of code in here
* would make the code in this function too hard to read.
*/
@@ -3046,7 +3022,7 @@ ApplyModifier_ToSep(const char **pp, ModChain *ch)
goto ok;
}
- /* ":ts<unrecognised><unrecognised>". */
+ /* ":ts<unrecognized><unrecognized>". */
if (sep[0] != '\\') {
(*pp)++; /* just for backwards compatibility */
return AMR_BAD;
@@ -3076,12 +3052,12 @@ ApplyModifier_ToSep(const char **pp, ModChain *ch)
p++;
} else if (!ch_isdigit(sep[1])) {
(*pp)++; /* just for backwards compatibility */
- return AMR_BAD; /* ":ts<backslash><unrecognised>". */
+ return AMR_BAD; /* ":ts<backslash><unrecognized>". */
}
if (!TryParseChar(&p, base, &ch->sep)) {
Parse_Error(PARSE_FATAL,
- "Invalid character number: %s", p);
+ "Invalid character number at \"%s\"", p);
return AMR_CLEANUP;
}
if (!IsDelimiter(*p, ch)) {
@@ -3133,7 +3109,7 @@ ApplyModifier_To(const char **pp, ModChain *ch)
const char *mod = *pp;
assert(mod[0] == 't');
- if (IsDelimiter(mod[1], ch) || mod[1] == '\0') {
+ if (IsDelimiter(mod[1], ch)) {
*pp = mod + 1;
return AMR_BAD; /* Found ":t<endc>" or ":t:". */
}
@@ -3141,7 +3117,7 @@ ApplyModifier_To(const char **pp, ModChain *ch)
if (mod[1] == 's')
return ApplyModifier_ToSep(pp, ch);
- if (!IsDelimiter(mod[2], ch)) { /* :t<unrecognized> */
+ if (!IsDelimiter(mod[2], ch)) { /* :t<any><any> */
*pp = mod + 1;
return AMR_BAD;
}
@@ -3154,15 +3130,15 @@ ApplyModifier_To(const char **pp, ModChain *ch)
if (mod[1] == 'u') { /* :tu */
*pp = mod + 2;
- if (ModChain_ShouldEval(ch))
- Expr_SetValueOwn(expr, str_toupper(expr->value.str));
+ if (Expr_ShouldEval(expr))
+ Expr_SetValueOwn(expr, str_toupper(Expr_Str(expr)));
return AMR_OK;
}
if (mod[1] == 'l') { /* :tl */
*pp = mod + 2;
- if (ModChain_ShouldEval(ch))
- Expr_SetValueOwn(expr, str_tolower(expr->value.str));
+ if (Expr_ShouldEval(expr))
+ Expr_SetValueOwn(expr, str_tolower(Expr_Str(expr)));
return AMR_OK;
}
@@ -3172,7 +3148,7 @@ ApplyModifier_To(const char **pp, ModChain *ch)
return AMR_OK;
}
- /* Found ":t<unrecognised>:" or ":t<unrecognised><endc>". */
+ /* Found ":t<unrecognized>:" or ":t<unrecognized><endc>". */
*pp = mod + 1; /* XXX: unnecessary but observable */
return AMR_BAD;
}
@@ -3182,19 +3158,16 @@ static ApplyModifierResult
ApplyModifier_Words(const char **pp, ModChain *ch)
{
Expr *expr = ch->expr;
- const char *estr;
int first, last;
- VarParseResult res;
const char *p;
- LazyBuf estrBuf;
- FStr festr;
+ LazyBuf argBuf;
+ FStr arg;
(*pp)++; /* skip the '[' */
- res = ParseModifierPart(pp, ']', expr->emode, ch, &estrBuf);
- if (res != VPR_OK)
+ if (!ParseModifierPart(pp, ']', expr->emode, ch, &argBuf))
return AMR_CLEANUP;
- festr = LazyBuf_DoneGet(&estrBuf);
- estr = festr.str;
+ arg = LazyBuf_DoneGet(&argBuf);
+ p = arg.str;
if (!IsDelimiter(**pp, ch))
goto bad_modifier; /* Found junk after ']' */
@@ -3202,139 +3175,209 @@ ApplyModifier_Words(const char **pp, ModChain *ch)
if (!ModChain_ShouldEval(ch))
goto ok;
- if (estr[0] == '\0')
- goto bad_modifier; /* Found ":[]". */
+ if (p[0] == '\0')
+ goto bad_modifier; /* Found ":[]". */
- if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */
- if (ch->oneBigWord) {
+ if (strcmp(p, "#") == 0) { /* Found ":[#]" */
+ if (ch->oneBigWord)
Expr_SetValueRefer(expr, "1");
- } else {
+ else {
Buffer buf;
- Words words = Str_Words(expr->value.str, false);
+ SubstringWords words = Expr_Words(expr);
size_t ac = words.len;
- Words_Free(words);
+ SubstringWords_Free(words);
- /* 3 digits + '\0' is usually enough */
- Buf_InitSize(&buf, 4);
+ Buf_Init(&buf);
Buf_AddInt(&buf, (int)ac);
Expr_SetValueOwn(expr, Buf_DoneData(&buf));
}
goto ok;
}
- if (estr[0] == '*' && estr[1] == '\0') { /* Found ":[*]" */
+ if (strcmp(p, "*") == 0) { /* ":[*]" */
ch->oneBigWord = true;
goto ok;
}
- if (estr[0] == '@' && estr[1] == '\0') { /* Found ":[@]" */
+ if (strcmp(p, "@") == 0) { /* ":[@]" */
ch->oneBigWord = false;
goto ok;
}
- /*
- * We expect estr to contain a single integer for :[N], or two
- * integers separated by ".." for :[start..end].
- */
- p = estr;
+ /* Expect ":[N]" or ":[start..end]" */
if (!TryParseIntBase0(&p, &first))
- goto bad_modifier; /* Found junk instead of a number */
+ goto bad_modifier;
- if (p[0] == '\0') { /* Found only one integer in :[N] */
+ if (p[0] == '\0') /* ":[N]" */
last = first;
- } else if (p[0] == '.' && p[1] == '.' && p[2] != '\0') {
- /* Expecting another integer after ".." */
+ else if (strncmp(p, "..", 2) == 0) {
p += 2;
if (!TryParseIntBase0(&p, &last) || *p != '\0')
- goto bad_modifier; /* Found junk after ".." */
+ goto bad_modifier;
} else
- goto bad_modifier; /* Found junk instead of ".." */
+ goto bad_modifier;
- /*
- * Now first and last are properly filled in, but we still have to
- * check for 0 as a special case.
- */
- if (first == 0 && last == 0) {
- /* ":[0]" or perhaps ":[0..0]" */
+ if (first == 0 && last == 0) { /* ":[0]" or ":[0..0]" */
ch->oneBigWord = true;
goto ok;
}
- /* ":[0..N]" or ":[N..0]" */
- if (first == 0 || last == 0)
+ if (first == 0 || last == 0) /* ":[0..N]" or ":[N..0]" */
goto bad_modifier;
- /* Normal case: select the words described by first and last. */
Expr_SetValueOwn(expr,
- VarSelectWords(expr->value.str, first, last,
- ch->sep, ch->oneBigWord));
+ VarSelectWords(Expr_Str(expr), first, last,
+ ch->sep, ch->oneBigWord));
ok:
- FStr_Done(&festr);
+ FStr_Done(&arg);
return AMR_OK;
bad_modifier:
- FStr_Done(&festr);
+ FStr_Done(&arg);
return AMR_BAD;
}
+#if __STDC__ >= 199901L || defined(HAVE_LONG_LONG_INT)
+# define NUM_TYPE long long
+# define PARSE_NUM_TYPE strtoll
+#else
+# define NUM_TYPE long
+# define PARSE_NUM_TYPE strtol
+#endif
+
+static NUM_TYPE
+num_val(Substring s)
+{
+ NUM_TYPE val;
+ char *ep;
+
+ val = PARSE_NUM_TYPE(s.start, &ep, 0);
+ if (ep != s.start) {
+ switch (*ep) {
+ case 'K':
+ case 'k':
+ val <<= 10;
+ break;
+ case 'M':
+ case 'm':
+ val <<= 20;
+ break;
+ case 'G':
+ case 'g':
+ val <<= 30;
+ break;
+ }
+ }
+ return val;
+}
+
+static int
+SubNumAsc(const void *sa, const void *sb)
+{
+ NUM_TYPE a, b;
+
+ a = num_val(*((const Substring *)sa));
+ b = num_val(*((const Substring *)sb));
+ return a > b ? 1 : b > a ? -1 : 0;
+}
+
+static int
+SubNumDesc(const void *sa, const void *sb)
+{
+ return SubNumAsc(sb, sa);
+}
+
+static int
+Substring_Cmp(Substring a, Substring b)
+{
+ for (; a.start < a.end && b.start < b.end; a.start++, b.start++)
+ if (a.start[0] != b.start[0])
+ return (unsigned char)a.start[0]
+ - (unsigned char)b.start[0];
+ return (int)((a.end - a.start) - (b.end - b.start));
+}
+
static int
-str_cmp_asc(const void *a, const void *b)
+SubStrAsc(const void *sa, const void *sb)
{
- return strcmp(*(const char *const *)a, *(const char *const *)b);
+ return Substring_Cmp(*(const Substring *)sa, *(const Substring *)sb);
}
static int
-str_cmp_desc(const void *a, const void *b)
+SubStrDesc(const void *sa, const void *sb)
{
- return strcmp(*(const char *const *)b, *(const char *const *)a);
+ return SubStrAsc(sb, sa);
}
static void
-ShuffleStrings(char **strs, size_t n)
+ShuffleSubstrings(Substring *strs, size_t n)
{
size_t i;
for (i = n - 1; i > 0; i--) {
size_t rndidx = (size_t)random() % (i + 1);
- char *t = strs[i];
+ Substring t = strs[i];
strs[i] = strs[rndidx];
strs[rndidx] = t;
}
}
-/* :O (order ascending) or :Or (order descending) or :Ox (shuffle) */
+/*
+ * :O order ascending
+ * :Or order descending
+ * :Ox shuffle
+ * :On numeric ascending
+ * :Onr, :Orn numeric descending
+ */
static ApplyModifierResult
ApplyModifier_Order(const char **pp, ModChain *ch)
{
- const char *mod = (*pp)++; /* skip past the 'O' in any case */
- Words words;
- enum SortMode {
- ASC, DESC, SHUFFLE
- } mode;
+ const char *mod = *pp;
+ SubstringWords words;
+ int (*cmp)(const void *, const void *);
if (IsDelimiter(mod[1], ch)) {
- mode = ASC;
- } else if ((mod[1] == 'r' || mod[1] == 'x') &&
- IsDelimiter(mod[2], ch)) {
+ cmp = SubStrAsc;
(*pp)++;
- mode = mod[1] == 'r' ? DESC : SHUFFLE;
+ } else if (IsDelimiter(mod[2], ch)) {
+ if (mod[1] == 'n')
+ cmp = SubNumAsc;
+ else if (mod[1] == 'r')
+ cmp = SubStrDesc;
+ else if (mod[1] == 'x')
+ cmp = NULL;
+ else
+ goto bad;
+ *pp += 2;
+ } else if (IsDelimiter(mod[3], ch)) {
+ if ((mod[1] == 'n' && mod[2] == 'r') ||
+ (mod[1] == 'r' && mod[2] == 'n'))
+ cmp = SubNumDesc;
+ else
+ goto bad;
+ *pp += 3;
} else
- return AMR_BAD;
+ goto bad;
if (!ModChain_ShouldEval(ch))
return AMR_OK;
- words = Str_Words(ch->expr->value.str, false);
- if (mode == SHUFFLE)
- ShuffleStrings(words.words, words.len);
- else
- qsort(words.words, words.len, sizeof words.words[0],
- mode == ASC ? str_cmp_asc : str_cmp_desc);
- Expr_SetValueOwn(ch->expr, Words_JoinFree(words));
+ words = Expr_Words(ch->expr);
+ if (cmp == NULL)
+ ShuffleSubstrings(words.words, words.len);
+ else {
+ assert(words.words[0].end[0] == '\0');
+ qsort(words.words, words.len, sizeof(words.words[0]), cmp);
+ }
+ Expr_SetValueOwn(ch->expr, SubstringWords_JoinFree(words));
return AMR_OK;
+
+bad:
+ (*pp)++;
+ return AMR_BAD;
}
/* :? then : else */
@@ -3342,53 +3385,53 @@ static ApplyModifierResult
ApplyModifier_IfElse(const char **pp, ModChain *ch)
{
Expr *expr = ch->expr;
- VarParseResult res;
- LazyBuf buf;
- FStr then_expr, else_expr;
+ LazyBuf thenBuf;
+ LazyBuf elseBuf;
- bool value = false;
VarEvalMode then_emode = VARE_PARSE_ONLY;
VarEvalMode else_emode = VARE_PARSE_ONLY;
- int cond_rc = COND_PARSE; /* anything other than COND_INVALID */
+ CondResult cond_rc = CR_TRUE; /* just not CR_ERROR */
if (Expr_ShouldEval(expr)) {
- cond_rc = Cond_EvalCondition(expr->name, &value);
- if (cond_rc != COND_INVALID && value)
+ cond_rc = Cond_EvalCondition(expr->name);
+ if (cond_rc == CR_TRUE)
then_emode = expr->emode;
- if (cond_rc != COND_INVALID && !value)
+ if (cond_rc == CR_FALSE)
else_emode = expr->emode;
}
- (*pp)++; /* skip past the '?' */
- res = ParseModifierPart(pp, ':', then_emode, ch, &buf);
- if (res != VPR_OK)
+ (*pp)++; /* skip past the '?' */
+ if (!ParseModifierPart(pp, ':', then_emode, ch, &thenBuf))
return AMR_CLEANUP;
- then_expr = LazyBuf_DoneGet(&buf);
- res = ParseModifierPart(pp, ch->endc, else_emode, ch, &buf);
- if (res != VPR_OK) {
- FStr_Done(&then_expr);
+ if (!ParseModifierPart(pp, ch->endc, else_emode, ch, &elseBuf)) {
+ LazyBuf_Done(&thenBuf);
return AMR_CLEANUP;
}
- else_expr = LazyBuf_DoneGet(&buf);
(*pp)--; /* Go back to the ch->endc. */
- if (cond_rc == COND_INVALID) {
- Error("Bad conditional expression '%s' in '%s?%s:%s'",
- expr->name, expr->name, then_expr.str, else_expr.str);
+ if (cond_rc == CR_ERROR) {
+ Substring thenExpr = LazyBuf_Get(&thenBuf);
+ Substring elseExpr = LazyBuf_Get(&elseBuf);
+ Error("Bad conditional expression '%s' before '?%.*s:%.*s'",
+ expr->name,
+ (int)Substring_Length(thenExpr), thenExpr.start,
+ (int)Substring_Length(elseExpr), elseExpr.start);
+ LazyBuf_Done(&thenBuf);
+ LazyBuf_Done(&elseBuf);
return AMR_CLEANUP;
}
- if (!ModChain_ShouldEval(ch)) {
- FStr_Done(&then_expr);
- FStr_Done(&else_expr);
- } else if (value) {
- Expr_SetValue(expr, then_expr);
- FStr_Done(&else_expr);
+ if (!Expr_ShouldEval(expr)) {
+ LazyBuf_Done(&thenBuf);
+ LazyBuf_Done(&elseBuf);
+ } else if (cond_rc == CR_TRUE) {
+ Expr_SetValue(expr, LazyBuf_DoneGet(&thenBuf));
+ LazyBuf_Done(&elseBuf);
} else {
- FStr_Done(&then_expr);
- Expr_SetValue(expr, else_expr);
+ LazyBuf_Done(&thenBuf);
+ Expr_SetValue(expr, LazyBuf_DoneGet(&elseBuf));
}
Expr_Define(expr);
return AMR_OK;
@@ -3422,37 +3465,26 @@ ApplyModifier_Assign(const char **pp, ModChain *ch)
Expr *expr = ch->expr;
GNode *scope;
FStr val;
- VarParseResult res;
LazyBuf buf;
const char *mod = *pp;
const char *op = mod + 1;
if (op[0] == '=')
- goto ok;
- if ((op[0] == '!' || op[0] == '+' || op[0] == '?') && op[1] == '=')
- goto ok;
- return AMR_UNKNOWN; /* "::<unrecognised>" */
+ goto found_op;
+ if ((op[0] == '+' || op[0] == '?' || op[0] == '!') && op[1] == '=')
+ goto found_op;
+ return AMR_UNKNOWN; /* "::<unrecognized>" */
-ok:
+found_op:
if (expr->name[0] == '\0') {
*pp = mod + 1;
return AMR_BAD;
}
- switch (op[0]) {
- case '+':
- case '?':
- case '!':
- *pp = mod + 3;
- break;
- default:
- *pp = mod + 2;
- break;
- }
+ *pp = mod + (op[0] != '=' ? 3 : 2);
- res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf);
- if (res != VPR_OK)
+ if (!ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf))
return AMR_CLEANUP;
val = LazyBuf_DoneGet(&buf);
@@ -3462,36 +3494,26 @@ ok:
goto done;
scope = expr->scope; /* scope where v belongs */
- if (expr->defined == DEF_REGULAR && expr->scope != SCOPE_GLOBAL) {
- Var *gv = VarFind(expr->name, expr->scope, false);
- if (gv == NULL)
- scope = SCOPE_GLOBAL;
- else
- VarFreeEnv(gv);
- }
+ if (expr->defined == DEF_REGULAR && expr->scope != SCOPE_GLOBAL
+ && VarFind(expr->name, expr->scope, false) == NULL)
+ scope = SCOPE_GLOBAL;
- switch (op[0]) {
- case '+':
+ if (op[0] == '+')
Var_Append(scope, expr->name, val.str);
- break;
- case '!': {
- const char *errfmt;
- char *cmd_output = Cmd_Exec(val.str, &errfmt);
- if (errfmt != NULL)
- Error(errfmt, val.str);
- else
- Var_Set(scope, expr->name, cmd_output);
- free(cmd_output);
- break;
- }
- case '?':
- if (expr->defined == DEF_REGULAR)
- break;
- /* FALLTHROUGH */
- default:
+ else if (op[0] == '!') {
+ char *output, *error;
+ output = Cmd_Exec(val.str, &error);
+ if (error != NULL) {
+ Error("%s", error);
+ free(error);
+ } else
+ Var_Set(scope, expr->name, output);
+ free(output);
+ } else if (op[0] == '?' && expr->defined == DEF_REGULAR) {
+ /* Do nothing. */
+ } else
Var_Set(scope, expr->name, val.str);
- break;
- }
+
Expr_SetValueRefer(expr, "");
done:
@@ -3518,7 +3540,7 @@ ApplyModifier_Remember(const char **pp, ModChain *ch)
/*
* XXX: This ad-hoc call to strcspn deviates from the usual
* behavior defined in ParseModifierPart. This creates an
- * unnecessary, undocumented inconsistency in make.
+ * unnecessary and undocumented inconsistency in make.
*/
const char *arg = mod + 2;
size_t argLen = strcspn(arg, ":)}");
@@ -3528,7 +3550,7 @@ ApplyModifier_Remember(const char **pp, ModChain *ch)
*pp = mod + 1;
if (Expr_ShouldEval(expr))
- Var_Set(expr->scope, name.str, expr->value.str);
+ Var_Set(SCOPE_GLOBAL, name.str, Expr_Str(expr));
FStr_Done(&name);
return AMR_OK;
@@ -3546,8 +3568,7 @@ ApplyModifier_WordFunc(const char **pp, ModChain *ch,
return AMR_UNKNOWN;
(*pp)++;
- if (ModChain_ShouldEval(ch))
- ModifyWords(ch, modifyWord, NULL, ch->oneBigWord);
+ ModifyWords(ch, modifyWord, NULL, ch->oneBigWord);
return AMR_OK;
}
@@ -3556,7 +3577,7 @@ ApplyModifier_WordFunc(const char **pp, ModChain *ch,
static ApplyModifierResult
ApplyModifier_Unique(const char **pp, ModChain *ch)
{
- Words words;
+ SubstringWords words;
if (!IsDelimiter((*pp)[1], ch))
return AMR_UNKNOWN;
@@ -3565,14 +3586,14 @@ ApplyModifier_Unique(const char **pp, ModChain *ch)
if (!ModChain_ShouldEval(ch))
return AMR_OK;
- words = Str_Words(ch->expr->value.str, false);
+ words = Expr_Words(ch->expr);
if (words.len > 1) {
- size_t si, di;
+ size_t di, si;
di = 0;
for (si = 1; si < words.len; si++) {
- if (strcmp(words.words[si], words.words[di]) != 0) {
+ if (!Substring_Eq(words.words[si], words.words[di])) {
di++;
if (di != si)
words.words[di] = words.words[si];
@@ -3581,18 +3602,36 @@ ApplyModifier_Unique(const char **pp, ModChain *ch)
words.len = di + 1;
}
- Expr_SetValueOwn(ch->expr, Words_JoinFree(words));
+ Expr_SetValueOwn(ch->expr, SubstringWords_JoinFree(words));
return AMR_OK;
}
-#ifdef SYSVVARSUB
+/* Test whether the modifier has the form '<lhs>=<rhs>'. */
+static bool
+IsSysVModifier(const char *p, char startc, char endc)
+{
+ bool eqFound = false;
+
+ int depth = 1;
+ while (*p != '\0' && depth > 0) {
+ if (*p == '=') /* XXX: should also test depth == 1 */
+ eqFound = true;
+ else if (*p == endc)
+ depth--;
+ else if (*p == startc)
+ depth++;
+ if (depth > 0)
+ p++;
+ }
+ return *p == endc && eqFound;
+}
+
/* :from=to */
static ApplyModifierResult
ApplyModifier_SysV(const char **pp, ModChain *ch)
{
Expr *expr = ch->expr;
- VarParseResult res;
LazyBuf lhsBuf, rhsBuf;
FStr rhs;
struct ModifyWord_SysVSubstArgs args;
@@ -3600,35 +3639,17 @@ ApplyModifier_SysV(const char **pp, ModChain *ch)
const char *lhsSuffix;
const char *mod = *pp;
- bool eqFound = false;
- /*
- * First we make a pass through the string trying to verify it is a
- * SysV-make-style translation. It must be: <lhs>=<rhs>
- */
- int depth = 1;
- const char *p = mod;
- while (*p != '\0' && depth > 0) {
- if (*p == '=') { /* XXX: should also test depth == 1 */
- eqFound = true;
- /* continue looking for ch->endc */
- } else if (*p == ch->endc)
- depth--;
- else if (*p == ch->startc)
- depth++;
- if (depth > 0)
- p++;
- }
- if (*p != ch->endc || !eqFound)
+ if (!IsSysVModifier(mod, ch->startc, ch->endc))
return AMR_UNKNOWN;
- res = ParseModifierPart(pp, '=', expr->emode, ch, &lhsBuf);
- if (res != VPR_OK)
+ if (!ParseModifierPart(pp, '=', expr->emode, ch, &lhsBuf))
return AMR_CLEANUP;
- /* The SysV modifier lasts until the end of the variable expression. */
- res = ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf);
- if (res != VPR_OK) {
+ /*
+ * The SysV modifier lasts until the end of the expression.
+ */
+ if (!ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf)) {
LazyBuf_Done(&lhsBuf);
return AMR_CLEANUP;
}
@@ -3637,7 +3658,7 @@ ApplyModifier_SysV(const char **pp, ModChain *ch)
(*pp)--; /* Go back to the ch->endc. */
/* Do not turn an empty expression into non-empty. */
- if (lhsBuf.len == 0 && expr->value.str[0] == '\0')
+ if (lhsBuf.len == 0 && Expr_Str(expr)[0] == '\0')
goto done;
lhs = LazyBuf_Get(&lhsBuf);
@@ -3654,11 +3675,10 @@ ApplyModifier_SysV(const char **pp, ModChain *ch)
done:
LazyBuf_Done(&lhsBuf);
+ FStr_Done(&rhs);
return AMR_OK;
}
-#endif
-#ifdef SUNSHCMD
/* :sh */
static ApplyModifierResult
ApplyModifier_SunShell(const char **pp, ModChain *ch)
@@ -3670,16 +3690,29 @@ ApplyModifier_SunShell(const char **pp, ModChain *ch)
*pp = p + 2;
if (Expr_ShouldEval(expr)) {
- const char *errfmt;
- char *output = Cmd_Exec(expr->value.str, &errfmt);
- if (errfmt != NULL)
- Error(errfmt, expr->value.str);
+ char *output, *error;
+ output = Cmd_Exec(Expr_Str(expr), &error);
+ if (error != NULL) {
+ Error("%s", error);
+ free(error);
+ }
Expr_SetValueOwn(expr, output);
}
return AMR_OK;
}
-#endif
+
+/*
+ * In cases where the evaluation mode and the definedness are the "standard"
+ * ones, don't log them, to keep the logs readable.
+ */
+static bool
+ShouldLogInSimpleFormat(const Expr *expr)
+{
+ return (expr->emode == VARE_WANTRES ||
+ expr->emode == VARE_UNDEFERR) &&
+ expr->defined == DEF_REGULAR;
+}
static void
LogBeforeApply(const ModChain *ch, const char *mod)
@@ -3698,18 +3731,17 @@ LogBeforeApply(const ModChain *ch, const char *mod)
return;
}
- if ((expr->emode == VARE_WANTRES || expr->emode == VARE_UNDEFERR) &&
- expr->defined == DEF_REGULAR) {
+ if (ShouldLogInSimpleFormat(expr)) {
debug_printf(
"Evaluating modifier ${%s:%c%s} on value \"%s\"\n",
expr->name, mod[0], is_single_char ? "" : "...",
- expr->value.str);
+ Expr_Str(expr));
return;
}
debug_printf(
"Evaluating modifier ${%s:%c%s} on value \"%s\" (%s, %s)\n",
- expr->name, mod[0], is_single_char ? "" : "...", expr->value.str,
+ expr->name, mod[0], is_single_char ? "" : "...", Expr_Str(expr),
VarEvalMode_Name[expr->emode], ExprDefined_Name[expr->defined]);
}
@@ -3717,12 +3749,10 @@ static void
LogAfterApply(const ModChain *ch, const char *p, const char *mod)
{
const Expr *expr = ch->expr;
- const char *value = expr->value.str;
+ const char *value = Expr_Str(expr);
const char *quot = value == var_Error ? "" : "\"";
- if ((expr->emode == VARE_WANTRES || expr->emode == VARE_UNDEFERR) &&
- expr->defined == DEF_REGULAR) {
-
+ if (ShouldLogInSimpleFormat(expr)) {
debug_printf("Result of ${%s:%.*s} is %s%s%s\n",
expr->name, (int)(p - mod), mod,
quot, value == var_Error ? "error" : value, quot);
@@ -3752,27 +3782,29 @@ ApplyModifier(const char **pp, ModChain *ch)
return ApplyModifier_Words(pp, ch);
case '_':
return ApplyModifier_Remember(pp, ch);
-#ifndef NO_REGEX
+#ifdef HAVE_REGEX_H
case 'C':
return ApplyModifier_Regex(pp, ch);
#endif
case 'D':
+ case 'U':
return ApplyModifier_Defined(pp, ch);
case 'E':
return ApplyModifier_WordFunc(pp, ch, ModifyWord_Suffix);
case 'g':
- return ApplyModifier_Gmtime(pp, ch);
+ case 'l':
+ return ApplyModifier_Time(pp, ch);
case 'H':
return ApplyModifier_WordFunc(pp, ch, ModifyWord_Head);
case 'h':
return ApplyModifier_Hash(pp, ch);
case 'L':
return ApplyModifier_Literal(pp, ch);
- case 'l':
- return ApplyModifier_Localtime(pp, ch);
case 'M':
case 'N':
return ApplyModifier_Match(pp, ch);
+ case 'm':
+ return ApplyModifier_Mtime(pp, ch);
case 'O':
return ApplyModifier_Order(pp, ch);
case 'P':
@@ -3786,16 +3818,12 @@ ApplyModifier(const char **pp, ModChain *ch)
return ApplyModifier_Range(pp, ch);
case 'S':
return ApplyModifier_Subst(pp, ch);
-#ifdef SUNSHCMD
case 's':
return ApplyModifier_SunShell(pp, ch);
-#endif
case 'T':
return ApplyModifier_WordFunc(pp, ch, ModifyWord_Tail);
case 't':
return ApplyModifier_To(pp, ch);
- case 'U':
- return ApplyModifier_Defined(pp, ch);
case 'u':
return ApplyModifier_Unique(pp, ch);
default:
@@ -3815,16 +3843,16 @@ typedef enum ApplyModifiersIndirectResult {
} ApplyModifiersIndirectResult;
/*
- * While expanding a variable expression, expand and apply indirect modifiers,
+ * While expanding an expression, expand and apply indirect modifiers,
* such as in ${VAR:${M_indirect}}.
*
- * All indirect modifiers of a group must come from a single variable
+ * All indirect modifiers of a group must come from a single
* expression. ${VAR:${M1}} is valid but ${VAR:${M1}${M2}} is not.
*
* Multiple groups of indirect modifiers can be chained by separating them
* with colons. ${VAR:${M1}:${M2}} contains 2 indirect modifiers.
*
- * If the variable expression is not followed by ch->endc or ':', fall
+ * If the expression is not followed by ch->endc or ':', fall
* back to trying the SysV modifier, such as in ${VAR:${FROM}=${TO}}.
*/
static ApplyModifiersIndirectResult
@@ -3832,12 +3860,10 @@ ApplyModifiersIndirect(ModChain *ch, const char **pp)
{
Expr *expr = ch->expr;
const char *p = *pp;
- FStr mods;
-
- (void)Var_Parse(&p, expr->scope, expr->emode, &mods);
+ FStr mods = Var_Parse(&p, expr->scope, expr->emode);
/* TODO: handle errors */
- if (mods.str[0] != '\0' && *p != '\0' && !IsDelimiter(*p, ch)) {
+ if (mods.str[0] != '\0' && !IsDelimiter(*p, ch)) {
FStr_Done(&mods);
return AMIR_SYSV;
}
@@ -3845,10 +3871,10 @@ ApplyModifiersIndirect(ModChain *ch, const char **pp)
DEBUG3(VAR, "Indirect modifier \"%s\" from \"%.*s\"\n",
mods.str, (int)(p - *pp), *pp);
- if (mods.str[0] != '\0') {
+ if (ModChain_ShouldEval(ch) && mods.str[0] != '\0') {
const char *modsp = mods.str;
ApplyModifiers(expr, &modsp, '\0', '\0');
- if (expr->value.str == var_Error || *modsp != '\0') {
+ if (Expr_Str(expr) == var_Error || *modsp != '\0') {
FStr_Done(&mods);
*pp = p;
return AMIR_OUT; /* error already reported */
@@ -3859,8 +3885,8 @@ ApplyModifiersIndirect(ModChain *ch, const char **pp)
if (*p == ':')
p++;
else if (*p == '\0' && ch->endc != '\0') {
- Error("Unclosed variable expression after indirect "
- "modifier, expecting '%c' for variable \"%s\"",
+ Error("Unclosed expression after indirect modifier, "
+ "expecting '%c' for variable \"%s\"",
ch->endc, expr->name);
*pp = p;
return AMIR_OUT;
@@ -3882,12 +3908,10 @@ ApplySingleModifier(const char **pp, ModChain *ch)
res = ApplyModifier(&p, ch);
-#ifdef SYSVVARSUB
if (res == AMR_UNKNOWN) {
assert(p == mod);
res = ApplyModifier_SysV(&p, ch);
}
-#endif
if (res == AMR_UNKNOWN) {
/*
@@ -3896,7 +3920,7 @@ ApplySingleModifier(const char **pp, ModChain *ch)
* errors and leads to wrong results.
* Parsing should rather stop here.
*/
- for (p++; !IsDelimiter(*p, ch) && *p != '\0'; p++)
+ for (p++; !IsDelimiter(*p, ch); p++)
continue;
Parse_Error(PARSE_FATAL, "Unknown modifier \"%.*s\"",
(int)(p - mod), mod);
@@ -3912,11 +3936,11 @@ ApplySingleModifier(const char **pp, ModChain *ch)
if (*p == '\0' && ch->endc != '\0') {
Error(
- "Unclosed variable expression, expecting '%c' for "
+ "Unclosed expression, expecting '%c' for "
"modifier \"%.*s\" of variable \"%s\" with value \"%s\"",
ch->endc,
(int)(p - mod), mod,
- ch->expr->name, ch->expr->value.str);
+ ch->expr->name, Expr_Str(ch->expr));
} else if (*p == ':') {
p++;
} else if (opts.strict && *p != '\0' && *p != ch->endc) {
@@ -3933,11 +3957,11 @@ ApplySingleModifier(const char **pp, ModChain *ch)
}
#if __STDC_VERSION__ >= 199901L
-#define ModChain_Literal(expr, startc, endc, sep, oneBigWord) \
+#define ModChain_Init(expr, startc, endc, sep, oneBigWord) \
(ModChain) { expr, startc, endc, sep, oneBigWord }
#else
MAKE_INLINE ModChain
-ModChain_Literal(Expr *expr, char startc, char endc, char sep, bool oneBigWord)
+ModChain_Init(Expr *expr, char startc, char endc, char sep, bool oneBigWord)
{
ModChain ch;
ch.expr = expr;
@@ -3958,19 +3982,19 @@ ApplyModifiers(
char endc /* ')' or '}'; or '\0' for indirect modifiers */
)
{
- ModChain ch = ModChain_Literal(expr, startc, endc, ' ', false);
+ ModChain ch = ModChain_Init(expr, startc, endc, ' ', false);
const char *p;
const char *mod;
assert(startc == '(' || startc == '{' || startc == '\0');
assert(endc == ')' || endc == '}' || endc == '\0');
- assert(expr->value.str != NULL);
+ assert(Expr_Str(expr) != NULL);
p = *pp;
if (*p == '\0' && endc != '\0') {
Error(
- "Unclosed variable expression (expecting '%c') for \"%s\"",
+ "Unclosed expression, expecting '%c' for \"%s\"",
ch.endc, expr->name);
goto cleanup;
}
@@ -3979,17 +4003,17 @@ ApplyModifiers(
ApplyModifierResult res;
if (*p == '$') {
+ /*
+ * TODO: Only evaluate the expression once, no matter
+ * whether it's an indirect modifier or the initial
+ * part of a SysV modifier.
+ */
ApplyModifiersIndirectResult amir =
ApplyModifiersIndirect(&ch, &p);
if (amir == AMIR_CONTINUE)
continue;
if (amir == AMIR_OUT)
break;
- /*
- * It's neither '${VAR}:' nor '${VAR}}'. Try to parse
- * it as a SysV modifier, as that is the only modifier
- * that can start with '$'.
- */
}
mod = p;
@@ -4002,11 +4026,11 @@ ApplyModifiers(
}
*pp = p;
- assert(expr->value.str != NULL); /* Use var_Error or varUndefined. */
+ assert(Expr_Str(expr) != NULL); /* Use var_Error or varUndefined. */
return;
bad_modifier:
- /* XXX: The modifier end is only guessed. */
+ /* Take a guess at where the modifier ends. */
Error("Bad modifier \":%.*s\" for variable \"%s\"",
(int)strcspn(mod, ":)}"), mod, expr->name);
@@ -4014,20 +4038,20 @@ cleanup:
/*
* TODO: Use p + strlen(p) instead, to stop parsing immediately.
*
- * In the unit tests, this generates a few unterminated strings in the
- * shell commands though. Instead of producing these unfinished
- * strings, commands with evaluation errors should not be run at all.
+ * In the unit tests, this generates a few shell commands with
+ * unbalanced quotes. Instead of producing these incomplete strings,
+ * commands with evaluation errors should not be run at all.
*
* To make that happen, Var_Subst must report the actual errors
- * instead of returning VPR_OK unconditionally.
+ * instead of returning the resulting string unconditionally.
*/
*pp = p;
Expr_SetValueRefer(expr, var_Error);
}
/*
- * Only 4 of the 7 local variables are treated specially as they are the only
- * ones that will be set when dynamic sources are expanded.
+ * Only 4 of the 7 built-in local variables are treated specially as they are
+ * the only ones that will be set when dynamic sources are expanded.
*/
static bool
VarnameIsDynamic(Substring varname)
@@ -4095,7 +4119,7 @@ ParseVarname(const char **pp, char startc, char endc,
LazyBuf *buf)
{
const char *p = *pp;
- int depth = 0; /* Track depth so we can spot parse errors. */
+ int depth = 0;
LazyBuf_Init(buf, p);
@@ -4107,10 +4131,8 @@ ParseVarname(const char **pp, char startc, char endc,
if (*p == endc)
depth--;
- /* A variable inside a variable, expand. */
if (*p == '$') {
- FStr nested_val;
- (void)Var_Parse(&p, scope, emode, &nested_val);
+ FStr nested_val = Var_Parse(&p, scope, emode);
/* TODO: handle errors */
LazyBuf_AddStr(buf, nested_val.str);
FStr_Done(&nested_val);
@@ -4122,26 +4144,26 @@ ParseVarname(const char **pp, char startc, char endc,
*pp = p;
}
-static VarParseResult
-ValidShortVarname(char varname, const char *start)
+static bool
+IsShortVarnameValid(char varname, const char *start)
{
if (varname != '$' && varname != ':' && varname != '}' &&
varname != ')' && varname != '\0')
- return VPR_OK;
+ return true;
if (!opts.strict)
- return VPR_ERR; /* XXX: Missing error message */
+ return false; /* XXX: Missing error message */
- if (varname == '$')
+ if (varname == '$' && save_dollars)
Parse_Error(PARSE_FATAL,
"To escape a dollar, use \\$, not $$, at \"%s\"", start);
else if (varname == '\0')
Parse_Error(PARSE_FATAL, "Dollar followed by nothing");
- else
+ else if (save_dollars)
Parse_Error(PARSE_FATAL,
"Invalid variable name '%c', at \"%s\"", varname, start);
- return VPR_ERR;
+ return false;
}
/*
@@ -4151,17 +4173,15 @@ ValidShortVarname(char varname, const char *start)
static bool
ParseVarnameShort(char varname, const char **pp, GNode *scope,
VarEvalMode emode,
- VarParseResult *out_false_res, const char **out_false_val,
+ const char **out_false_val,
Var **out_true_var)
{
char name[2];
Var *v;
- VarParseResult vpr;
+ const char *val;
- vpr = ValidShortVarname(varname, *pp);
- if (vpr != VPR_OK) {
- (*pp)++;
- *out_false_res = vpr;
+ if (!IsShortVarnameValid(varname, *pp)) {
+ (*pp)++; /* only skip the '$' */
*out_false_val = var_Error;
return false;
}
@@ -4169,41 +4189,25 @@ ParseVarnameShort(char varname, const char **pp, GNode *scope,
name[0] = varname;
name[1] = '\0';
v = VarFind(name, scope, true);
- if (v == NULL) {
- const char *val;
- *pp += 2;
+ if (v != NULL) {
+ /* No need to advance *pp, the calling code handles this. */
+ *out_true_var = v;
+ return true;
+ }
- val = UndefinedShortVarValue(varname, scope);
- if (val == NULL)
- val = emode == VARE_UNDEFERR
- ? var_Error : varUndefined;
+ *pp += 2;
- if (opts.strict && val == var_Error) {
- Parse_Error(PARSE_FATAL,
- "Variable \"%s\" is undefined", name);
- *out_false_res = VPR_ERR;
- *out_false_val = val;
- return false;
- }
+ val = UndefinedShortVarValue(varname, scope);
+ if (val == NULL)
+ val = emode == VARE_UNDEFERR ? var_Error : varUndefined;
- /*
- * XXX: This looks completely wrong.
- *
- * If undefined expressions are not allowed, this should
- * rather be VPR_ERR instead of VPR_UNDEF, together with an
- * error message.
- *
- * If undefined expressions are allowed, this should rather
- * be VPR_UNDEF instead of VPR_OK.
- */
- *out_false_res = emode == VARE_UNDEFERR
- ? VPR_UNDEF : VPR_OK;
- *out_false_val = val;
- return false;
+ if (opts.strict && val == var_Error) {
+ Parse_Error(PARSE_FATAL,
+ "Variable \"%s\" is undefined", name);
}
- *out_true_var = v;
- return true;
+ *out_false_val = val;
+ return false;
}
/* Find variables like @F or <D. */
@@ -4224,7 +4228,8 @@ FindLocalLegacyVar(Substring varname, GNode *scope,
if (strchr("@%?*!<>", varname.start[0]) == NULL)
return NULL;
- v = VarFindSubstring(Substring_Sub(varname, 0, 1), scope, false);
+ v = VarFindSubstring(Substring_Init(varname.start, varname.start + 1),
+ scope, false);
if (v == NULL)
return NULL;
@@ -4232,30 +4237,22 @@ FindLocalLegacyVar(Substring varname, GNode *scope,
return v;
}
-static VarParseResult
+static FStr
EvalUndefined(bool dynamic, const char *start, const char *p,
- Substring varname, VarEvalMode emode, FStr *out_val)
+ Substring varname, VarEvalMode emode)
{
- if (dynamic) {
- *out_val = FStr_InitOwn(bmake_strsedup(start, p));
- return VPR_OK;
- }
+ if (dynamic)
+ return FStr_InitOwn(bmake_strsedup(start, p));
if (emode == VARE_UNDEFERR && opts.strict) {
Parse_Error(PARSE_FATAL,
"Variable \"%.*s\" is undefined",
(int)Substring_Length(varname), varname.start);
- *out_val = FStr_InitRefer(var_Error);
- return VPR_ERR;
+ return FStr_InitRefer(var_Error);
}
- if (emode == VARE_UNDEFERR) {
- *out_val = FStr_InitRefer(var_Error);
- return VPR_UNDEF; /* XXX: Should be VPR_ERR instead. */
- }
-
- *out_val = FStr_InitRefer(varUndefined);
- return VPR_OK;
+ return FStr_InitRefer(
+ emode == VARE_UNDEFERR ? var_Error : varUndefined);
}
/*
@@ -4272,7 +4269,6 @@ ParseVarnameLong(
VarEvalMode emode,
const char **out_false_pp,
- VarParseResult *out_false_res,
FStr *out_false_val,
char *out_true_endc,
@@ -4284,73 +4280,80 @@ ParseVarnameLong(
)
{
LazyBuf varname;
+ Substring name;
Var *v;
bool haveModifier;
bool dynamic = false;
const char *p = *pp;
- const char *const start = p;
+ const char *start = p;
char endc = startc == '(' ? ')' : '}';
p += 2; /* skip "${" or "$(" or "y(" */
ParseVarname(&p, startc, endc, scope, emode, &varname);
+ name = LazyBuf_Get(&varname);
- if (*p == ':') {
+ if (*p == ':')
haveModifier = true;
- } else if (*p == endc) {
+ else if (*p == endc)
haveModifier = false;
- } else {
- Substring name = LazyBuf_Get(&varname);
+ else {
Parse_Error(PARSE_FATAL, "Unclosed variable \"%.*s\"",
(int)Substring_Length(name), name.start);
LazyBuf_Done(&varname);
*out_false_pp = p;
*out_false_val = FStr_InitRefer(var_Error);
- *out_false_res = VPR_ERR;
return false;
}
- v = VarFindSubstring(LazyBuf_Get(&varname), scope, true);
+ v = VarFindSubstring(name, scope, true);
- /* At this point, p points just after the variable name,
- * either at ':' or at endc. */
+ /*
+ * At this point, p points just after the variable name, either at
+ * ':' or at endc.
+ */
- if (v == NULL) {
- v = FindLocalLegacyVar(LazyBuf_Get(&varname), scope,
- out_true_extraModifiers);
- }
+ if (v == NULL && Substring_Equals(name, ".SUFFIXES")) {
+ char *suffixes = Suff_NamesStr();
+ v = VarNew(FStr_InitRefer(".SUFFIXES"), suffixes,
+ true, false, true);
+ free(suffixes);
+ } else if (v == NULL)
+ v = FindLocalLegacyVar(name, scope, out_true_extraModifiers);
if (v == NULL) {
/*
* Defer expansion of dynamic variables if they appear in
* non-local scope since they are not defined there.
*/
- dynamic = VarnameIsDynamic(LazyBuf_Get(&varname)) &&
+ dynamic = VarnameIsDynamic(name) &&
(scope == SCOPE_CMDLINE || scope == SCOPE_GLOBAL);
if (!haveModifier) {
p++; /* skip endc */
*out_false_pp = p;
- *out_false_res = EvalUndefined(dynamic, start, p,
- LazyBuf_Get(&varname), emode, out_false_val);
+ *out_false_val = EvalUndefined(dynamic, start, p,
+ name, emode);
+ LazyBuf_Done(&varname);
return false;
}
/*
- * The variable expression is based on an undefined variable.
+ * The expression is based on an undefined variable.
* Nevertheless it needs a Var, for modifiers that access the
* variable name, such as :L or :?.
*
* Most modifiers leave this expression in the "undefined"
- * state (VES_UNDEF), only a few modifiers like :D, :U, :L,
+ * state (DEF_UNDEF), only a few modifiers like :D, :U, :L,
* :P turn this undefined expression into a defined
- * expression (VES_DEF).
+ * expression (DEF_DEFINED).
*
* In the end, after applying all modifiers, if the expression
* is still undefined, Var_Parse will return an empty string
* instead of the actually computed value.
*/
- v = VarNew(LazyBuf_DoneGet(&varname), "", false, false);
+ v = VarNew(LazyBuf_DoneGet(&varname), "",
+ true, false, false);
*out_true_exprDefined = DEF_UNDEF;
} else
LazyBuf_Done(&varname);
@@ -4363,27 +4366,13 @@ ParseVarnameLong(
return true;
}
-/* Free the environment variable now since we own it. */
-static void
-FreeEnvVar(Var *v, FStr *inout_val)
-{
- char *varValue = Buf_DoneData(&v->val);
- if (inout_val->str == varValue)
- inout_val->freeIt = varValue;
- else
- free(varValue);
-
- FStr_Done(&v->name);
- free(v);
-}
-
#if __STDC_VERSION__ >= 199901L
-#define Expr_Literal(name, value, emode, scope, defined) \
- { name, value, emode, scope, defined }
+#define Expr_Init(name, value, emode, scope, defined) \
+ (Expr) { name, value, emode, scope, defined }
#else
MAKE_INLINE Expr
-Expr_Literal(const char *name, FStr value,
- VarEvalMode emode, GNode *scope, ExprDefined defined)
+Expr_Init(const char *name, FStr value,
+ VarEvalMode emode, GNode *scope, ExprDefined defined)
{
Expr expr;
@@ -4398,8 +4387,7 @@ Expr_Literal(const char *name, FStr value,
/*
* Expressions of the form ${:U...} with a trivial value are often generated
- * by .for loops and are boring, therefore parse and evaluate them in a fast
- * lane without debug logging.
+ * by .for loops and are boring, so evaluate them without debug logging.
*/
static bool
Var_Parse_FastLane(const char **pp, VarEvalMode emode, FStr *out_value)
@@ -4417,28 +4405,24 @@ Var_Parse_FastLane(const char **pp, VarEvalMode emode, FStr *out_value)
if (*p != '}')
return false;
- if (emode == VARE_PARSE_ONLY)
- *out_value = FStr_InitRefer("");
- else
- *out_value = FStr_InitOwn(bmake_strsedup(*pp + 4, p));
+ *out_value = emode == VARE_PARSE_ONLY
+ ? FStr_InitRefer("")
+ : FStr_InitOwn(bmake_strsedup(*pp + 4, p));
*pp = p + 1;
return true;
}
/*
- * Given the start of a variable expression (such as $v, $(VAR),
- * ${VAR:Mpattern}), extract the variable name and value, and the modifiers,
- * if any. While doing that, apply the modifiers to the value of the
- * expression, forming its final value. A few of the modifiers such as :!cmd!
- * or ::= have side effects.
+ * Given the start of an expression (such as $v, $(VAR), ${VAR:Mpattern}),
+ * extract the variable name and the modifiers, if any. While parsing, apply
+ * the modifiers to the value of the expression.
*
* Input:
* *pp The string to parse.
- * When parsing a condition in ParseEmptyArg, it may also
- * point to the "y" of "empty(VARNAME:Modifiers)", which
- * is syntactically the same.
- * scope The scope for finding variables
- * emode Controls the exact details of parsing and evaluation
+ * When called from CondParser_FuncCallEmpty, it can
+ * also point to the "y" of "empty(VARNAME:Modifiers)".
+ * scope The scope for finding variables.
+ * emode Controls the exact details of parsing and evaluation.
*
* Output:
* *pp The position where to continue parsing.
@@ -4447,84 +4431,90 @@ Var_Parse_FastLane(const char **pp, VarEvalMode emode, FStr *out_value)
* point to some random character in the string, to the
* location of the parse error, or at the end of the
* string.
- * *out_val The value of the variable expression, never NULL.
- * *out_val var_Error if there was a parse error.
- * *out_val var_Error if the base variable of the expression was
+ * return The value of the expression, never NULL.
+ * return var_Error if there was a parse error.
+ * return var_Error if the base variable of the expression was
* undefined, emode is VARE_UNDEFERR, and none of
* the modifiers turned the undefined expression into a
* defined expression.
* XXX: It is not guaranteed that an error message has
* been printed.
- * *out_val varUndefined if the base variable of the expression
+ * return varUndefined if the base variable of the expression
* was undefined, emode was not VARE_UNDEFERR,
* and none of the modifiers turned the undefined
* expression into a defined expression.
* XXX: It is not guaranteed that an error message has
* been printed.
*/
-VarParseResult
-Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val)
+FStr
+Var_Parse(const char **pp, GNode *scope, VarEvalMode emode)
{
const char *p = *pp;
const char *const start = p;
- /* true if have modifiers for the variable. */
- bool haveModifier;
- /* Starting character if variable in parens or braces. */
- char startc;
- /* Ending character if variable in parens or braces. */
- char endc;
+ bool haveModifier; /* true for ${VAR:...}, false for ${VAR} */
+ char startc; /* the actual '{' or '(' or '\0' */
+ char endc; /* the expected '}' or ')' or '\0' */
/*
- * true if the variable is local and we're expanding it in a
- * non-local scope. This is done to support dynamic sources.
- * The result is just the expression, unaltered.
+ * true if the expression is based on one of the 7 predefined
+ * variables that are local to a target, and the expression is
+ * expanded in a non-local scope. The result is the text of the
+ * expression, unaltered. This is needed to support dynamic sources.
*/
bool dynamic;
const char *extramodifiers;
Var *v;
- Expr expr = Expr_Literal(NULL, FStr_InitRefer(NULL), emode,
+ Expr expr = Expr_Init(NULL, FStr_InitRefer(NULL), emode,
scope, DEF_REGULAR);
+ FStr val;
- if (Var_Parse_FastLane(pp, emode, out_val))
- return VPR_OK;
+ if (Var_Parse_FastLane(pp, emode, &val))
+ return val;
DEBUG2(VAR, "Var_Parse: %s (%s)\n", start, VarEvalMode_Name[emode]);
- *out_val = FStr_InitRefer(NULL);
+ val = FStr_InitRefer(NULL);
extramodifiers = NULL; /* extra modifiers to apply first */
dynamic = false;
- /*
- * Appease GCC, which thinks that the variable might not be
- * initialized.
- */
- endc = '\0';
+ endc = '\0'; /* Appease GCC. */
startc = p[1];
if (startc != '(' && startc != '{') {
- VarParseResult res;
- if (!ParseVarnameShort(startc, pp, scope, emode, &res,
- &out_val->str, &v))
- return res;
+ if (!ParseVarnameShort(startc, pp, scope, emode, &val.str, &v))
+ return val;
haveModifier = false;
p++;
} else {
- VarParseResult res;
if (!ParseVarnameLong(&p, startc, scope, emode,
- pp, &res, out_val,
+ pp, &val,
&endc, &v, &haveModifier, &extramodifiers,
&dynamic, &expr.defined))
- return res;
+ return val;
}
expr.name = v->name.str;
- if (v->inUse)
+ if (v->inUse && VarEvalMode_ShouldEval(emode)) {
+ if (scope->fname != NULL) {
+ fprintf(stderr, "In a command near ");
+ PrintLocation(stderr, false, scope);
+ }
Fatal("Variable %s is recursive.", v->name.str);
+ }
/*
- * XXX: This assignment creates an alias to the current value of the
+ * FIXME: This assignment creates an alias to the current value of the
* variable. This means that as long as the value of the expression
- * stays the same, the value of the variable must not change.
- * Using the '::=' modifier, it could be possible to do exactly this.
+ * stays the same, the value of the variable must not change, and the
+ * variable must not be deleted. Using the ':@' modifier, it is
+ * possible (since var.c 1.212 from 2017-02-01) to delete the variable
+ * while its value is still being used:
+ *
+ * VAR= value
+ * _:= ${VAR:${:U@VAR@loop@}:S,^,prefix,}
+ *
+ * The same effect might be achievable using the '::=' or the ':_'
+ * modifiers.
+ *
* At the bottom of this function, the resulting value is compared to
* the then-current value of the variable. This might also invoke
* undefined behavior.
@@ -4535,15 +4525,14 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val)
* Before applying any modifiers, expand any nested expressions from
* the variable value.
*/
- if (strchr(expr.value.str, '$') != NULL &&
- VarEvalMode_ShouldEval(emode)) {
+ if (VarEvalMode_ShouldEval(emode) &&
+ strchr(Expr_Str(&expr), '$') != NULL) {
char *expanded;
VarEvalMode nested_emode = emode;
if (opts.strict)
nested_emode = VarEvalMode_UndefOk(nested_emode);
v->inUse = true;
- (void)Var_Subst(expr.value.str, scope, nested_emode,
- &expanded);
+ expanded = Var_Subst(Expr_Str(&expr), scope, nested_emode);
v->inUse = false;
/* TODO: handle errors */
Expr_SetValueOwn(&expr, expanded);
@@ -4555,7 +4544,7 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val)
}
if (haveModifier) {
- p++; /* Skip initial colon. */
+ p++; /* Skip initial colon. */
ApplyModifiers(&expr, &p, startc, endc);
}
@@ -4564,33 +4553,31 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode, FStr *out_val)
*pp = p;
- if (v->fromEnv) {
- FreeEnvVar(v, &expr.value);
-
- } else if (expr.defined != DEF_REGULAR) {
- if (expr.defined == DEF_UNDEF) {
- if (dynamic) {
- Expr_SetValueOwn(&expr,
- bmake_strsedup(start, p));
- } else {
- /*
- * The expression is still undefined,
- * therefore discard the actual value and
- * return an error marker instead.
- */
- Expr_SetValueRefer(&expr,
- emode == VARE_UNDEFERR
- ? var_Error : varUndefined);
- }
+ if (expr.defined == DEF_UNDEF) {
+ if (dynamic)
+ Expr_SetValueOwn(&expr, bmake_strsedup(start, p));
+ else {
+ /*
+ * The expression is still undefined, therefore
+ * discard the actual value and return an error marker
+ * instead.
+ */
+ Expr_SetValueRefer(&expr,
+ emode == VARE_UNDEFERR
+ ? var_Error : varUndefined);
+ }
+ }
+
+ if (v->shortLived) {
+ if (expr.value.str == v->val.data) {
+ /* move ownership */
+ expr.value.freeIt = v->val.data;
+ v->val.data = NULL;
}
- /* XXX: This is not standard memory management. */
- if (expr.value.str != v->val.data)
- Buf_Done(&v->val);
- FStr_Done(&v->name);
- free(v);
+ VarFreeShortLived(v);
}
- *out_val = expr.value;
- return VPR_OK; /* XXX: Is not correct in all cases */
+
+ return expr.value;
}
static void
@@ -4609,21 +4596,19 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope,
{
const char *p = *pp;
const char *nested_p = p;
- FStr val;
-
- (void)Var_Parse(&nested_p, scope, emode, &val);
+ FStr val = Var_Parse(&nested_p, scope, emode);
/* TODO: handle errors */
if (val.str == var_Error || val.str == varUndefined) {
if (!VarEvalMode_ShouldKeepUndef(emode)) {
p = nested_p;
- } else if (emode == VARE_UNDEFERR || val.str == var_Error) {
+ } else if (val.str == var_Error) {
/*
- * XXX: This condition is wrong. If val == var_Error,
- * this doesn't necessarily mean there was an undefined
- * variable. It could equally well be a parse error;
- * see unit-tests/varmod-order.exp.
+ * FIXME: The condition 'val.str == var_Error' doesn't
+ * mean there was an undefined variable. It could
+ * equally well be a parse error; see
+ * unit-tests/varmod-order.mk.
*/
/*
@@ -4634,15 +4619,17 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope,
if (!*inout_errorReported) {
Parse_Error(PARSE_FATAL,
"Undefined variable \"%.*s\"",
- (int)(size_t)(nested_p - p), p);
+ (int)(nested_p - p), p);
+ *inout_errorReported = true;
}
p = nested_p;
- *inout_errorReported = true;
} else {
- /* Copy the initial '$' of the undefined expression,
+ /*
+ * Copy the initial '$' of the undefined expression,
* thereby deferring expansion of the expression, but
- * expand nested expressions if already possible.
- * See unit-tests/varparse-undef-partial.mk. */
+ * expand nested expressions if already possible. See
+ * unit-tests/varparse-undef-partial.mk.
+ */
Buf_AddByte(buf, *p);
p++;
}
@@ -4657,8 +4644,8 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope,
}
/*
- * Skip as many characters as possible -- either to the end of the string
- * or to the next dollar sign (variable expression).
+ * Skip as many characters as possible -- either to the end of the string,
+ * or to the next dollar sign, which may start an expression.
*/
static void
VarSubstPlain(const char **pp, Buffer *res)
@@ -4668,30 +4655,31 @@ VarSubstPlain(const char **pp, Buffer *res)
for (p++; *p != '$' && *p != '\0'; p++)
continue;
- Buf_AddBytesBetween(res, start, p);
+ Buf_AddRange(res, start, p);
*pp = p;
}
/*
- * Expand all variable expressions like $V, ${VAR}, $(VAR:Modifiers) in the
+ * Expand all expressions like $V, ${VAR}, $(VAR:Modifiers) in the
* given string.
*
* Input:
- * str The string in which the variable expressions are
+ * str The string in which the expressions are
* expanded.
* scope The scope in which to start searching for
* variables. The other scopes are searched as well.
* emode The mode for parsing or evaluating subexpressions.
*/
-VarParseResult
-Var_Subst(const char *str, GNode *scope, VarEvalMode emode, char **out_res)
+char *
+Var_Subst(const char *str, GNode *scope, VarEvalMode emode)
{
const char *p = str;
Buffer res;
- /* Set true if an error has already been reported,
- * to prevent a plethora of messages when recursing */
- /* XXX: Why is the 'static' necessary here? */
+ /*
+ * Set true if an error has already been reported, to prevent a
+ * plethora of messages when recursing
+ */
static bool errorReported;
Buf_Init(&res);
@@ -4706,8 +4694,20 @@ Var_Subst(const char *str, GNode *scope, VarEvalMode emode, char **out_res)
VarSubstPlain(&p, &res);
}
- *out_res = Buf_DoneDataCompact(&res);
- return VPR_OK;
+ return Buf_DoneDataCompact(&res);
+}
+
+void
+Var_Expand(FStr *str, GNode *scope, VarEvalMode emode)
+{
+ char *expanded;
+
+ if (strchr(str->str, '$') == NULL)
+ return;
+ expanded = Var_Subst(str->str, scope, emode);
+ /* TODO: handle errors */
+ FStr_Done(str);
+ *str = FStr_InitOwn(expanded);
}
/* Initialize the variables module. */
@@ -4732,6 +4732,14 @@ Var_Stats(void)
HashTable_DebugStats(&SCOPE_GLOBAL->vars, "Global variables");
}
+static int
+StrAsc(const void *sa, const void *sb)
+{
+ return strcmp(
+ *((const char *const *)sa), *((const char *const *)sb));
+}
+
+
/* Print all variables in a scope, sorted by name. */
void
Var_Dump(GNode *scope)
@@ -4748,12 +4756,13 @@ Var_Dump(GNode *scope)
*(const char **)Vector_Push(&vec) = hi.entry->key;
varnames = vec.items;
- qsort(varnames, vec.len, sizeof varnames[0], str_cmp_asc);
+ qsort(varnames, vec.len, sizeof varnames[0], StrAsc);
for (i = 0; i < vec.len; i++) {
const char *varname = varnames[i];
- Var *var = HashTable_FindValue(&scope->vars, varname);
- debug_printf("%-16s = %s\n", varname, var->val.data);
+ const Var *var = HashTable_FindValue(&scope->vars, varname);
+ debug_printf("%-16s = %s%s\n", varname,
+ var->val.data, ValueDescription(var->val.data));
}
Vector_Done(&vec);