parse_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, c_type=None):
129         self.name = name
130         self.role = role
131         self.human_name = human_name
132         if c_type:
133             if c_type.endswith('*'):
134                 self.c_type = c_type
135             else:
136                 self.c_type = c_type + ' '
137
138     def new_atom(name):
139         return Type(name, 'atom', name)
140
141     def new_any(name, c_type, atom, mangle, human_name, stack, missing_value):
142         new = Type(name, 'any', human_name, c_type)
143         new.atom = atom
144         new.mangle = mangle
145         new.stack = stack
146         new.missing_value = missing_value
147         return new
148
149     def new_leaf(name, c_type, atom, mangle, human_name):
150         new = Type(name, 'leaf', human_name, c_type)
151         new.atom = atom
152         new.mangle = mangle
153         return new
154
155     def new_fixed(name, c_type, fixed_value):
156         new = Type(name, 'fixed', name, c_type)
157         new.fixed_value = fixed_value
158         return new
159
160     def parse():
161         """If the current token is an identifier that names a type, returns
162         the type and skips to the next token.  Otherwise, returns
163         None.
164         """
165         if toktype == 'id':
166             for type_ in types.values():
167                 if type_.name == token:
168                     get_token()
169                     return type_
170         return None
171
172 def init_type(type_):
173     global types
174     types[type_.name] = type_
175
176 \f
177 # Input parsing.
178
179 class Op:
180     def __init__(self,
181                  name, category,
182                  returns, args, aux,
183                  expression, block,
184                  min_valid,
185                  optimizable, unimplemented, extension, perm_only, absorb_miss, no_abbrev):
186         self.name = name
187         self.category = category
188         self.returns = returns
189         self.args = args
190         self.aux = aux
191         self.expression = expression
192         self.block = block
193         self.min_valid = min_valid
194         self.optimizable = optimizable
195         self.unimplemented = unimplemented
196         self.extension = extension
197         self.perm_only = perm_only
198         self.absorb_miss = absorb_miss
199         self.no_abbrev = no_abbrev
200
201         self.opname = ('OP_%s' % name).replace('.', '_')
202         if category == 'function':
203             self.mangle = ''.join([a.type_.mangle for a in args])
204             self.opname += '_%s' % self.mangle
205
206     def array_arg(self):
207         """If this operation has an array argument, returns it.  Otherwise,
208         returns None.
209         """
210         if self.args and self.args[-1].idx is not None:
211             return self.args[-1]
212         else:
213             return None
214
215     def sysmis_decl(self, min_valid_src):
216         """Returns a declaration for a boolean variable called `force_sysmis',
217         which will be true when this operation should be
218         system-missing.  Returns None if there are no such
219         circumstances.
220
221         If this operation has a minimum number of valid arguments,
222         'min_valid_src' should be an an expression that evaluates to
223         the minimum number of valid arguments for this operation.
224
225         """
226         sysmis_cond = []
227         if not self.absorb_miss:
228             for arg in self.args:
229                 arg_name = 'arg_%s' % arg.name
230                 if arg.idx is None:
231                     if arg.type_.name in ['number', 'boolean']:
232                         sysmis_cond += ["!is_valid (%s)" % arg_name]
233                 elif arg.type_.name == 'number':
234                     a = arg_name
235                     n = 'arg_%s' % arg.idx
236                     sysmis_cond += ['count_valid (%s, %s) < %s' % (a, n, n)]
237         elif self.min_valid > 0:
238             args = self.args
239             arg = args[-1]
240             a = 'arg_%s' % arg.name
241             n = 'arg_%s' % arg.idx
242             sysmis_cond += ["count_valid (%s, %s) < %s" % (a, n, min_valid_src)]
243         for arg in self.args:
244             if arg.condition is not None:
245                 sysmis_cond += ['!(%s)' % arg.condition]
246         if sysmis_cond:
247             return 'bool force_sysmis = %s' % ' || '.join(sysmis_cond)
248         return None
249
250 def parse_input():
251     """Parses the entire input.
252
253     Initializes ops, funcs, opers."""
254
255     global token
256     global toktype
257     global line_number
258     token = None
259     toktype = None
260     line_number = 0
261     get_line()
262     get_token()
263
264     global ops
265     global funcs
266     global opers
267     global order
268     ops = {}
269     funcs = []
270     opers = []
271
272     while toktype != 'eof':
273         optimizable = True
274         unimplemented = False
275         extension = False
276         perm_only = False
277         absorb_miss = False
278         no_abbrev = False
279         while True:
280             if match('extension'):
281                 extension = True
282             elif match('no_opt'):
283                 optimizable = False
284             elif match('absorb_miss'):
285                 absorb_miss = True
286             elif match('perm_only'):
287                 perm_only = True
288             elif match('no_abbrev'):
289                 no_abbrev = True
290             else:
291                 break
292
293         return_type = Type.parse()
294         if return_type is None:
295             return_type = types['number']
296         if return_type.name not in ['number', 'string', 'boolean']:
297             sys.stderr.write('%s is not a valid return type\n' % return_type.name)
298             sys.exit(1)
299
300         category = token
301         if category not in ['operator', 'function']:
302             sys.stderr.write("'operator' or 'function' expected at '%s'" % token)
303             sys.exit(1)
304         get_token()
305
306         name = force('id')
307         if category == 'function' and '_' in name:
308             sys.stderr.write("function name '%s' may not contain underscore\n" % name)
309             sys.exit(1)
310         elif category == 'operator' and '.' in name:
311             sys.stderr.write("operator name '%s' may not contain period\n" % name)
312             sys.exit(1)
313
314         m = re.match(r'(.*)\.(\d+)$', name)
315         if m:
316             prefix, suffix = m.groups()
317             name = prefix
318             min_valid = int(suffix)
319             absorb_miss = True
320         else:
321             min_valid = 0
322
323         force_match('(')
324         args = []
325         while not match(')'):
326             arg = parse_arg()
327             args += [arg]
328             if arg.idx is not None:
329                 if match(')'):
330                     break
331                 sys.stderr.write('array must be last argument\n')
332                 sys.exit(1)
333             if not match(','):
334                 force_match(')')
335                 break
336
337         for arg in args:
338             if arg.condition is not None:
339                 any_arg = '|'.join([a.name for a in args])
340                 arg.condition = re.sub(r'\b(%s)\b' % any_arg, r'arg_\1', arg.condition)
341
342         aux = []
343         while toktype == 'id':
344             type_ = Type.parse()
345             if type_ is None:
346                 sys.stderr.write('parse error\n')
347                 sys.exit(1)
348             if type_.role not in ['leaf', 'fixed']:
349                 sys.stderr.write("'%s' is not allowed as auxiliary data\n" % type_.name)
350                 sys.exit(1)
351             aux_name = force('id')
352             aux += [{'TYPE': type_, 'NAME': aux_name}]
353             force_match(';')
354
355         if optimizable:
356             if name.startswith('RV.'):
357                 sys.stderr.write("random variate functions must be marked 'no_opt'\n")
358                 sys.exit(1)
359             for key in ['CASE', 'CASE_IDX']:
360                 if key in aux:
361                     sys.stderr.write("operators with %s aux data must be marked 'no_opt'\n" % key)
362                     sys.exit(1)
363
364         if return_type.name == 'string' and not absorb_miss:
365             for arg in args:
366                 if arg.type_.name in ['number', 'boolean']:
367                     sys.stderr.write("'%s' returns string and has double or bool "
368                                      "argument, but is not marked ABSORB_MISS\n"
369                                      % name)
370                     sys.exit(1)
371                 if arg.condition is not None:
372                     sys.stderr.write("'%s' returns string but has argument with condition\n")
373                     sys.exit(1)
374
375         if toktype == 'block':
376             block = force('block')
377             expression = None
378         elif toktype == 'expression':
379             if token == 'unimplemented':
380                 unimplemented = True
381             else:
382                 expression = token
383             block = None
384             get_token()
385         else:
386             sys.stderr.write("block or expression expected\n")
387             sys.exit(1)
388
389         op = Op(name, category,
390                 return_type, args, aux,
391                 expression, block,
392                 min_valid,
393                 optimizable, unimplemented, extension, perm_only, absorb_miss,
394                 no_abbrev)
395         if op.opname in ops:
396             sys.stderr.write("duplicate operation name %s\n" % op.opname)
397             sys.exit(1)
398
399         if min_valid > 0:
400             aa = op.array_arg()
401             if aa is None:
402                 sys.stderr.write("can't have minimum valid count without array arg\n")
403                 sys.exit(1)
404             if aa.type_.name != 'number':
405                 sys.stderr.write('minimum valid count allowed only with double array\n')
406                 sys.exit(1)
407             if aa.times != 1:
408                 sys.stderr.write("can't have minimu valid count if array has multiplication factor\n")
409                 sys.exit(1)
410
411         ops[op.opname] = op
412         if category == 'function':
413             funcs += [op.opname]
414         else:
415             opers += [op.opname]
416
417     in_file.close()
418
419     funcs = sorted(funcs, key=lambda name: (ops[name].name, ops[name].opname))
420     opers = sorted(opers, key=lambda name: ops[name].name)
421     order = funcs + opers
422
423 def get_token():
424     """Reads the next token into 'token' and 'toktype'."""
425
426     global line
427     global token
428     global toktype
429
430     lookahead()
431     if toktype == 'eof':
432         return
433
434     m = re.match(r'([a-zA-Z_][a-zA-Z_.0-9]*)(.*)$', line)
435     if m:
436         token, line = m.groups()
437         toktype = 'id'
438         return
439
440     m = re.match(r'([0-9]+)(.*)$', line)
441     if m:
442         token, line = m.groups()
443         token = int(token)
444         toktype = 'int'
445         return
446
447     m = re.match(r'([][(),*;.])(.*)$', line)
448     if m:
449         token, line = m.groups()
450         toktype = 'punct'
451         return
452
453     m = re.match(r'=\s*(.*)$', line)
454     if m:
455         toktype = 'expression'
456         line = m.group(1)
457         token = accumulate_balanced(';')
458         return
459
460     m = re.match(r'{(.*)$', line)
461     if m:
462         toktype = 'block'
463         line = m.group(1)
464         token = accumulate_balanced('}')
465         token = token.rstrip('\n')
466         return
467
468     sys.stderr.write("bad character '%s' in input\n" % line[0])
469     sys.exit(1)
470
471 def lookahead():
472     """Skip whitespace."""
473     global line
474     if line is None:
475         sys.stderr.write("unexpected end of file\n")
476         sys.exit(1)
477
478     while True:
479         line = line.lstrip()
480         if line != "":
481             break
482         get_line()
483         if line is None:
484             global token
485             global toktype
486             token = 'eof'
487             toktype = 'eof'
488             return
489
490 def accumulate_balanced(end, swallow_end=True):
491     """Accumulates input until a character in 'end' is encountered,
492     except that balanced pairs of (), [], or {} cause 'end' to be
493     ignored.  Returns the input read.
494     """
495     s = ""
496     nest = 0
497     global line
498     while True:
499         for idx, c in enumerate(line):
500             if c in end and nest == 0:
501                 line = line[idx:]
502                 if swallow_end:
503                     line = line[1:]
504                 s = s.strip('\r\n')
505                 return s
506             elif c in "[({":
507                 nest += 1
508             elif c in "])}":
509                 if nest > 0:
510                     nest -= 1
511                 else:
512                     sys.stderr.write('unbalanced parentheses\n')
513                     sys.exit(1)
514             s += c
515         s += '\n'
516         get_line()
517
518 def get_line():
519     """Reads the next line from INPUT into 'line'."""
520     global line
521     global line_number
522     line = in_file.readline()
523     line_number += 1
524     if line == '':
525         line = None
526     else:
527         line = line.rstrip('\r\n')
528         comment_ofs = line.find('//')
529         if comment_ofs >= 0:
530             line = line[:comment_ofs]
531
532 def force(type_):
533     """Makes sure that 'toktype' equals 'type', reads the next token, and
534     returns the previous 'token'.
535
536     """
537     if type_ != toktype:
538         sys.stderr.write("parse error at `%s' expecting %s\n" % (token, type_))
539         sys.exit(1)
540     tok = token
541     get_token()
542     return tok
543
544 def match(tok):
545     """If 'token' equals 'tok', reads the next token and returns true.
546     Otherwise, returns false."""
547     if token == tok:
548         get_token()
549         return True
550     else:
551         return False
552
553 def force_match(tok):
554     """If 'token' equals 'tok', reads the next token.  Otherwise, flags an
555     error in the input.
556     """
557     if not match(tok):
558         sys.stderr.write("parse error at `%s' expecting `%s'\n" % (token, tok))
559         sys.exit(1)
560
561 class Arg:
562     def __init__(self, name, type_, idx, times, condition):
563         self.name = name
564         self.type_ = type_
565         self.idx = idx
566         self.times = times
567         self.condition = condition
568
569 def parse_arg():
570     """Parses and returns a function argument."""
571     type_ = Type.parse()
572     if type_ is None:
573         type_ = types['number']
574
575     if toktype != 'id':
576         sys.stderr.write("argument name expected at `%s'\n" % token)
577         sys.exit(1)
578     name = token
579
580     lookahead()
581     global line
582
583     idx = None
584     times = 1
585
586     if line[0] in "[,)":
587         get_token()
588         if match('['):
589             if type_.name not in ('number', 'string'):
590                 sys.stderr.write('only double and string arrays supported\n')
591                 sys.exit(1)
592             idx = force('id')
593             if match('*'):
594                 times = force('int')
595                 if times != 2:
596                     sys.stderr.write('multiplication factor must be two\n')
597                     sys.exit(1)
598             force_match(']')
599         condition = None
600     else:
601         condition = name + ' ' + accumulate_balanced(',)', swallow_end=False)
602         get_token()
603
604     return Arg(name, type_, idx, times, condition)
605
606 def print_header():
607     """Prints the output file header."""
608     out_file.write("""\
609 /* %s
610    Generated from %s by generate.py.
611    Do not modify! */
612
613 """ % (out_file_name, in_file_name))
614
615 def print_trailer():
616     """Prints the output file trailer."""
617     out_file.write("""\
618
619 /*
620    Local Variables:
621    mode: c
622    buffer-read-only: t
623    End:
624 */
625 """)
626
627 def generate_evaluate_h():
628     out_file.write("#include \"helpers.h\"\n\n")
629
630     for opname in order:
631         op = ops[opname]
632         if op.unimplemented:
633             continue
634
635         args = []
636         for arg in op.args:
637             if arg.idx is None:
638                 args += [arg.type_.c_type + arg.name]
639             else:
640                 args += [arg.type_.c_type + arg.name + '[]']
641                 args += ['size_t %s' % arg.idx]
642         for aux in op.aux:
643             args += [aux['TYPE'].c_type + aux['NAME']]
644         if not args:
645             args += ['void']
646
647         if op.block:
648             statements = op.block + '\n'
649         else:
650             statements = "  return %s;\n" % op.expression
651
652         out_file.write("static inline %s\n" % op.returns.c_type)
653         out_file.write("eval_%s (%s)\n" % (opname, ', '.join(args)))
654         out_file.write("{\n")
655         out_file.write(statements)
656         out_file.write("}\n\n")
657
658 def generate_evaluate_inc():
659     for opname in order:
660         op = ops[opname]
661         if op.unimplemented:
662             out_file.write("case %s:\n" % opname)
663             out_file.write("  NOT_REACHED ();\n\n")
664             continue
665
666         decls = []
667         args = []
668         for arg in op.args:
669             type_ = arg.type_
670             c_type = type_.c_type
671             args += ['arg_%s' % arg.name]
672             if arg.idx is None:
673                 decl = '%sarg_%s' % (c_type, arg.name)
674                 if type_.role == 'any':
675                     decls = ['%s = *--%s' % (decl, type_.stack)] + decls
676                 elif type_.role == 'leaf':
677                     decls += ['%s = op++->%s' % (decl, type_.atom)]
678                 else:
679                     assert False
680             else:
681                 idx = arg.idx
682                 decls = ['%s*arg_%s = %s -= arg_%s' % (c_type, arg.name, type_.stack, idx)] + decls
683                 decls = ['size_t arg_%s = op++->integer' % idx] + decls
684
685                 idx = 'arg_%s' % idx
686                 if arg.times != 1:
687                     idx += ' / %s' % arg.times
688                 args += [idx]
689         for aux in op.aux:
690             type_ = aux['TYPE']
691             name = aux['NAME']
692             if type_.role == 'leaf':
693                 decls += ['%saux_%s = op++->%s' % (type_.c_type, name, type_.atom)]
694                 args += ['aux_%s' % name]
695             elif type_.role == 'fixed':
696                 args += [type_.fixed_value]
697
698         sysmis_cond = op.sysmis_decl('op++->integer')
699         if sysmis_cond is not None:
700             decls += [sysmis_cond]
701
702         result = 'eval_%s (%s)' % (op.opname, ', '.join(args))
703
704         stack = op.returns.stack
705
706         out_file.write("case %s:\n" % opname)
707         if decls:
708             out_file.write("  {\n")
709             for decl in decls:
710                 out_file.write("    %s;\n" % decl)
711             if sysmis_cond is not None:
712                 miss_ret = op.returns.missing_value
713                 out_file.write("    *%s++ = force_sysmis ? %s : %s;\n" % (stack, miss_ret, result))
714             else:
715                 out_file.write("    *%s++ = %s;\n" % (stack, result))
716             out_file.write("  }\n")
717         else:
718             out_file.write("  *%s++ = %s;\n" % (stack, result))
719         out_file.write("  break;\n\n")
720
721 def generate_operations_h():
722     out_file.write("#include <stdlib.h>\n")
723     out_file.write("#include <stdbool.h>\n\n")
724
725     out_file.write("typedef enum")
726     out_file.write("  {\n")
727     atoms = []
728     for type_ in types.values():
729         if type_.role != 'fixed':
730             atoms += ["OP_%s" % type_.name]
731
732     print_operations('atom', 1, atoms)
733     print_operations('function', "OP_atom_last + 1", funcs)
734     print_operations('operator', "OP_function_last + 1", opers)
735     print_range("OP_composite", "OP_function_first", "OP_operator_last")
736     out_file.write(",\n\n")
737     print_range("OP", "OP_atom_first", "OP_composite_last")
738     out_file.write("\n  }\n")
739     out_file.write("operation_type, atom_type;\n")
740
741     print_predicate('is_operation', 'OP')
742     for key in ('atom', 'composite', 'function', 'operator'):
743         print_predicate("is_%s" % key, "OP_%s" % key)
744
745 def print_operations(type_, first, names):
746     out_file.write("    /* %s types. */\n" % type_.title())
747     out_file.write("    %s = %s,\n" % (names[0], first))
748     for name in names[1:]:
749         out_file.write("    %s,\n" % name)
750     print_range("OP_%s" % type_, names[0], names[len(names) - 1])
751     out_file.write(",\n\n")
752
753 def print_range(prefix, first, last):
754     out_file.write("    %s_first = %s,\n" % (prefix, first))
755     out_file.write("    %s_last = %s,\n" % (prefix, last))
756     out_file.write("    n_%s = %s_last - %s_first + 1" % (prefix, prefix, prefix))
757
758 def print_predicate(function, category):
759     assertion = ""
760
761     out_file.write("\nstatic inline bool\n")
762     out_file.write("%s (operation_type op)\n" % function)
763     out_file.write("{\n")
764     if function != 'is_operation':
765         out_file.write("  assert (is_operation (op));\n")
766     out_file.write("  return op >= %s_first && op <= %s_last;\n" % (category, category))
767     out_file.write("}\n")
768
769 def generate_optimize_inc():
770     for opname in order:
771         op = ops[opname]
772
773         if not op.optimizable or op.unimplemented:
774             out_file.write("case %s:\n" % opname)
775             out_file.write("  NOT_REACHED ();\n\n")
776             continue
777
778         decls = []
779         arg_idx = 0
780         for arg in op.args:
781             name = arg.name
782             type_ = arg.type_
783             c_type = type_.c_type
784             if arg.idx is None:
785                 func = "get_%s_arg" % type_.atom
786                 decls += ["%sarg_%s = %s (node, %s)" % (c_type, name, func, arg_idx)]
787             else:
788                 decl = "size_t arg_%s = node->n_args" % arg.idx
789                 if arg_idx > 0:
790                     decl += " - %s" % arg_idx
791                 decls += [decl]
792
793                 decls += ["%s*arg_%s = get_%s_args  (node, %s, arg_%s, e)" % (c_type, name, type_.atom, arg_idx, arg.idx)]
794             arg_idx += 1
795
796         sysmis_cond = op.sysmis_decl("node->min_valid")
797         if sysmis_cond is not None:
798             decls += [sysmis_cond]
799
800         args = []
801         for arg in op.args:
802             args += ["arg_%s" % arg.name]
803             if arg.idx is not None:
804                 idx = 'arg_%s' % arg.idx
805                 if arg.times != 1:
806                     idx += " / %s" % arg.times
807                 args += [idx]
808
809         for aux in op.aux:
810             type_ = aux['TYPE']
811             if type_.role == 'leaf':
812                 func = "get_%s_arg" % type_.atom
813                 args += "%s (node, %s)" % (func, arg_idx)
814                 arg_idx += 1
815             elif type_.role == 'fixed':
816                 args += [type_.fixed_value]
817             else:
818                 assert False
819
820         result = "eval_%s (%s)" % (op.opname, ', '.join(args))
821         if decls and sysmis_cond is not None:
822             miss_ret = op.returns.missing_value
823             decls += ['%sresult = force_sysmis ? %s : %s' % (op.returns.c_type, miss_ret, result)]
824             result = 'result'
825
826         out_file.write("case %s:\n" % opname)
827         alloc_func = "expr_allocate_%s" % op.returns.name
828         if decls:
829             out_file.write("  {\n")
830             for decl in decls:
831                 out_file.write("    %s;\n" % decl)
832             out_file.write("    return %s (e, %s);\n" % (alloc_func, result))
833             out_file.write("  }\n")
834         else:
835             out_file.write("  return %s (e, %s);\n" % (alloc_func, result))
836         out_file.write("\n")
837
838 def generate_parse_inc():
839     members = ['""', '""', '0', '0', '0', "{}", '0', '0']
840     out_file.write("{%s},\n" % ', '.join(members))
841
842     for type_ in types.values():
843         if type_.role != 'fixed':
844             members = ('"%s"' % type_.name, '"%s"' % type_.human_name, '0', "OP_%s" % type_.name, '0', "{}", '0', '0')
845             out_file.write("{%s},\n" % ', '.join(members))
846
847     for opname in order:
848         op = ops[opname]
849
850         members = []
851         members += ['"%s"' % op.name]
852
853         if op.category == 'function':
854             args = []
855             opt_args = []
856             for arg in op.args:
857                 if arg.idx is None:
858                     args += [arg.type_.human_name]
859
860             array = op.array_arg()
861             if array is not None:
862                 if op.min_valid == 0:
863                     array_args = []
864                     for i in range(array.times):
865                         array_args += [array.type_.human_name]
866                     args += array_args
867                     opt_args = array_args
868                 else:
869                     for i in range(op.min_valid):
870                         args += [array.type_.human_name]
871                     opt_args += [array.type_.human_name]
872             human = "%s(%s" % (op.name, ', '.join(args))
873             if opt_args:
874                 human += '[, %s]...' % ', '.join(opt_args)
875             human += ')'
876             members += ['"%s"' % human]
877         else:
878             members += ['NULL']
879
880         flags = []
881         if op.absorb_miss:
882             flags += ['OPF_ABSORB_MISS']
883         if op.array_arg():
884             flags += ['OPF_ARRAY_OPERAND']
885         if op.min_valid > 0:
886             flags += ['OPF_MIN_VALID']
887         if not op.optimizable:
888             flags += ['OPF_NONOPTIMIZABLE']
889         if op.extension:
890             flags += ['OPF_EXTENSION']
891         if op.unimplemented:
892             flags += ['OPF_UNIMPLEMENTED']
893         if op.perm_only:
894             flags += ['OPF_PERM_ONLY']
895         if op.no_abbrev:
896             flags += ['OPF_NO_ABBREV']
897         members += [' | '.join(flags) if flags else '0']
898
899         members += ['OP_%s' % op.returns.name]
900
901         members += ['%s' % len(op.args)]
902
903         arg_types = ["OP_%s" % arg.type_.name for arg in op.args]
904         members += ['{%s}' % ', '.join(arg_types)]
905
906         members += ['%s' % op.min_valid]
907
908         members += ['%s' % (op.array_arg().times if op.array_arg() else 0)]
909
910         out_file.write('{%s},\n' % ', '.join(members))
911 \f
912 # Utilities.
913
914 def usage():
915     print("""\
916 %(argv0)s, for generating expression parsers and evaluators from definitions
917 usage: generate.pl -o OUTPUT [-i INPUT] [-h]
918   -i INPUT    input file containing definitions (default: operations.def)
919   -o OUTPUT   output file
920   -h          display this help message
921 """ % {'argv0': argv0})
922     sys.exit(0)
923
924 if __name__ == "__main__":
925     try:
926         options, args = getopt.gnu_getopt(sys.argv[1:], 'hi:o:',
927                                           ['input=s',
928                                            'output=s',
929                                            'help'])
930     except getopt.GetoptError as geo:
931         sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
932         sys.exit(1)
933
934     in_file_name = 'operations.def'
935     out_file_name = None
936     for key, value in options:
937         if key in ['-h', '--help']:
938             usage()
939         elif key in ['-i', '--input']:
940             in_file_name = value
941         elif key in ['-o', '--output']:
942             out_file_name = value
943         else:
944             sys.exit(0)
945
946     if out_file_name is None:
947         sys.stderr.write("%(argv0)s: output file must be specified "
948                          "(use --help for help)\n" % {'argv0': argv0})
949         sys.exit(1)
950
951     in_file = open(in_file_name, 'r')
952     out_file = open(out_file_name, 'w')
953
954     init_all_types()
955     parse_input()
956
957     print_header()
958     if out_file_name.endswith('evaluate.h'):
959         generate_evaluate_h()
960     elif out_file_name.endswith('evaluate.inc'):
961         generate_evaluate_inc()
962     elif out_file_name.endswith('operations.h'):
963         generate_operations_h()
964     elif out_file_name.endswith('optimize.inc'):
965         generate_optimize_inc()
966     elif out_file_name.endswith('parse.inc'):
967         generate_parse_inc()
968     else:
969         sys.stderr.write("%(argv0)s: unknown output type\n")
970         sys.exit(1)
971     print_trailer()