Introduce Op type.
[pspp] / src / language / expressions / generate.py
1 #! /usr/bin/python3
2 # PSPP - a program for statistical analysis.
3 # Copyright (C) 2017, 2021 Free Software Foundation, Inc.
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 import getopt
19 import re
20 import sys
21
22 argv0 = sys.argv[0]
23
24 def init_all_types():
25     """
26     Defines all our types.
27     
28     Initializes 'types' global.
29 """
30
31     global types
32     types = {}
33
34     # Common user-visible types used throughout evaluation trees.
35     init_type(Type.new_any('number', 'double', 'number', 'n', 'number', 'ns', 'SYSMIS'))
36     init_type(Type.new_any('string', 'struct substring', 'string', 's', 'string', 'ss', 'empty_string'))
37     init_type(Type.new_any('boolean', 'double', 'number', 'n', 'boolean', 'ns', 'SYSMIS'))
38
39     # Format types.
40     init_type(Type.new_atom('format'))
41     init_type(Type.new_leaf('ni_format', 'const struct fmt_spec *', 'format', 'f', 'num_input_format'))
42     init_type(Type.new_leaf('no_format', 'const struct fmt_spec *', 'format', 'f', 'num_output_format'))
43
44     # Integer types.
45     init_type(Type.new_leaf('integer', 'int', 'integer', 'n', 'integer'))
46     init_type(Type.new_leaf('pos_int', 'int', 'integer', 'n', 'positive_integer_constant'))
47
48     # Variable names.
49     init_type(Type.new_atom('variable'))
50     init_type(Type.new_leaf('num_var', 'const struct variable *', 'variable', 'Vn', 'num_variable'))
51     init_type(Type.new_leaf('str_var', 'const struct variable *', 'variable', 'Vs', 'string_variable'))
52     init_type(Type.new_leaf('var', 'const struct variable *', 'variable', 'V', 'variable'))
53
54     # Vectors.
55     init_type(Type.new_leaf('vector', 'const struct vector *', 'vector', 'v', 'vector'))
56
57     # Fixed types.
58     init_type(Type.new_fixed('expression', 'struct expression *', 'e'))
59     init_type(Type.new_fixed('case', 'const struct ccase *', 'c'))
60     init_type(Type.new_fixed('case_idx', 'size_t', 'case_idx'))
61     init_type(Type.new_fixed('dataset', 'struct dataset *', 'ds'))
62
63     # One of these is emitted at the end of each expression as a sentinel
64     # that tells expr_evaluate() to return the value on the stack.
65     init_type(Type.new_atom('return_number'))
66     init_type(Type.new_atom('return_string'))
67
68     # Used only for debugging purposes.
69     init_type(Type.new_atom('operation'))
70
71 """
72 init_type has 2 required arguments:
73
74   NAME: Type name.
75
76           'name' is the type's name in operations.def.
77
78           `OP_$name' is the terminal's type in operations.h.
79
80           `expr_allocate_$name()' allocates a node of the given type.
81
82   ROLE: How the type may be used:
83
84           "any": Usable as operands and function arguments, and
85           function and operator results.
86
87           "leaf": Usable as operands and function arguments, but not
88           results.  (Thus, they appear only in leaf nodes in the parse
89           tree.)
90
91           "fixed": Not allowed either as an operand or argument
92           type or a result type.  Used only as auxiliary data.
93
94           "atom": Not allowed anywhere; just adds the name to
95           the list of atoms.
96
97 All types except those with "atom" as their role also require:
98
99   C_TYPE: The C type that represents this abstract type.
100
101 Types with "any" or "leaf" role require:
102
103   ATOM:
104
105           `$atom' is the `struct operation_data' member name.
106
107           get_$atom_name() obtains the corresponding data from a
108           node.
109
110   MANGLE: Short string for name mangling.  Use identical strings
111   if two types should not be overloaded.
112
113   HUMAN_NAME: Name for a type when we describe it to the user.
114
115 Types with role "any" require:
116
117   STACK: Name of the local variable in expr_evaluate(), used for
118   maintaining the stack for this type.
119
120   MISSING_VALUE: Expression used for the missing value of this
121   type.
122
123 Types with role "fixed" require:
124
125   FIXED_VALUE: Expression used for the value of this type.
126 """
127 class Type:
128     def __init__(self, name, role, human_name):
129         self.name = name
130         self.role = role
131         self.human_name = human_name
132
133     def new_atom(name):
134         return Type(name, 'atom', name)
135
136     def new_any(name, c_type, atom, mangle, human_name, stack, missing_value):
137         new = Type(name, 'any', human_name)
138         new.c_type = c_type
139         new.atom = atom
140         new.mangle = mangle
141         new.stack = stack
142         new.missing_value = missing_value
143         return new
144
145     def new_leaf(name, c_type, atom, mangle, human_name):
146         new = Type(name, 'leaf', human_name)
147         new.c_type = c_type
148         new.atom = atom
149         new.mangle = mangle
150         return new
151
152     def new_fixed(name, c_type, fixed_value):
153         new = Type(name, 'fixed', name)
154         new.c_type = c_type
155         new.fixed_value = fixed_value
156         return new
157
158 def init_type(type_):
159     global types
160     types[type_.name] = type_
161
162 # c_type(type).
163 #
164 def c_type(type_):
165     """Returns the C type of the given type as a string designed to be
166     prepended to a variable name to produce a declaration.  (That
167     won't work in general but it works well enough for our types.)
168     """
169     c_type = type_.c_type
170     if not c_type.endswith('*'):
171         c_type += ' '
172     return c_type
173 \f
174 # Input parsing.
175
176 class Op:
177     def __init__(self,
178                  name, category,
179                  returns, args, aux,
180                  expression, block,
181                  min_valid,
182                  optimizable, unimplemented, extension, perm_only, absorb_miss, no_abbrev,
183                  opname, mangle):
184         self.name = name
185         self.category = category
186         self.returns = returns
187         self.args = args
188         self.aux = aux
189         self.expression = expression
190         self.block = block
191         self.min_valid = min_valid
192         self.optimizable = optimizable
193         self.unimplemented = unimplemented
194         self.extension = extension
195         self.perm_only = perm_only
196         self.absorb_miss = absorb_miss
197         self.no_abbrev = no_abbrev
198         self.opname = opname
199         if mangle is not None:
200             self.mangle = mangle
201
202 def parse_input():
203     """Parses the entire input.
204
205     Initializes ops, funcs, opers."""
206
207     global token
208     global toktype
209     global line_number
210     token = None
211     toktype = None
212     line_number = 0
213     get_line()
214     get_token()
215
216     global ops
217     global funcs
218     global opers
219     global order
220     ops = {}
221     funcs = []
222     opers = []
223
224     while toktype != 'eof':
225         optimizable = True
226         unimplemented = False
227         extension = False
228         perm_only = False
229         absorb_miss = False
230         no_abbrev = False
231         while True:
232             if match('extension'):
233                 extension = True
234             elif match('no_opt'):
235                 optimizable = False
236             elif match('absorb_miss'):
237                 absorb_miss = True
238             elif match('perm_only'):
239                 perm_only = True
240             elif match('no_abbrev'):
241                 no_abbrev = True
242             else:
243                 break
244
245         return_type = parse_type()
246         if return_type is None:
247             return_type = types['number']
248         if return_type.name not in ['number', 'string', 'boolean']:
249             sys.stderr.write('%s is not a valid return type\n' % return_type.name)
250             sys.exit(1)
251
252         category = token
253         if category not in ['operator', 'function']:
254             sys.stderr.write("'operator' or 'function' expected at '%s'" % token)
255             sys.exit(1)
256         get_token()
257
258         name = force('id')
259         if category == 'function' and '_' in name:
260             sys.stderr.write("function name '%s' may not contain underscore\n" % name)
261             sys.exit(1)
262         elif category == 'operator' and '.' in name:
263             sys.stderr.write("operator name '%s' may not contain period\n" % name)
264             sys.exit(1)
265
266         m = re.match(r'(.*)\.(\d+)$', name)
267         if m:
268             prefix, suffix = m.groups()
269             name = prefix
270             min_valid = int(suffix)
271             absorb_miss = True
272         else:
273             min_valid = 0
274
275         force_match('(')
276         args = []
277         while not match(')'):
278             arg = parse_arg()
279             args += [arg]
280             if arg.idx is not None:
281                 if match(')'):
282                     break
283                 sys.stderr.write('array must be last argument\n')
284                 sys.exit(1)
285             if not match(','):
286                 force_match(')')
287                 break
288
289         for arg in args:
290             if arg.condition is not None:
291                 any_arg = '|'.join([a.name for a in args])
292                 arg.condition = re.sub(r'\b(%s)\b' % any_arg, r'arg_\1', arg.condition)
293
294         opname = 'OP_' + name
295         opname = opname.replace('.', '_')
296         if category == 'function':
297             mangle = ''.join([a.type_.mangle for a in args])
298             opname += '_' + mangle
299         else:
300             mangle = None
301
302         aux = []
303         while toktype == 'id':
304             type_ = parse_type()
305             if type_ is None:
306                 sys.stderr.write('parse error\n')
307                 sys.exit(1)
308             if type_.role not in ['leaf', 'fixed']:
309                 sys.stderr.write("'%s' is not allowed as auxiliary data\n" % type_.name)
310                 sys.exit(1)
311             aux_name = force('id')
312             aux += [{'TYPE': type_, 'NAME': aux_name}]
313             force_match(';')
314
315         if optimizable:
316             if name.startswith('RV.'):
317                 sys.stderr.write("random variate functions must be marked 'no_opt'\n")
318                 sys.exit(1)
319             for key in ['CASE', 'CASE_IDX']:
320                 if key in aux:
321                     sys.stderr.write("operators with %s aux data must be marked 'no_opt'\n" % key)
322                     sys.exit(1)
323
324         if return_type.name == 'string' and not absorb_miss:
325             for arg in args:
326                 if arg.type_.name in ['number', 'boolean']:
327                     sys.stderr.write("'%s' returns string and has double or bool "
328                                      "argument, but is not marked ABSORB_MISS\n"
329                                      % name)
330                     sys.exit(1)
331                 if arg.condition is not None:
332                     sys.stderr.write("'%s' returns string but has argument with condition\n")
333                     sys.exit(1)
334
335         if toktype == 'block':
336             block = force('block')
337             expression = None
338         elif toktype == 'expression':
339             if token == 'unimplemented':
340                 unimplemented = True
341             else:
342                 expression = token
343             block = None
344             get_token()
345         else:
346             sys.stderr.write("block or expression expected\n")
347             sys.exit(1)
348
349         if opname in ops:
350             sys.stderr.write("duplicate operation name %s\n" % opname)
351             sys.exit(1)
352
353         op = Op(name, category,
354                 return_type, args, aux,
355                 expression, block,
356                 min_valid,
357                 optimizable, unimplemented, extension, perm_only, absorb_miss,
358                 no_abbrev,
359                 opname, mangle)
360
361         if min_valid > 0:
362             aa = array_arg(op)
363             if aa is None:
364                 sys.stderr.write("can't have minimum valid count without array arg\n")
365                 sys.exit(1)
366             if aa.type_.name != 'number':
367                 sys.stderr.write('minimum valid count allowed only with double array\n')
368                 sys.exit(1)
369             if aa.times != 1:
370                 sys.stderr.write("can't have minimu valid count if array has multiplication factor\n")
371                 sys.exit(1)
372
373         ops[opname] = op
374         if category == 'function':
375             funcs += [opname]
376         else:
377             opers += [opname]
378
379     in_file.close()
380
381     funcs = sorted(funcs, key=lambda name: (ops[name].name, ops[name].opname))
382     opers = sorted(opers, key=lambda name: ops[name].name)
383     order = funcs + opers
384
385 def get_token():
386     """Reads the next token into 'token' and 'toktype'."""
387
388     global line
389     global token
390     global toktype
391
392     lookahead()
393     if toktype == 'eof':
394         return
395
396     m = re.match(r'([a-zA-Z_][a-zA-Z_.0-9]*)(.*)$', line)
397     if m:
398         token, line = m.groups()
399         toktype = 'id'
400         return
401
402     m = re.match(r'([0-9]+)(.*)$', line)
403     if m:
404         token, line = m.groups()
405         token = int(token)
406         toktype = 'int'
407         return
408
409     m = re.match(r'([][(),*;.])(.*)$', line)
410     if m:
411         token, line = m.groups()
412         toktype = 'punct'
413         return
414
415     m = re.match(r'=\s*(.*)$', line)
416     if m:
417         toktype = 'expression'
418         line = m.group(1)
419         token = accumulate_balanced(';')
420         return
421
422     m = re.match(r'{(.*)$', line)
423     if m:
424         toktype = 'block'
425         line = m.group(1)
426         token = accumulate_balanced('}')
427         token = token.rstrip('\n')
428         return
429
430     sys.stderr.write("bad character '%s' in input\n" % line[0])
431     sys.exit(1)
432
433 def lookahead():
434     """Skip whitespace."""
435     global line
436     if line is None:
437         sys.stderr.write("unexpected end of file\n")
438         sys.exit(1)
439
440     while True:
441         line = line.lstrip()
442         if line != "":
443             break
444         get_line()
445         if line is None:
446             global token
447             global toktype
448             token = 'eof'
449             toktype = 'eof'
450             return
451
452 def accumulate_balanced(end, swallow_end=True):
453     """Accumulates input until a character in 'end' is encountered,
454     except that balanced pairs of (), [], or {} cause 'end' to be
455     ignored.  Returns the input read.
456     """
457     s = ""
458     nest = 0
459     global line
460     while True:
461         for idx, c in enumerate(line):
462             if c in end and nest == 0:
463                 line = line[idx:]
464                 if swallow_end:
465                     line = line[1:]
466                 s = s.strip('\r\n')
467                 return s
468             elif c in "[({":
469                 nest += 1
470             elif c in "])}":
471                 if nest > 0:
472                     nest -= 1
473                 else:
474                     sys.stderr.write('unbalanced parentheses\n')
475                     sys.exit(1)
476             s += c
477         s += '\n'
478         get_line()
479
480 def get_line():
481     """Reads the next line from INPUT into 'line'."""
482     global line
483     global line_number
484     line = in_file.readline()
485     line_number += 1
486     if line == '':
487         line = None
488     else:
489         line = line.rstrip('\r\n')
490         comment_ofs = line.find('//')
491         if comment_ofs >= 0:
492             line = line[:comment_ofs]
493
494 def parse_type():
495     """If the current token is an identifier that names a type, returns
496     the type and skips to the next token.  Otherwise, returns
497     undef.
498     """
499     if toktype == 'id':
500         for type_ in types.values():
501             if type_.name == token:
502                 get_token()
503                 return type_
504     return None
505
506 def force(type_):
507     """Makes sure that 'toktype' equals 'type', reads the next token, and
508     returns the previous 'token'.
509
510     """
511     if type_ != toktype:
512         sys.stderr.write("parse error at `%s' expecting %s\n" % (token, type_))
513         sys.exit(1)
514     tok = token
515     get_token()
516     return tok
517
518 def match(tok):
519     """If 'token' equals 'tok', reads the next token and returns true.
520     Otherwise, returns false."""
521     if token == tok:
522         get_token()
523         return True
524     else:
525         return False
526
527 def force_match(tok):
528     """If 'token' equals 'tok', reads the next token.  Otherwise, flags an
529     error in the input.
530     """
531     if not match(tok):
532         sys.stderr.write("parse error at `%s' expecting `%s'\n" % (token, tok))
533         sys.exit(1)
534
535 class Arg:
536     def __init__(self, name, type_, idx, times, condition):
537         self.name = name
538         self.type_ = type_
539         self.idx = idx
540         self.times = times
541         self.condition = condition
542
543 def parse_arg():
544     """Parses and returns a function argument."""
545     type_ = parse_type()
546     if type_ is None:
547         type_ = types['number']
548
549     if toktype != 'id':
550         sys.stderr.write("argument name expected at `%s'\n" % token)
551         sys.exit(1)
552     name = token
553
554     lookahead()
555     global line
556
557     idx = None
558     times = 1
559
560     if line[0] in "[,)":
561         get_token()
562         if match('['):
563             if type_.name not in ('number', 'string'):
564                 sys.stderr.write('only double and string arrays supported\n')
565                 sys.exit(1)
566             idx = force('id')
567             if match('*'):
568                 times = force('int')
569                 if times != 2:
570                     sys.stderr.write('multiplication factor must be two\n')
571                     sys.exit(1)
572             force_match(']')
573         condition = None
574     else:
575         condition = name + ' ' + accumulate_balanced(',)', swallow_end=False)
576         get_token()
577
578     return Arg(name, type_, idx, times, condition)
579
580 def print_header():
581     """Prints the output file header."""
582     out_file.write("""\
583 /* %s
584    Generated from %s by generate.py.
585    Do not modify! */
586
587 """ % (out_file_name, in_file_name))
588
589 def print_trailer():
590     """Prints the output file trailer."""
591     out_file.write("""\
592
593 /*
594    Local Variables:
595    mode: c
596    buffer-read-only: t
597    End:
598 */
599 """)
600
601 def generate_evaluate_h():
602     out_file.write("#include \"helpers.h\"\n\n")
603
604     for opname in order:
605         op = ops[opname]
606         if op.unimplemented:
607             continue
608
609         args = []
610         for arg in op.args:
611             if arg.idx is None:
612                 args += [c_type(arg.type_) + arg.name]
613             else:
614                 args += [c_type(arg.type_) + arg.name + '[]']
615                 args += ['size_t %s' % arg.idx]
616         for aux in op.aux:
617             args += [c_type(aux['TYPE']) + aux['NAME']]
618         if not args:
619             args += ['void']
620
621         if op.block:
622             statements = op.block + '\n'
623         else:
624             statements = "  return %s;\n" % op.expression
625
626         out_file.write("static inline %s\n" % c_type (op.returns))
627         out_file.write("eval_%s (%s)\n" % (opname, ', '.join(args)))
628         out_file.write("{\n")
629         out_file.write(statements)
630         out_file.write("}\n\n")
631
632 def generate_evaluate_inc():
633     for opname in order:
634         op = ops[opname]
635         if op.unimplemented:
636             out_file.write("case %s:\n" % opname)
637             out_file.write("  NOT_REACHED ();\n\n")
638             continue
639
640         decls = []
641         args = []
642         for arg in op.args:
643             type_ = arg.type_
644             ctype = c_type(type_)
645             args += ['arg_%s' % arg.name]
646             if arg.idx is None:
647                 decl = '%sarg_%s' % (ctype, arg.name)
648                 if type_.role == 'any':
649                     decls = ['%s = *--%s' % (decl, type_.stack)] + decls
650                 elif type_.role == 'leaf':
651                     decls += ['%s = op++->%s' % (decl, type_.atom)]
652                 else:
653                     assert False
654             else:
655                 idx = arg.idx
656                 decls = ['%s*arg_%s = %s -= arg_%s' % (ctype, arg.name, type_.stack, idx)] + decls
657                 decls = ['size_t arg_%s = op++->integer' % idx] + decls
658
659                 idx = 'arg_%s' % idx
660                 if arg.times != 1:
661                     idx += ' / %s' % arg.times
662                 args += [idx]
663         for aux in op.aux:
664             type_ = aux['TYPE']
665             name = aux['NAME']
666             if type_.role == 'leaf':
667                 ctype = c_type(type_)
668                 decls += ['%saux_%s = op++->%s' % (ctype, name, type_.atom)]
669                 args += ['aux_%s' % name]
670             elif type_.role == 'fixed':
671                 args += [type_.fixed_value]
672
673         sysmis_cond = make_sysmis_decl(op, 'op++->integer')
674         if sysmis_cond is not None:
675             decls += [sysmis_cond]
676
677         result = 'eval_%s (%s)' % (op.opname, ', '.join(args))
678
679         stack = op.returns.stack
680
681         out_file.write("case %s:\n" % opname)
682         if decls:
683             out_file.write("  {\n")
684             for decl in decls:
685                 out_file.write("    %s;\n" % decl)
686             if sysmis_cond is not None:
687                 miss_ret = op.returns.missing_value
688                 out_file.write("    *%s++ = force_sysmis ? %s : %s;\n" % (stack, miss_ret, result))
689             else:
690                 out_file.write("    *%s++ = %s;\n" % (stack, result))
691             out_file.write("  }\n")
692         else:
693             out_file.write("  *%s++ = %s;\n" % (stack, result))
694         out_file.write("  break;\n\n")
695
696 def generate_operations_h():
697     out_file.write("#include <stdlib.h>\n")
698     out_file.write("#include <stdbool.h>\n\n")
699
700     out_file.write("typedef enum")
701     out_file.write("  {\n")
702     atoms = []
703     for type_ in types.values():
704         if type_.role != 'fixed':
705             atoms += ["OP_%s" % type_.name]
706
707     print_operations('atom', 1, atoms)
708     print_operations('function', "OP_atom_last + 1", funcs)
709     print_operations('operator', "OP_function_last + 1", opers)
710     print_range("OP_composite", "OP_function_first", "OP_operator_last")
711     out_file.write(",\n\n")
712     print_range("OP", "OP_atom_first", "OP_composite_last")
713     out_file.write("\n  }\n")
714     out_file.write("operation_type, atom_type;\n")
715
716     print_predicate('is_operation', 'OP')
717     for key in ('atom', 'composite', 'function', 'operator'):
718         print_predicate("is_%s" % key, "OP_%s" % key)
719
720 def print_operations(type_, first, names):
721     out_file.write("    /* %s types. */\n" % type_.title())
722     out_file.write("    %s = %s,\n" % (names[0], first))
723     for name in names[1:]:
724         out_file.write("    %s,\n" % name)
725     print_range("OP_%s" % type_, names[0], names[len(names) - 1])
726     out_file.write(",\n\n")
727
728 def print_range(prefix, first, last):
729     out_file.write("    %s_first = %s,\n" % (prefix, first))
730     out_file.write("    %s_last = %s,\n" % (prefix, last))
731     out_file.write("    n_%s = %s_last - %s_first + 1" % (prefix, prefix, prefix))
732
733 def print_predicate(function, category):
734     assertion = ""
735
736     out_file.write("\nstatic inline bool\n")
737     out_file.write("%s (operation_type op)\n" % function)
738     out_file.write("{\n")
739     if function != 'is_operation':
740         out_file.write("  assert (is_operation (op));\n")
741     out_file.write("  return op >= %s_first && op <= %s_last;\n" % (category, category))
742     out_file.write("}\n")
743
744 def generate_optimize_inc():
745     for opname in order:
746         op = ops[opname]
747
748         if not op.optimizable or op.unimplemented:
749             out_file.write("case %s:\n" % opname)
750             out_file.write("  NOT_REACHED ();\n\n")
751             continue
752
753         decls = []
754         arg_idx = 0
755         for arg in op.args:
756             name = arg.name
757             type_ = arg.type_
758             ctype = c_type(type_)
759             if arg.idx is None:
760                 func = "get_%s_arg" % type_.atom
761                 decls += ["%sarg_%s = %s (node, %s)" % (ctype, name, func, arg_idx)]
762             else:
763                 decl = "size_t arg_%s = node->n_args" % arg.idx
764                 if arg_idx > 0:
765                     decl += " - %s" % arg_idx
766                 decls += [decl]
767
768                 decls += ["%s*arg_%s = get_%s_args  (node, %s, arg_%s, e)" % (ctype, name, type_.atom, arg_idx, arg.idx)]
769             arg_idx += 1
770
771         sysmis_cond = make_sysmis_decl (op, "node->min_valid")
772         if sysmis_cond is not None:
773             decls += [sysmis_cond]
774
775         args = []
776         for arg in op.args:
777             args += ["arg_%s" % arg.name]
778             if arg.idx is not None:
779                 idx = 'arg_%s' % arg.idx
780                 if arg.times != 1:
781                     idx += " / %s" % arg.times
782                 args += [idx]
783
784         for aux in op.aux:
785             type_ = aux['TYPE']
786             if type_.role == 'leaf':
787                 func = "get_%s_arg" % type_.atom
788                 args += "%s (node, %s)" % (func, arg_idx)
789                 arg_idx += 1
790             elif type_.role == 'fixed':
791                 args += [type_.fixed_value]
792             else:
793                 assert False
794
795         result = "eval_%s (%s)" % (op.opname, ', '.join(args))
796         if decls and sysmis_cond is not None:
797             miss_ret = op.returns.missing_value
798             decls += ['%sresult = force_sysmis ? %s : %s' % (c_type(op.returns), miss_ret, result)]
799             result = 'result'
800
801         out_file.write("case %s:\n" % opname)
802         alloc_func = "expr_allocate_%s" % op.returns.name
803         if decls:
804             out_file.write("  {\n")
805             for decl in decls:
806                 out_file.write("    %s;\n" % decl)
807             out_file.write("    return %s (e, %s);\n" % (alloc_func, result))
808             out_file.write("  }\n")
809         else:
810             out_file.write("  return %s (e, %s);\n" % (alloc_func, result))
811         out_file.write("\n")
812
813 def generate_parse_inc():
814     members = ['""', '""', '0', '0', '0', "{}", '0', '0']
815     out_file.write("{%s},\n" % ', '.join(members))
816
817     for type_ in types.values():
818         if type_.role != 'fixed':
819             members = ('"%s"' % type_.name, '"%s"' % type_.human_name, '0', "OP_%s" % type_.name, '0', "{}", '0', '0')
820             out_file.write("{%s},\n" % ', '.join(members))
821
822     for opname in order:
823         op = ops[opname]
824
825         members = []
826         members += ['"%s"' % op.name]
827
828         if op.category == 'function':
829             args = []
830             opt_args = []
831             for arg in op.args:
832                 if arg.idx is None:
833                     args += [arg.type_.human_name]
834
835             array = array_arg(op)
836             if array is not None:
837                 if op.min_valid == 0:
838                     array_args = []
839                     for i in range(array.times):
840                         array_args += [array.type_.human_name]
841                     args += array_args
842                     opt_args = array_args
843                 else:
844                     for i in range(op.min_valid):
845                         args += [array.type_.human_name]
846                     opt_args += [array.type_.human_name]
847             human = "%s(%s" % (op.name, ', '.join(args))
848             if opt_args:
849                 human += '[, %s]...' % ', '.join(opt_args)
850             human += ')'
851             members += ['"%s"' % human]
852         else:
853             members += ['NULL']
854
855         flags = []
856         if op.absorb_miss:
857             flags += ['OPF_ABSORB_MISS']
858         if array_arg(op):
859             flags += ['OPF_ARRAY_OPERAND']
860         if op.min_valid > 0:
861             flags += ['OPF_MIN_VALID']
862         if not op.optimizable:
863             flags += ['OPF_NONOPTIMIZABLE']
864         if op.extension:
865             flags += ['OPF_EXTENSION']
866         if op.unimplemented:
867             flags += ['OPF_UNIMPLEMENTED']
868         if op.perm_only:
869             flags += ['OPF_PERM_ONLY']
870         if op.no_abbrev:
871             flags += ['OPF_NO_ABBREV']
872         members += [' | '.join(flags) if flags else '0']
873
874         members += ['OP_%s' % op.returns.name]
875
876         members += ['%s' % len(op.args)]
877
878         arg_types = ["OP_%s" % arg.type_.name for arg in op.args]
879         members += ['{%s}' % ', '.join(arg_types)]
880
881         members += ['%s' % op.min_valid]
882
883         members += ['%s' % (array_arg(op).times if array_arg(op) else 0)]
884
885         out_file.write('{%s},\n' % ', '.join(members))
886 \f
887 # Utilities.
888
889 def make_sysmis_decl(op, min_valid_src):
890     """Returns a declaration for a boolean variable called `force_sysmis',
891     which will be true when operation 'op' should be system-missing.
892     Returns None if there are no such circumstances.
893
894     If 'op' has a minimum number of valid arguments, 'min_valid_src'
895     should be an an expression that evaluates to the minimum number of
896     valid arguments for 'op'.
897
898     """
899     sysmis_cond = []
900     if not op.absorb_miss:
901         for arg in op.args:
902             arg_name = 'arg_%s' % arg.name
903             if arg.idx is None:
904                 if arg.type_.name in ['number', 'boolean']:
905                     sysmis_cond += ["!is_valid (%s)" % arg_name]
906             elif arg.type_.name == 'number':
907                 a = arg_name
908                 n = 'arg_%s' % arg.idx
909                 sysmis_cond += ['count_valid (%s, %s) < %s' % (a, n, n)]
910     elif op.min_valid > 0:
911         args = op.args
912         arg = args[-1]
913         a = 'arg_%s' % arg.name
914         n = 'arg_%s' % arg.idx
915         sysmis_cond += ["count_valid (%s, %s) < %s" % (a, n, min_valid_src)]
916     for arg in op.args:
917         if arg.condition is not None:
918             sysmis_cond += ['!(%s)' % arg.condition]
919     if sysmis_cond:
920         return 'bool force_sysmis = %s' % ' || '.join(sysmis_cond)
921     return None
922
923 def array_arg(op):
924     """If 'op' has an array argument, returns it.  Otherwise, returns
925     None."""
926     args = op.args
927     if not args:
928         return None
929     last_arg = args[-1]
930     if last_arg.idx is not None:
931         return last_arg
932     return None
933
934 def usage():
935     print("""\
936 %(argv0)s, for generating expression parsers and evaluators from definitions
937 usage: generate.pl -o OUTPUT [-i INPUT] [-h]
938   -i INPUT    input file containing definitions (default: operations.def)
939   -o OUTPUT   output file
940   -h          display this help message
941 """ % {'argv0': argv0})
942     sys.exit(0)
943
944 if __name__ == "__main__":
945     try:
946         options, args = getopt.gnu_getopt(sys.argv[1:], 'hi:o:',
947                                           ['input=s',
948                                            'output=s',
949                                            'help'])
950     except getopt.GetoptError as geo:
951         sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
952         sys.exit(1)
953
954     in_file_name = 'operations.def'
955     out_file_name = None
956     for key, value in options:
957         if key in ['-h', '--help']:
958             usage()
959         elif key in ['-i', '--input']:
960             in_file_name = value
961         elif key in ['-o', '--output']:
962             out_file_name = value
963         else:
964             sys.exit(0)
965
966     if out_file_name is None:
967         sys.stderr.write("%(argv0)s: output file must be specified "
968                          "(use --help for help)\n" % {'argv0': argv0})
969         sys.exit(1)
970
971     in_file = open(in_file_name, 'r')
972     out_file = open(out_file_name, 'w')
973
974     init_all_types()
975     parse_input()
976
977     print_header()
978     if out_file_name.endswith('evaluate.h'):
979         generate_evaluate_h()
980     elif out_file_name.endswith('evaluate.inc'):
981         generate_evaluate_inc()
982     elif out_file_name.endswith('operations.h'):
983         generate_operations_h()
984     elif out_file_name.endswith('optimize.inc'):
985         generate_optimize_inc()
986     elif out_file_name.endswith('parse.inc'):
987         generate_parse_inc()
988     else:
989         sys.stderr.write("%(argv0)s: unknown output type\n")
990         sys.exit(1)
991     print_trailer()