Fix rest of PR 13054.
[pspp] / src / expressions / generate.pl
1 use strict;
2 use warnings 'all';
3
4 use Getopt::Long;
5
6 # Parse command line.
7 our ($default_output_file) = $0;
8 $default_output_file =~ s/\.pl//;
9 our ($input_file);
10 our ($output_file);
11 parse_cmd_line ();
12
13 # Initialize type system.
14 our (%type, @types);
15 init_all_types ();
16
17 # Parse input file.
18 our (%ops);
19 our (@funcs, @opers);
20 parse_input ();
21
22 # Produce output.
23 print_header ();
24 generate_output ();
25 print_trailer ();
26 \f
27 # Command line.
28
29 # Parses the command line.
30 #
31 # Initializes $input_file, $output_file.
32 sub parse_cmd_line {
33     GetOptions ("i|input=s" => \$input_file,
34                 "o|output=s" => \$output_file,
35                 "h|help" => sub { usage (); })
36       or exit 1;
37
38     $input_file = "operations.def" if !defined $input_file;
39     $output_file = $default_output_file if !defined $output_file;
40
41     open (INPUT, "<$input_file") or die "$input_file: open: $!\n";
42     open (OUTPUT, ">$output_file") or die "$output_file: create: $!\n";
43
44     select (OUTPUT);
45 }
46
47 sub usage {
48     print <<EOF;
49 $0, for generating $default_output_file from definitions
50 usage: generate.pl [-i INPUT] [-o OUTPUT] [-h]
51   -i INPUT    input file containing definitions (default: operations.def)
52   -o OUTPUT   output file (default: $default_output_file)
53   -h          display this help message
54 EOF
55     exit (0);
56 }
57
58 our ($token);
59 our ($toktype);
60 \f
61 # Types.
62
63 # Defines all our types.
64 #
65 # Initializes %type, @types.
66 sub init_all_types {
67     # Common user-visible types used throughout evaluation trees.
68     init_type ('number', 'any', C_TYPE => 'double',
69                ATOM => 'number', MANGLE => 'n', HUMAN_NAME => 'num',
70                STACK => 'ns', MISSING_VALUE => 'SYSMIS');
71     init_type ('string', 'any', C_TYPE => 'struct fixed_string',
72                ATOM => 'string', MANGLE => 's', HUMAN_NAME => 'string',
73                STACK => 'ss', MISSING_VALUE => 'empty_string');
74     init_type ('boolean', 'any', C_TYPE => 'double',
75                ATOM => 'number', MANGLE => 'n', HUMAN_NAME => 'boolean',
76                STACK => 'ns', MISSING_VALUE => 'SYSMIS');
77
78     # Format types.
79     init_type ('format', 'atom');
80     init_type ('ni_format', 'leaf', C_TYPE => 'const struct fmt_spec *',
81                ATOM => 'format', MANGLE => 'f',
82                HUMAN_NAME => 'num_input_format');
83     init_type ('no_format', 'leaf', C_TYPE => 'const struct fmt_spec *',
84                ATOM => 'format', MANGLE => 'f',
85                HUMAN_NAME => 'num_output_format');
86
87     # Integer types.
88     init_type ('integer', 'leaf', C_TYPE => 'int',
89                ATOM => 'integer', MANGLE => 'n', HUMAN_NAME => 'integer');
90     init_type ('pos_int', 'leaf', C_TYPE => 'int',
91                ATOM => 'integer', MANGLE => 'n',
92                HUMAN_NAME => 'positive_integer_constant');
93
94     # Variable names.
95     init_type ('variable', 'atom');
96     init_type ('num_var', 'leaf', C_TYPE => 'const struct variable *',
97                ATOM => 'variable', MANGLE => 'Vn',
98                HUMAN_NAME => 'num_variable');
99     init_type ('str_var', 'leaf', C_TYPE => 'const struct variable *',
100                ATOM => 'variable', MANGLE => 'Vs',
101                HUMAN_NAME => 'string_variable');
102
103     # Vectors.
104     init_type ('vector', 'leaf', C_TYPE => 'const struct vector *',
105                ATOM => 'vector', MANGLE => 'v', HUMAN_NAME => 'vector');
106
107     # Fixed types.
108     init_type ('expression', 'fixed', C_TYPE => 'struct expression *',
109                FIXED_VALUE => 'e');
110     init_type ('case', 'fixed', C_TYPE => 'const struct ccase *',
111                FIXED_VALUE => 'c');
112     init_type ('case_idx', 'fixed', C_TYPE => 'size_t',
113                FIXED_VALUE => 'case_idx');
114
115     # One of these is emitted at the end of each expression as a sentinel
116     # that tells expr_evaluate() to return the value on the stack.
117     init_type ('return_number', 'atom');
118     init_type ('return_string', 'atom');
119
120     # Used only for debugging purposes.
121     init_type ('operation', 'atom');
122 }
123
124 # init_type has 2 required arguments:
125 #
126 #   NAME: Type name.
127 #
128 #           `$name' is the type's name in operations.def.
129 #
130 #           `OP_$name' is the terminal's type in operations.h.
131 #
132 #           `expr_allocate_$name()' allocates a node of the given type.
133 #
134 #   ROLE: How the type may be used:
135 #
136 #           "any": Usable as operands and function arguments, and
137 #           function and operator results.
138 #
139 #           "leaf": Usable as operands and function arguments, but
140 #           not function arguments or results.  (Thus, they appear
141 #           only in leaf nodes in the parse type.)
142 #
143 #           "fixed": Not allowed either as an operand or argument
144 #           type or a result type.  Used only as auxiliary data.
145 #
146 #           "atom": Not allowed anywhere; just adds the name to
147 #           the list of atoms.
148 #
149 # All types except those with "atom" as their role also require:
150 #
151 #   C_TYPE: The C type that represents this abstract type.
152 #
153 # Types with "any" or "leaf" role require:
154 #
155 #   ATOM:
156 #
157 #           `$atom' is the `struct operation_data' member name.
158 #
159 #           get_$atom_name() obtains the corresponding data from a
160 #           node.
161 #
162 #   MANGLE: Short string for name mangling.  Use identical strings
163 #   if two types should not be overloaded.
164 #
165 #   HUMAN_NAME: Name for a type when we describe it to the user.
166 #
167 # Types with role "any" require:
168 #
169 #   STACK: Name of the local variable in expr_evaluate(), used for
170 #   maintaining the stack for this type.
171 #
172 #   MISSING_VALUE: Expression used for the missing value of this
173 #   type.
174 #
175 # Types with role "fixed" require:
176 #
177 #   FIXED_VALUE: Expression used for the value of this type.
178 sub init_type {
179     my ($name, $role, %rest) = @_;
180     my ($type) = $type{"\U$name"} = {NAME => $name, ROLE => $role, %rest};
181
182     my (@need_keys) = qw (NAME ROLE);
183     if ($role eq 'any') {
184         push (@need_keys, qw (C_TYPE ATOM MANGLE HUMAN_NAME STACK MISSING_VALUE));
185     } elsif ($role eq 'leaf') {
186         push (@need_keys, qw (C_TYPE ATOM MANGLE HUMAN_NAME));
187     } elsif ($role eq 'fixed') {
188         push (@need_keys, qw (C_TYPE FIXED_VALUE));
189     } elsif ($role eq 'atom') {
190     } else {
191         die "no role `$role'";
192     }
193
194     my (%have_keys);
195     $have_keys{$_} = 1 foreach keys %$type;
196     for my $key (@need_keys) {
197         defined $type->{$key} or die "$name lacks $key";
198         delete $have_keys{$key};
199     }
200     scalar (keys (%have_keys)) == 0
201       or die "$name has superfluous key(s) " . join (', ', keys (%have_keys));
202
203     push (@types, $type);
204 }
205
206 # c_type(type).
207 #
208 # Returns the C type of the given type as a string designed to be
209 # prepended to a variable name to produce a declaration.  (That won't
210 # work in general but it works well enough for our types.)
211 sub c_type {
212     my ($type) = @_;
213     my ($c_type) = $type->{C_TYPE};
214     defined $c_type or die;
215
216     # Append a space unless (typically) $c_type ends in `*'.
217     $c_type .= ' ' if $c_type =~ /\w$/;
218
219     return $c_type;
220 }
221 \f
222 # Input parsing.
223
224 # Parses the entire input.
225 #
226 # Initializes %ops, @funcs, @opers.
227 sub parse_input {
228     get_line ();
229     get_token ();
230     while ($toktype ne 'eof') {
231         my (%op);
232
233         $op{OPTIMIZABLE} = 1;
234         $op{UNIMPLEMENTED} = 0;
235         $op{EXTENSION} = 0;
236         for (;;) {
237             if (match ('extension')) {
238                 $op{EXTENSION} = 1;
239             } elsif (match ('no_opt')) {
240                 $op{OPTIMIZABLE} = 0;
241             } elsif (match ('absorb_miss')) {
242                 $op{ABSORB_MISS} = 1;
243             } else {
244                 last;
245             }
246         }
247
248         $op{RETURNS} = parse_type () || $type{NUMBER};
249         die "$op{RETURNS} is not a valid return type"
250           if !any ($op{RETURNS}, @type{qw (NUMBER STRING BOOLEAN)});
251
252         $op{CATEGORY} = $token;
253         if (!any ($op{CATEGORY}, qw (operator function))) {
254             die "`operator' or `function' expected at `$token'";
255         }
256         get_token ();
257
258         my ($name) = force ("id");
259
260         die "function name may not contain underscore"
261           if $op{CATEGORY} eq 'function' && $name =~ /_/;
262         die "operator name may not contain period"
263           if $op{CATEGORY} eq 'operator' && $name =~ /\./;
264
265         if (my ($prefix, $suffix) = $name =~ /^(.*)\.(\d+)$/) {
266             $name = $prefix;
267             $op{MIN_VALID} = $suffix;
268             $op{ABSORB_MISS} = 1;
269         }
270         $op{NAME} = $name;
271
272         force_match ('(');
273         @{$op{ARGS}} = ();
274         while (!match (')')) {
275             my ($arg) = parse_arg ();
276             push (@{$op{ARGS}}, $arg);
277             if (defined ($arg->{IDX})) {
278                 last if match (')');
279                 die "array must be last argument";
280             }
281             if (!match (',')) {
282                 force_match (')');
283                 last;
284             }
285         }
286
287         for my $arg (@{$op{ARGS}}) {
288             next if !defined $arg->{CONDITION};
289             my ($any_arg) = join ('|', map ($_->{NAME}, @{$op{ARGS}}));
290             $arg->{CONDITION} =~ s/\b($any_arg)\b/arg_$1/g;
291         }
292
293         my ($opname) = "OP_$op{NAME}";
294         $opname =~ tr/./_/;
295         if ($op{CATEGORY} eq 'function') {
296             my ($mangle) = join ('', map ($_->{TYPE}{MANGLE}, @{$op{ARGS}}));
297             $op{MANGLE} = $mangle;
298             $opname .= "_$mangle";
299         }
300         $op{OPNAME} = $opname;
301
302         if ($op{MIN_VALID}) {
303             my ($array_arg) = array_arg (\%op);
304             die "can't have minimum valid count without array arg"
305               if !defined $array_arg;
306             die "minimum valid count allowed only with double array"
307               if $array_arg->{TYPE} ne $type{NUMBER};
308             die "can't have minimum valid count if array has multiplication factor"
309               if $array_arg->{TIMES} != 1;
310         }
311
312         while ($toktype eq 'id') {
313             my ($type) = parse_type () or die "parse error";
314             die "`$type->{NAME}' is not allowed as auxiliary data"
315               unless $type->{ROLE} eq 'leaf' || $type->{ROLE} eq 'fixed';
316             my ($name) = force ("id");
317             push (@{$op{AUX}}, {TYPE => $type, NAME => $name});
318             force_match (';');
319         }
320
321         if ($op{OPTIMIZABLE}) {
322             die "random variate functions must be marked `no_opt'"
323               if $op{NAME} =~ /^RV\./;
324             for my $aux (@{$op{AUX}}) {
325                 if (any ($aux->{TYPE}, @type{qw (CASE CASE_IDX)})) {
326                     die "operators with $aux->{TYPE} aux data must be "
327                       . "marked `no_opt'";
328                 }
329             }
330         }
331
332         if ($op{RETURNS} eq $type{STRING} && !defined ($op{ABSORB_MISS})) {
333             my (@args);
334             for my $arg (@{$op{ARGS}}) {
335                 if (any ($arg->{TYPE}, @type{qw (NUMBER BOOLEAN)})) {
336                     die "$op{NAME} returns string and has double or bool "
337                       . "argument, but is not marked ABSORB_MISS";
338                 }
339                 if (defined $arg->{CONDITION}) {
340                     die "$op{NAME} returns string but has argument with condition";
341                 }
342             }
343         }
344
345         if ($toktype eq 'block') {
346             $op{BLOCK} = force ('block');
347         } elsif ($toktype eq 'expression') {
348             if ($token eq 'unimplemented') {
349                 $op{UNIMPLEMENTED} = 1;
350             } else {
351                 $op{EXPRESSION} = $token;
352             }
353             get_token ();
354         } else {
355             die "block or expression expected";
356         }
357
358         die "duplicate operation name $opname" if defined $ops{$opname};
359         $ops{$opname} = \%op;
360         if ($op{CATEGORY} eq 'function') {
361             push (@funcs, $opname);
362         } else {
363             push (@opers, $opname);
364         }
365     }
366     close(INPUT);
367
368     @funcs = sort {$ops{$a}->{NAME} cmp $ops{$b}->{NAME}
369                      ||
370                        $ops{$a}->{OPNAME} cmp $ops{$b}->{OPNAME}}
371       @funcs;
372     @opers = sort {$ops{$a}->{NAME} cmp $ops{$b}->{NAME}} @opers;
373     our (@order) = (@funcs, @opers);
374 }
375
376 # Reads the next token into $token, $toktype.
377 sub get_token {
378     our ($line);
379     lookahead ();
380     return if defined ($toktype) && $toktype eq 'eof';
381     $toktype = 'id', $token = $1, return
382         if $line =~ /\G([a-zA-Z_][a-zA-Z_.0-9]*)/gc;
383     $toktype = 'int', $token = $1, return if $line =~ /\G([0-9]+)/gc;
384     $toktype = 'punct', $token = $1, return if $line =~ /\G([][(),*;.])/gc;
385     if ($line =~ /\G=/gc) {
386         $toktype = "expression";
387         $line =~ /\G\s+/gc;
388         $token = accumulate_balanced (';');
389     } elsif ($line =~ /\G\{/gc) {
390         $toktype = "block";
391         $token = accumulate_balanced ('}');
392         $token =~ s/^\n+//;
393     } else {
394         die "bad character `" . substr ($line, pos $line, 1) . "' in input";
395     }
396 }
397
398 # Skip whitespace, then return the remainder of the line.
399 sub lookahead {
400     our ($line);
401     die "unexpected end of file" if !defined ($line);
402     for (;;) {
403         $line =~ /\G\s+/gc;
404         last if pos ($line) < length ($line);
405         get_line ();
406         $token = $toktype = 'eof', return if !defined ($line);
407     }
408     return substr ($line, pos ($line));
409 }
410
411 # accumulate_balanced($chars)
412 #
413 # Accumulates input until a character in $chars is encountered, except
414 # that balanced pairs of (), [], or {} cause $chars to be ignored.
415 #
416 # Returns the input read.
417 sub accumulate_balanced {
418     my ($end) = @_;
419     my ($s) = "";
420     my ($nest) = 0;
421     our ($line);
422     for (;;) {
423         my ($start) = pos ($line);
424         if ($line =~ /\G([^][(){};,]*)([][(){};,])/gc) {
425             $s .= substr ($line, $start, pos ($line) - $start - 1)
426                 if pos ($line) > $start;
427             my ($last) = substr ($line, pos ($line) - 1, 1);
428             if ($last =~ /[[({]/) {
429                 $nest++;
430                 $s .= $last;
431             } elsif ($last =~ /[])}]/) {
432                 if ($nest > 0) {
433                     $nest--;
434                     $s .= $last;
435                 } elsif (index ($end, $last) >= 0) {
436                     return $s;
437                 } else {
438                     die "unbalanced parentheses";
439                 }
440             } elsif (index ($end, $last) >= 0) {
441                 return $s if !$nest;
442                 $s .= $last;
443             } else {
444                 $s .= $last;
445             }
446         } else {
447             $s .= substr ($line, pos ($line)) . "\n";
448             get_line ();
449         }
450     }
451 }
452
453 # Reads the next line from INPUT into $line.
454 sub get_line {
455     our ($line);
456     $line = <INPUT>;
457     if (defined ($line)) {
458         chomp $line;
459         $line =~ s%//.*%%;
460         pos ($line) = 0;
461     }
462 }
463
464 # If the current token is an identifier that names a type,
465 # returns the type and skips to the next token.
466 # Otherwise, returns undef.
467 sub parse_type {
468     if ($toktype eq 'id') {
469         foreach my $type (values (%type)) {
470             get_token (), return $type
471               if defined ($type->{NAME}) && $type->{NAME} eq $token;
472         }
473     }
474     return;
475 }
476
477 # force($type).
478 #
479 # Makes sure that $toktype equals $type, reads the next token, and
480 # returns the previous $token.
481 sub force {
482     my ($type) = @_;
483     die "parse error at `$token' expecting $type"
484         if $type ne $toktype;
485     my ($tok) = $token;
486     get_token ();
487     return $tok;
488 }
489
490 # force($tok).
491 #
492 # If $token equals $tok, reads the next token and returns true.
493 # Otherwise, returns false.
494 sub match {
495     my ($tok) = @_;
496     if ($token eq $tok) {
497         get_token ();
498         return 1;
499     } else {
500         return 0;
501     }
502 }
503
504 # force_match($tok).
505 #
506 # If $token equals $tok, reads the next token.
507 # Otherwise, flags an error in the input.
508 sub force_match {
509     my ($tok) = @_;
510     die "parse error at `$token' expecting `$tok'" if !match ($tok);
511 }
512
513 # Parses and returns a function argument.
514 sub parse_arg {
515     my (%arg);
516     $arg{TYPE} = parse_type () || $type{NUMBER};
517     die "argument name expected at `$token'" if $toktype ne 'id';
518     $arg{NAME} = $token;
519
520     if (lookahead () =~ /^[[,)]/) {
521         get_token ();
522         if (match ('[')) {
523             die "only double and string arrays supported"
524               if !any ($arg{TYPE}, @type{qw (NUMBER STRING)});
525             $arg{IDX} = force ('id');
526             if (match ('*')) {
527                 $arg{TIMES} = force ('int');
528                 die "multiplication factor must be positive"
529                   if $arg{TIMES} < 1;
530             } else {
531                 $arg{TIMES} = 1;
532             }
533             force_match (']');
534         }
535     } else {
536         $arg{CONDITION} = $arg{NAME} . ' ' . accumulate_balanced (',)');
537         our ($line);
538         pos ($line) -= 1;
539         get_token ();
540     }
541     return \%arg;
542 }
543 \f
544 # Output.
545
546 # Prints the output file header.
547 sub print_header {
548     print <<EOF;
549 /* $output_file
550    Generated from $input_file by generate.pl.  
551    Do not modify! */
552
553 EOF
554 }
555
556 # Prints the output file trailer.
557 sub print_trailer {
558     print <<EOF;
559
560 /*
561    Local Variables:
562    mode: c
563    buffer-read-only: t
564    End:
565 */
566 EOF
567 }
568 \f
569 # Utilities.
570
571 # any($target, @list)
572 #
573 # Returns true if $target appears in @list,
574 # false otherwise.
575 sub any {
576     $_ eq $_[0] and return 1 foreach @_[1...$#_];
577     return 0;
578 }
579
580 # make_sysmis_decl($op, $min_valid_src)
581 #
582 # Returns a declaration for a boolean variable called `force_sysmis',
583 # which will be true when operation $op should be system-missing.
584 # Returns undef if there are no such circumstances.
585 #
586 # If $op has a minimum number of valid arguments, $min_valid_src
587 # should be an an expression that evaluates to the minimum number of
588 # valid arguments for $op.
589 sub make_sysmis_decl {
590     my ($op, $min_valid_src) = @_;
591     my (@sysmis_cond); 
592     if (!$op->{ABSORB_MISS}) {
593         for my $arg (@{$op->{ARGS}}) {
594             my ($arg_name) = "arg_$arg->{NAME}";
595             if (!defined $arg->{IDX}) {
596                 if (any ($arg->{TYPE}, @type{qw (NUMBER BOOLEAN)})) {
597                     push (@sysmis_cond, "!is_valid ($arg_name)");
598                 }
599             } elsif ($arg->{TYPE} eq $type{NUMBER}) {
600                 my ($a) = "$arg_name";
601                 my ($n) = "arg_$arg->{IDX}";
602                 push (@sysmis_cond, "count_valid ($a, $n) < $n");
603             }
604         }
605     } elsif (defined $op->{MIN_VALID}) {
606         my ($args) = $op->{ARGS};
607         my ($arg) = ${$args}[$#{$args}];
608         my ($a) = "arg_$arg->{NAME}";
609         my ($n) = "arg_$arg->{IDX}";
610         push (@sysmis_cond, "count_valid ($a, $n) < $min_valid_src");
611     }
612     for my $arg (@{$op->{ARGS}}) {
613         push (@sysmis_cond, "!($arg->{CONDITION})")
614           if defined $arg->{CONDITION};
615     }
616     return "bool force_sysmis = " . join (' || ', @sysmis_cond)
617       if @sysmis_cond;
618     return;
619 }
620
621 # array_arg($op)
622 #
623 # If $op has an array argument, return it.
624 # Otherwise, returns undef.
625 sub array_arg {
626     my ($op) = @_;
627     my ($args) = $op->{ARGS};
628     return if !@$args;
629     my ($last_arg) = $args->[@$args - 1];
630     return $last_arg if defined $last_arg->{IDX};
631     return;
632 }