#include "language/lexer/lexer.h"
#include "language/command.h"
+static bool default_optimize = true;
+
int
cmd_debug_evaluate (struct lexer *lexer, struct dataset *dsother UNUSED)
{
- bool optimize = true;
+ bool optimize = default_optimize;
int retval = CMD_FAILURE;
bool dump_postfix = false;
+ bool set_defaults = false;
struct ccase *c = NULL;
{
struct dictionary *d = NULL;
if (lex_match_id (lexer, "NOOPTIMIZE"))
- optimize = 0;
+ optimize = false;
+ else if (lex_match_id (lexer, "OPTIMIZE"))
+ optimize = true;
else if (lex_match_id (lexer, "POSTFIX"))
dump_postfix = 1;
+ else if (lex_match_id (lexer, "SET"))
+ set_defaults = true;
else if (lex_match (lexer, T_LPAREN))
{
struct variable *v;
break;
}
+ if (set_defaults)
+ {
+ retval = CMD_SUCCESS;
+ default_optimize = optimize;
+ goto done;
+ }
+
if (!lex_force_match (lexer, T_SLASH))
goto done;
case OP_integer:
ds_put_format (&s, "i<%d>", op->integer);
break;
- case OP_exprnode:
+ case OP_expr_node:
ds_put_cstr (&s, "expr_node");
break;
default:
Type.new_leaf('vector', 'const struct vector *',
'vector', 'v', 'vector'),
+ # Types as leaves or auxiliary data.
+ Type.new_leaf('expr_node', 'const struct expr_node *',
+ 'expr_node', 'e', 'expr_node'),
+
# Types that appear only as auxiliary data.
Type.new_auxonly('expression', 'struct expression *', 'e'),
- Type.new_auxonly('expr_node', 'const struct expr_node *', 'n'),
Type.new_auxonly('case', 'const struct ccase *', 'c'),
Type.new_auxonly('case_idx', 'size_t', 'case_idx'),
Type.new_auxonly('dataset', 'struct dataset *', 'ds'),
# Used only for debugging purposes.
Type.new_atom('operation'),
- Type.new_atom('exprnode'),
]:
types[t.name] = t
'c_type' is the type used for C objects of this type.
+ 'atom' should be the name of the member of "union
+ operation_data" that holds a value of this type.
+
'mangle' should be a short string for name mangling purposes,
to allow overloading functions with the same name but
different argument types. Use the same 'mangle' for two
different types if those two types should not be overloaded.
- 'atom' should be the name of the member of "union
- operation_data" that holds a value of this type.
-
'human_name' should be a name to use when describing this type
to the user (see Op.prototype()).
for aux in op.aux:
type_ = aux['TYPE']
if type_.role == 'leaf':
- func = 'get_%s_arg' % type_.atom
- args += '%s (node, %s)' % (func, arg_idx)
- arg_idx += 1
- elif type_.name == 'expr_node':
+ assert type_.name == 'expr_node'
args += ['node']
elif type_.role == 'auxonly':
args += [type_.auxonly_value]
// Artificial.
operator SQUARE (x) = x * x;
-absorb_miss boolean operator NUM_TO_BOOLEAN (x)
+
+absorb_miss boolean operator OPERAND_TO_BOOLEAN (x, expr_node parent)
+ expression e;
+ expr_node n;
+{
+ if (x == 0. || x == 1. || x == SYSMIS)
+ return x;
+
+ msg_at (SE, expr_location (e, parent),
+ _("The operands of %s must have value 0 or 1."),
+ operations[parent->type].name);
+ msg_at (SN, expr_location (e, n),
+ _("This operand with unexpected value %g will be treated as 0."), x);
+ return 0.;
+}
+
+absorb_miss boolean operator EXPR_TO_BOOLEAN (x)
expression e;
expr_node n;
{
return x;
msg_at (SE, expr_location (e, n),
- _("This logical expression must evaluate to 0 or 1. "
- "Treating unexpected value %g as 0."), x);
+ _("This expression, which must be 0 or 1, evaluated to %g. "
+ "It will be treated as 0."), x);
return 0.;
}
}
op = &operations[node->type];
+
+ struct expr_node *new;
if (n_sysmis && (op->flags & OPF_ABSORB_MISS) == 0)
{
/* Most operations produce SYSMIS given any SYSMIS
argument. */
assert (op->returns == OP_number || op->returns == OP_boolean);
- if (op->returns == OP_number)
- return expr_allocate_number (e, SYSMIS);
- else
- return expr_allocate_boolean (e, SYSMIS);
+ new = (op->returns == OP_number
+ ? expr_allocate_number (e, SYSMIS)
+ : expr_allocate_boolean (e, SYSMIS));
}
else if (!n_nonconst && (op->flags & OPF_NONOPTIMIZABLE) == 0)
{
/* Evaluate constant expressions. */
- return evaluate_tree (node, e);
+ new = evaluate_tree (node, e);
}
else
{
/* A few optimization possibilities are still left. */
- return optimize_tree (node, e);
+ new = optimize_tree (node, e);
}
+
+ if (new != node && !new->location)
+ {
+ const struct msg_location *loc = expr_location (e, node);
+ new->location = CONST_CAST (struct msg_location *, loc);
+ }
+ return new;
}
static int
return &n->args[arg_idx]->format;
}
+static const struct expr_node *
+get_expr_node_arg (struct expr_node *n, size_t arg_idx)
+{
+ assert (arg_idx < n->n_args);
+ assert (n->args[arg_idx]->type == OP_expr_node);
+ return n->args[arg_idx]->expr_node;
+}
+
static struct expr_node *
evaluate_tree (struct expr_node *node, struct expression *e)
{
case OP_no_format:
case OP_ni_format:
case OP_pos_int:
+ case OP_expr_node:
/* These are passed as aux data following the
operation. */
break;
emit_integer (e, arg->integer);
break;
+ case OP_expr_node:
+ allocate_aux (e, OP_expr_node)->expr_node = arg->expr_node;
+ break;
+
default:
/* Nothing to do. */
break;
if (op->flags & OPF_MIN_VALID)
emit_integer (e, n->min_valid);
if (op->flags & OPF_EXPR_NODE)
- allocate_aux (e, OP_exprnode)->node = n;
+ allocate_aux (e, OP_expr_node)->expr_node = n;
}
void
atom_type actual_type = expr_node_returns (n);
if (actual_type == OP_number)
- n = expr_allocate_unary (e, OP_NUM_TO_BOOLEAN, n);
+ n = expr_allocate_binary (e, OP_EXPR_TO_BOOLEAN, n,
+ expr_allocate_expr_node (e, n));
else if (actual_type != OP_boolean)
{
msg_at (SE, expr_location (e, n),
case OP_integer:
case OP_pos_int:
case OP_vector:
+ case OP_expr_node:
return ¬_on_stack;
default:
{
/* Convert numeric to boolean. */
if (do_coercion)
- *argp = expr_allocate_unary (e, OP_NUM_TO_BOOLEAN, arg);
+ *argp = expr_allocate_binary (e, OP_OPERAND_TO_BOOLEAN, arg,
+ expr_allocate_expr_node (e, node));
return true;
}
break;
return n;
}
+struct expr_node *
+expr_allocate_expr_node (struct expression *e,
+ const struct expr_node *expr_node)
+{
+ struct expr_node *n = pool_alloc (e->expr_pool, sizeof *n);
+ *n = (struct expr_node) { .type = OP_expr_node, .expr_node = expr_node };
+ return n;
+}
+
/* Allocates a unary composite node that represents the value of
variable V in expression E. */
static struct expr_node *
struct expr_node **args; /* Arguments. */
size_t min_valid; /* Min valid array args to get valid result. */
};
+
+ /* OP_exprnode. */
+ const struct expr_node *expr_node;
};
};
const struct variable *variable;
const struct vector *vector;
struct fmt_spec *format;
- const struct expr_node *node;
+ const struct expr_node *expr_node;
int integer;
};
const struct variable *);
struct expr_node *expr_allocate_format (struct expression *e,
const struct fmt_spec *);
+struct expr_node *expr_allocate_expr_node (struct expression *,
+ const struct expr_node *);
struct expr_node *expr_allocate_vector (struct expression *e,
const struct vector *);
done
AT_CLEANUP
-CHECK_EXPR_EVAL([coercion to/from Boolean],
- [[0 AND 1], [false]],
- [[$true AND 1], [true]],
- [[1 OR $false], [true]],
- [[1 OR $sysmis], [true]],
- [[2 OR $sysmis], [sysmis],
- [error: DEBUG EVALUATE: An operand of the logical disjunction (`OR') operator was found to have a value other than 0 (false), 1 (true), or the system-missing value. The result was forced to 0.]],
- [[2 AND $sysmis], [false],
- [error: DEBUG EVALUATE: An operand of the logical conjunction (`AND') operator was found to have a value other than 0 (false), 1 (true), or the system-missing value. The result was forced to 0.]],
- [['string' AND $sysmis], [error],
- [error: DEBUG EVALUATE: Type mismatch while applying logical conjunction (`AND') operator: cannot convert string to boolean.]],
- [[0 AND $sysmis], [false]],
- [[(1>2) + 1], [1.00]],
- [[$true + $false], [1.00]])
-
-CHECK_EXPR_EVAL([addition and subtraction],
- [[1 + 2], [3.00]],
- [[1 + $true], [2.00]],
- [[$sysmis + 1], [sysmis]],
- [[7676 + $sysmis], [sysmis]],
- [[('foo') + 5], [error],
- [error: DEBUG EVALUATE: Type mismatch while applying addition (`+') operator: cannot convert string to number.]],
- dnl Arithmetic concatenation requires CONCAT:
- [[('foo') + ('bar')], [error],
- [error: DEBUG EVALUATE: Type mismatch while applying addition (`+') operator: cannot convert string to number.]],
- dnl Lexical concatenation succeeds:
- [['foo' + 'bar'], ["foobar"]],
- [[1 +3 - 2 +4 -5], [1.00]],
- [[1 - $true], [0.00]],
- [[$true - 4/3], [-0.33]],
- [['string' - 1e10], [error],
- [error: DEBUG EVALUATE: Type mismatch while applying subtraction (`-') operator: cannot convert string to number.]],
- [[9.5 - ''], [error],
- [error: DEBUG EVALUATE: Type mismatch while applying subtraction (`-') operator: cannot convert string to number.]],
- [[1 - 2], [-1.00]],
- [[52 -23], [29.00]])
-
-CHECK_EXPR_EVAL([multiplication and division],
- [[5 * 10], [50.00]],
- [[10 * $true], [10.00]],
- [[$true * 5], [5.00]],
- [[1.5 * $true], [1.50]],
- [[5 * $sysmis], [sysmis]],
- [[$sysmis * 15], [sysmis]],
- [[2 * 5 / 10], [1.00]],
- [[1 / 2], [0.50]],
- [[2 / 5], [0.40]],
- [[12 / 3 / 2], [2.00]])
+AT_SETUP([expressions - coercion to and from Boolean])
+AT_KEYWORDS([expression expressions evaluate])
+AT_DATA([evaluate-base.sps], [
+DEBUG EVALUATE SET opt.
+DEBUG EVALUATE/0 AND 1.
+DEBUG EVALUATE/$true AND 1.
+DEBUG EVALUATE/1 OR $false.
+DEBUG EVALUATE/1 OR $sysmis.
+DEBUG EVALUATE/2 OR $sysmis.
+DEBUG EVALUATE/1 AND 3.
+])
+
+for opt in OPT NOOPT; do
+ sed "s/opt/$opt/" < evaluate-base.sps > evaluate.sps
+ AT_CHECK([pspp --testing-mode evaluate.sps], [1], [dnl
+0 AND 1 => false
+
+$true AND 1 => true
+
+1 OR $false => true
+
+1 OR $sysmis => true
+
+evaluate.sps:7.16-7.27: error: DEBUG EVALUATE: The operands of OR must have
+value 0 or 1.
+ 7 | DEBUG EVALUATE/2 OR $sysmis.
+ | ^~~~~~~~~~~~
+
+evaluate.sps:7.16: note: DEBUG EVALUATE: This operand with unexpected value 2
+will be treated as 0.
+ 7 | DEBUG EVALUATE/2 OR $sysmis.
+ | ^
+
+2 OR $sysmis => sysmis
+
+evaluate.sps:8.16-8.22: error: DEBUG EVALUATE: The operands of AND must have
+value 0 or 1.
+ 8 | DEBUG EVALUATE/1 AND 3.
+ | ^~~~~~~
+
+evaluate.sps:8.22: note: DEBUG EVALUATE: This operand with unexpected value 3
+will be treated as 0.
+ 8 | DEBUG EVALUATE/1 AND 3.
+ | ^
+
+1 AND 3 => false
+])
+done
+AT_CLEANUP
+
+AT_SETUP([expressions - addition and subtraction])
+AT_KEYWORDS([expression expressions evaluate])
+AT_DATA([evaluate-base.sps], [
+DEBUG EVALUATE SET opt.
+DEBUG EVALUATE /1 + $true.
+DEBUG EVALUATE /$sysmis + 1.
+DEBUG EVALUATE /7676 + $sysmis.
+DEBUG EVALUATE /1 +3 - 2 +4 - 5.
+DEBUG EVALUATE /$true - 4/3.
+DEBUG EVALUATE /1 - 2.
+DEBUG EVALUATE /52 -23.
+
+DEBUG EVALUATE /('foo') + 5.
+DEBUG EVALUATE /('foo') + ('bar'). /* Concatenation requires CONCAT.
+DEBUG EVALUATE /'foo' + 'bar'. /* Lexical concatenation succeeds.
+
+DEBUG EVALUATE /'string' - 1e10.
+DEBUG EVALUATE /9.5 - ''.
+
+DEBUG EVALUATE /F2.0 + 3.
+])
+
+for opt in OPT NOOPT; do
+ sed "s/opt/$opt/" < evaluate-base.sps > evaluate.sps
+ AT_CHECK([pspp --testing-mode evaluate.sps], [1], [dnl
+1 + $true => 2.00
+
+$sysmis + 1 => sysmis
+
+7676 + $sysmis => sysmis
+
+1 +3 - 2 +4 - 5 => 1.00
+
+$true - 4/3 => -0.33
+
+1 - 2 => -1.00
+
+52 -23 => 29.00
+
+evaluate.sps:11.18-11.27: error: DEBUG EVALUATE: Both operands of + must be
+numeric.
+ 11 | DEBUG EVALUATE /('foo') + 5.
+ | ^~~~~~~~~~
+
+evaluate.sps:11.18-11.22: note: DEBUG EVALUATE: This operand has type 'string'.
+ 11 | DEBUG EVALUATE /('foo') + 5.
+ | ^~~~~
+
+evaluate.sps:11.27: note: DEBUG EVALUATE: This operand has type 'number'.
+ 11 | DEBUG EVALUATE /('foo') + 5.
+ | ^
+
+('foo') + 5 => error
+
+evaluate.sps:12.18-12.32: error: DEBUG EVALUATE: Both operands of + must be
+numeric.
+ 12 | DEBUG EVALUATE /('foo') + ('bar'). /* Concatenation requires CONCAT.
+ | ^~~~~~~~~~~~~~~
+
+evaluate.sps:12.18-12.22: note: DEBUG EVALUATE: This operand has type 'string'.
+ 12 | DEBUG EVALUATE /('foo') + ('bar'). /* Concatenation requires CONCAT.
+ | ^~~~~
+
+evaluate.sps:12.28-12.32: note: DEBUG EVALUATE: This operand has type 'string'.
+ 12 | DEBUG EVALUATE /('foo') + ('bar'). /* Concatenation requires CONCAT.
+ | ^~~~~
+
+('foo') + ('bar') => error
+
+'foo' + 'bar' => "foobar"
+
+evaluate.sps:15.17-15.31: error: DEBUG EVALUATE: Both operands of - must be
+numeric.
+ 15 | DEBUG EVALUATE /'string' - 1e10.
+ | ^~~~~~~~~~~~~~~
+
+evaluate.sps:15.17-15.24: note: DEBUG EVALUATE: This operand has type 'string'.
+ 15 | DEBUG EVALUATE /'string' - 1e10.
+ | ^~~~~~~~
+
+evaluate.sps:15.26-15.31: note: DEBUG EVALUATE: This operand has type 'number'.
+ 15 | DEBUG EVALUATE /'string' - 1e10.
+ | ^~~~~~
+
+'string' - 1e10 => error
+
+evaluate.sps:16.17-16.24: error: DEBUG EVALUATE: Both operands of - must be
+numeric.
+ 16 | DEBUG EVALUATE /9.5 - ''.
+ | ^~~~~~~~
+
+evaluate.sps:16.17-16.19: note: DEBUG EVALUATE: This operand has type 'number'.
+ 16 | DEBUG EVALUATE /9.5 - ''.
+ | ^~~
+
+evaluate.sps:16.23-16.24: note: DEBUG EVALUATE: This operand has type 'string'.
+ 16 | DEBUG EVALUATE /9.5 - ''.
+ | ^~
+
+9.5 - '' => error
+
+evaluate.sps:18.17-18.24: error: DEBUG EVALUATE: Both operands of + must be
+numeric.
+ 18 | DEBUG EVALUATE /F2.0 + 3.
+ | ^~~~~~~~
+
+evaluate.sps:18.17-18.20: note: DEBUG EVALUATE: This operand has type 'format'.
+ 18 | DEBUG EVALUATE /F2.0 + 3.
+ | ^~~~
+
+evaluate.sps:18.24: note: DEBUG EVALUATE: This operand has type 'number'.
+ 18 | DEBUG EVALUATE /F2.0 + 3.
+ | ^
+
+F2.0 + 3 => error
+])
+done
+AT_CLEANUP
+
+AT_SETUP([expressions - multiplication and division])
+AT_KEYWORDS([expression expressions evaluate])
+AT_DATA([evaluate-base.sps], [
+DEBUG EVALUATE SET opt.
+DEBUG EVALUATE /5 * 10.
+DEBUG EVALUATE /10 * $true.
+DEBUG EVALUATE /$true * 5.
+DEBUG EVALUATE /1.5 * $true.
+DEBUG EVALUATE /$sysmis * 15.
+DEBUG EVALUATE /8.5 / $sysmis.
+DEBUG EVALUATE /2 * 5 / 10.
+DEBUG EVALUATE /1 / 2.
+DEBUG EVALUATE /2 / 5.
+DEBUG EVALUATE /12 / 3 / 2.
+
+DEBUG EVALUATE /'x' * 1.
+DEBUG EVALUATE /2 / 'x'.
+])
+
+for opt in OPT NOOPT; do
+ sed "s/opt/$opt/" < evaluate-base.sps > evaluate.sps
+ AT_CHECK([pspp --testing-mode evaluate.sps], [1], [dnl
+5 * 10 => 50.00
+
+10 * $true => 10.00
+
+$true * 5 => 5.00
+
+1.5 * $true => 1.50
+
+$sysmis * 15 => sysmis
+
+8.5 / $sysmis => sysmis
+
+2 * 5 / 10 => 1.00
+
+1 / 2 => 0.50
+
+2 / 5 => 0.40
+
+12 / 3 / 2 => 2.00
+
+evaluate.sps:14.17-14.23: error: DEBUG EVALUATE: Both operands of * must be
+numeric.
+ 14 | DEBUG EVALUATE /'x' * 1.
+ | ^~~~~~~
+
+evaluate.sps:14.17-14.19: note: DEBUG EVALUATE: This operand has type 'string'.
+ 14 | DEBUG EVALUATE /'x' * 1.
+ | ^~~
+
+evaluate.sps:14.23: note: DEBUG EVALUATE: This operand has type 'number'.
+ 14 | DEBUG EVALUATE /'x' * 1.
+ | ^
+
+'x' * 1 => error
+
+evaluate.sps:15.17-15.23: error: DEBUG EVALUATE: Both operands of / must be
+numeric.
+ 15 | DEBUG EVALUATE /2 / 'x'.
+ | ^~~~~~~
+
+evaluate.sps:15.17: note: DEBUG EVALUATE: This operand has type 'number'.
+ 15 | DEBUG EVALUATE /2 / 'x'.
+ | ^
+
+evaluate.sps:15.21-15.23: note: DEBUG EVALUATE: This operand has type 'string'.
+ 15 | DEBUG EVALUATE /2 / 'x'.
+ | ^~~
+
+2 / 'x' => error
+])
+done
+AT_CLEANUP
CHECK_EXPR_EVAL([exponentiation],
[[2**8], [256.00]],