logical operator bug fixes
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 13 Jun 2021 03:37:51 +0000 (20:37 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 13 Jun 2021 03:37:51 +0000 (20:37 -0700)
doc/flow-control.texi
src/language/control/define.c
src/language/lexer/macro.c
tests/language/control/define.at

index 3b52205ef337430db413ae26bdbe8d9cd29cb05a..27955800e6d228ca4d169776dced683871d3894e 100644 (file)
@@ -533,8 +533,8 @@ The @code{!IF} construct may be used inside a macro body to allow for
 conditional expansion.  It takes the following forms:
 
 @example
-!IF (condition) !THEN true-expansion !ENDIF
-!IF (condition) !THEN true-expansion !ELSE false-expansion !ENDIF
+!IF (condition) !THEN true-expansion !IFEND
+!IF (condition) !THEN true-expansion !ELSE false-expansion !IFEND
 @end example
 
 When the @var{condition} evaluates to true, @var{true-expansion} is
index 187eb9fcf840b1e7932d36c49cfc77f1bde1b1ae..7e9e5f37d4273e59506fe549a5ea9867dbd25e28 100644 (file)
@@ -37,12 +37,12 @@ force_macro_id (struct lexer *lexer)
 }
 
 static bool
-match_macro_id (struct lexer *lexer, const char *id)
+match_macro_id (struct lexer *lexer, const char *keyword)
 {
-  if (id[0] != '!')
-    return lex_match_id (lexer, id);
+  if (keyword[0] != '!')
+    return lex_match_id (lexer, keyword);
   else if (lex_token (lexer) == T_MACRO_ID
-           && ss_equals_case (lex_tokss (lexer), ss_cstr (id)))
+           && lex_id_match_n (ss_cstr (keyword), lex_tokss (lexer), 4))
     {
       lex_get (lexer);
       return true;
index fdf76bacea12b2a07d5d3e6ead9811e7cdb84922..718f268e658505dd3c74f7e76cbafd3c28ad011e 100644 (file)
@@ -32,6 +32,8 @@
 #include "libpspp/str.h"
 #include "libpspp/string-array.h"
 
+#include "gl/c-ctype.h"
+
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
@@ -1099,19 +1101,52 @@ macro_evaluate_literal (const struct expr_context *ctx,
     .expand = ctx->expand,
   };
   struct string function_output = DS_EMPTY_INITIALIZER;
-  size_t function_consumed;
-  if (expand_macro_function (&fctx, &function_output, &function_consumed))
+  size_t function_consumed = parse_function_arg (&fctx, 0, &function_output);
+  *tokens = p + function_consumed;
+  return ds_steal_cstr (&function_output);
+}
+
+/* Returns true if MT is valid as a macro operator.  Only operators written as
+   symbols (e.g. <>) are usable in macro expressions, not operator written as
+   letters (e.g. EQ). */
+static bool
+is_macro_operator (const struct macro_token *mt)
+{
+  return (mt->representation.length > 0
+          && !c_isalpha (mt->representation.string[0]));
+}
+
+static enum token_type
+parse_relational_op (const struct macro_token *mt)
+{
+  switch (mt->token.type)
     {
-      *tokens = p + function_consumed;
-      return ds_steal_cstr (&function_output);
-    }
+    case T_EQUALS:
+      return T_EQ;
 
-  *tokens = p + 1;
-  return ss_xstrdup (p->representation);
+    case T_NE:
+    case T_LT:
+    case T_GT:
+    case T_LE:
+    case T_GE:
+      return is_macro_operator (mt) ? mt->token.type : T_STOP;
+
+    case T_MACRO_ID:
+      return (ss_equals_case (mt->token.string, ss_cstr ("!EQ")) ? T_EQ
+              : ss_equals_case (mt->token.string, ss_cstr ("!NE")) ? T_NE
+              : ss_equals_case (mt->token.string, ss_cstr ("!LT")) ? T_LT
+              : ss_equals_case (mt->token.string, ss_cstr ("!GT")) ? T_GT
+              : ss_equals_case (mt->token.string, ss_cstr ("!LE")) ? T_LE
+              : ss_equals_case (mt->token.string, ss_cstr ("!GE")) ? T_GE
+              : T_STOP);
+
+    default:
+      return T_STOP;
+    }
 }
 
 static char *
-macro_evaluate_logical (const struct expr_context *ctx,
+macro_evaluate_relational (const struct expr_context *ctx,
                         const struct macro_token **tokens,
                         const struct macro_token *end)
 {
@@ -1120,9 +1155,8 @@ macro_evaluate_logical (const struct expr_context *ctx,
   if (!lhs)
     return NULL;
 
-  enum token_type op = p < end ? p->token.type : T_STOP;
-  if (op != T_EQUALS && op != T_EQ && op != T_NE && op != T_LT
-      && op != T_GT && op != T_LE && op != T_GE)
+  enum token_type op = p >= end ? T_STOP : parse_relational_op (p);
+  if (op == T_STOP)
     {
       *tokens = p;
       return lhs;
@@ -1144,7 +1178,7 @@ macro_evaluate_logical (const struct expr_context *ctx,
 
   free (lhs);
   free (rhs);
-  
+
   bool b = (op == T_EQUALS || op == T_EQ ? !cmp
             : op == T_NE ? cmp
             : op == T_LT ? cmp < 0
@@ -1164,13 +1198,15 @@ macro_evaluate_not (const struct expr_context *ctx,
   const struct macro_token *p = *tokens;
 
   unsigned int negations = 0;
-  while (p < end && p->token.type == T_NOT)
+  while (p < end
+         && (ss_equals_case (p->representation, ss_cstr ("!NOT"))
+             || ss_equals (p->representation, ss_cstr ("~"))))
     {
       p++;
       negations++;
     }
 
-  char *operand = macro_evaluate_logical (ctx, &p, end);
+  char *operand = macro_evaluate_relational (ctx, &p, end);
   if (!operand || !negations)
     {
       *tokens = p;
@@ -1193,7 +1229,9 @@ macro_evaluate_and (const struct expr_context *ctx,
   if (!lhs)
     return NULL;
 
-  while (p < end && p->token.type == T_AND)
+  while (p < end
+         && (ss_equals_case (p->representation, ss_cstr ("!AND"))
+             || ss_equals (p->representation, ss_cstr ("&"))))
     {
       p++;
       char *rhs = macro_evaluate_not (ctx, &p, end);
@@ -1222,7 +1260,9 @@ macro_evaluate_or (const struct expr_context *ctx,
   if (!lhs)
     return NULL;
 
-  while (p < end && p->token.type == T_OR)
+  while (p < end
+         && (ss_equals_case (p->representation, ss_cstr ("!OR"))
+             || ss_equals (p->representation, ss_cstr ("|"))))
     {
       p++;
       char *rhs = macro_evaluate_and (ctx, &p, end);
index acafa4b4ea94bd9b53655971eb46b2b7df458017..76685791a1e8f734e6ec05c5a0dfbfec6c88f699 100644 (file)
@@ -563,4 +563,146 @@ AT_CHECK([pspp define.sps], [1], [dnl
 maximum nesting level exceeded
 define.sps.1: error: Syntax error at `!macro': expecting command name.
 ])
-AT_CLEANUP
\ No newline at end of file
+AT_CLEANUP
+
+AT_SETUP([macro !IF condition])
+AT_KEYWORDS([if])
+for operators in \
+    '!eq !ne !lt !gt !le !ge' \
+    '  =  <>   <   >  <=  >='
+do
+    set $operators
+    AS_BOX([$operators])
+    cat > define.sps <<EOF
+DEFINE !test(!positional !tokens(1))
+!if (!1 $1 1) !then true !else false !ifend
+!if (!1 $2 1) !then true !else false !ifend
+!if (!1 $3 1) !then true !else false !ifend
+!if (!1 $4 1) !then true !else false !ifend
+!if (!1 $5 1) !then true !else false !ifend
+!if (!1 $6 1) !then true !else false !ifend.
+!ENDDEFINE.
+DEBUG EXPAND.
+!test 0
+!test 1
+!test 2
+!test '1'
+!test 1.0
+EOF
+    AT_CAPTURE_FILE([define.sps])
+    AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+false true true false true false.
+
+true false false false true true.
+
+false true false true false true.
+
+true false false false true true.
+
+false true false true false true.
+])
+done
+AT_CLEANUP
+
+AT_SETUP([macro !IF condition -- case sensitivity])
+AT_KEYWORDS([if])
+for operators in \
+    '!eq !ne !lt !gt !le !ge' \
+    '  =  <>   <   >  <=  >='
+do
+    set $operators
+    AS_BOX([$operators])
+    cat > define.sps <<EOF
+DEFINE !test(!positional !tokens(1))
+!if (!1 $1 a) !then true !else false !ifend
+!if (!1 $1 A) !then true !else false !ifend
+!if (!1 $2 a) !then true !else false !ifend
+!if (!1 $2 A) !then true !else false !ifend
+!if (!1 $3 a) !then true !else false !ifend
+!if (!1 $3 A) !then true !else false !ifend
+!if (!1 $4 a) !then true !else false !ifend
+!if (!1 $4 A) !then true !else false !ifend
+!if (!1 $5 a) !then true !else false !ifend
+!if (!1 $5 A) !then true !else false !ifend
+!if (!1 $6 a) !then true !else false !ifend
+!if (!1 $6 A) !then true !else false !ifend.
+!ENDDEFINE.
+DEBUG EXPAND.
+!test a
+!test A
+!test b
+!test B
+EOF
+    AT_CAPTURE_FILE([define.sps])
+    AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+true false false true false false false true true false true true.
+
+false true true false true false false false true true false true.
+
+false false true true false false true true false false true true.
+
+false false true true true false false true true false false true.
+])
+done
+AT_CLEANUP
+
+AT_SETUP([macro !IF condition -- logical operators])
+AT_KEYWORDS([if])
+for operators in \
+    '!and !or !not' \
+    '   &   |    ~'
+do
+    set $operators
+    AS_BOX([$operators])
+    cat > define.sps <<EOF
+DEFINE !test_binary(!positional !tokens(1)/!positional !tokens(1))
+!if !1 $1 !2 !then true !else false !ifend
+!if !1 $2 !2 !then true !else false !ifend.
+!ENDDEFINE.
+
+DEFINE !test_unary(!positional !tokens(1))
+!if $3 !1 !then true !else false !ifend.
+!ENDDEFINE.
+
+* This is ((not A) and B) or C.
+DEFINE !test_prec(!pos !tokens(1)/!pos !tokens(1)/!pos !tokens(1))
+!if $3 !1 $1 !2 $2 !3 !then true !else false !ifend
+!ENDDEFINE.
+
+DEBUG EXPAND.
+!test_binary 0 0
+!test_binary 0 1
+!test_binary 1 0
+!test_binary 1 1
+!test_unary 0
+!test_unary 1
+!test_prec 0 0 0 !test_prec 0 0 1 !test_prec 0 1 0 !test_prec 0 1 1.
+!test_prec 1 0 0 !test_prec 1 0 1 !test_prec 1 1 0 !test_prec 1 1 1.
+EOF
+    AT_CAPTURE_FILE([define.sps])
+    AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+false false.
+
+false true.
+
+false true.
+
+true true.
+
+true.
+
+false.
+
+false
+true
+true
+true
+
+false
+true
+false
+true
+])
+done
+AT_CLEANUP
+