From 36cd305ade2cf16d891e6f78404cf77196d60a02 Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Thu, 17 Nov 2022 15:29:10 -0800 Subject: [PATCH] preproc: add %abs(), modify %num(), refactor Add the %abs() function, to produce the absolute value as an always-positive decimal constant. Change the order of the arguments for %num(). Refactor the handling of optional arguments, to reduce the amount of redundant code. This is currently only used for builtin functions, but might be extended in the future. Signed-off-by: H. Peter Anvin --- asm/preproc.c | 208 ++++++++++++++++++++++++++++++---------------- doc/nasmdoc.src | 17 +++- nasmlib/readnum.c | 11 +-- test/numtest.asm | 47 +++++++++++ 4 files changed, 202 insertions(+), 81 deletions(-) create mode 100644 test/numtest.asm diff --git a/asm/preproc.c b/asm/preproc.c index 8f3a2cb5..c59ba4fb 100644 --- a/asm/preproc.c +++ b/asm/preproc.c @@ -201,20 +201,24 @@ typedef Token *(*ExpandSMacro)(const SMacro *s, Token **params, int nparams); /* * Store the definition of a single-line macro. * - * Note: SPARM_VARADIC is only used by internal "magic" macros. + * Note: for user-defined macros, SPARM_VARADIC and SPARM_DEFAULT are + * currently never set, and SPARM_OPTIONAL is set if and only + * if SPARM_GREEDY is set. */ enum sparmflags { - SPARM_PLAIN = 0, - SPARM_EVAL = 1, /* Evaluate as a numeric expression (=) */ - SPARM_STR = 2, /* Convert to quoted string ($) */ - SPARM_NOSTRIP = 4, /* Don't strip braces (!) */ - SPARM_GREEDY = 8, /* Greedy final parameter (+) */ - SPARM_VARADIC = 16 /* Zero or more individual arguments (...) */ + SPARM_PLAIN = 0, + SPARM_EVAL = 1, /* Evaluate as a numeric expression (=) */ + SPARM_STR = 2, /* Convert to quoted string ($) */ + SPARM_NOSTRIP = 4, /* Don't strip braces (!) */ + SPARM_GREEDY = 8, /* Greedy final parameter (+) */ + SPARM_VARADIC = 16, /* Any number of separate arguments */ + SPARM_OPTIONAL = 32 /* Optional argument */ }; struct smac_param { Token name; enum sparmflags flags; + const Token *def; /* Default, if any */ }; struct SMacro { @@ -224,7 +228,8 @@ struct SMacro { ExpandSMacro expand; intorptr expandpvt; struct smac_param *params; - int nparam; + int nparam; /* length of the params structure */ + int nparam_min; /* allows < nparam arguments */ int in_progress; bool recursive; bool varadic; /* greedy or supports > nparam arguments */ @@ -978,6 +983,8 @@ static void free_smacro_members(SMacro *s) for (i = 0; i < s->nparam; i++) { if (s->params[i].name.len > INLINE_TEXT) nasm_free(s->params[i].name.text.p.ptr); + if (s->params[i].def) + free_tlist((Token *)s->params[i].def); } nasm_free(s->params); } @@ -2404,8 +2411,9 @@ restart: while (m) { if (!mstrcmp(m->name, name, m->casesense && nocase) && - (nparam <= 0 || m->nparam == 0 || nparam == m->nparam || - (m->varadic && nparam >= m->nparam-1))) { + (nparam <= 0 || m->nparam == 0 || + (nparam >= m->nparam_min && + (m->varadic || nparam <= m->nparam)))) { if (m->alias && !find_alias) { if (!ppconf.noaliases) { name = tok_text(m->expansion); @@ -3015,7 +3023,7 @@ static int parse_smacro_template(Token ***tpp, SMacro *tmpl) flags |= SPARM_NOSTRIP; break; case '+': - flags |= SPARM_GREEDY; + flags |= SPARM_GREEDY|SPARM_OPTIONAL; greedy = true; break; case ',': @@ -3058,9 +3066,9 @@ finish: } *tpp = tn; if (tmpl) { - tmpl->nparam = nparam; - tmpl->varadic = greedy; - tmpl->params = params; + tmpl->nparam = nparam; + tmpl->varadic = greedy; + tmpl->params = params; } return nparam; } @@ -3185,11 +3193,12 @@ static SMacro *define_smacro(const char *mname, bool casesense, *smhead = smac; } - smac->name = nasm_strdup(mname); - smac->casesense = casesense; - smac->expansion = reverse_tokens(expansion); - smac->expand = smacro_expand_default; - smac->nparam = nparam; + smac->name = nasm_strdup(mname); + smac->casesense = casesense; + smac->expansion = reverse_tokens(expansion); + smac->expand = smacro_expand_default; + smac->nparam = nparam; + smac->nparam_min = nparam; if (tmpl) { smac->params = tmpl->params; smac->alias = tmpl->alias; @@ -3198,9 +3207,21 @@ static SMacro *define_smacro(const char *mname, bool casesense, smac->expand = tmpl->expand; smac->expandpvt = tmpl->expandpvt; } - if (nparam && (tmpl->params[nparam-1].flags & - (SPARM_GREEDY|SPARM_VARADIC))) - smac->varadic = true; + if (nparam) { + int nparam_min = nparam; + + smac->varadic = + !!(tmpl->params[nparam-1].flags & + (SPARM_GREEDY|SPARM_VARADIC)); + + while (nparam_min > 1) { + if (!(tmpl->params[nparam_min-1].flags & SPARM_OPTIONAL)) + break; + nparam_min--; + } + + smac->nparam_min = nparam_min; + } } if (ppdbg & (PDBG_SMACROS|PDBG_LIST_SMACROS)) { list_smacro_def((smac->alias ? PP_DEFALIAS : PP_DEFINE) @@ -5690,10 +5711,9 @@ static SMacro *expand_one_smacro(Token ***tpp) } if (!mstrcmp(m->name, mname, m->casesense)) { - if (nparam == m->nparam) + if (nparam >= m->nparam_min && + (m->varadic || nparam <= m->nparam)) break; /* It's good */ - if (m->varadic && nparam >= m->nparam-1) - break; /* Also good */ } m = m->next; } @@ -5717,13 +5737,19 @@ static SMacro *expand_one_smacro(Token ***tpp) bool bracketed = false; bool bad_bracket = false; enum sparmflags flags; + const struct smac_param *mparm; - if (m->params[m->nparam-1].flags & SPARM_GREEDY) - nparam = m->nparam; + if (nparam > m->nparam) { + if (m->params[m->nparam-1].flags & SPARM_GREEDY) + nparam = m->nparam; + } else if (nparam < m->nparam) { + nparam = m->nparam; /* Missing optional arguments = empty */ + } paren = 1; nasm_newn(params, nparam); i = 0; - flags = m->params[i].flags; + mparm = m->params; + flags = mparm->flags; phead = pep = ¶ms[i]; *pep = NULL; @@ -5755,8 +5781,10 @@ static SMacro *expand_one_smacro(Token ***tpp) *pep = NULL; bracketed = false; skip = true; - if (!(flags & SPARM_VARADIC)) - flags = m->params[i].flags; + if (!(flags & SPARM_VARADIC)) { + mparm++; + flags = mparm->flags; + } } break; @@ -5812,11 +5840,15 @@ static SMacro *expand_one_smacro(Token ***tpp) /* * Possible further processing of parameters. Note that the * ordering matters here. + * + * mparm points to the current parameter specification + * structure (struct smac_param); this may not match the index + * i in the case of varadic parameters. */ - flags = 0; - for (i = 0; i < nparam; i++) { - if (!(flags & SPARM_VARADIC)) - flags = m->params[i].flags; + for (i = 0, mparm = m->params; + i < nparam; + i++, mparm += !(flags & SPARM_VARADIC)) { + const enum sparmflags flags = mparm->flags; if (flags & SPARM_EVAL) { /* Evaluate this parameter as a number */ @@ -5828,8 +5860,16 @@ static SMacro *expand_one_smacro(Token ***tpp) eval_param = zap_white(expand_smacro_noreset(params[i])); params[i] = NULL; - if ((flags & (SPARM_GREEDY|SPARM_VARADIC)) && !eval_param) - continue; + if (!eval_param) { + /* empty argument */ + if (mparm->def) { + params[i] = dup_tlist(mparm->def, NULL); + continue; + } else if (flags & SPARM_OPTIONAL) { + continue; + } + /* otherwise, allow evaluate() to generate an error */ + } pps.tptr = eval_param; pps.ntokens = -1; @@ -7090,19 +7130,11 @@ static Token * stdmac_cond_sel(const SMacro *s, Token **params, int nparams) { int64_t which; - bool err; /* * params[0] will have been generated by make_tok_num. */ - which = get_tok_num(params[0], &err); - if (err) { - /* - * Not a valid number; an error message will already have - * been generated by expand_one_smacro(). - */ - return NULL; - } + which = get_tok_num(params[0], NULL); if (s->expandpvt.u) { /* Booleanize (for %cond): true -> 1, false -> 2 (else) */ @@ -7133,7 +7165,7 @@ stdmac_cond_sel(const SMacro *s, Token **params, int nparams) return new_Token(NULL, tok_smac_param(which), "", 0); } -/* %count() */ +/* %count() function */ static Token * stdmac_count(const SMacro *s, Token **params, int nparams) { @@ -7143,9 +7175,9 @@ stdmac_count(const SMacro *s, Token **params, int nparams) return make_tok_num(NULL, nparams); } -/* %num() */ +/* %num() function */ static Token * -stdmac_num(const SMacro *s, Token **params, int nparams) +stdmac_num(const SMacro *s, Token **params, int nparam) { static const char num_digits[] = "0123456789" @@ -7155,7 +7187,8 @@ stdmac_num(const SMacro *s, Token **params, int nparams) int64_t parm[3]; uint64_t n; int64_t dparm, bparm; - int i, nd; + unsigned int i; + int nd; unsigned int base; char numstr[256]; char * const endstr = numstr + sizeof numstr - 1; @@ -7164,24 +7197,14 @@ stdmac_num(const SMacro *s, Token **params, int nparams) char *p; bool moredigits; - if (nparams < 1 || nparams > (int)ARRAY_SIZE(parm)) { - nasm_nonfatal("invalid number of parameters to %s()", s->name); - return NULL; - } + (void)nparam; - parm[1] = 10; /* Default base */ - parm[2] = -1; /* Default digits */ - - for (i = 0; i < nparams; i++) { - bool err; - parm[i] = get_tok_num(params[i], &err); - if (err) - return NULL; - } + for (i = 0; i < (int)ARRAY_SIZE(parm); i++) + parm[i] = get_tok_num(params[i], NULL); n = parm[0]; - bparm = parm[1]; - dparm = parm[2]; + dparm = parm[1]; + bparm = parm[2]; if (bparm < 2 || bparm > maxbase) { nasm_nonfatal("invalid base %"PRId64" given to %s()", @@ -7190,7 +7213,7 @@ stdmac_num(const SMacro *s, Token **params, int nparams) } base = bparm; - + if (dparm < -maxlen || dparm > maxlen) { nasm_nonfatal("digit count %"PRId64" specified to %s() too large", dparm, s->name); @@ -7217,6 +7240,29 @@ stdmac_num(const SMacro *s, Token **params, int nparams) return new_Token(NULL, TOKEN_STR, p, endstr - p); } +/* %abs() function */ +static Token * +stdmac_abs(const SMacro *s, Token **params, int nparam) +{ + char numbuf[24]; + int len; + int64_t v; + uint64_t u; + + (void)s; + (void)nparam; + + v = get_tok_num(params[0], NULL); + u = v < 0 ? -v : v; + + /* + * Don't use make_tok_num() here, to make sure we don't emit + * a minus sign for the case of v = -2^63 + */ + len = snprintf(numbuf, sizeof numbuf, "%"PRIu64, u); + return new_Token(NULL, TOKEN_NUM, numbuf, len); +} + /* Add magic standard macros */ struct magic_macros { const char *name; @@ -7239,9 +7285,9 @@ static void pp_add_magic_stdmac(void) { "__?LINE?__", true, 0, 0, stdmac_line }, { "__?BITS?__", true, 0, 0, stdmac_bits }, { "__?PTR?__", true, 0, 0, stdmac_ptr }, + { "%abs", false, 1, SPARM_EVAL, stdmac_abs }, { "%count", false, 1, SPARM_VARADIC, stdmac_count }, { "%eval", false, 1, SPARM_EVAL|SPARM_VARADIC, stdmac_join }, - { "%num", false, 1, SPARM_EVAL|SPARM_VARADIC, stdmac_num }, { "%str", false, 1, SPARM_GREEDY|SPARM_STR, stdmac_join }, { "%strcat", false, 1, SPARM_GREEDY, stdmac_strcat }, { "%strlen", false, 1, 0, stdmac_strlen }, @@ -7271,7 +7317,8 @@ static void pp_add_magic_stdmac(void) nasm_newn(tmpl.params, m->nparam); for (i = m->nparam-1; i >= 0; i--) { tmpl.params[i].flags = flags; - flags &= ~(SPARM_GREEDY|SPARM_VARADIC); /* Last arg only */ + /* These flags for the last arg only */ + flags &= ~(SPARM_GREEDY|SPARM_VARADIC|SPARM_OPTIONAL); } } define_smacro(m->name, m->casesense, NULL, &tmpl); @@ -7283,21 +7330,36 @@ static void pp_add_magic_stdmac(void) } /* %sel() function */ + nasm_zero(tmpl); tmpl.nparam = 2; tmpl.recursive = true; tmpl.expand = stdmac_cond_sel; - nasm_newn(tmpl.params, 2); + nasm_newn(tmpl.params, tmpl.nparam); tmpl.params[0].flags = SPARM_EVAL; tmpl.params[1].flags = SPARM_VARADIC; define_smacro("%sel", false, NULL, &tmpl); /* %cond() function, a variation on %sel */ + tmpl.nparam = 3; tmpl.expandpvt.u = 1; /* Booleanize */ - for (tmpl.nparam = 2; tmpl.nparam <= 3; tmpl.nparam++) { - nasm_newn(tmpl.params, tmpl.nparam); - tmpl.params[0].flags = SPARM_EVAL; - define_smacro("%cond", false, NULL, &tmpl); - } + nasm_newn(tmpl.params, tmpl.nparam); + tmpl.params[0].flags = SPARM_EVAL; + tmpl.params[1].flags = 0; + tmpl.params[2].flags = SPARM_OPTIONAL; + define_smacro("%cond", false, NULL, &tmpl); + + /* %num() function */ + nasm_zero(tmpl); + tmpl.nparam = 3; + tmpl.expand = stdmac_num; + tmpl.recursive = true; + nasm_newn(tmpl.params, tmpl.nparam); + tmpl.params[0].flags = SPARM_EVAL; + tmpl.params[1].flags = SPARM_EVAL|SPARM_OPTIONAL; + tmpl.params[1].def = make_tok_num(NULL, -1); + tmpl.params[2].flags = SPARM_EVAL|SPARM_OPTIONAL; + tmpl.params[2].def = make_tok_num(NULL, 10); + define_smacro("%num", false, NULL, &tmpl); /* %is...() macro functions */ nasm_zero(tmpl); diff --git a/doc/nasmdoc.src b/doc/nasmdoc.src index 39c38480..0883be4b 100644 --- a/doc/nasmdoc.src +++ b/doc/nasmdoc.src @@ -2860,6 +2860,13 @@ arguments, and can be used in any context where single-line macro expansion would be performed. Preprocessor functions were introduced in NASM 2.16. +\S{f_abs} \i\c{%abs()} Function + +The \c{%abs()} function evaluates its first argument as an expression, +and then emits the absolute value. This will always be emitted as a +single token containing a decimal number; no minus sign will be +emitted even if the input value is the maximum negative number. + \S{f_cond} \i\c{%cond()} Function The \c{%cond()} function evaluates its first argument as an @@ -2934,9 +2941,9 @@ argument to the conditional using \c{\{\}}: The \c{%num()} function evaluates its arguments as expressions, and then produces a quoted string encoding the first argument as an -\e{unsigned} integer. The second argument is the encoding base (from 2 -to 64, default 10) and the third argument is the desired number of -digits (max 253, default -1). +\e{unsigned} integer. The second argument is the desired number of +digits (max 253, default -1), and the second argument is the encoding +base (from 2 to 64, default 10.) Only the first argument is required. @@ -2949,6 +2956,10 @@ The full 64-symbol set used is, in order: \c 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@_ +If a \e{signed} number needs to be converted to a string, use +\c{%abs()}, \c{%cond()}, and \c{%strcat()} to format the signed number +string to your specific output requirements. + \S{f_sel} \i\c{%sel()} Function diff --git a/nasmlib/readnum.c b/nasmlib/readnum.c index f2ec20cf..947bda53 100644 --- a/nasmlib/readnum.c +++ b/nasmlib/readnum.c @@ -75,7 +75,8 @@ int64_t readnum(const char *str, bool *error) bool warn = false; int sign = 1; - *error = false; + if (error) + *error = true; while (nasm_isspace(*r)) r++; /* find start of number */ @@ -97,8 +98,7 @@ int64_t readnum(const char *str, bool *error) len = q-r; if (!len) { /* Not numeric */ - *error = true; - return 0; + return 0; } /* @@ -150,8 +150,7 @@ int64_t readnum(const char *str, bool *error) if (*r != '_') { if (*r < '0' || (*r > '9' && *r < 'A') || (digit = numvalue(*r)) >= radix) { - *error = true; - return 0; + return 0; } if (result > checklimit || (result == checklimit && digit >= last)) { @@ -174,5 +173,7 @@ int64_t readnum(const char *str, bool *error) str); } + if (error) + *error = false; return result * sign; } diff --git a/test/numtest.asm b/test/numtest.asm new file mode 100644 index 00000000..bfe0237e --- /dev/null +++ b/test/numtest.asm @@ -0,0 +1,47 @@ +%define a 64 +%define b -30 + + dq a*b + db %num(a*b,,16), `\n` + db %num(a*b,16,16), `\n` + + db %num(a*b), `\n` + db %num(a*b,10), `\n` + db %num(a*b,3), `\n` + db %num(a*b,-3), `\n` + db %num(a*b,10,2), `\n` + db %num(a*b,-10,2), `\n` + db %num(a*b,,2), `\n` + + dq -a*b + db %num(-a*b,,16), `\n` + db %num(-a*b,16,16), `\n` + + db %num(-a*b), `\n` + db %num(-a*b,10), `\n` + db %num(-a*b,3), `\n` + db %num(-a*b,-3), `\n` + db %num(-a*b,10,2), `\n` + db %num(-a*b,-10,2), `\n` + + dq %abs(a*b) + db %num(%abs(a*b),,16), `\n` + db %num(%abs(a*b),16,16), `\n` + + db %num(%abs(a*b)), `\n` + db %num(%abs(a*b),10), `\n` + db %num(%abs(a*b),3), `\n` + db %num(%abs(a*b),-3), `\n` + db %num(%abs(a*b),10,2), `\n` + db %num(%abs(a*b),-10,2), `\n` + + dq %abs(-a*b) + db %num(%abs(-a*b),,16), `\n` + db %num(%abs(-a*b),16,16), `\n` + + db %num(%abs(-a*b)), `\n` + db %num(%abs(-a*b),10), `\n` + db %num(%abs(-a*b),3), `\n` + db %num(%abs(-a*b),-3), `\n` + db %num(%abs(-a*b),10,2), `\n` + db %num(%abs(-a*b),-10,2), `\n`