1 # PSPP - a program for statistical analysis.
2 # Copyright (C) 2017 Free Software Foundation, Inc.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 # Initialize type system.
33 our (@funcs, @opers, @order);
38 if ($output_file =~ /evaluate\.h$/) {
39 generate_evaluate_h ();
40 } elsif ($output_file =~ /evaluate\.inc$/) {
41 generate_evaluate_inc ();
42 } elsif ($output_file =~ /operations\.h$/) {
43 generate_operations_h ();
44 } elsif ($output_file =~ /optimize\.inc$/) {
45 generate_optimize_inc ();
46 } elsif ($output_file =~ /parse\.inc$/) {
47 generate_parse_inc ();
49 die "$output_file: unknown output type\n";
55 # Parses the command line.
57 # Initializes $input_file, $output_file.
59 GetOptions ("i|input=s" => \$input_file,
60 "o|output=s" => \$output_file,
61 "h|help" => sub { usage (); })
64 $input_file = "operations.def" if !defined $input_file;
65 die "$0: output file must be specified\n" if !defined $output_file;
67 open (INPUT, "<$input_file") or die "$input_file: open: $!\n";
68 open (OUTPUT, ">$output_file") or die "$output_file: create: $!\n";
75 $0, for generating expression parsers and evaluators from definitions
76 usage: generate.pl -o OUTPUT [-i INPUT] [-h]
77 -i INPUT input file containing definitions (default: operations.def)
79 -h display this help message
89 # Defines all our types.
91 # Initializes %type, @types.
93 # Common user-visible types used throughout evaluation trees.
94 init_type ('number', 'any', C_TYPE => 'double',
95 ATOM => 'number', MANGLE => 'n', HUMAN_NAME => 'number',
96 STACK => 'ns', MISSING_VALUE => 'SYSMIS');
97 init_type ('string', 'any', C_TYPE => 'struct substring',
98 ATOM => 'string', MANGLE => 's', HUMAN_NAME => 'string',
99 STACK => 'ss', MISSING_VALUE => 'empty_string');
100 init_type ('boolean', 'any', C_TYPE => 'double',
101 ATOM => 'number', MANGLE => 'n', HUMAN_NAME => 'boolean',
102 STACK => 'ns', MISSING_VALUE => 'SYSMIS');
105 init_type ('format', 'atom');
106 init_type ('ni_format', 'leaf', C_TYPE => 'const struct fmt_spec *',
107 ATOM => 'format', MANGLE => 'f',
108 HUMAN_NAME => 'num_input_format');
109 init_type ('no_format', 'leaf', C_TYPE => 'const struct fmt_spec *',
110 ATOM => 'format', MANGLE => 'f',
111 HUMAN_NAME => 'num_output_format');
114 init_type ('integer', 'leaf', C_TYPE => 'int',
115 ATOM => 'integer', MANGLE => 'n', HUMAN_NAME => 'integer');
116 init_type ('pos_int', 'leaf', C_TYPE => 'int',
117 ATOM => 'integer', MANGLE => 'n',
118 HUMAN_NAME => 'positive_integer_constant');
121 init_type ('variable', 'atom');
122 init_type ('num_var', 'leaf', C_TYPE => 'const struct variable *',
123 ATOM => 'variable', MANGLE => 'Vn',
124 HUMAN_NAME => 'num_variable');
125 init_type ('str_var', 'leaf', C_TYPE => 'const struct variable *',
126 ATOM => 'variable', MANGLE => 'Vs',
127 HUMAN_NAME => 'string_variable');
128 init_type ('var', 'leaf', C_TYPE => 'const struct variable *',
129 ATOM => 'variable', MANGLE => 'V',
130 HUMAN_NAME => 'variable');
133 init_type ('vector', 'leaf', C_TYPE => 'const struct vector *',
134 ATOM => 'vector', MANGLE => 'v', HUMAN_NAME => 'vector');
137 init_type ('expression', 'fixed', C_TYPE => 'struct expression *',
139 init_type ('case', 'fixed', C_TYPE => 'const struct ccase *',
141 init_type ('case_idx', 'fixed', C_TYPE => 'size_t',
142 FIXED_VALUE => 'case_idx');
143 init_type ('dataset', 'fixed', C_TYPE => 'struct dataset *',
144 FIXED_VALUE => 'ds');
146 # One of these is emitted at the end of each expression as a sentinel
147 # that tells expr_evaluate() to return the value on the stack.
148 init_type ('return_number', 'atom');
149 init_type ('return_string', 'atom');
151 # Used only for debugging purposes.
152 init_type ('operation', 'atom');
155 # init_type has 2 required arguments:
159 # `$name' is the type's name in operations.def.
161 # `OP_$name' is the terminal's type in operations.h.
163 # `expr_allocate_$name()' allocates a node of the given type.
165 # ROLE: How the type may be used:
167 # "any": Usable as operands and function arguments, and
168 # function and operator results.
170 # "leaf": Usable as operands and function arguments, but
171 # not function arguments or results. (Thus, they appear
172 # only in leaf nodes in the parse type.)
174 # "fixed": Not allowed either as an operand or argument
175 # type or a result type. Used only as auxiliary data.
177 # "atom": Not allowed anywhere; just adds the name to
180 # All types except those with "atom" as their role also require:
182 # C_TYPE: The C type that represents this abstract type.
184 # Types with "any" or "leaf" role require:
188 # `$atom' is the `struct operation_data' member name.
190 # get_$atom_name() obtains the corresponding data from a
193 # MANGLE: Short string for name mangling. Use identical strings
194 # if two types should not be overloaded.
196 # HUMAN_NAME: Name for a type when we describe it to the user.
198 # Types with role "any" require:
200 # STACK: Name of the local variable in expr_evaluate(), used for
201 # maintaining the stack for this type.
203 # MISSING_VALUE: Expression used for the missing value of this
206 # Types with role "fixed" require:
208 # FIXED_VALUE: Expression used for the value of this type.
210 my ($name, $role, %rest) = @_;
211 my ($type) = $type{"\U$name"} = {NAME => $name, ROLE => $role, %rest};
213 my (@need_keys) = qw (NAME ROLE);
214 if ($role eq 'any') {
215 push (@need_keys, qw (C_TYPE ATOM MANGLE HUMAN_NAME STACK MISSING_VALUE));
216 } elsif ($role eq 'leaf') {
217 push (@need_keys, qw (C_TYPE ATOM MANGLE HUMAN_NAME));
218 } elsif ($role eq 'fixed') {
219 push (@need_keys, qw (C_TYPE FIXED_VALUE));
220 } elsif ($role eq 'atom') {
222 die "no role `$role'";
226 $have_keys{$_} = 1 foreach keys %$type;
227 for my $key (@need_keys) {
228 defined $type->{$key} or die "$name lacks $key";
229 delete $have_keys{$key};
231 scalar (keys (%have_keys)) == 0
232 or die "$name has superfluous key(s) " . join (', ', keys (%have_keys));
234 push (@types, $type);
239 # Returns the C type of the given type as a string designed to be
240 # prepended to a variable name to produce a declaration. (That won't
241 # work in general but it works well enough for our types.)
244 my ($c_type) = $type->{C_TYPE};
245 defined $c_type or die;
247 # Append a space unless (typically) $c_type ends in `*'.
248 $c_type .= ' ' if $c_type =~ /\w$/;
255 # Parses the entire input.
257 # Initializes %ops, @funcs, @opers.
261 while ($toktype ne 'eof') {
264 $op{OPTIMIZABLE} = 1;
265 $op{UNIMPLEMENTED} = 0;
269 if (match ('extension')) {
271 } elsif (match ('no_opt')) {
272 $op{OPTIMIZABLE} = 0;
273 } elsif (match ('absorb_miss')) {
274 $op{ABSORB_MISS} = 1;
275 } elsif (match ('perm_only')) {
277 } elsif (match ('no_abbrev')) {
284 $op{RETURNS} = parse_type () || $type{NUMBER};
285 die "$op{RETURNS} is not a valid return type"
286 if !any ($op{RETURNS}, @type{qw (NUMBER STRING BOOLEAN)});
288 $op{CATEGORY} = $token;
289 if (!any ($op{CATEGORY}, qw (operator function))) {
290 die "`operator' or `function' expected at `$token'";
294 my ($name) = force ("id");
296 die "function name may not contain underscore"
297 if $op{CATEGORY} eq 'function' && $name =~ /_/;
298 die "operator name may not contain period"
299 if $op{CATEGORY} eq 'operator' && $name =~ /\./;
301 if (my ($prefix, $suffix) = $name =~ /^(.*)\.(\d+)$/) {
303 $op{MIN_VALID} = $suffix;
304 $op{ABSORB_MISS} = 1;
310 while (!match (')')) {
311 my ($arg) = parse_arg ();
312 push (@{$op{ARGS}}, $arg);
313 if (defined ($arg->{IDX})) {
315 die "array must be last argument";
323 for my $arg (@{$op{ARGS}}) {
324 next if !defined $arg->{CONDITION};
325 my ($any_arg) = join ('|', map ($_->{NAME}, @{$op{ARGS}}));
326 $arg->{CONDITION} =~ s/\b($any_arg)\b/arg_$1/g;
329 my ($opname) = "OP_$op{NAME}";
331 if ($op{CATEGORY} eq 'function') {
332 my ($mangle) = join ('', map ($_->{TYPE}{MANGLE}, @{$op{ARGS}}));
333 $op{MANGLE} = $mangle;
334 $opname .= "_$mangle";
336 $op{OPNAME} = $opname;
338 if ($op{MIN_VALID}) {
339 my ($array_arg) = array_arg (\%op);
340 die "can't have minimum valid count without array arg"
341 if !defined $array_arg;
342 die "minimum valid count allowed only with double array"
343 if $array_arg->{TYPE} ne $type{NUMBER};
344 die "can't have minimum valid count if array has multiplication factor"
345 if $array_arg->{TIMES} != 1;
348 while ($toktype eq 'id') {
349 my ($type) = parse_type () or die "parse error";
350 die "`$type->{NAME}' is not allowed as auxiliary data"
351 unless $type->{ROLE} eq 'leaf' || $type->{ROLE} eq 'fixed';
352 my ($name) = force ("id");
353 push (@{$op{AUX}}, {TYPE => $type, NAME => $name});
357 if ($op{OPTIMIZABLE}) {
358 die "random variate functions must be marked `no_opt'"
359 if $op{NAME} =~ /^RV\./;
360 for my $aux (@{$op{AUX}}) {
361 if (any ($aux->{TYPE}, @type{qw (CASE CASE_IDX)})) {
362 die "operators with $aux->{TYPE} aux data must be "
368 if ($op{RETURNS} eq $type{STRING} && !defined ($op{ABSORB_MISS})) {
370 for my $arg (@{$op{ARGS}}) {
371 if (any ($arg->{TYPE}, @type{qw (NUMBER BOOLEAN)})) {
372 die "$op{NAME} returns string and has double or bool "
373 . "argument, but is not marked ABSORB_MISS";
375 if (defined $arg->{CONDITION}) {
376 die "$op{NAME} returns string but has argument with condition";
381 if ($toktype eq 'block') {
382 $op{BLOCK} = force ('block');
383 } elsif ($toktype eq 'expression') {
384 if ($token eq 'unimplemented') {
385 $op{UNIMPLEMENTED} = 1;
387 $op{EXPRESSION} = $token;
391 die "block or expression expected";
394 die "duplicate operation name $opname" if defined $ops{$opname};
395 $ops{$opname} = \%op;
396 if ($op{CATEGORY} eq 'function') {
397 push (@funcs, $opname);
399 push (@opers, $opname);
404 @funcs = sort {$ops{$a}->{NAME} cmp $ops{$b}->{NAME}
406 $ops{$a}->{OPNAME} cmp $ops{$b}->{OPNAME}}
408 @opers = sort {$ops{$a}->{NAME} cmp $ops{$b}->{NAME}} @opers;
409 @order = (@funcs, @opers);
412 # Reads the next token into $token, $toktype.
416 return if defined ($toktype) && $toktype eq 'eof';
417 $toktype = 'id', $token = $1, return
418 if $line =~ /\G([a-zA-Z_][a-zA-Z_.0-9]*)/gc;
419 $toktype = 'int', $token = $1, return if $line =~ /\G([0-9]+)/gc;
420 $toktype = 'punct', $token = $1, return if $line =~ /\G([][(),*;.])/gc;
421 if ($line =~ /\G=/gc) {
422 $toktype = "expression";
424 $token = accumulate_balanced (';');
425 } elsif ($line =~ /\G\{/gc) {
427 $token = accumulate_balanced ('}');
430 die "bad character `" . substr ($line, pos $line, 1) . "' in input";
434 # Skip whitespace, then return the remainder of the line.
437 die "unexpected end of file" if !defined ($line);
440 last if pos ($line) < length ($line);
442 $token = $toktype = 'eof', return if !defined ($line);
444 return substr ($line, pos ($line));
447 # accumulate_balanced($chars)
449 # Accumulates input until a character in $chars is encountered, except
450 # that balanced pairs of (), [], or {} cause $chars to be ignored.
452 # Returns the input read.
453 sub accumulate_balanced {
459 my ($start) = pos ($line);
460 if ($line =~ /\G([^][(){};,]*)([][(){};,])/gc) {
461 $s .= substr ($line, $start, pos ($line) - $start - 1)
462 if pos ($line) > $start;
463 my ($last) = substr ($line, pos ($line) - 1, 1);
464 if ($last =~ /[[({]/) {
467 } elsif ($last =~ /[])}]/) {
471 } elsif (index ($end, $last) >= 0) {
474 die "unbalanced parentheses";
476 } elsif (index ($end, $last) >= 0) {
483 $s .= substr ($line, pos ($line)) . "\n";
489 # Reads the next line from INPUT into $line.
493 if (defined ($line)) {
500 # If the current token is an identifier that names a type,
501 # returns the type and skips to the next token.
502 # Otherwise, returns undef.
504 if ($toktype eq 'id') {
505 foreach my $type (values (%type)) {
506 get_token (), return $type
507 if defined ($type->{NAME}) && $type->{NAME} eq $token;
515 # Makes sure that $toktype equals $type, reads the next token, and
516 # returns the previous $token.
519 die "parse error at `$token' expecting $type"
520 if $type ne $toktype;
528 # If $token equals $tok, reads the next token and returns true.
529 # Otherwise, returns false.
532 if ($token eq $tok) {
542 # If $token equals $tok, reads the next token.
543 # Otherwise, flags an error in the input.
546 die "parse error at `$token' expecting `$tok'" if !match ($tok);
549 # Parses and returns a function argument.
552 $arg{TYPE} = parse_type () || $type{NUMBER};
553 die "argument name expected at `$token'" if $toktype ne 'id';
556 if (lookahead () =~ /^[[,)]/) {
559 die "only double and string arrays supported"
560 if !any ($arg{TYPE}, @type{qw (NUMBER STRING)});
561 $arg{IDX} = force ('id');
563 $arg{TIMES} = force ('int');
564 die "multiplication factor must be positive"
572 $arg{CONDITION} = $arg{NAME} . ' ' . accumulate_balanced (',)');
582 # Prints the output file header.
586 Generated from $input_file by generate.pl.
592 # Prints the output file trailer.
605 sub generate_evaluate_h {
606 print "#include \"helpers.h\"\n\n";
608 for my $opname (@order) {
609 my ($op) = $ops{$opname};
610 next if $op->{UNIMPLEMENTED};
613 for my $arg (@{$op->{ARGS}}) {
614 if (!defined $arg->{IDX}) {
615 push (@args, c_type ($arg->{TYPE}) . $arg->{NAME});
617 push (@args, c_type ($arg->{TYPE}) . "$arg->{NAME}" . "[]");
618 push (@args, "size_t $arg->{IDX}");
621 for my $aux (@{$op->{AUX}}) {
622 push (@args, c_type ($aux->{TYPE}) . $aux->{NAME});
624 push (@args, "void") if !@args;
626 my ($statements) = $op->{BLOCK} || " return $op->{EXPRESSION};\n";
628 print "static inline ", c_type ($op->{RETURNS}), "\n";
629 print "eval_$opname (", join (', ', @args), ")\n";
636 sub generate_evaluate_inc {
637 for my $opname (@order) {
638 my ($op) = $ops{$opname};
640 if ($op->{UNIMPLEMENTED}) {
641 print "case $opname:\n";
642 print " NOT_REACHED ();\n\n";
648 for my $arg (@{$op->{ARGS}}) {
649 my ($name) = $arg->{NAME};
650 my ($type) = $arg->{TYPE};
651 my ($c_type) = c_type ($type);
652 my ($idx) = $arg->{IDX};
653 push (@args, "arg_$arg->{NAME}");
654 if (!defined ($idx)) {
655 my ($decl) = "${c_type}arg_$name";
656 if ($type->{ROLE} eq 'any') {
657 unshift (@decls, "$decl = *--$type->{STACK}");
658 } elsif ($type->{ROLE} eq 'leaf') {
659 push (@decls, "$decl = op++->$type->{ATOM}");
664 my ($stack) = $type->{STACK};
665 defined $stack or die;
667 "$c_type*arg_$arg->{NAME} = $stack -= arg_$idx");
668 unshift (@decls, "size_t arg_$arg->{IDX} = op++->integer");
670 my ($idx) = "arg_$idx";
671 if ($arg->{TIMES} != 1) {
672 $idx .= " / $arg->{TIMES}";
677 for my $aux (@{$op->{AUX}}) {
678 my ($type) = $aux->{TYPE};
679 my ($name) = $aux->{NAME};
680 if ($type->{ROLE} eq 'leaf') {
681 my ($c_type) = c_type ($type);
682 push (@decls, "${c_type}aux_$name = op++->$type->{ATOM}");
683 push (@args, "aux_$name");
684 } elsif ($type->{ROLE} eq 'fixed') {
685 push (@args, $type->{FIXED_VALUE});
689 my ($sysmis_cond) = make_sysmis_decl ($op, "op++->integer");
690 push (@decls, $sysmis_cond) if defined $sysmis_cond;
692 my ($result) = "eval_$op->{OPNAME} (" . join (', ', @args) . ")";
694 my ($stack) = $op->{RETURNS}{STACK};
696 print "case $opname:\n";
699 print " $_;\n" foreach @decls;
700 if (defined $sysmis_cond) {
701 my ($miss_ret) = $op->{RETURNS}{MISSING_VALUE};
702 print " *$stack++ = force_sysmis ? $miss_ret : $result;\n";
704 print " *$stack++ = $result;\n";
708 print " *$stack++ = $result;\n";
714 sub generate_operations_h {
715 print "#include <stdlib.h>\n";
716 print "#include <stdbool.h>\n\n";
718 print "typedef enum";
721 foreach my $type (@types) {
722 next if $type->{ROLE} eq 'fixed';
723 push (@atoms, "OP_$type->{NAME}");
725 print_operations ('atom', 1, \@atoms);
726 print_operations ('function', "OP_atom_last + 1", \@funcs);
727 print_operations ('operator', "OP_function_last + 1", \@opers);
728 print_range ("OP_composite", "OP_function_first", "OP_operator_last");
730 print_range ("OP", "OP_atom_first", "OP_composite_last");
732 print "operation_type, atom_type;\n";
734 print_predicate ('is_operation', 'OP');
735 print_predicate ("is_$_", "OP_$_")
736 foreach qw (atom composite function operator);
739 sub print_operations {
740 my ($type, $first, $names) = @_;
741 print " /* \u$type types. */\n";
742 print " $names->[0] = $first,\n";
743 print " $_,\n" foreach @$names[1...$#{$names}];
744 print_range ("OP_$type", $names->[0], $names->[$#{$names}]);
749 my ($prefix, $first, $last) = @_;
750 print " ${prefix}_first = $first,\n";
751 print " ${prefix}_last = $last,\n";
752 print " ${prefix}_cnt = ${prefix}_last - ${prefix}_first + 1";
755 sub print_predicate {
756 my ($function, $category) = @_;
757 my ($assertion) = "";
759 print "\nstatic inline bool\n";
760 print "$function (operation_type op)\n";
762 print " assert (is_operation (op));\n" if $function ne 'is_operation';
763 print " return op >= ${category}_first && op <= ${category}_last;\n";
767 sub generate_optimize_inc {
768 for my $opname (@order) {
769 my ($op) = $ops{$opname};
771 if (!$op->{OPTIMIZABLE} || $op->{UNIMPLEMENTED}) {
772 print "case $opname:\n";
773 print " NOT_REACHED ();\n\n";
779 for my $arg (@{$op->{ARGS}}) {
781 my ($name) = $arg->{NAME};
782 my ($type) = $arg->{TYPE};
783 my ($ctype) = c_type ($type);
784 my ($idx) = $arg->{IDX};
785 if (!defined ($idx)) {
786 my ($func) = "get_$type->{ATOM}_arg";
787 push (@decls, "${ctype}arg_$name = $func (node, $arg_idx)");
789 my ($decl) = "size_t arg_$idx = node->arg_cnt";
790 $decl .= " - $arg_idx" if $arg_idx;
791 push (@decls, $decl);
793 push (@decls, "${ctype}*arg_$name = "
794 . "get_$type->{ATOM}_args "
795 . " (node, $arg_idx, arg_$idx, e)");
800 my ($sysmis_cond) = make_sysmis_decl ($op, "node->min_valid");
801 push (@decls, $sysmis_cond) if defined $sysmis_cond;
804 for my $arg (@{$op->{ARGS}}) {
805 push (@args, "arg_$arg->{NAME}");
806 if (defined $arg->{IDX}) {
807 my ($idx) = "arg_$arg->{IDX}";
808 $idx .= " / $arg->{TIMES}" if $arg->{TIMES} != 1;
812 for my $aux (@{$op->{AUX}}) {
813 my ($type) = $aux->{TYPE};
814 if ($type->{ROLE} eq 'leaf') {
815 my ($func) = "get_$type->{ATOM}_arg";
816 push (@args, "$func (node, $arg_idx)");
818 } elsif ($type->{ROLE} eq 'fixed') {
819 push (@args, $type->{FIXED_VALUE});
825 my ($result) = "eval_$op->{OPNAME} (" . join (', ', @args) . ")";
826 if (@decls && defined ($sysmis_cond)) {
827 my ($miss_ret) = $op->{RETURNS}{MISSING_VALUE};
828 push (@decls, c_type ($op->{RETURNS}) . "result = "
829 . "force_sysmis ? $miss_ret : $result");
833 print "case $opname:\n";
834 my ($alloc_func) = "expr_allocate_$op->{RETURNS}{NAME}";
837 print " $_;\n" foreach @decls;
838 print " return $alloc_func (e, $result);\n";
841 print " return $alloc_func (e, $result);\n";
847 sub generate_parse_inc {
848 my (@members) = ("\"\"", "\"\"", 0, 0, 0, "{}", 0, 0);
849 print "{", join (', ', @members), "},\n";
851 for my $type (@types) {
852 next if $type->{ROLE} eq 'fixed';
854 my ($human_name) = $type->{HUMAN_NAME};
855 $human_name = $type->{NAME} if !defined $human_name;
857 my (@members) = ("\"$type->{NAME}\"", "\"$human_name\"",
858 0, "OP_$type->{NAME}", 0, "{}", 0, 0);
859 print "{", join (', ', @members), "},\n";
862 for my $opname (@order) {
863 my ($op) = $ops{$opname};
867 push (@members, "\"$op->{NAME}\"");
869 if ($op->{CATEGORY} eq 'function') {
870 my (@args, @opt_args);
871 for my $arg (@{$op->{ARGS}}) {
872 push (@args, $arg->{TYPE}{HUMAN_NAME}) if !defined $arg->{IDX};
875 if (my ($array) = array_arg ($op)) {
876 if (!defined $op->{MIN_VALID}) {
878 for (my $i = 0; $i < $array->{TIMES}; $i++) {
879 push (@array_args, $array->{TYPE}{HUMAN_NAME});
881 push (@args, @array_args);
882 @opt_args = @array_args;
884 for (my $i = 0; $i < $op->{MIN_VALID}; $i++) {
885 push (@args, $array->{TYPE}{HUMAN_NAME});
887 push (@opt_args, $array->{TYPE}{HUMAN_NAME});
890 my ($human) = "$op->{NAME}(" . join (', ', @args);
891 $human .= '[, ' . join (', ', @opt_args) . ']...' if @opt_args;
893 push (@members, "\"$human\"");
895 push (@members, "NULL");
899 push (@flags, "OPF_ABSORB_MISS") if defined $op->{ABSORB_MISS};
900 push (@flags, "OPF_ARRAY_OPERAND") if array_arg ($op);
901 push (@flags, "OPF_MIN_VALID") if defined $op->{MIN_VALID};
902 push (@flags, "OPF_NONOPTIMIZABLE") if !$op->{OPTIMIZABLE};
903 push (@flags, "OPF_EXTENSION") if $op->{EXTENSION};
904 push (@flags, "OPF_UNIMPLEMENTED") if $op->{UNIMPLEMENTED};
905 push (@flags, "OPF_PERM_ONLY") if $op->{PERM_ONLY};
906 push (@flags, "OPF_NO_ABBREV") if $op->{NO_ABBREV};
907 push (@members, @flags ? join (' | ', @flags) : 0);
909 push (@members, "OP_$op->{RETURNS}{NAME}");
911 push (@members, scalar (@{$op->{ARGS}}));
913 my (@arg_types) = map ("OP_$_->{TYPE}{NAME}", @{$op->{ARGS}});
914 push (@members, "{" . join (', ', @arg_types) . "}");
916 push (@members, $op->{MIN_VALID} || 0);
918 push (@members, array_arg ($op) ? ${array_arg ($op)}{TIMES} : 0);
920 print "{", join (', ', @members), "},\n";
926 # any($target, @list)
928 # Returns true if $target appears in @list,
931 $_ eq $_[0] and return 1 foreach @_[1...$#_];
935 # make_sysmis_decl($op, $min_valid_src)
937 # Returns a declaration for a boolean variable called `force_sysmis',
938 # which will be true when operation $op should be system-missing.
939 # Returns undef if there are no such circumstances.
941 # If $op has a minimum number of valid arguments, $min_valid_src
942 # should be an an expression that evaluates to the minimum number of
943 # valid arguments for $op.
944 sub make_sysmis_decl {
945 my ($op, $min_valid_src) = @_;
947 if (!$op->{ABSORB_MISS}) {
948 for my $arg (@{$op->{ARGS}}) {
949 my ($arg_name) = "arg_$arg->{NAME}";
950 if (!defined $arg->{IDX}) {
951 if (any ($arg->{TYPE}, @type{qw (NUMBER BOOLEAN)})) {
952 push (@sysmis_cond, "!is_valid ($arg_name)");
954 } elsif ($arg->{TYPE} eq $type{NUMBER}) {
955 my ($a) = "$arg_name";
956 my ($n) = "arg_$arg->{IDX}";
957 push (@sysmis_cond, "count_valid ($a, $n) < $n");
960 } elsif (defined $op->{MIN_VALID}) {
961 my ($args) = $op->{ARGS};
962 my ($arg) = ${$args}[$#{$args}];
963 my ($a) = "arg_$arg->{NAME}";
964 my ($n) = "arg_$arg->{IDX}";
965 push (@sysmis_cond, "count_valid ($a, $n) < $min_valid_src");
967 for my $arg (@{$op->{ARGS}}) {
968 push (@sysmis_cond, "!($arg->{CONDITION})")
969 if defined $arg->{CONDITION};
971 return "bool force_sysmis = " . join (' || ', @sysmis_cond)
978 # If $op has an array argument, return it.
979 # Otherwise, returns undef.
982 my ($args) = $op->{ARGS};
984 my ($last_arg) = $args->[@$args - 1];
985 return $last_arg if defined $last_arg->{IDX};