Some basic macros and tests work.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 30 May 2021 22:50:46 +0000 (15:50 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 30 May 2021 22:50:57 +0000 (15:50 -0700)
src/language/command.def
src/language/control/define.c
src/language/lexer/lexer.c
src/language/lexer/macro.c
src/language/lexer/macro.h
tests/language/control/define.at

index 12f30c7c037283dd5b76f56b49299208bd2b11c3..63df224bde598e249819da9ad500f81207e2fbb0 100644 (file)
@@ -155,6 +155,7 @@ DEF_CMD (S_INPUT_PROGRAM, 0, "END INPUT PROGRAM", cmd_end_input_program)
 DEF_CMD (S_INPUT_PROGRAM, 0, "REREAD", cmd_reread)
 
 /* Commands for testing PSPP. */
+DEF_CMD (S_ANY, F_TESTING, "DEBUG EXPAND", cmd_debug_expand)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG EVALUATE", cmd_debug_evaluate)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG FORMAT GUESSER", cmd_debug_format_guesser)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG MOMENTS", cmd_debug_moments)
index 686685fc9cb313c5e7b4456eedd021ccbcab050b..6c71f02c09e3986d40f5f35011a8590d1be6b210 100644 (file)
@@ -221,3 +221,13 @@ error:
   macro_destroy (m);
   return CMD_FAILURE;
 }
+
+int
+cmd_debug_expand (struct lexer *lexer, struct dataset *ds UNUSED)
+{
+  settings_set_mprint (true);
+
+  while (lex_token (lexer) != T_STOP)
+    lex_get (lexer);
+  return CMD_SUCCESS;
+}
index 5ff5099652ac0405eebb02c45bd94622e216239b..f577c598b89c9bf5a0ce939e94476feef5cfd417 100644 (file)
@@ -1694,9 +1694,10 @@ lex_source_get (const struct lex_source *src_)
     {
       if (!lex_source_get__ (src))
         {
-          /* This should not be reachable because we always get a T_STOP at the
-             end of input and the macro_expander should always terminate
-             expansion on T_STOP. */
+          /* This should not be reachable because we always get a T_ENDCMD at
+             the end of an input file (transformed from T_STOP by
+             lex_source_try_get()) and the macro_expander should always
+             terminate expansion on T_ENDCMD. */
           NOT_REACHED ();
         }
 
@@ -1723,6 +1724,15 @@ lex_source_get (const struct lex_source *src_)
   macro_expander_get_expansion (me, &expansion);
   macro_expander_destroy (me);
 
+  if (settings_get_mprint ())
+    {
+      struct string mprint = DS_EMPTY_INITIALIZER;
+      macro_tokens_to_representation (&expansion, &mprint);
+      output_item_submit (text_item_create (TEXT_ITEM_LOG, ds_cstr (&mprint),
+                                            _("Macro Expansion")));
+      ds_destroy (&mprint);
+    }
+
   for (size_t i = 0; i < expansion.n; i++)
     {
       *lex_push_token__ (src) = (struct lex_token) {
index a15b7064a5d96537f84998aedd9c0b6ea5ed2bed..32185fb9cf03016bc3c8cf1bab602e0e536d2149 100644 (file)
@@ -46,6 +46,12 @@ macro_token_uninit (struct macro_token *mt)
   ss_dealloc (&mt->representation);
 }
 
+void
+macro_token_to_representation (struct macro_token *mt, struct string *s)
+{
+  ds_put_substring (s, mt->representation);
+}
+
 void
 macro_tokens_copy (struct macro_tokens *dst, const struct macro_tokens *src)
 {
@@ -153,6 +159,115 @@ macro_tokens_print (const struct macro_tokens *mts, FILE *stream)
     token_print (&mts->mts[i].token, stream);
 }
 
+enum token_class
+  {
+    TC_ENDCMD,                  /* No space before or after (new-line after). */
+    TC_BINOP,                   /* Space on both sides. */
+    TC_COMMA,                   /* Space afterward. */
+    TC_ID,                      /* Don't need spaces except sequentially. */
+    TC_PUNCT,                   /* Don't need spaces except sequentially. */
+  };
+
+static bool
+needs_space (enum token_class prev, enum token_class next)
+{
+  /* Don't need a space before or after the end of a command.
+     (A new-line is needed afterward as a special case.) */
+  if (prev == TC_ENDCMD || next == TC_ENDCMD)
+    return false;
+
+  /* Binary operators always have a space on both sides. */
+  if (prev == TC_BINOP || next == TC_BINOP)
+    return true;
+
+  /* A comma always has a space afterward. */
+  if (prev == TC_COMMA)
+    return true;
+
+  /* Otherwise, PREV is TC_ID or TC_PUNCT, which only need a space if there are
+     two or them in a row. */
+  return prev == next;
+}
+
+static enum token_class
+classify_token (enum token_type type)
+{
+  switch (type)
+    {
+    case T_ID:
+    case T_MACRO_ID:
+    case T_POS_NUM:
+    case T_NEG_NUM:
+    case T_STRING:
+      return TC_ID;
+
+    case T_STOP:
+      return TC_PUNCT;
+
+    case T_ENDCMD:
+      return TC_ENDCMD;
+
+    case T_LPAREN:
+    case T_RPAREN:
+    case T_LBRACK:
+    case T_RBRACK:
+      return TC_PUNCT;
+
+    case T_PLUS:
+    case T_DASH:
+    case T_ASTERISK:
+    case T_SLASH:
+    case T_EQUALS:
+    case T_AND:
+    case T_OR:
+    case T_NOT:
+    case T_EQ:
+    case T_GE:
+    case T_GT:
+    case T_LE:
+    case T_LT:
+    case T_NE:
+    case T_ALL:
+    case T_BY:
+    case T_TO:
+    case T_WITH:
+    case T_EXP:
+    case T_MACRO_PUNCT:
+      return TC_BINOP;
+
+    case T_COMMA:
+      return TC_COMMA;
+    }
+
+  NOT_REACHED ();
+}
+
+void
+macro_tokens_to_representation (struct macro_tokens *mts, struct string *s)
+{
+  if (!mts->n)
+    return;
+
+  macro_token_to_representation (&mts->mts[0], s);
+  for (size_t i = 1; i < mts->n; i++)
+    {
+      enum token_type prev = mts->mts[i - 1].token.type;
+      enum token_type next = mts->mts[i].token.type;
+
+      if (prev == T_ENDCMD)
+        ds_put_byte (s, '\n');
+      else
+        {
+          enum token_class pc = classify_token (prev);
+          enum token_class nc = classify_token (next);
+          if (needs_space (pc, nc))
+            ds_put_byte (s, ' ');
+        }
+
+      macro_token_to_representation (&mts->mts[i], s);
+    }
+}
+
 void
 macro_destroy (struct macro *m)
 {
@@ -341,9 +456,9 @@ static int
 me_add_arg (struct macro_expander *me, const struct macro_token *mt)
 {
   const struct token *token = &mt->token;
-  if (token->type == T_STOP)
+  if (token->type == T_ENDCMD || token->type == T_STOP)
     {
-      msg (SE, _("Unexpected end of file reading argument %s "
+      msg (SE, _("Unexpected end of command reading argument %s "
                  "to macro %s."), me->param->name, me->macro->name);
 
       return me_error (me);
@@ -373,7 +488,7 @@ me_add_arg (struct macro_expander *me, const struct macro_token *mt)
   else
     {
       const struct token *end
-        = p->arg_type == ARG_CMDEND ? &p->charend : &p->enclose[1];
+        = p->arg_type == ARG_CHAREND ? &p->charend : &p->enclose[1];
       if (token_equal (token, end))
         return me_next_arg (me);
       macro_tokens_add (arg, mt);
@@ -851,9 +966,9 @@ macro_expand (const struct macro_tokens *mts,
           int retval = macro_expander_create (macros, token, &subme);
           for (size_t j = 1; !retval; j++)
             {
-              const struct macro_token stop = { .token = { .type = T_STOP } };
+              const struct macro_token endcmd = { .token = { .type = T_ENDCMD } };
               retval = macro_expander_add (
-                subme, i + j < mts->n ? &mts->mts[i + j] : &stop);
+                subme, i + j < mts->n ? &mts->mts[i + j] : &endcmd);
             }
           if (retval > 0)
             {
index 23ae1d9a1869e4f618f62ce0a80874ae666dfa3c..0b6fd0dac3f942bf244309152c698afd39710cb5 100644 (file)
@@ -36,6 +36,8 @@ struct macro_token
 void macro_token_copy (struct macro_token *, const struct macro_token *);
 void macro_token_uninit (struct macro_token *);
 
+void macro_token_to_representation (struct macro_token *, struct string *);
+
 struct macro_tokens
   {
     struct macro_token *mts;
@@ -51,6 +53,8 @@ void macro_tokens_add (struct macro_tokens *, const struct macro_token *);
 void macro_tokens_from_string (struct macro_tokens *, const struct substring,
                                enum segmenter_mode);
 
+void macro_tokens_to_representation (struct macro_tokens *, struct string *);
+
 void macro_tokens_print (const struct macro_tokens *, FILE *);
 
 struct macro_param
index d187b046d5523f53409806bdcf5423e405507073..f1f3f41fa062bbdb7346dcad911d9c4729177009 100644 (file)
@@ -16,11 +16,57 @@ dnl along with this program.  If not, see <http://www.gnu.org/licenses/>.
 dnl
 AT_BANNER([DEFINE])
 
-AT_SETUP([DEFINE])
+AT_SETUP([simple macro expansion])
 AT_DATA([define.sps], [dnl
-DEFINE !variables()
-  brand model license color
+DEFINE !macro()
+a b c d
+e f g h.
+i j k l
+1,2,3,4.
+5+6+7.
+m(n,o).
+"a" "b" "c" 'a' 'b' 'c'.
+"x "" y".
 !ENDDEFINE.
+DEBUG EXPAND.
+!macro()
+])
+AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+a b c d e f g h.
+i j k l 1, 2, 3, 4.
+5 + 6 + 7.
+m(n, o).
+"a" "b" "c" 'a' 'b' 'c'.
+"x "" y".
+])
+AT_CLEANUP
+
+AT_SETUP([macro expansion with arguments])
+AT_DATA([define.sps], [dnl
+DEFINE !t1(!positional !tokens(1)) (!1) !ENDDEFINE.
+DEFINE !t2(!positional !tokens(2)) (!1) !ENDDEFINE.
+DEFINE !ce(!positional !charend('/')) (!1) !ENDDEFINE.
+DEBUG EXPAND.
+!t1 a.
+!t1 b.
+!t1 a b.
+
+!t2 a b.
+!t2 b c d.
+
+!ce x y z/.
+])
+AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+(a)
+
+(b)
+
+(a)
+
+(a b)
+
+(b c)
+
+(x y z)
 ])
-AT_CHECK([pspp define.sps])
 AT_CLEANUP