Implemented all the functions except !EVAL and !UPCASE.
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 10 Jun 2021 06:23:28 +0000 (23:23 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 10 Jun 2021 06:23:28 +0000 (23:23 -0700)
src/language/lexer/macro.c
tests/language/control/define.at

index d4d7b322b9218b28636e165bfcd0b80f6c088825..b17d467faab6506649c7e8e62325a07e8035332c 100644 (file)
@@ -826,7 +826,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 +854,18 @@ string_is_quoted_string (const char *s, struct string *content)
   return true;
 }
 
+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 +879,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 +893,34 @@ 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 content = DS_EMPTY_INITIALIZER;
+      const char *s = (unquote_string (args.strings[0], &content)
+                       ? ds_cstr (&content) : args.strings[0]);
+
+      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 (&content);
+    }
+  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,10 +935,49 @@ 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 content = DS_EMPTY_INITIALIZER;
+      const char *s = (unquote_string (args.strings[0], &content)
+                       ? ds_cstr (&content) : args.strings[0]);
+
+      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 (&content);
+    }
   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 (ctx->n_input > 0
index c192beee0570ad464db7fefad20feabbeca40523..9a13ea45cde5796062422467eb4b5aabda1c524d 100644 (file)
@@ -391,6 +391,52 @@ xy.
 1234.
 123.])
 
+dnl Keep this test in sync with the examples for !HEAD in the manual.
+PSPP_CHECK_MACRO_EXPANSION([!HEAD],
+  [DEFINE !h()
+!HEAD('a b c').
+!HEAD('a').
+!HEAD(!NULL).
+!HEAD('').
+!ENDDEFINE],
+  [!h.],
+  [a.
+a.
+.
+.])
+
+dnl Keep this test in sync with the examples for !TAIL in the manual.
+PSPP_CHECK_MACRO_EXPANSION([!TAIL],
+  [DEFINE !t()
+!TAIL('a b c').
+!TAIL('a').
+!TAIL(!NULL).
+!TAIL('').
+!ENDDEFINE],
+  [!t.],
+  [b c.
+.
+.
+.])
+
+dnl Keep this test in sync with the examples for !INDEX in the manual.
+PSPP_CHECK_MACRO_EXPANSION([!INDEX],
+  [DEFINE !i()
+!INDEX(banana, an).
+!INDEX(banana, nan).
+!INDEX(banana, apple).
+!INDEX("banana", nan).
+!INDEX("banana", "nan").
+!INDEX(!UNQUOTE("banana"), !UNQUOTE("nan")).
+!ENDDEFINE],
+  [!i.],
+  [2.
+3.
+0.
+4.
+0.
+3.])
+
 dnl Keep this test in sync with the examples for !LENGTH in the manual.
 PSPP_CHECK_MACRO_EXPANSION([!LENGTH],
   [DEFINE !l()
@@ -423,3 +469,25 @@ DEFINE !la(!positional !enclose('(',')'))
 0.
 5.
 0.])
+
+dnl Keep this test in sync with the examples for !SUBSTR in the manual.
+PSPP_CHECK_MACRO_EXPANSION([!SUBSTR],
+  [DEFINE !s()
+!SUBSTR(banana, 3).
+!SUBSTR(banana, 3, 3).
+!SUBSTR("banana", 3).
+!SUBSTR("banana", 3, 3).
+!SUBSTR(banana, 3, 0).
+!SUBSTR(banana, 3, 10).
+!SUBSTR(banana, 10, 3).
+!ENDDEFINE.],
+  [!s.],
+  [nana.
+nan.
+anana". dnl"
+
+ana.
+.
+nana.
+.])
+