[nasm:nasm-2.16.xx] preproc: add %map() function and radix specifiers

nasm-bot for H. Peter Anvin hpa at zytor.com
Sun Oct 15 22:44:06 PDT 2023


Commit-ID:  34eefd38031921a239ffa9fa9a05a7f5e8591079
Gitweb:     http://repo.or.cz/w/nasm.git?a=commitdiff;h=34eefd38031921a239ffa9fa9a05a7f5e8591079
Author:     H. Peter Anvin <hpa at zytor.com>
AuthorDate: Sun, 15 Oct 2023 22:40:07 -0700
Committer:  H. Peter Anvin <hpa at zytor.com>
CommitDate: Sun, 15 Oct 2023 22:43:28 -0700

preproc: add %map() function and radix specifiers

Add the %map() function which can apply arguments to a macro from a
list.

Allow the user to specify the desired radix for an evaluated
parameter. It doesn't make any direct difference, but can be nice for
debugging or turning into strings.

As part of this, split expand_one_smacro() into two parts: parameter
parsing and macro expansion. This is a very straightforward splitting
of two mostly unrelated pieces of functionality.

Signed-off-by: H. Peter Anvin <hpa at zytor.com>


---
 Makefile.in                      |   3 +-
 asm/preproc.c                    | 712 +++++++++++++++++++++++++--------------
 doc/nasmdoc.src                  |  39 ++-
 include/nasmlib.h                |  33 +-
 nasmlib/{filename.c => numstr.c} |  58 ++--
 nasmlib/readnum.c                |  20 --
 test/badrep.asm                  |  66 ++++
 test/evalerr.asm                 |   6 +
 test/evalmacro.asm               |   6 +-
 test/map.asm                     |   7 +
 10 files changed, 647 insertions(+), 303 deletions(-)

diff --git a/Makefile.in b/Makefile.in
index 95268fd2..5be6fc17 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -115,7 +115,8 @@ LIBOBJ_NW = stdlib/snprintf.$(O) stdlib/vsnprintf.$(O) stdlib/strlcpy.$(O) \
 	nasmlib/file.$(O) nasmlib/mmap.$(O) nasmlib/ilog2.$(O) \
 	nasmlib/realpath.$(O) nasmlib/path.$(O) \
 	nasmlib/filename.$(O) nasmlib/rlimit.$(O) \
-	nasmlib/zerobuf.$(O) nasmlib/readnum.$(O) nasmlib/bsi.$(O) \
+	nasmlib/readnum.$(O) nasmlib/numstr.$(O) \
+	nasmlib/zerobuf.$(O) nasmlib/bsi.$(O) \
 	nasmlib/rbtree.$(O) nasmlib/hashtbl.$(O) \
 	nasmlib/raa.$(O) nasmlib/saa.$(O) \
 	nasmlib/strlist.$(O) \
diff --git a/asm/preproc.c b/asm/preproc.c
index 928b93b6..08734d3c 100644
--- a/asm/preproc.c
+++ b/asm/preproc.c
@@ -214,12 +214,13 @@ enum sparmflags {
     SPARM_VARADIC   =  16,  /* Any number of separate arguments */
     SPARM_OPTIONAL  =  32,  /* Optional argument */
     SPARM_CONDQUOTE =  64,  /* With SPARM_STR, don't re-quote a string */
-    SPARM_HEX       = 128   /* With SPARM_EVAL, generate hexadecimal numbers */
+    SPARM_UNSIGNED  = 128   /* With SPARM_EVAL, generate unsigned numbers */
 };
 
 struct smac_param {
     Token name;
     enum sparmflags flags;
+    char radix;                 /* Radix type for SPARM_EVAL */
     const Token *def;           /* Default, if any */
 };
 
@@ -651,7 +652,8 @@ static Token *expand_smacro(Token * tline);
 static Token *expand_id(Token * tline);
 static Context *get_ctx(const char *name, const char **namep);
 static Token *make_tok_num(Token *next, int64_t val);
-static Token *make_tok_hex(Token *next, int64_t val);
+static Token *
+make_tok_num_radix(Token *next, int64_t val, char radix, bool uns);
 static int64_t get_tok_num(const Token *t, bool *err);
 static Token *make_tok_qstr(Token *next, const char *str);
 static Token *make_tok_qstr_len(Token *next, const char *str, size_t len);
@@ -2893,11 +2895,11 @@ list_smacro_def(enum preproc_token op, const Context *ctx, const SMacro *m)
     if (m->nparam) {
         /*
          * Space for ( and either , or ) around each
-         * parameter, plus up to 5 flags.
+         * parameter, plus up to 5 flags + /ux
          */
         int i;
 
-        size += 1 + 5 * m->nparam;
+        size += 1 + 8 * m->nparam;
         for (i = 0; i < m->nparam; i++)
             size += m->params[i].name.len;
     }
@@ -2921,6 +2923,19 @@ list_smacro_def(enum preproc_token op, const Context *ctx, const SMacro *m)
 
         for (i = m->nparam-1; i >= 0; i--) {
             enum sparmflags flags = m->params[i].flags;
+            bool slash = false;
+
+            if (m->params[i].radix) {
+                *--p = m->params[i].radix;
+                slash = true;
+            }
+            if (flags & SPARM_UNSIGNED) {
+                *--p = 'u';
+                slash = true;
+            }
+            if (slash)
+                *--p = '/';
+
             if (flags & (SPARM_GREEDY|SPARM_VARADIC))
                 *--p = '+';
 	    p -= m->params[i].name.len;
@@ -2983,6 +2998,8 @@ static int parse_smacro_template(Token ***tpp, SMacro *tmpl)
     struct smac_param *params = NULL;
     bool err, done;
     bool greedy = false;
+    bool parsing_radix;
+    char radix;
     Token **tn = *tpp;
     Token *t = *tn;
     Token *name;
@@ -3017,6 +3034,8 @@ static int parse_smacro_template(Token ***tpp, SMacro *tmpl)
 
     name = NULL;
     flags = 0;
+    radix = 0;
+    parsing_radix = false;
     err = done = false;
 
     while (!done) {
@@ -3030,9 +3049,34 @@ static int parse_smacro_template(Token ***tpp, SMacro *tmpl)
 
         switch (t->type) {
         case TOKEN_ID:
-            if (name)
-                goto bad;
-            name = t;
+            if (parsing_radix) {
+                const char *cp;
+                for (cp = tok_text(t); cp && *cp; cp++) {
+                    switch (*cp | 0x20) {
+                    case 'b': case 'y':
+                    case 'd': case 't':
+                    case 'o': case 'q':
+                    case 'h': case 'x':
+                        radix = *cp;
+                        break;
+                    case 's':
+                        flags &= ~SPARM_UNSIGNED;
+                        break;
+                    case 'u':
+                        flags |= SPARM_UNSIGNED;
+                        break;
+                    default:
+                        nasm_nonfatal("invalid radix specifier `/%s'",
+                                      tok_text(t));
+                        cp = NULL;
+                        break;
+                    }
+                }
+            } else {
+                if (name)
+                    goto bad;
+                name = t;
+            }
             break;
         case '=':
             flags |= SPARM_EVAL;
@@ -3050,6 +3094,11 @@ static int parse_smacro_template(Token ***tpp, SMacro *tmpl)
             flags |= SPARM_GREEDY|SPARM_OPTIONAL;
             greedy = true;
             break;
+        case '/':
+            if (!(flags & SPARM_EVAL))
+                nasm_nonfatal("radix specifier for parameter without `='");
+            parsing_radix = true;
+            break;
         case ',':
             if (greedy)
                 nasm_nonfatal("greedy parameter must be last");
@@ -3062,10 +3111,13 @@ static int parse_smacro_template(Token ***tpp, SMacro *tmpl)
                 if (name)
                     steal_Token(&params[nparam].name, name);
                 params[nparam].flags = flags;
+                params[nparam].radix = radix;
             }
             nparam++;
             name = NULL;
             flags = 0;
+            parsing_radix = false;
+            radix = 0;
             break;
         case TOKEN_WHITESPACE:
             break;
@@ -3438,12 +3490,19 @@ static bool is_macro_id(const Token *t)
     return tok_is(t, TOKEN_ID) || tok_is(t, TOKEN_LOCAL_MACRO);
 }
 
+static const char *get_id_noskip(Token **tp, const char *dname);
+
 static const char *get_id(Token **tp, const char *dname)
+{
+    *tp = (*tp)->next;          /* Skip directive */
+    return get_id_noskip(tp, dname);
+}
+
+static const char *get_id_noskip(Token **tp, const char *dname)
 {
     const char *id;
     Token *t = *tp;
 
-    t = t->next;                /* Skip directive */
     t = skip_white(t);
     t = expand_id(t);
 
@@ -3453,7 +3512,7 @@ static const char *get_id(Token **tp, const char *dname)
     }
 
     id = tok_text(t);
-    t = skip_white(t);
+    nasm_assert(!tok_white(t)); /* Had skip_white() here?? */
     *tp = t;
     return id;
 }
@@ -3554,7 +3613,7 @@ static int line_directive(Token *origline, Token *tline)
      *
      * "flags" are for gcc compatibility and are currently ignored.
      *
-     * '#' at the beginning of the line is also treated as a %line
+     * "#" at the beginning of the line is also treated as a %line
      * directive, again for compatibility with gcc.
      */
     if ((ppopt & PP_NOLINE) || istk->mstk.mstk)
@@ -5587,6 +5646,220 @@ static Token *expand_mmac_params(Token * tline)
 }
 
 static Token *expand_smacro_noreset(Token * tline);
+static SMacro *expand_one_smacro(Token ***tpp);
+
+/*
+ * Expand one single-line macro instance given a specific macro and a
+ * specific set of parameters. Returns a pointer to the expansion, and
+ * the pointer *epp pointing to the next pointer of the last token of
+ * the expansion; if the expansion is empty return NULL and *epp is
+ * unchanged.
+ *
+ * mstart is the token containing the token name *as invoked*.
+ */
+static Token *
+expand_smacro_with_params(SMacro *m, Token *mstart, Token **params,
+                          int nparam, Token ***epp)
+{
+    /* Is it a macro or a preprocessor function? Used for diagnostics. */
+    const char * const mtype = m->name[0] == '%' ? "function" : "macro";
+    Token *t, *tline, *tup;
+    bool cond_comma;
+    const struct smac_param *mparm;
+    int i;
+
+    /* Expand the macro */
+    m->in_progress++;
+
+    /*
+     * Postprocessing of 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.
+     */
+    if (nparam) {
+        for (i = 0, mparm = m->params; i < nparam;
+             i++, mparm += !(mparm->flags & SPARM_VARADIC)) {
+            const enum sparmflags flags = mparm->flags;
+
+            if (flags & SPARM_EVAL) {
+                /* Evaluate this parameter as a number */
+                struct ppscan pps;
+                struct tokenval tokval;
+                expr *evalresult;
+                Token *eval_param;
+
+                eval_param = zap_white(expand_smacro_noreset(params[i]));
+                params[i] = NULL;
+
+                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;
+                tokval.t_type = TOKEN_INVALID;
+                evalresult = evaluate(ppscan, &pps, &tokval, NULL, true, NULL);
+
+                free_tlist(eval_param);
+
+                if (!evalresult) {
+                    /* Nothing meaningful to do */
+                } else if (tokval.t_type) {
+                    nasm_nonfatal("invalid expression in parameter %d of %s `%s'",
+                                  i+1, mtype, m->name);
+                } else if (!is_simple(evalresult)) {
+                    nasm_nonfatal("non-constant expression in parameter %d of %s `%s'",
+                                  i+1, mtype, m->name);
+                } else {
+                    int64_t v = reloc_value(evalresult);
+                    params[i] = make_tok_num_radix(NULL, v, mparm->radix,
+                                                   !!(flags & SPARM_UNSIGNED));
+                }
+            }
+
+            if (flags & SPARM_STR) {
+                /* Convert expansion to a quoted string */
+                Token *qs;
+
+                qs = expand_smacro_noreset(params[i]);
+                if ((flags & SPARM_CONDQUOTE) &&
+                    tok_is(qs, TOKEN_STR) && !qs->next) {
+                    /* A single quoted string token */
+                    params[i] = qs;
+                } else {
+                    char *arg = detoken(qs, false);
+                    free_tlist(qs);
+                    params[i] = make_tok_qstr(NULL, arg);
+                    nasm_free(arg);
+                }
+            }
+        }
+    }
+
+
+    /* Note: we own the expansion this returns. */
+    t = m->expand(m, params, nparam);
+
+    tup = tline = NULL;
+    cond_comma = false;
+
+    while (t) {
+        enum token_type type = t->type;
+        Token *tnext = t->next;
+
+        switch (type) {
+        case TOKEN_PREPROC_Q:
+        case TOKEN_PREPROC_SQ:
+            delete_Token(t);
+            t = dup_Token(tline, mstart);
+            break;
+
+        case TOKEN_PREPROC_QQ:
+        case TOKEN_PREPROC_SQQ:
+        {
+            size_t mlen = strlen(m->name);
+	    size_t len;
+            char *p, *from;
+
+            t->type = mstart->type;
+            if (t->type == TOKEN_LOCAL_MACRO) {
+		const char *psp; /* prefix start pointer */
+                const char *pep; /* prefix end pointer */
+		size_t plen;
+
+		psp = tok_text(mstart);
+                get_ctx(psp, &pep);
+                plen = pep - psp;
+
+                len = mlen + plen;
+                from = p = nasm_malloc(len + 1);
+                p = mempcpy(p, psp, plen);
+            } else {
+                len = mlen;
+                from = p = nasm_malloc(len + 1);
+            }
+            p = mempcpy(p, m->name, mlen);
+            *p = '\0';
+	    set_text_free(t, from, len);
+
+            t->next = tline;
+            break;
+        }
+
+        case TOKEN_COND_COMMA:
+            delete_Token(t);
+            t = cond_comma ? make_tok_char(tline, ',') : NULL;
+            break;
+
+        case TOKEN_ID:
+        case TOKEN_PREPROC_ID:
+	case TOKEN_LOCAL_MACRO:
+        {
+            /*
+             * Chain this into the target line *before* expanding,
+             * that way we pick up any arguments to the new macro call,
+             * if applicable.
+             */
+            Token **tp = &t;
+            t->next = tline;
+            expand_one_smacro(&tp);
+            tline = *tp;        /* First token left after any macro call */
+            break;
+        }
+        default:
+            if (is_smac_param(t->type)) {
+                int param = smac_nparam(t->type);
+                nasm_assert(!tup && param < nparam);
+                delete_Token(t);
+                t = NULL;
+                tup = tnext;
+                tnext = dup_tlist_reverse(params[param], NULL);
+                cond_comma = false;
+            } else {
+                t->next = tline;
+            }
+        }
+
+        if (t) {
+            Token *endt = tline;
+
+            tline = t;
+            while (!cond_comma && t && t != endt) {
+                cond_comma = t->type != TOKEN_WHITESPACE;
+                t = t->next;
+            }
+        }
+
+        if (tnext) {
+            t = tnext;
+        } else {
+            t = tup;
+            tup = NULL;
+        }
+    }
+
+    if (epp) {
+        Token **ep = *epp;
+        for (t = tline; t; t = t->next)
+            ep = &t->next;
+        *epp = ep;
+    }
+
+    /* Expansion complete */
+    m->in_progress--;
+
+    return tline;
+}
 
 /*
  * Expand *one* single-line macro instance. If the first token is not
@@ -5609,9 +5882,8 @@ static SMacro *expand_one_smacro(Token ***tpp)
     Token *tline  = mstart;
     SMacro *head, *m;
     int i;
-    Token *t, *tup, *tafter;
+    Token *tafter, **tep;
     int nparam = 0;
-    bool cond_comma;
 
     if (!tline)
         return false;           /* Empty line, nothing to do */
@@ -5761,9 +6033,6 @@ static SMacro *expand_one_smacro(Token ***tpp)
     if (m->in_progress && !m->recursive)
         goto not_a_macro;
 
-    /* Expand the macro */
-    m->in_progress++;
-
     /* Is it a macro or a preprocessor function? Used for diagnostics. */
     mtype = m->name[0] == '%' ? "function" : "macro";
 
@@ -5875,196 +6144,19 @@ static SMacro *expand_one_smacro(Token ***tpp)
                 pep = &t->next;
             }
         }
-
-        /*
-         * 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.
-         */
-        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 */
-                struct ppscan pps;
-                struct tokenval tokval;
-                expr *evalresult;
-                Token *eval_param;
-
-                eval_param = zap_white(expand_smacro_noreset(params[i]));
-                params[i] = NULL;
-
-                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;
-                tokval.t_type = TOKEN_INVALID;
-                evalresult = evaluate(ppscan, &pps, &tokval, NULL, true, NULL);
-
-                free_tlist(eval_param);
-
-                if (!evalresult) {
-                    /* Nothing meaningful to do */
-                } else if (tokval.t_type) {
-                    nasm_nonfatal("invalid expression in parameter %d of %s `%s'",
-                                  i+1, mtype, m->name);
-                } else if (!is_simple(evalresult)) {
-                    nasm_nonfatal("non-constant expression in parameter %d of %s `%s'",
-                                  i+1, mtype, m->name);
-                } else {
-                    int64_t v = reloc_value(evalresult);
-                    if (flags & SPARM_HEX)
-                        params[i] = make_tok_hex(NULL, v);
-                    else
-                        params[i] = make_tok_num(NULL, v);
-                }
-            }
-
-            if (flags & SPARM_STR) {
-                /* Convert expansion to a quoted string */
-                Token *qs;
-
-                qs = expand_smacro_noreset(params[i]);
-                if ((flags & SPARM_CONDQUOTE) &&
-                    tok_is(qs, TOKEN_STR) && !qs->next) {
-                    /* A single quoted string token */
-                    params[i] = qs;
-                } else {
-                    char *arg = detoken(qs, false);
-                    free_tlist(qs);
-                    params[i] = make_tok_qstr(NULL, arg);
-                    nasm_free(arg);
-                }
-            }
-        }
     }
 
-    /* Note: we own the expansion this returns. */
-    t = m->expand(m, params, nparam);
-
     tafter = tline->next;   /* Skip past the macro call */
-    tline->next = NULL;     /* Truncate list at the macro call end */
-    tline = tafter;
-
-    tup = NULL;
-    cond_comma = false;
-
-    while (t) {
-        enum token_type type = t->type;
-        Token *tnext = t->next;
-
-        switch (type) {
-        case TOKEN_PREPROC_Q:
-        case TOKEN_PREPROC_SQ:
-            delete_Token(t);
-            t = dup_Token(tline, mstart);
-            break;
-
-        case TOKEN_PREPROC_QQ:
-        case TOKEN_PREPROC_SQQ:
-        {
-            size_t mlen = strlen(m->name);
-	    size_t len;
-            char *p, *from;
-
-            t->type = mstart->type;
-            if (t->type == TOKEN_LOCAL_MACRO) {
-		const char *psp; /* prefix start pointer */
-                const char *pep; /* prefix end pointer */
-		size_t plen;
-
-		psp = tok_text(mstart);
-                get_ctx(psp, &pep);
-                plen = pep - psp;
-
-                len = mlen + plen;
-                from = p = nasm_malloc(len + 1);
-                p = mempcpy(p, psp, plen);
-            } else {
-                len = mlen;
-                from = p = nasm_malloc(len + 1);
-            }
-            p = mempcpy(p, m->name, mlen);
-            *p = '\0';
-	    set_text_free(t, from, len);
-
-            t->next = tline;
-            break;
-        }
-
-        case TOKEN_COND_COMMA:
-            delete_Token(t);
-            t = cond_comma ? make_tok_char(tline, ',') : NULL;
-            break;
-
-        case TOKEN_ID:
-        case TOKEN_PREPROC_ID:
-	case TOKEN_LOCAL_MACRO:
-        {
-            /*
-             * Chain this into the target line *before* expanding,
-             * that way we pick up any arguments to the new macro call,
-             * if applicable.
-             */
-            Token **tp = &t;
-            t->next = tline;
-            expand_one_smacro(&tp);
-            tline = *tp;        /* First token left after any macro call */
-            break;
-        }
-        default:
-            if (is_smac_param(t->type)) {
-                int param = smac_nparam(t->type);
-                nasm_assert(!tup && param < nparam);
-                delete_Token(t);
-                t = NULL;
-                tup = tnext;
-                tnext = dup_tlist_reverse(params[param], NULL);
-                cond_comma = false;
-            } else {
-                t->next = tline;
-            }
-        }
-
-        if (t) {
-            Token *endt = tline;
-
-            tline = t;
-            while (!cond_comma && t && t != endt) {
-                cond_comma = t->type != TOKEN_WHITESPACE;
-                t = t->next;
-            }
-        }
-
-        if (tnext) {
-            t = tnext;
-        } else {
-            t = tup;
-            tup = NULL;
-        }
+    tline->next = NULL;     /* Truncate mstart list at the macro call end */
+    tline = expand_smacro_with_params(m, mstart, params, nparam, &tep);
+    if (tline) {
+        **tpp = tline;
+        *tep = tafter;
+        *tpp = tep;
+    } else {
+        **tpp = tafter;
     }
 
-    **tpp = tline;
-    for (t = tline; t && t != tafter; t = t->next)
-        *tpp = &t->next;
-
-    /* Expansion complete */
-    m->in_progress--;
-
     /* Don't do this until after expansion or we will clobber mname */
     free_tlist(mstart);
     goto done;
@@ -6078,8 +6170,8 @@ not_a_macro:
     *tpp = &mstart->next;
     m = NULL;
 done:
-    smacro_deadman.levels++;
     free_tlist_array(params, nparam);
+    smacro_deadman.levels++;
     return m;
 }
 
@@ -7210,24 +7302,14 @@ stdmac_count(const SMacro *s, Token **params, int nparams)
 static Token *
 stdmac_num(const SMacro *s, Token **params, int nparam)
 {
-    static const char num_digits[] =
-        "0123456789"
-        "abcdefghijklmnopqrstuvwxyz"
-        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-        "@_";                   /* Compatible with bash */
     int64_t parm[3];
     uint64_t n;
     int64_t dparm, bparm;
-    unsigned int i;
-    int nd;
-    unsigned int base;
-    char numstr[262];
-    char * const endstr = numstr + sizeof numstr - 1;
-    const int maxlen = sizeof numstr - 5;
-    const int maxbase = sizeof num_digits - 1;
+    const int maxlen = 256;
+    char numbuf[256+5];
     char *p;
-    bool moredigits;
     char decorate;
+    int i;
 
     (void)nparam;
 
@@ -7260,50 +7342,33 @@ stdmac_num(const SMacro *s, Token **params, int nparam)
         }
     }
 
-    if (bparm < 2 || bparm > maxbase) {
-        nasm_nonfatal("invalid base %"PRId64" given to %s()",
-                      bparm, s->name);
+    if (bparm < 2 || bparm > NUMSTR_MAXBASE) {
+        nasm_nonfatal("invalid base %"PRId64" in %s()\n", bparm, s->name);
         return NULL;
     }
 
-    base = bparm;
-
     if (dparm < -maxlen || dparm > maxlen) {
         nasm_nonfatal("digit count %"PRId64" specified to %s() too large",
                       dparm, s->name);
-        moredigits = true;
-        nd = 1;
-    } else if (dparm <= 0) {
-        moredigits = true;
-        nd = -dparm;
-    } else {
-        moredigits = false;
-        nd = dparm;
+        dparm = -1;
     }
 
     /* Are we supposed to generate an empty string for zero? */
-    if (!nd && !n)
+    if (!dparm && !n)
         decorate = 0;
 
-    p = endstr;
-    *p = '\0';
-    *--p = '\'';
-
-    /*
-     * Note that the actual maximum number of digits can never exceed 64,
-     * so even if moredigits is set it cannot cause string overrun.
-     */
-    while (nd-- > 0 || (moredigits && n)) {
-        *--p = num_digits[n % base];
-        n /= base;
-    }
+    p = numbuf;
+    *p++ = '\'';
     if (decorate) {
-        *--p = decorate;
-        *--p = '0';
+        *p++ = '0';
+        *p++ = decorate;
     }
-    *--p = '\'';
 
-    return new_Token(NULL, TOKEN_STR, p, endstr - p);
+    p += numstr(p, maxlen, n, dparm, bparm, false);
+    *p++ = '\'';
+    *p = '\0';
+
+    return new_Token(NULL, TOKEN_STR, numbuf, p - numbuf);
 }
 
 /* %abs() function */
@@ -7329,6 +7394,115 @@ stdmac_abs(const SMacro *s, Token **params, int nparam)
     return new_Token(NULL, TOKEN_NUM, numbuf, len);
 }
 
+/* %map() function */
+static Token *
+stdmac_map(const SMacro *s, Token **params, int nparam)
+{
+    const char *mname, *ctxname;
+    SMacro *smac;
+    Context *ctx;
+    Token *t, *tline, *mstart;
+    int mparams;
+    int greedify;
+
+    t = params[0];
+    mname = get_id_noskip(&t, "%map");
+    if (!mname)
+        return NULL;
+
+    mstart = t;
+    t = t->next;
+
+    mparams = 1;
+    if (tok_is(t, ':')) {
+        struct ppscan pps;
+        struct tokenval tokval;
+        expr *evalresult;
+        Token *ep;
+
+        pps.tptr = ep = zap_white(expand_smacro_noreset(t->next));
+        t->next = NULL;
+        pps.ntokens = -1;
+        tokval.t_type = TOKEN_INVALID;
+        evalresult = evaluate(ppscan, &pps, &tokval, NULL, true, NULL);
+        free_tlist(ep);
+
+        if (!evalresult || tokval.t_type) {
+            nasm_nonfatal("invalid expression in parameter count for `%s' in function %s",
+                          mname, s->name);
+            return NULL;
+        } else if (!is_simple(evalresult)) {
+            nasm_nonfatal("non-constant expression in parameter count for `%s' in function %s",
+                          mname, s->name);
+            return NULL;
+        }
+        mparams = reloc_value(evalresult);
+        if (mparams < 1) {
+            nasm_nonfatal("invalid parameter count for `%s' in function %s",
+                          mname, s->name);
+            return NULL;
+        }
+    }
+
+    nparam--;
+    params++;
+    if (nparam % mparams) {
+        nasm_nonfatal("%s expected a multiple of %d expansion parameters, got %d\n",
+                      s->name, mparams, nparam);
+        nparam -= nparam % mparams;
+    }
+
+    ctx = get_ctx(mname, &ctxname);
+    if (!smacro_defined(ctx, ctxname, mparams, &smac, true, false) ||
+        smac->nparam == 0 || (smac->in_progress && !smac->recursive)) {
+        nasm_nonfatal("macro `%s' taking %d parameters not found in function %s",
+                      mname, mparams, s->name);
+        return NULL;
+    }
+
+    greedify = 0;
+    if (unlikely(mparams > smac->nparam)) {
+        if (smac->params[smac->nparam-1].flags & SPARM_GREEDY)
+            greedify = smac->nparam;
+    }
+
+    tline = NULL;
+    while (1) {
+        int xparams = mparams;
+        if (unlikely(greedify)) {
+            /* Need to re-concatenate some number of arguments as
+               comma-separated lists... */
+            int i;
+            Token **tp = &params[greedify-1];
+            while (*tp)
+                tp = &(*tp)->next;
+
+            for (i = greedify; i < mparams; i++) {
+                *tp = make_tok_char(NULL, ',');
+                tp = steal_tlist(params[i], &(*tp)->next);
+                params[i] = NULL;
+            }
+            xparams = greedify;
+        }
+
+        t = expand_smacro_with_params(smac, mstart, params, xparams, NULL);
+        if (t) {
+            Token *rt = reverse_tokens(t);
+            t->next = tline;
+            tline = rt;
+        }
+
+        nparam -= mparams;
+        if (!nparam)
+            break;
+
+        params += mparams;
+        tline = make_tok_char(tline, ',');
+    }
+
+    return tline;
+}
+
 /* Add magic standard macros */
 struct magic_macros {
     const char *name;
@@ -7348,7 +7522,7 @@ static void pp_add_magic_stdmac(void)
         { "%abs",       false, 1, SPARM_EVAL, stdmac_abs },
         { "%count",     false, 1, SPARM_VARADIC, stdmac_count },
         { "%eval",      false, 1, SPARM_EVAL|SPARM_VARADIC, stdmac_join },
-        { "%hex",       false, 1, SPARM_EVAL|SPARM_HEX|SPARM_VARADIC, stdmac_join },
+        { "%map",	false, 1, SPARM_VARADIC, stdmac_map },
         { "%str",       false, 1, SPARM_GREEDY|SPARM_STR, stdmac_join },
         { "%strcat",    false, 1, SPARM_STR|SPARM_CONDQUOTE|SPARM_VARADIC, stdmac_strcat },
         { "%strlen",    false, 1, SPARM_STR|SPARM_CONDQUOTE, stdmac_strlen },
@@ -7389,6 +7563,16 @@ static void pp_add_magic_stdmac(void)
         }
     }
 
+    /* %hex() function */
+    nasm_zero(tmpl);
+    tmpl.nparam    = 1;
+    tmpl.recursive = true;
+    tmpl.expand    =  stdmac_join;
+    nasm_newn(tmpl.params, tmpl.nparam);
+    tmpl.params[0].flags = SPARM_EVAL|SPARM_UNSIGNED|SPARM_VARADIC;
+    tmpl.params[0].radix = 'x';
+    define_smacro("%hex", false, NULL, &tmpl);
+
     /* %sel() function */
     nasm_zero(tmpl);
     tmpl.nparam    = 2;
@@ -8045,15 +8229,39 @@ static Token *make_tok_num(Token *next, int64_t val)
     return next;
 }
 
-/* Create a numeric token as unsigned hexadecimal */
-static Token *make_tok_hex(Token *next, int64_t val)
+/*
+ * Create a numeric token with specified radix and signedness;
+ * prefix the number with 0<radix> if a radix letter is specified,
+ * otherwise generate a decimal constant without prefix.
+ */
+static Token *
+make_tok_num_radix(Token *next, int64_t val, char radix, bool uns)
 {
-    char numbuf[32];
-    int len;
-    uint64_t uval = val;
+    char numbuf[2+64+1]; /* Maximum possible: 0b + binary + null */
+    char *p;
+    uint64_t uval;
+    bool minus = val < 0 && !uns;
+    unsigned int base;
+    bool upper;
 
-    len = snprintf(numbuf, sizeof numbuf, "%#"PRIx64, uval);
-    next = new_Token(next, TOKEN_NUM, numbuf, len);
+    uval = minus ? -val : val;
+
+    p = numbuf;
+    base = 10;
+    upper = false;
+    if (radix) {
+        *p++ = '0';
+        *p++ = radix;
+
+        base = radix_letter(radix);
+        upper = !(radix & 0x20);
+    }
+
+    p += numstr(p, 64, uval, -1, base, upper);
+    next = new_Token(next, TOKEN_NUM, numbuf, p - numbuf);
+
+    if (minus)
+        next = make_tok_char(next, '-');
 
     return next;
 }
diff --git a/doc/nasmdoc.src b/doc/nasmdoc.src
index d4965e07..42bee347 100644
--- a/doc/nasmdoc.src
+++ b/doc/nasmdoc.src
@@ -2425,7 +2425,13 @@ A single pair of parentheses is a subcase of a single, unused argument:
 This is similar to the behavior of the C preprocessor.
 
 \b If declared with an \c{=}, NASM will expand the argument and then
-evaluate it as a numeric expression.
+evaluate it as a numeric expression. The name of the argument may
+optionally be followed by \c{/} followed by a numeric radix character
+(\c{b}, \c{y}, \c{o}, \c{q}, \c{d}, \c{t}, \c{h} or \c{x}) and/or the
+letters \c{u} (unsigned) or \c{s} (signed), in which the number is
+formatted accordingly, with a radix prefix if a radix letter is
+specified. For the case of hexadecimal, if the radix letter is in
+upper case, alphabetic hex digits will be in upper case.
 
 \b If declared with an \c{&}, NASM will expand the argument and then
 turn into a quoted string; if the argument already \e{is} a quoted
@@ -2443,9 +2449,9 @@ braces (potentially useful in conjunction with \c{&} or \c{&&}.)
 
 For example:
 
-\c     %define xyzzy(=expr,&val) expr, str
-\c     %define plugh(x) xyzzy(x,x)
-\c     db plugh(3+5), `\0` ; Expands to: db 8, "3+5", `\0`
+\c     %define xyzzy(=expr,&val,=hex/x) expr, str, hex
+\c     %define plugh(x) xyzzy(x,x,x)
+\c     db plugh(13+5), `\0` ; Expands to: db 18, "13+5", 0x12, `\0`
 
 You can \i{pre-define} single-line macros using the `-d' option on
 the NASM command line: see \k{opt-d}.
@@ -2922,9 +2928,7 @@ see \k{crit}.
 \S{f_hex} \i\c{%hex()} Function
 
 Equivalent to \i\c\{%eval()}, except that the results generated are
-given as hexadecimal. A \c{0x} prefix is added unless the value is
-zero, equivalent to the C \c{printf("%#x")} format. See also the
-\c{%num()} function, (\k{f_num}).
+given as unsigned hexadecimal, with a \c{0x} prefix.
 
 
 \S{f_is} \i\c{%is()} Family Functions
@@ -2952,6 +2956,27 @@ argument to the conditional using \c{\{\}}:
 \c %endif
 
 
+\S{f_map} \i\c{%map()} Function
+
+The \c{%map()} function takes as its first parameter the name of a
+single-line macro, optionally followed by a colon and an integer
+expression (default 1), specifying the number of parameter to the
+macro, \e{n}.
+
+The following parameters are then passed as parameters to the given
+macro for expansion, in groups of \e{n}, and the results turned into a
+comma-separated list.
+
+For example:
+
+\c %define alpha(&x,y) y dup (x)
+\c       db %map(alpha:2,foo,bar,baz,quux)
+
+... expands to:
+
+\c       db bar dup ("foo"),quux dup ("baz")
+
+
 \S{f_num} \i\c{%num()} Function
 
 The \c{%num()} function evaluates its arguments as expressions, and
diff --git a/include/nasmlib.h b/include/nasmlib.h
index 87a7fc6f..fdfb9a96 100644
--- a/include/nasmlib.h
+++ b/include/nasmlib.h
@@ -1,6 +1,6 @@
 /* ----------------------------------------------------------------------- *
  *
- *   Copyright 1996-2020 The NASM Authors - All Rights Reserved
+ *   Copyright 1996-2023 The NASM Authors - All Rights Reserved
  *   See the file AUTHORS included with the NASM distribution for
  *   the specific copyright holders.
  *
@@ -237,6 +237,29 @@ static inline unsigned int numvalue(unsigned char c)
  */
 int64_t readnum(const char *str, bool *error);
 
+/*
+ * Get the numeric base corresponding to a character
+ */
+static inline unsigned int radix_letter(char c)
+{
+    switch (c) {
+    case 'b': case 'B':
+    case 'y': case 'Y':
+	return 2;		/* Binary */
+    case 'o': case 'O':
+    case 'q': case 'Q':
+	return 8;		/* Octal */
+    case 'h': case 'H':
+    case 'x': case 'X':
+	return 16;		/* Hexadecimal */
+    case 'd': case 'D':
+    case 't': case 'T':
+	return 10;		/* Decimal */
+    default:
+	return 0;		/* Not a known radix letter */
+    }
+}
+
 /*
  * Convert a character constant into a number. Sets
  * `*warn' to true if an overflow occurs, and false otherwise.
@@ -245,6 +268,14 @@ int64_t readnum(const char *str, bool *error);
  */
 int64_t readstrnum(char *str, int length, bool *warn);
 
+/*
+ * Produce an unsigned integer string from a number with a specified
+ * base, digits and signedness
+ */
+#define NUMSTR_MAXBASE 64
+int numstr(char *buf, size_t buflen, uint64_t n,
+           int digits, unsigned int base, bool ucase);
+
 /*
  * seg_alloc: allocate a hitherto unused segment number.
  */
diff --git a/nasmlib/filename.c b/nasmlib/numstr.c
similarity index 60%
copy from nasmlib/filename.c
copy to nasmlib/numstr.c
index 172ae0bc..f47d0cb6 100644
--- a/nasmlib/filename.c
+++ b/nasmlib/numstr.c
@@ -1,6 +1,6 @@
 /* ----------------------------------------------------------------------- *
  *
- *   Copyright 1996-2017 The NASM Authors - All Rights Reserved
+ *   Copyright 2023 The NASM Authors - All Rights Reserved
  *   See the file AUTHORS included with the NASM distribution for
  *   the specific copyright holders.
  *
@@ -31,33 +31,51 @@
  *
  * ----------------------------------------------------------------------- */
 
-/*
- * nasmlib.c	library routines for the Netwide Assembler
- */
-
-#include "compiler.h"
 #include "nasmlib.h"
-#include "error.h"
 
 /*
- * Add/modify a filename extension, assumed to be a period-delimited
- * field at the very end of the filename.  Returns a newly allocated
- * string buffer.
+ * Produce an unsigned integer string from a number with a specified
+ * base, digits and signedness.
  */
-const char *filename_set_extension(const char *inname, const char *extension)
+int numstr(char *buf, size_t buflen, uint64_t n,
+           int digits, unsigned int base, bool ucase)
 {
-    const char *q = inname;
+    static const char digit_chars[2][NUMSTR_MAXBASE+1] =
+    {
+        /* Lower case version */
+        "0123456789"
+        "abcdefghijklmnopqrstuvwxyz"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "@_",
+
+        /* Upper case version */
+        "0123456789"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "abcdefghijklmnopqrstuvwxyz"
+        "@_"
+    };
+    const char * const dchars = digit_chars[ucase];
+    bool moredigits = digits <= 0;
     char *p;
-    size_t elen = strlen(extension);
-    size_t baselen;
+    int len;
+
+    if (base < 2 || base > NUMSTR_MAXBASE)
+        return -1;
+
+    if (moredigits)
+        digits = -digits;
 
-    q = strrchrnul(inname, '.');   /* find extension or end of string */
-    baselen = q - inname;
+    p = buf + buflen;
+    *--p = '\0';
 
-    p = nasm_malloc(baselen + elen + 1);
+    while (p > buf && (digits-- > 0 || (moredigits && n))) {
+        *--p = dchars[n % base];
+        n /= base;
+    }
 
-    memcpy(p, inname, baselen);
-    memcpy(p+baselen, extension, elen+1);
+    len = buflen - (p - buf);   /* Including final null */
+    if (p != buf)
+        memmove(buf, p, len);
 
-    return p;
+    return len - 1;
 }
diff --git a/nasmlib/readnum.c b/nasmlib/readnum.c
index 947bda53..b389c652 100644
--- a/nasmlib/readnum.c
+++ b/nasmlib/readnum.c
@@ -45,26 +45,6 @@
 
 #define lib_isnumchar(c)    (nasm_isalnum(c) || (c) == '$' || (c) == '_')
 
-static int radix_letter(char c)
-{
-    switch (c) {
-    case 'b': case 'B':
-    case 'y': case 'Y':
-	return 2;		/* Binary */
-    case 'o': case 'O':
-    case 'q': case 'Q':
-	return 8;		/* Octal */
-    case 'h': case 'H':
-    case 'x': case 'X':
-	return 16;		/* Hexadecimal */
-    case 'd': case 'D':
-    case 't': case 'T':
-	return 10;		/* Decimal */
-    default:
-	return 0;		/* Not a known radix letter */
-    }
-}
-
 int64_t readnum(const char *str, bool *error)
 {
     const char *r = str, *q;
diff --git a/test/badrep.asm b/test/badrep.asm
new file mode 100644
index 00000000..d526e90e
--- /dev/null
+++ b/test/badrep.asm
@@ -0,0 +1,66 @@
+%macro mmac 0
+%rep 1
+%endrep
+%endmacro
+	mmac
+
+%macro mmac1 0
+%rep 1
+%rep 1
+	mmac
+%endrep
+%endrep
+%endmacro
+
+%macro mmac2 0
+%rep 1
+
+%endrep
+%endmacro
+	mmac2
+
+%macro mmac3 1
+%rep 1
+%rep 1
+%endrep
+%endrep
+%endmacro
+	mmac3 x
+
+%macro mmac4 0
+%rep 1
+%rep 1
+	mmac3 x
+%endrep
+%endrep
+%endmacro
+	mmac4
+
+%macro mmac5 0
+%rep 1
+%rep 1
+nop
+%endrep
+%endrep
+%endmacro
+	mmac5
+
+%macro mmac6 0
+%endmacro
+	mmac6
+
+%macro mmac7 0
+	mmac6
+%endmacro
+	mmac7
+
+%macro mmac8 0
+%rep 1
+	mmac6
+%endrep
+%endmacro
+	mmac8
+
+%rep 1
+	mmac3 y
+%endrep
diff --git a/test/evalerr.asm b/test/evalerr.asm
new file mode 100644
index 00000000..2bc056d4
--- /dev/null
+++ b/test/evalerr.asm
@@ -0,0 +1,6 @@
+	bits 32
+start:
+	nop
+%warning We are at %hex($-$$)
+;%warning We are at %hex($)
+%warning We are at %hex($-$$)
diff --git a/test/evalmacro.asm b/test/evalmacro.asm
index f93f6939..4c887715 100644
--- a/test/evalmacro.asm
+++ b/test/evalmacro.asm
@@ -3,11 +3,13 @@
 	dd tonum(1+3)
 	dd tonum(5*7)
 
-%define mixed(a,=b,c)	(a + b)
-%define mixed2(a,=b,)	(a + b)
+%define mixed(a,=b,c)		(a + b)
+%define mixed2(a,=b,)		(a + b)
+%define mixed3(=a/u,=b/x,=c/ux)	(a + b + c)
 %define ALPHA (1 + 2)
 %define BETA  (3 + 4)
 %define GAMMA (5 + 6)
 
 	dd mixed(ALPHA, BETA, GAMMA)
 	dd mixed2(ALPHA, BETA, GAMMA)
+	dd mixed3(-ALPHA, -BETA, -GAMMA)
diff --git a/test/map.asm b/test/map.asm
new file mode 100644
index 00000000..48f4739d
--- /dev/null
+++ b/test/map.asm
@@ -0,0 +1,7 @@
+%define foo(x) (x+1)
+%define bar(=x,y) (x*y)
+%define baz(x+) %(x)
+
+	dw %map(foo,1,2,3,4)
+	dw %map(bar:2,1+2,3+4,5+6,7+8)
+	dw %map(baz:2,1+2,3+4,5+6,7+8)


More information about the Nasm-commits mailing list