From: Ben Pfaff Date: Thu, 23 Dec 2021 23:58:21 +0000 (-0800) Subject: better tests X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=daa6b310fb333148f7db500858b54aa07d56fc75;p=pspp better tests --- diff --git a/src/language/expressions/evaluate.c b/src/language/expressions/evaluate.c index 10d239841e..bdd21e1dbe 100644 --- a/src/language/expressions/evaluate.c +++ b/src/language/expressions/evaluate.c @@ -108,12 +108,15 @@ expr_evaluate_str (struct expression *e, const struct ccase *c, int case_idx, #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; @@ -128,9 +131,13 @@ cmd_debug_evaluate (struct lexer *lexer, struct dataset *dsother UNUSED) { 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; @@ -184,6 +191,13 @@ cmd_debug_evaluate (struct lexer *lexer, struct dataset *dsother UNUSED) break; } + if (set_defaults) + { + retval = CMD_SUCCESS; + default_optimize = optimize; + goto done; + } + if (!lex_force_match (lexer, T_SLASH)) goto done; @@ -303,7 +317,7 @@ expr_debug_print_postfix (const struct expression *e) 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: diff --git a/src/language/expressions/generate.py b/src/language/expressions/generate.py index ed041f7efd..d989a14e39 100644 --- a/src/language/expressions/generate.py +++ b/src/language/expressions/generate.py @@ -73,9 +73,12 @@ def init_all_types(): 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'), @@ -88,7 +91,6 @@ def init_all_types(): # Used only for debugging purposes. Type.new_atom('operation'), - Type.new_atom('exprnode'), ]: types[t.name] = t @@ -120,14 +122,14 @@ class Type: '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()). @@ -891,10 +893,7 @@ def generate_optimize_inc(): 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] diff --git a/src/language/expressions/operations.def b/src/language/expressions/operations.def index cbdc247c56..7360f426c1 100644 --- a/src/language/expressions/operations.def +++ b/src/language/expressions/operations.def @@ -770,7 +770,23 @@ absorb_miss no_opt no_abbrev string function VALUELABEL (var v) // 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; { @@ -778,8 +794,8 @@ absorb_miss boolean operator NUM_TO_BOOLEAN (x) 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.; } diff --git a/src/language/expressions/optimize.c b/src/language/expressions/optimize.c index 30c3a89a10..7d683d0181 100644 --- a/src/language/expressions/optimize.c +++ b/src/language/expressions/optimize.c @@ -67,26 +67,34 @@ expr_optimize (struct expr_node *node, struct expression *e) } 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 @@ -185,6 +193,14 @@ get_format_arg (struct expr_node *n, size_t arg_idx) 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) { @@ -279,6 +295,7 @@ flatten_atom (struct expr_node *n, 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; @@ -323,6 +340,10 @@ flatten_composite (struct expr_node *n, struct expression *e) 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; @@ -334,7 +355,7 @@ flatten_composite (struct expr_node *n, struct expression *e) 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 diff --git a/src/language/expressions/parse.c b/src/language/expressions/parse.c index 7eccdac2b1..5a81f6bd3e 100644 --- a/src/language/expressions/parse.c +++ b/src/language/expressions/parse.c @@ -101,7 +101,8 @@ expr_parse_bool (struct lexer *lexer, struct dataset *ds) 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), @@ -210,6 +211,7 @@ atom_type_stack (atom_type type) case OP_integer: case OP_pos_int: case OP_vector: + case OP_expr_node: return ¬_on_stack; default: @@ -449,7 +451,8 @@ type_coercion__ (struct expression *e, struct expr_node *node, size_t arg_idx, { /* 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; @@ -1562,6 +1565,15 @@ expr_allocate_format (struct expression *e, const struct fmt_spec *format) 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 * diff --git a/src/language/expressions/private.h b/src/language/expressions/private.h index 19cac31be7..235c2ecf20 100644 --- a/src/language/expressions/private.h +++ b/src/language/expressions/private.h @@ -117,6 +117,9 @@ 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; }; }; @@ -128,7 +131,7 @@ union operation_data 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; }; @@ -172,6 +175,8 @@ struct expr_node *expr_allocate_variable (struct expression *e, 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 *); diff --git a/tests/language/expressions/evaluate.at b/tests/language/expressions/evaluate.at index 2d3f361f67..8af9d3ca4d 100644 --- a/tests/language/expressions/evaluate.at +++ b/tests/language/expressions/evaluate.at @@ -81,54 +81,249 @@ for opt in '' 'NOOPT '; do 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]],