Mostly documentation update but some code clarification too.
[pspp] / src / language / lexer / macro.c
index 415b84bade8c4477742ccd43859b25fe50fb5000..ca32800f30a9dc5129d8b6f38f52a597da4398e5 100644 (file)
@@ -34,6 +34,7 @@
 #include "libpspp/string-map.h"
 
 #include "gl/c-ctype.h"
+#include "gl/ftoastr.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
@@ -103,7 +104,7 @@ macro_tokens_from_string (struct macro_tokens *mts, const struct substring src,
     };
 
   struct state state = {
-    .segmenter = SEGMENTER_INIT (mode),
+    .segmenter = segmenter_init (mode, true),
     .body = src,
   };
   struct state saved = state;
@@ -821,7 +822,7 @@ parse_macro_function (struct parse_macro_function_ctx *ctx,
 
   if (!n_tokens
       || tokens[0].token.type != T_MACRO_ID
-      || !ss_equals_case (tokens[0].token.string, function))
+      || !ss_equals_case (tokens[0].token.string, function)) /* XXX abbrevs allowed */
     return false;
 
   if (n_tokens < 2 || tokens[1].token.type != T_LPAREN)
@@ -1120,6 +1121,12 @@ macro_evaluate_literal (const struct expr_context *ctx,
   };
   struct string function_output = DS_EMPTY_INITIALIZER;
   size_t function_consumed = parse_function_arg (&fctx, 0, &function_output);
+  struct string unquoted = DS_EMPTY_INITIALIZER;
+  if (unquote_string (ds_cstr (&function_output), &unquoted))
+    {
+      ds_swap (&function_output, &unquoted);
+      ds_destroy (&unquoted);
+    }
   *tokens = p + function_consumed;
   return ds_steal_cstr (&function_output);
 }
@@ -1315,6 +1322,34 @@ macro_evaluate_expression (const struct macro_token **tokens, size_t n_tokens,
   return macro_evaluate_or (&ctx, tokens, *tokens + n_tokens);
 }
 
+static bool
+macro_evaluate_number (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, double *number)
+{
+  char *s = macro_evaluate_expression (tokens, n_tokens, nesting_countdown,
+                                       macros, me, vars, expand);
+  if (!s)
+    return false;
+
+  struct macro_tokens mts = { .n = 0 };
+  macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+  if (mts.n != 1 || !token_is_number (&mts.mts[0].token))
+    {
+      macro_tokens_print (&mts, stdout);
+      printf ("expression must evaluate to a number (not %s)\n", s);
+      free (s);
+      macro_tokens_uninit (&mts);
+      return false;
+    }
+
+  *number = token_number (&mts.mts[0].token);
+  free (s);
+  macro_tokens_uninit (&mts);
+  return true;
+}
+
 static const struct macro_token *
 find_ifend_clause (const struct macro_token *p, const struct macro_token *end)
 {
@@ -1460,6 +1495,151 @@ macro_parse_let (const struct macro_token *tokens, size_t n_tokens,
   return p - tokens;
 }
 
+static const struct macro_token *
+find_doend (const struct macro_token *p, const struct macro_token *end)
+{
+  size_t nesting = 0;
+  for (; p < end; p++)
+    {
+      if (p->token.type != T_MACRO_ID)
+        continue;
+
+      if (ss_equals_case (p->token.string, ss_cstr ("!DO")))
+        nesting++;
+      else if (ss_equals_case (p->token.string, ss_cstr ("!DOEND")))
+        {
+          if (!nesting)
+            return p;
+          nesting--;
+        }
+    }
+  printf ("missing !DOEND\n");
+  return NULL;
+}
+
+static size_t
+macro_expand_do (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, struct macro_tokens *exp)
+{
+  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 ("!DO")))
+    return 0;
+  p++;
+
+  if (p >= end || p->token.type != T_MACRO_ID)
+    {
+      printf ("expected macro variable name following !DO\n");
+      return 0;
+    }
+  const struct substring var_name = p->token.string;
+  p++;
+
+  if (p < end && p->token.type == T_MACRO_ID
+      && ss_equals_case (p->token.string, ss_cstr ("!IN")))
+    {
+      p++;
+      char *list = macro_evaluate_expression (&p, end - p,
+                                              nesting_countdown, macros, me, vars,
+                                              expand);
+      if (!list)
+        return 0;
+
+      struct macro_tokens items = { .n = 0 };
+      macro_tokens_from_string (&items, ss_cstr (list),
+                                SEG_MODE_INTERACTIVE /* XXX */);
+      free (list);
+
+      const struct macro_token *do_end = find_doend (p, end);
+      if (!do_end)
+        {
+          macro_tokens_uninit (&items);
+          return 0;
+        }
+
+      const struct macro_tokens inner = {
+        .mts = CONST_CAST (struct macro_token *, p),
+        .n = do_end - p
+      };
+      for (size_t i = 0; i < items.n; i++)
+        {
+          string_map_replace_nocopy (vars, ss_xstrdup (var_name),
+                                     ss_xstrdup (items.mts[i].representation));
+          macro_expand (&inner, nesting_countdown, macros,
+                        me, vars, expand, exp);
+        }
+      return do_end - tokens + 1;
+    }
+  else if (p < end && p->token.type == T_EQUALS)
+    {
+      p++;
+      double first;
+      if (!macro_evaluate_number (&p, end - p, nesting_countdown, macros, me,
+                                  vars, expand, &first))
+        return 0;
+
+      if (p >= end || p->token.type != T_MACRO_ID
+          || !ss_equals_case (p->token.string, ss_cstr ("!TO")))
+        {
+          printf ("expecting !TO\n");
+          return 0;
+        }
+      p++;
+
+      double last;
+      if (!macro_evaluate_number (&p, end - p, nesting_countdown, macros, me,
+                                  vars, expand, &last))
+        return 0;
+
+      double by = 1.0;
+      if (p < end && p->token.type == T_MACRO_ID
+          && ss_equals_case (p->token.string, ss_cstr ("!BY")))
+        {
+          p++;
+          if (!macro_evaluate_number (&p, end - p, nesting_countdown, macros, me,
+                                      vars, expand, &by))
+            return 0;
+
+          if (by == 0.0)
+            {
+              printf ("!BY value cannot be zero\n");
+              return 0;
+            }
+        }
+
+      const struct macro_token *do_end = find_doend (p, end);
+      if (!do_end)
+        return 0;
+      const struct macro_tokens inner = {
+        .mts = CONST_CAST (struct macro_token *, p),
+        .n = do_end - p
+      };
+
+      if ((by > 0 && first <= last) || (by < 0 && first >= last))
+        for (double index = first;
+             by > 0 ? (index <= last) : (index >= last);
+             index += by)
+          {
+            char index_s[DBL_BUFSIZE_BOUND];
+            c_dtoastr (index_s, sizeof index_s, 0, 0, index);
+            string_map_replace_nocopy (vars, ss_xstrdup (var_name),
+                                       xstrdup (index_s));
+            macro_expand (&inner, nesting_countdown, macros,
+                          me, vars, expand, exp);
+          }
+
+      return do_end - tokens + 1;
+    }
+  else
+    {
+      printf ("expecting = or !IN in !DO loop\n");
+      return 0;
+    }
+}
+
 static void
 macro_expand (const struct macro_tokens *mts,
               int nesting_countdown, const struct macro_set *macros,
@@ -1599,6 +1779,15 @@ macro_expand (const struct macro_tokens *mts,
           continue;
         }
 
+      n = macro_expand_do (&mts->mts[i], mts->n - i,
+                           nesting_countdown, macros, me, vars,
+                           expand, exp);
+      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")))