From 97650194a14bde3c89cf3164497d93a58110c23f Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 12 Jun 2021 20:37:51 -0700 Subject: [PATCH] logical operator bug fixes --- doc/flow-control.texi | 4 +- src/language/control/define.c | 8 +- src/language/lexer/macro.c | 72 ++++++++++++---- tests/language/control/define.at | 144 ++++++++++++++++++++++++++++++- 4 files changed, 205 insertions(+), 23 deletions(-) diff --git a/doc/flow-control.texi b/doc/flow-control.texi index 3b52205ef3..27955800e6 100644 --- a/doc/flow-control.texi +++ b/doc/flow-control.texi @@ -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 diff --git a/src/language/control/define.c b/src/language/control/define.c index 187eb9fcf8..7e9e5f37d4 100644 --- a/src/language/control/define.c +++ b/src/language/control/define.c @@ -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; diff --git a/src/language/lexer/macro.c b/src/language/lexer/macro.c index fdf76bacea..718f268e65 100644 --- a/src/language/lexer/macro.c +++ b/src/language/lexer/macro.c @@ -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); diff --git a/tests/language/control/define.at b/tests/language/control/define.at index acafa4b4ea..76685791a1 100644 --- a/tests/language/control/define.at +++ b/tests/language/control/define.at @@ -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 < < > <= >=' +do + set $operators + AS_BOX([$operators]) + cat > define.sps < define.sps <