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