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