!*
[pspp] / src / language / lexer / macro.c
index d4d7b322b9218b28636e165bfcd0b80f6c088825..b120b35d725e432fecbb96f5f503ee9031cc0b2f 100644 (file)
@@ -142,6 +142,7 @@ macro_tokens_from_string (struct macro_tokens *mts, const struct substring src,
         {
           if (token->type != SCAN_SKIP)
             {
+              printf ("error\n");
               /* XXX report error */
             }
         }
@@ -720,6 +721,17 @@ static bool
 expand_macro_function (struct parse_macro_function_ctx *ctx,
                        struct string *output, size_t *input_consumed);
 
+/* Returns true if the pair of tokens starting at offset OFS within MTS are !*,
+   false otherwise. */
+static bool
+is_bang_star (const struct macro_token *mts, size_t n, size_t ofs)
+{
+  return (ofs + 1 < n
+          && mts[ofs].token.type == T_MACRO_ID
+          && ss_equals (mts[ofs].token.string, ss_cstr ("!"))
+          && mts[ofs + 1].token.type == T_ASTERISK);
+}
+
 static size_t
 parse_function_arg (struct parse_macro_function_ctx *ctx,
                     size_t i, struct string *farg)
@@ -743,6 +755,24 @@ parse_function_arg (struct parse_macro_function_ctx *ctx,
           return 1;
         }
 
+      if (is_bang_star (ctx->input, ctx->n_input, i))
+        {
+          for (size_t i = 0; i < ctx->me->macro->n_params; i++)
+            {
+              if (!ctx->me->macro->params[i].positional)
+                break;
+
+              const struct macro_tokens *marg = ctx->me->args[i];
+              for (size_t j = 0; j < marg->n; j++)
+                {
+                  if (i || j)
+                    ds_put_byte (farg, ' ');
+                  ds_put_substring (farg, marg->mts[j].representation);
+                }
+            }
+          return 2;
+        }
+
       struct parse_macro_function_ctx subctx = {
         .input = &ctx->input[i],
         .n_input = ctx->n_input - i,
@@ -826,7 +856,7 @@ error:
 }
 
 static bool
-string_is_quoted_string (const char *s, struct string *content)
+unquote_string (const char *s, struct string *content)
 {
   struct string_lexer slex;
   string_lexer_init (&slex, s, strlen (s), SEG_MODE_INTERACTIVE /* XXX */);
@@ -854,6 +884,25 @@ string_is_quoted_string (const char *s, struct string *content)
   return true;
 }
 
+static const char *
+unquote_string_in_place (const char *s, struct string *tmp)
+{
+  ds_init_empty (tmp);
+  return unquote_string (s, tmp) ? ds_cstr (tmp) : s;
+}
+
+static bool
+parse_integer (const char *s, int *np)
+{
+  errno = 0;
+
+  char *tail;
+  long int n = strtol (s, &tail, 10);
+  *np = n < INT_MIN ? INT_MIN : n > INT_MAX ? INT_MAX : n;
+  tail += strspn (tail, CC_SPACES);
+  return *tail == '\0' && errno != ERANGE && n == *np;
+}
+
 static bool
 expand_macro_function (struct parse_macro_function_ctx *ctx,
                        struct string *output,
@@ -867,10 +916,8 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
   else if (parse_macro_function (ctx, &args, ss_cstr ("!blanks"), 1, 1,
                                  input_consumed))
     {
-      char *tail;
-      errno = 0;
-      int n = strtol (args.strings[0], &tail, 10);
-      if (*tail != '\0' || n < 0 || errno == ERANGE)
+      int n;
+      if (!parse_integer (args.strings[0], &n))
         {
           printf ("argument to !BLANKS must be non-negative integer (not \"%s\")\n", args.strings[0]);
           string_array_destroy (&args);
@@ -883,13 +930,33 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
                                  input_consumed))
     {
       for (size_t i = 0; i < args.n; i++)
-        if (!string_is_quoted_string (args.strings[i], output))
+        if (!unquote_string (args.strings[i], output))
           ds_put_cstr (output, args.strings[i]);
     }
+  else if (parse_macro_function (ctx, &args, ss_cstr ("!head"), 1, 1,
+                                 input_consumed))
+    {
+      struct string tmp;
+      const char *s = unquote_string_in_place (args.strings[0], &tmp);
+
+      struct macro_tokens mts = { .n = 0 };
+      macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+      if (mts.n > 0)
+        ds_put_substring (output, mts.mts[0].representation);
+      macro_tokens_uninit (&mts);
+      ds_destroy (&tmp);
+    }
+  else if (parse_macro_function (ctx, &args, ss_cstr ("!index"), 2, 2,
+                                 input_consumed))
+    {
+      const char *haystack = args.strings[0];
+      const char *needle = strstr (haystack, args.strings[1]);
+      ds_put_format (output, "%zu", needle ? needle - haystack + 1 : 0);
+    }
   else if (parse_macro_function (ctx, &args, ss_cstr ("!quote"), 1, 1,
                                  input_consumed))
     {
-      if (string_is_quoted_string (args.strings[0], NULL))
+      if (unquote_string (args.strings[0], NULL))
         ds_put_cstr (output, args.strings[0]);
       else
         {
@@ -904,12 +971,73 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
           ds_put_byte (output, '\'');
         }
     }
+  else if (parse_macro_function (ctx, &args, ss_cstr ("!substr"), 2, 3,
+                                 input_consumed))
+    {
+      int start;
+      if (!parse_integer (args.strings[1], &start) || start < 1)
+        {
+          printf ("second argument to !SUBSTR must be positive integer (not \"%s\")\n", args.strings[1]);
+          string_array_destroy (&args);
+          return false;
+        }
+
+      int count = INT_MAX;
+      if (args.n > 2 && (!parse_integer (args.strings[2], &count) || count < 0))
+        {
+          printf ("third argument to !SUBSTR must be non-negative integer (not \"%s\")\n", args.strings[1]);
+          string_array_destroy (&args);
+          return false;
+        }
+
+      struct substring s = ss_cstr (args.strings[0]);
+      ds_put_substring (output, ss_substr (s, start - 1, count));
+    }
+  else if (parse_macro_function (ctx, &args, ss_cstr ("!tail"), 1, 1,
+                                 input_consumed))
+    {
+      struct string tmp;
+      const char *s = unquote_string_in_place (args.strings[0], &tmp);
+
+      struct macro_tokens mts = { .n = 0 };
+      macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+      if (mts.n > 1)
+        {
+          struct macro_tokens tail = { .mts = mts.mts + 1, .n = mts.n - 1 };
+          macro_tokens_to_representation (&tail, output);
+        }
+      macro_tokens_uninit (&mts);
+      ds_destroy (&tmp);
+    }
   else if (parse_macro_function (ctx, &args, ss_cstr ("!unquote"), 1, 1,
                                  input_consumed))
     {
-      if (!string_is_quoted_string (args.strings[0], output))
+      if (!unquote_string (args.strings[0], output))
         ds_put_cstr (output, args.strings[0]);
     }
+  else if (parse_macro_function (ctx, &args, ss_cstr ("!upcase"), 1, 1,
+                                 input_consumed))
+    {
+      struct string tmp;
+      const char *s = unquote_string_in_place (args.strings[0], &tmp);
+      char *upper = utf8_to_upper (s);
+      ds_put_cstr (output, upper);
+      free (upper);
+      ds_destroy (&tmp);
+    }
+  else if (parse_macro_function (ctx, &args, ss_cstr ("!eval"), 1, 1,
+                                 input_consumed))
+    {
+      struct macro_tokens mts = { .n = 0 };
+      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_tokens_to_representation (&exp, output);
+      macro_tokens_uninit (&exp);
+      macro_tokens_uninit (&mts);
+    }
   else if (ctx->n_input > 0
            && ctx->input[0].token.type == T_MACRO_ID
            && ss_equals_case (ctx->input[0].token.string, ss_cstr ("!null")))
@@ -930,13 +1058,6 @@ macro_expand (const struct macro_tokens *mts,
               const struct macro_expander *me, bool *expand,
               struct macro_tokens *exp)
 {
-  /* Macro expansion:
-
-     - Macro names in macro bodies are not expanded by default.  !EVAL()
-       expands them.
-
-     - Macro names in arguments to macro invocations (outside of macro bodies)
-       are expanded by default, unless !NOEXPAND. */
   if (nesting_countdown <= 0)
     {
       printf ("maximum nesting level exceeded\n");
@@ -964,6 +1085,25 @@ macro_expand (const struct macro_tokens *mts,
                   macro_tokens_add (exp, &arg->mts[i]);
               continue;
             }
+
+          if (is_bang_star (mts->mts, mts->n, i))
+            {
+              for (size_t j = 0; j < me->macro->n_params; j++)
+                {
+                  const struct macro_param *param = &me->macro->params[j];
+                  if (!param->positional)
+                    break;
+
+                  const struct macro_tokens *arg = me->args[j];
+                  if (*expand && param->expand_arg)
+                    macro_expand (arg, nesting_countdown, macros, NULL, expand, exp);
+                  else
+                    for (size_t k = 0; k < arg->n; k++)
+                      macro_tokens_add (exp, &arg->mts[k]);
+                }
+              i++;
+              continue;
+            }
         }
 
       if (*expand)