Add support for primary expansion to the expression parser
And also wire it to %[ ... ] as new syntax to expand expressions. We'll add short-circuit support in the next commit.
This commit is contained in:
parent
bebd15081a
commit
cb4e5e755a
|
@ -501,7 +501,7 @@ retry:
|
|||
else
|
||||
checkCondition = spec->readStack->readable;
|
||||
if (checkCondition) {
|
||||
match = rpmExprBool(s);
|
||||
match = rpmExprBool(s, 0);
|
||||
if (match < 0) {
|
||||
rpmlog(RPMLOG_ERR,
|
||||
_("%s:%d: bad %s condition: %s\n"),
|
||||
|
|
|
@ -50,12 +50,7 @@ if the flag was present. The negative form, "%{!-f:Y}", expanding to (the
|
|||
expansion of) Y if -f was *not* present, is also supported.
|
||||
|
||||
In addition to the "%{...}" form, shell expansion can be performed
|
||||
using "%(shell command)". The expansion of "%(...)" is the output of
|
||||
(the expansion of) ... fed to /bin/sh. For example, "%(date
|
||||
+%%y%%m%%d)" expands to the string "YYMMDD" (final newline is
|
||||
deleted). Note the 2nd % needed to escape the arguments to /bin/date.
|
||||
There is currently an 8K limit on the size that this macro can expand
|
||||
to.
|
||||
using "%(shell command)".
|
||||
|
||||
\section macros_builtin Builtin Macros
|
||||
|
||||
|
@ -232,6 +227,45 @@ replaced by the first argument to the macro, but only if the macro
|
|||
is invoked as "%mymacro 5". Invoking as "%{mymacro} 5" will not work
|
||||
as desired in this case.
|
||||
|
||||
\section macros_shell_expansion Shell Expansion
|
||||
|
||||
Shell expansion can be performed using "%(shell command)". The expansion
|
||||
of "%(...)" is the output of (the expansion of) ... fed to /bin/sh.
|
||||
For example, "%(date +%%y%%m%%d)" expands to the string "YYMMDD" (final
|
||||
newline is deleted). Note the 2nd % needed to escape the arguments to
|
||||
/bin/date.
|
||||
|
||||
\section macros_expression_expansion Expression Expansion
|
||||
|
||||
Expression expansion can be performed using "%[expression]". An
|
||||
expression consists of terms that can be combined using
|
||||
operators. Rpm supports two kinds of terms, numbers made up
|
||||
from digits and strings enclosed in double quotes. Rpm will
|
||||
expand macros when evaluating terms.
|
||||
|
||||
You can use the standard operators to combine terms: logical
|
||||
operators &&, ||, !, relational operators !=, ==, <, > , <=, >=,
|
||||
arithmetic operators +, -, /, *, the ternary operator ? :, and
|
||||
parentheses. For example, "%[ 3 + 4 * (1 + %two) ]" will expand
|
||||
to "15" if "%two" expands to "2".
|
||||
|
||||
Note that the "%[expression]" expansion is different to the
|
||||
"%{expr:expression}" macro. With the latter, the macros in the
|
||||
expression are expanded first and then the expression is
|
||||
evaluated (without re-expanding the terms). Thus
|
||||
\verbatim
|
||||
rpm --define 'foo 1 + 2' --eval '%{expr:%foo}'
|
||||
\endverbatim
|
||||
will print "3". Using '%[%foo]' instead will result in the
|
||||
error that "1 + 2" is not a number.
|
||||
|
||||
Doing the macro expansion when evaluating the terms has two
|
||||
advantages. First, it allows rpm to do correct short-circuit
|
||||
processing when evaluation logical operators. Second, the
|
||||
expansion result does not influence the expression parsing,
|
||||
e.g. '%["%file"] will even work if the "%file" macro expands
|
||||
to a string that contains a double quote.
|
||||
|
||||
\section macros_commandline Command Line Options
|
||||
|
||||
When the command line option "--define 'macroname value'" allows the
|
||||
|
|
|
@ -22,7 +22,7 @@ librpmio_la_SOURCES = \
|
|||
rpmpgp.c rpmsq.c rpmsw.c url.c \
|
||||
rpmio_internal.h rpmhook.h \
|
||||
rpmstring.c rpmfileutil.c rpmglob.c \
|
||||
rpmkeyring.c rpmstrpool.c
|
||||
rpmkeyring.c rpmstrpool.c rpmmacro_internal.h
|
||||
|
||||
if WITH_BEECRYPT
|
||||
librpmio_la_SOURCES += digest_beecrypt.c
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <rpm/rpmlog.h>
|
||||
#include <rpm/rpmmacro.h>
|
||||
#include "rpmio/rpmmacro_internal.h"
|
||||
#include "debug.h"
|
||||
|
||||
/* #define DEBUG_PARSER 1 */
|
||||
|
@ -105,6 +106,7 @@ typedef struct _parseState {
|
|||
const char *p; /*!< current position in expression string */
|
||||
int nextToken; /*!< current lookahead token */
|
||||
Value tokenValue; /*!< valid when TOK_INTEGER or TOK_STRING */
|
||||
int flags; /*!< parser flags */
|
||||
} *ParseState;
|
||||
|
||||
static void exprErr(const struct _parseState *state, const char *msg,
|
||||
|
@ -188,6 +190,40 @@ static const char *prToken(int val)
|
|||
}
|
||||
#endif /* DEBUG_PARSER */
|
||||
|
||||
static char *getValuebuf(ParseState state, const char *p, size_t size)
|
||||
{
|
||||
char *temp;
|
||||
temp = xmalloc(size + 1);
|
||||
memcpy(temp, p, size);
|
||||
temp[size] = '\0';
|
||||
if (size && (state->flags & RPMEXPR_EXPAND) != 0) {
|
||||
char *temp2 = NULL;
|
||||
rpmExpandMacros(NULL, temp, &temp2, 0);
|
||||
free(temp);
|
||||
temp = temp2;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
static size_t skipMacro(const char *p, size_t ts)
|
||||
{
|
||||
const char *pe;
|
||||
if (p[ts] == '%')
|
||||
return ts + 1; /* %% is special */
|
||||
pe = findMacroEnd(p + ts);
|
||||
return pe ? pe - p : strlen(p);
|
||||
}
|
||||
|
||||
static int wellformedInteger(const char *p)
|
||||
{
|
||||
if (*p == '-')
|
||||
p++;
|
||||
for (; *p; p++)
|
||||
if (!risdigit(*p))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param state expression parser state
|
||||
*/
|
||||
|
@ -196,6 +232,7 @@ static int rdToken(ParseState state)
|
|||
int token;
|
||||
Value v = NULL;
|
||||
const char *p = state->p;
|
||||
int expand = (state->flags & RPMEXPR_EXPAND) != 0;
|
||||
|
||||
/* Skip whitespace before the next token. */
|
||||
while (*p && risspace(*p)) p++;
|
||||
|
@ -279,14 +316,29 @@ static int rdToken(ParseState state)
|
|||
break;
|
||||
|
||||
default:
|
||||
if (risdigit(*p)) {
|
||||
if (risdigit(*p) || (*p == '%' && expand)) {
|
||||
char *temp;
|
||||
size_t ts;
|
||||
|
||||
for (ts=1; p[ts] && risdigit(p[ts]); ts++);
|
||||
temp = xmalloc(ts+1);
|
||||
memcpy(temp, p, ts);
|
||||
temp[ts] = '\0';
|
||||
for (ts=0; p[ts]; ts++) {
|
||||
if (p[ts] == '%' && expand)
|
||||
ts = skipMacro(p, ts + 1) - 1;
|
||||
else if (!risdigit(p[ts]))
|
||||
break;
|
||||
}
|
||||
temp = getValuebuf(state, p, ts);
|
||||
if (!temp)
|
||||
goto err;
|
||||
/* make sure that the expanded buffer only contains digits */
|
||||
if (expand && !wellformedInteger(temp)) {
|
||||
if (risalpha(*temp))
|
||||
exprErr(state, _("macro expansion returned a bare word, please use \"...\""), p + 1);
|
||||
else
|
||||
exprErr(state, _("macro expansion did not return an integer"), p + 1);
|
||||
rpmlog(RPMLOG_ERR, _("expanded string: %s\n"), temp);
|
||||
free(temp);
|
||||
goto err;
|
||||
}
|
||||
p += ts-1;
|
||||
token = TOK_INTEGER;
|
||||
v = valueMakeInteger(atoi(temp));
|
||||
|
@ -301,14 +353,19 @@ static int rdToken(ParseState state)
|
|||
size_t ts;
|
||||
|
||||
p++;
|
||||
for (ts=0; p[ts] && p[ts] != '\"'; ts++);
|
||||
for (ts=0; p[ts]; ts++) {
|
||||
if (p[ts] == '%' && expand)
|
||||
ts = skipMacro(p, ts + 1) - 1;
|
||||
else if (p[ts] == '\"')
|
||||
break;
|
||||
}
|
||||
if (p[ts] != '\"') {
|
||||
exprErr(state, _("unterminated string in expression"), p + ts + 1);
|
||||
goto err;
|
||||
}
|
||||
temp = xmalloc(ts+1);
|
||||
memcpy(temp, p, ts);
|
||||
temp[ts] = '\0';
|
||||
temp = getValuebuf(state, p, ts);
|
||||
if (!temp)
|
||||
goto err;
|
||||
p += ts;
|
||||
token = TOK_STRING;
|
||||
v = valueMakeString( temp );
|
||||
|
@ -732,7 +789,7 @@ err:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
int rpmExprBool(const char *expr)
|
||||
int rpmExprBool(const char *expr, int flags)
|
||||
{
|
||||
struct _parseState state;
|
||||
int result = -1;
|
||||
|
@ -744,6 +801,7 @@ int rpmExprBool(const char *expr)
|
|||
state.p = state.str = xstrdup(expr);
|
||||
state.nextToken = 0;
|
||||
state.tokenValue = NULL;
|
||||
state.flags = flags;
|
||||
if (rdToken(&state))
|
||||
goto exit;
|
||||
|
||||
|
@ -777,7 +835,7 @@ exit:
|
|||
return result;
|
||||
}
|
||||
|
||||
char *rpmExprStr(const char *expr)
|
||||
char *rpmExprStr(const char *expr, int flags)
|
||||
{
|
||||
struct _parseState state;
|
||||
char *result = NULL;
|
||||
|
@ -789,6 +847,7 @@ char *rpmExprStr(const char *expr)
|
|||
state.p = state.str = xstrdup(expr);
|
||||
state.nextToken = 0;
|
||||
state.tokenValue = NULL;
|
||||
state.flags = flags;
|
||||
if (rdToken(&state))
|
||||
goto exit;
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ extern int optind;
|
|||
#include "rpmio/rpmlua.h"
|
||||
#endif
|
||||
|
||||
#include "rpmio/rpmmacro_internal.h"
|
||||
#include "debug.h"
|
||||
|
||||
enum macroFlags_e {
|
||||
|
@ -221,7 +222,7 @@ rdcl(char * buf, size_t size, FILE *f)
|
|||
char *q = buf - 1; /* initialize just before buffer. */
|
||||
size_t nb = 0;
|
||||
size_t nread = 0;
|
||||
int pc = 0, bc = 0;
|
||||
int pc = 0, bc = 0, xc = 0;
|
||||
int nlines = 0;
|
||||
char *p = buf;
|
||||
|
||||
|
@ -247,6 +248,7 @@ rdcl(char * buf, size_t size, FILE *f)
|
|||
switch (*(p+1)) {
|
||||
case '{': p++, bc++; break;
|
||||
case '(': p++, pc++; break;
|
||||
case '[': p++, xc++; break;
|
||||
case '%': p++; break;
|
||||
}
|
||||
break;
|
||||
|
@ -254,9 +256,11 @@ rdcl(char * buf, size_t size, FILE *f)
|
|||
case '}': if (bc > 0) bc--; break;
|
||||
case '(': if (pc > 0) pc++; break;
|
||||
case ')': if (pc > 0) pc--; break;
|
||||
case '[': if (xc > 0) xc++; break;
|
||||
case ']': if (xc > 0) xc--; break;
|
||||
}
|
||||
}
|
||||
if (nb == 0 || (*q != '\\' && !bc && !pc) || *(q+1) == '\0') {
|
||||
if (nb == 0 || (*q != '\\' && !bc && !pc && !xc) || *(q+1) == '\0') {
|
||||
*(++q) = '\0'; /* trim trailing \r, \n */
|
||||
break;
|
||||
}
|
||||
|
@ -499,6 +503,28 @@ exit:
|
|||
_free(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand an expression into target buffer.
|
||||
* @param mb macro expansion state
|
||||
* @param expr expression
|
||||
* @param len no. bytes in expression
|
||||
*/
|
||||
static void doExpressionExpansion(MacroBuf mb, const char * expr, size_t len)
|
||||
{
|
||||
char *buf = xmalloc(len + 1);
|
||||
char *result;
|
||||
strncpy(buf, expr, len);
|
||||
buf[len] = 0;
|
||||
result = rpmExprStr(buf, RPMEXPR_EXPAND);
|
||||
if (!result) {
|
||||
mb->error = 1;
|
||||
goto exit;
|
||||
}
|
||||
mbAppendStr(mb, result);
|
||||
free(result);
|
||||
exit:
|
||||
_free(buf);
|
||||
}
|
||||
|
||||
static unsigned int getncpus(void)
|
||||
{
|
||||
|
@ -663,7 +689,7 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody)
|
|||
be += strlen(b);
|
||||
s = se; /* move scan forward */
|
||||
} else { /* otherwise free-field */
|
||||
int bc = 0, pc = 0;
|
||||
int bc = 0, pc = 0, xc = 0;
|
||||
while (*s && (bc || pc || !iseol(*s))) {
|
||||
switch (*s) {
|
||||
case '\\':
|
||||
|
@ -676,6 +702,7 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody)
|
|||
switch (*(s+1)) {
|
||||
case '{': *be++ = *s++; bc++; break;
|
||||
case '(': *be++ = *s++; pc++; break;
|
||||
case '[': *be++ = *s++; xc++; break;
|
||||
case '%': *be++ = *s++; break;
|
||||
}
|
||||
break;
|
||||
|
@ -683,12 +710,14 @@ doDefine(MacroBuf mb, const char * se, int level, int expandbody)
|
|||
case '}': if (bc > 0) bc--; break;
|
||||
case '(': if (pc > 0) pc++; break;
|
||||
case ')': if (pc > 0) pc--; break;
|
||||
case '[': if (xc > 0) xc++; break;
|
||||
case ']': if (xc > 0) xc--; break;
|
||||
}
|
||||
*be++ = *s++;
|
||||
}
|
||||
*be = '\0';
|
||||
|
||||
if (bc || pc) {
|
||||
if (bc || pc || xc) {
|
||||
mbErr(mb, 1, _("Macro %%%s has unterminated body\n"), n);
|
||||
se = s; /* XXX W2DO? */
|
||||
goto exit;
|
||||
|
@ -1099,7 +1128,7 @@ doFoo(MacroBuf mb, int chkexist, int negate, const char * f, size_t fn,
|
|||
} else if (STREQ("expand", f, fn) || STREQ("verbose", f, fn)) {
|
||||
b = buf;
|
||||
} else if (STREQ("expr", f, fn)) {
|
||||
char *expr = rpmExprStr(buf);
|
||||
char *expr = rpmExprStr(buf, 0);
|
||||
if (expr) {
|
||||
free(buf);
|
||||
b = buf = expr;
|
||||
|
@ -1227,13 +1256,21 @@ static const char *setNegateAndCheck(const char *str, int *pnegate, int *pchkexi
|
|||
return str;
|
||||
}
|
||||
|
||||
static const char *findMacroEnd(const char *str)
|
||||
/**
|
||||
* Find the end of a macro call
|
||||
* @param str pointer to the character after the initial '%'
|
||||
* @return pointer to the next character after the macro
|
||||
*/
|
||||
RPM_GNUC_INTERNAL
|
||||
const char *findMacroEnd(const char *str)
|
||||
{
|
||||
int c;
|
||||
if (*str == '(')
|
||||
return matchchar(str, *str, ')');
|
||||
if (*str == '{')
|
||||
return matchchar(str, *str, '}');
|
||||
if (*str == '[')
|
||||
return matchchar(str, *str, ']');
|
||||
while (*str == '?' || *str == '!')
|
||||
str++;
|
||||
if (*str == '-') /* %-f */
|
||||
|
@ -1349,6 +1386,13 @@ expandMacro(MacroBuf mb, const char *src, size_t slen)
|
|||
doShellEscape(mb, s, (se - 1 - s));
|
||||
s = se;
|
||||
continue;
|
||||
case '[': /* %[...] expression expansion */
|
||||
if (mb->macro_trace)
|
||||
printMacro(mb, s, se);
|
||||
s++; /* skip [ */
|
||||
doExpressionExpansion(mb, s, (se - 1 - s));
|
||||
s = se;
|
||||
continue;
|
||||
case '{': /* %{...}/%{...:...} substitution */
|
||||
f = s+1; /* skip { */
|
||||
f = setNegateAndCheck(f, &negate, &chkexist);
|
||||
|
|
|
@ -49,6 +49,9 @@ extern const char * macrofiles;
|
|||
#define addMacro(_mc, _n, _o, _b, _l) rpmPushMacro(_mc, _n, _o, _b, _l)
|
||||
#define delMacro(_mc, _n) rpmPopMacro(_mc, _n)
|
||||
|
||||
/* rpm expression parser flags */
|
||||
#define RPMEXPR_EXPAND (1 << 0) /*!< expand primary terms */
|
||||
|
||||
/** \ingroup rpmmacro
|
||||
* Print macros to file stream.
|
||||
* @param mc macro context (NULL uses global context).
|
||||
|
@ -156,16 +159,18 @@ const char *rpmConfigDir(void);
|
|||
/** \ingroup rpmmacro
|
||||
* Evaluate boolean expression.
|
||||
* @param expr expression to parse
|
||||
* @param flags parser flags
|
||||
* @return
|
||||
*/
|
||||
int rpmExprBool(const char * expr);
|
||||
int rpmExprBool(const char * expr, int flags);
|
||||
|
||||
/** \ingroup rpmmacro
|
||||
* Evaluate string expression.
|
||||
* @param expr expression to parse
|
||||
* @param flags parser flags
|
||||
* @return
|
||||
*/
|
||||
char * rpmExprStr(const char * expr);
|
||||
char * rpmExprStr(const char * expr, int flags);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef _H_MACRO_INTERNAL
|
||||
#define _H_MACRO_INTERNAL
|
||||
|
||||
/** \ingroup rpmio
|
||||
* \file rpmio/rpmmacro_internal.h
|
||||
*
|
||||
* Internal Macro API
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \ingroup rpmmacro
|
||||
* Find the end of a macro call
|
||||
* @param str pointer to the character after the initial '%'
|
||||
* @return pointer to the next character after the macro
|
||||
*/
|
||||
RPM_GNUC_INTERNAL
|
||||
const char *findMacroEnd(const char *str);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _H_ MACRO_INTERNAL */
|
|
@ -336,6 +336,45 @@ runroot rpm \
|
|||
])
|
||||
AT_CLEANUP
|
||||
|
||||
AT_SETUP([expression expansion 1])
|
||||
AT_KEYWORDS([macros])
|
||||
AT_CHECK([[
|
||||
runroot rpm \
|
||||
--define "aaa 5" \
|
||||
--define "bbb 0" \
|
||||
--eval '%[4*1024]' \
|
||||
--eval '%[5 < 1]' \
|
||||
--eval '%[%aaa]' \
|
||||
--eval '%[%{aaa}]' \
|
||||
--eval '%["%{aaa}"]' \
|
||||
--eval '%[%{?ccc}]' \
|
||||
]],
|
||||
[0],
|
||||
[4096
|
||||
0
|
||||
5
|
||||
5
|
||||
5
|
||||
0
|
||||
],
|
||||
[])
|
||||
AT_CLEANUP
|
||||
|
||||
AT_SETUP([expression expansion 2])
|
||||
AT_KEYWORDS([macros])
|
||||
AT_CHECK([[
|
||||
runroot rpm --define "aaa hello" --eval '%[%aaa]'
|
||||
runroot rpm --eval '%[%{foo]'
|
||||
]],
|
||||
[1],
|
||||
[],
|
||||
[error: macro expansion returned a bare word, please use "...": %aaa
|
||||
error: ^
|
||||
error: expanded string: hello
|
||||
error: Unterminated {: {foo
|
||||
])
|
||||
AT_CLEANUP
|
||||
|
||||
AT_SETUP([simple lua --eval])
|
||||
AT_KEYWORDS([macros lua])
|
||||
AT_CHECK([
|
||||
|
|
Loading…
Reference in New Issue