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
}
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;
#include "libpspp/str.h"
#include "libpspp/string-array.h"
+#include "gl/c-ctype.h"
+
#include "gettext.h"
#define _(msgid) gettext (msgid)
.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)
{
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;
free (lhs);
free (rhs);
-
+
bool b = (op == T_EQUALS || op == T_EQ ? !cmp
: op == T_NE ? cmp
: op == T_LT ? cmp < 0
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;
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);
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);
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
+