!LET
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 13 Jun 2021 16:31:30 +0000 (09:31 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 13 Jun 2021 16:31:30 +0000 (09:31 -0700)
doc/flow-control.texi
src/language/lexer/macro.c
tests/language/control/define.at

index 27955800e6d228ca4d169776dced683871d3894e..0b3cd2786606226199f49c9d75fc5abc83d423bb 100644 (file)
@@ -363,7 +363,8 @@ results visible.
 @deffn {Macro Function} !CONCAT (arg@dots{})
 Expands to the concatenation of all of the arguments.  Before
 concatenation, each quoted string argument is unquoted, as if
-@code{!UNQUOTE} were applied.
+@code{!UNQUOTE} were applied.  This allows for ``token pasting'',
+combining two (or more) tokens into a single one:
 
 @c Keep these examples in sync with the test for !CONCAT in
 @c tests/language/control/define.at:
@@ -373,6 +374,29 @@ concatenation, each quoted string argument is unquoted, as if
 !CONCAT(12, 34)              @expansion{} 1234
 !CONCAT(!NULL, 123)          @expansion{} 123
 @end example
+
+@code{!CONCAT} is often used for constructing a series of similar
+variable names from a prefix followed by a number and perhaps a
+suffix.  For example:
+
+@c Keep these examples in sync with the test for !CONCAT in
+@c tests/language/control/define.at:
+@example
+!CONCAT(x, 0)                @expansion{} x0
+!CONCAT(x, 0, y)             @expansion{} x0y
+@end example
+
+An identifier token must begin with a letter (or @samp{#} or
+@samp{@@}), which means that attempting to use a number as the first
+part of an identifier will produce a pair of distinct tokens rather
+than a single one.  For example:
+
+@c Keep these examples in sync with the test for !CONCAT in
+@c tests/language/control/define.at:
+@example
+!CONCAT(0, x)                @expansion{} 0 x
+!CONCAT(0, x, y)             @expansion{} 0 xy
+@end example
 @end deffn
 
 @deffn {Macro Function} !EVAL (arg)
index 718f268e658505dd3c74f7e76cbafd3c28ad011e..415b84bade8c4477742ccd43859b25fe50fb5000 100644 (file)
@@ -31,6 +31,7 @@
 #include "libpspp/message.h"
 #include "libpspp/str.h"
 #include "libpspp/string-array.h"
+#include "libpspp/string-map.h"
 
 #include "gl/c-ctype.h"
 
@@ -712,13 +713,15 @@ struct parse_macro_function_ctx
     int nesting_countdown;
     const struct macro_set *macros;
     const struct macro_expander *me;
+    struct string_map *vars;
     bool *expand;
   };
 
 static void
 macro_expand (const struct macro_tokens *,
               int nesting_countdown, const struct macro_set *,
-              const struct macro_expander *, bool *expand, struct macro_tokens *exp);
+              const struct macro_expander *, struct string_map *vars,
+              bool *expand, struct macro_tokens *exp);
 
 static bool
 expand_macro_function (struct parse_macro_function_ctx *ctx,
@@ -776,12 +779,25 @@ parse_function_arg (struct parse_macro_function_ctx *ctx,
           return 2;
         }
 
+      if (ctx->vars)
+        {
+          const char *value = string_map_find__ (ctx->vars,
+                                                 token->string.string,
+                                                 token->string.length);
+          if (value)
+            {
+              ds_put_cstr (farg, value);
+              return 1;
+            }
+        }
+
       struct parse_macro_function_ctx subctx = {
         .input = &ctx->input[i],
         .n_input = ctx->n_input - i,
         .nesting_countdown = ctx->nesting_countdown,
         .macros = ctx->macros,
         .me = ctx->me,
+        .vars = ctx->vars,
         .expand = ctx->expand,
       };
       size_t subinput_consumed;
@@ -1035,8 +1051,8 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
       macro_tokens_from_string (&mts, ss_cstr (args.strings[0]),
                                 SEG_MODE_INTERACTIVE /* XXX */);
       struct macro_tokens exp = { .n = 0 };
-      macro_expand (&mts, ctx->nesting_countdown - 1, ctx->macros,
-                    ctx->me, ctx->expand, &exp);
+      macro_expand (&mts, ctx->nesting_countdown - 1, ctx->macros, ctx->me,
+                    ctx->vars, ctx->expand, &exp);
       macro_tokens_to_representation (&exp, output);
       macro_tokens_uninit (&exp);
       macro_tokens_uninit (&mts);
@@ -1060,6 +1076,7 @@ struct expr_context
     int nesting_countdown;
     const struct macro_set *macros;
     const struct macro_expander *me;
+    struct string_map *vars;
     bool *expand;
   };
 
@@ -1098,6 +1115,7 @@ macro_evaluate_literal (const struct expr_context *ctx,
     .nesting_countdown = ctx->nesting_countdown,
     .macros = ctx->macros,
     .me = ctx->me,
+    .vars = ctx->vars,
     .expand = ctx->expand,
   };
   struct string function_output = DS_EMPTY_INITIALIZER;
@@ -1147,8 +1165,8 @@ parse_relational_op (const struct macro_token *mt)
 
 static char *
 macro_evaluate_relational (const struct expr_context *ctx,
-                        const struct macro_token **tokens,
-                        const struct macro_token *end)
+                           const struct macro_token **tokens,
+                           const struct macro_token *end)
 {
   const struct macro_token *p = *tokens;
   char *lhs = macro_evaluate_literal (ctx, &p, end);
@@ -1284,12 +1302,14 @@ macro_evaluate_or (const struct expr_context *ctx,
 static char *
 macro_evaluate_expression (const struct macro_token **tokens, size_t n_tokens,
                            int nesting_countdown, const struct macro_set *macros,
-                           const struct macro_expander *me, bool *expand)
+                           const struct macro_expander *me, struct string_map *vars,
+                           bool *expand)
 {
   const struct expr_context ctx = {
     .nesting_countdown = nesting_countdown,
     .macros = macros,
     .me = me,
+    .vars = vars,
     .expand = expand,
   };
   return macro_evaluate_or (&ctx, tokens, *tokens + n_tokens);
@@ -1321,8 +1341,8 @@ find_ifend_clause (const struct macro_token *p, const struct macro_token *end)
 static size_t
 macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
                  int nesting_countdown, const struct macro_set *macros,
-                 const struct macro_expander *me, bool *expand,
-                 struct macro_tokens *exp)
+                 const struct macro_expander *me, struct string_map *vars,
+                 bool *expand, struct macro_tokens *exp)
 {
   const struct macro_token *p = tokens;
   const struct macro_token *end = tokens + n_tokens;
@@ -1332,7 +1352,8 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
 
   p++;
   char *result = macro_evaluate_expression (&p, end - p,
-                                            nesting_countdown, macros, me, expand);
+                                            nesting_countdown, macros, me, vars,
+                                            expand);
   if (!result)
     return 0;
   bool b = strcmp (result, "0");
@@ -1396,16 +1417,54 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
         .mts = CONST_CAST (struct macro_token *, start),
         .n = n,
       };
-      macro_expand (&mts, nesting_countdown, macros, me, expand, exp);
+      macro_expand (&mts, nesting_countdown, macros, me, vars, expand, exp);
     }
   return (end_if + 1) - tokens;
 }
 
+static size_t
+macro_parse_let (const struct macro_token *tokens, size_t n_tokens,
+                 int nesting_countdown, const struct macro_set *macros,
+                 const struct macro_expander *me, struct string_map *vars,
+                 bool *expand)
+{
+  const struct macro_token *p = tokens;
+  const struct macro_token *end = tokens + n_tokens;
+
+  if (p >= end || !ss_equals_case (p->token.string, ss_cstr ("!LET")))
+    return 0;
+  p++;
+
+  if (p >= end || p->token.type != T_MACRO_ID)
+    {
+      printf ("expected macro variable name following !LET\n");
+      return 0;
+    }
+  const struct substring var_name = p->token.string;
+  p++;
+
+  if (p >= end || p->token.type != T_EQUALS)
+    {
+      printf ("expected = following !LET\n");
+      return 0;
+    }
+  p++;
+
+  char *value = macro_evaluate_expression (&p, end - p,
+                                           nesting_countdown, macros, me, vars,
+                                           expand);
+  if (!value)
+    return 0;
+
+  string_map_replace_nocopy (vars, ss_xstrdup (var_name), value);
+  return p - tokens;
+}
+
 static void
 macro_expand (const struct macro_tokens *mts,
               int nesting_countdown, const struct macro_set *macros,
-              const struct macro_expander *me, bool *expand,
-              struct macro_tokens *exp)
+              const struct macro_expander *me, struct string_map *vars,
+              bool *expand, struct macro_tokens *exp)
 {
   if (nesting_countdown <= 0)
     {
@@ -1415,6 +1474,9 @@ macro_expand (const struct macro_tokens *mts,
       return;
     }
 
+  struct string_map own_vars = STRING_MAP_INITIALIZER (own_vars);
+  if (!vars)
+    vars = &own_vars;
   for (size_t i = 0; i < mts->n; i++)
     {
       const struct macro_token *mt = &mts->mts[i];
@@ -1428,7 +1490,8 @@ macro_expand (const struct macro_tokens *mts,
               const struct macro_tokens *arg = me->args[param - me->macro->params];
               //macro_tokens_print (arg, stdout);
               if (*expand && param->expand_arg)
-                macro_expand (arg, nesting_countdown, macros, NULL, expand, exp);
+                macro_expand (arg, nesting_countdown, macros, NULL, NULL,
+                              expand, exp);
               else
                 for (size_t i = 0; i < arg->n; i++)
                   macro_tokens_add (exp, &arg->mts[i]);
@@ -1445,7 +1508,8 @@ macro_expand (const struct macro_tokens *mts,
 
                   const struct macro_tokens *arg = me->args[j];
                   if (*expand && param->expand_arg)
-                    macro_expand (arg, nesting_countdown, macros, NULL, expand, exp);
+                    macro_expand (arg, nesting_countdown, macros, NULL, NULL,
+                                  expand, exp);
                   else
                     for (size_t k = 0; k < arg->n; k++)
                       macro_tokens_add (exp, &arg->mts[k]);
@@ -1455,8 +1519,8 @@ macro_expand (const struct macro_tokens *mts,
             }
 
           size_t n = macro_expand_if (&mts->mts[i], mts->n - i,
-                                      nesting_countdown, macros, me, expand,
-                                      exp);
+                                      nesting_countdown, macros, me, vars,
+                                      expand, exp);
           if (n > 0)
             {
               i += n - 1;
@@ -1464,6 +1528,18 @@ macro_expand (const struct macro_tokens *mts,
             }
         }
 
+      if (token->type == T_MACRO_ID && vars)
+        {
+          const char *value = string_map_find__ (vars, token->string.string,
+                                                 token->string.length);
+          if (value)
+            {
+              macro_tokens_from_string (exp, ss_cstr (value),
+                                        SEG_MODE_INTERACTIVE /* XXX */);
+              continue;
+            }
+        }
+
       if (*expand)
         {
           struct macro_expander *subme;
@@ -1478,7 +1554,7 @@ macro_expand (const struct macro_tokens *mts,
             {
               i += retval - 1;
               macro_expand (&subme->macro->body, nesting_countdown - 1, macros,
-                            subme, expand, exp);
+                            subme, NULL, expand, exp);
               macro_expander_destroy (subme);
               continue;
             }
@@ -1498,6 +1574,7 @@ macro_expand (const struct macro_tokens *mts,
         .nesting_countdown = nesting_countdown,
         .macros = macros,
         .me = me,
+        .vars = vars,
         .expand = expand,
       };
       struct string function_output = DS_EMPTY_INITIALIZER;
@@ -1513,6 +1590,15 @@ macro_expand (const struct macro_tokens *mts,
           continue;
         }
 
+      size_t n = macro_parse_let (&mts->mts[i], mts->n - i,
+                                  nesting_countdown, macros, me, vars,
+                                  expand);
+      if (n > 0)
+        {
+          i += n - 1;
+          continue;
+        }
+
       if (ss_equals_case (token->string, ss_cstr ("!onexpand")))
         *expand = true;
       else if (ss_equals_case (token->string, ss_cstr ("!offexpand")))
@@ -1520,6 +1606,8 @@ macro_expand (const struct macro_tokens *mts,
       else
         macro_tokens_add (exp, mt);
     }
+  if (vars == &own_vars)
+    string_map_destroy (&own_vars);
 }
 
 void
@@ -1535,7 +1623,7 @@ macro_expander_get_expansion (struct macro_expander *me, struct macro_tokens *ex
 
   bool expand = true;
   macro_expand (&me->macro->body, settings_get_mnest (),
-                me->macros, me, &expand, exp);
+                me->macros, me, NULL, &expand, exp);
 
 #if 0
   printf ("expansion:\n");
index 1f57851732df3367dd6fe76043d52dbfd4247526..0571303da1b8a6f049f9a63c692328419f9ecb68 100644 (file)
@@ -384,12 +384,20 @@ PSPP_CHECK_MACRO_EXPANSION([!CONCAT],
 !CONCAT('x', 'y').
 !CONCAT(12, 34).
 !CONCAT(!NULL, 123).
+!CONCAT(x, 0).
+!CONCAT(x, 0, y).
+!CONCAT(0, x).
+!CONCAT(0, x, y).
 !ENDDEFINE],
   [!c.],
   [xy.
 xy.
 1234.
-123.])
+123.
+x0.
+x0y.
+0 x.
+0 xy.])
 
 dnl Keep this test in sync with the examples for !EVAL in the manual.
 PSPP_CHECK_MACRO_EXPANSION([!EVAL],
@@ -713,3 +721,38 @@ true true false
 done
 AT_CLEANUP
 
+AT_SETUP([macro !LET])
+AT_KEYWORDS([let])
+AT_DATA([define.sps], [dnl
+DEFINE !macro(!POS !CMDEND)
+!LET !v1 = !CONCAT('x',!1,'y')
+!LET !v2 = !QUOTE(!v1)
+!LET !v3 = (!LENGTH(!1) = 1)
+!LET !v4 = (!SUBSTR(!1, 3) = !NULL)
+v1=!v1.
+v2=!v2.
+v3=!v3.
+v4=!v4.
+!ENDDEFINE.
+DEBUG EXPAND.
+!macro 0.
+!macro.
+!macro xyzzy.
+])
+AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+v1 = x0y.
+v2 = 'x0y'.
+v3 = 1.
+v4 = 1.
+
+v1 = xy.
+v2 = 'xy'.
+v3 = 0.
+v4 = 1.
+
+v1 = xxyzzyy.
+v2 = 'xxyzzyy'.
+v3 = 0.
+v4 = 0.
+])
+AT_CLEANUP
\ No newline at end of file