6cb6a9f257370e5be8ac4132798a445e0009968a
[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 arg.idx is not None:
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 arg.condition is not None:
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', arg.condition)
288
289         opname = 'OP_' + op['NAME']
290         opname = opname.replace('.', '_')
291         if op['CATEGORY'] == 'function':
292             mangle = ''.join([a.type_['MANGLE'] for a in op['ARGS']])
293             op['MANGLE'] = mangle
294             opname += '_' + mangle
295         op['OPNAME'] = opname
296
297         if op['MIN_VALID'] > 0:
298             aa = array_arg(op)
299             if aa is None:
300                 sys.stderr.write("can't have minimum valid count without array arg\n")
301                 sys.exit(1)
302             if aa.type_['NAME'] != 'number':
303                 sys.stderr.write('minimum valid count allowed only with double array\n')
304                 sys.exit(1)
305             if aa.times != 1:
306                 sys.stderr.write("can't have minimu valid count if array has multiplication factor\n")
307                 sys.exit(1)
308
309         op['AUX'] = []
310         while toktype == 'id':
311             type_ = parse_type()
312             if type_ is None:
313                 sys.stderr.write('parse error\n')
314                 sys.exit(1)
315             if type_['ROLE'] not in ['leaf', 'fixed']:
316                 sys.stderr.write("'%s' is not allowed as auxiliary data\n"
317                                  % type_['NAME'])
318                 sys.exit(1)
319             name = force('id')
320             op['AUX'] += [{'TYPE': type_, 'NAME': name}]
321             force_match(';')
322
323         if op['OPTIMIZABLE']:
324             if op['NAME'].startswith('RV.'):
325                 sys.stderr.write("random variate functions must be marked 'no_opt'\n")
326                 sys.exit(1)
327             for key in ['CASE', 'CASE_IDX']:
328                 if key in op['AUX']:
329                     sys.stderr.write("operators with %s aux data must be marked 'no_opt'\n" % key)
330                     sys.exit(1)
331
332         if op['RETURNS']['NAME'] == 'string' and not op['ABSORB_MISS']:
333             for arg in op['ARGS']:
334                 if arg.type_['NAME'] in ['number', 'boolean']:
335                     sys.stderr.write("'%s' returns string and has double or bool "
336                                      "argument, but is not marked ABSORB_MISS\n"
337                                      % op['NAME'])
338                     sys.exit(1)
339                 if arg.condition is not None:
340                     sys.stderr.write("'%s' returns string but has argument with condition\n")
341                     sys.exit(1)
342
343         if toktype == 'block':
344             op['BLOCK'] = force('block')
345         elif toktype == 'expression':
346             if token == 'unimplemented':
347                 op['UNIMPLEMENTED'] = True
348             else:
349                 op['EXPRESSION'] = token
350             get_token()
351         else:
352             sys.stderr.write("block or expression expected\n")
353             sys.exit(1)
354
355         if opname in ops:
356             sys.stderr.write("duplicate operation name %s\n" % opname)
357             sys.exit(1)
358         ops[opname] = op
359         if op['CATEGORY'] == 'function':
360             funcs += [opname]
361         else:
362             opers += [opname]
363     in_file.close()
364
365     funcs = sorted(funcs, key=lambda name: (ops[name]['NAME'], ops[name]['OPNAME']))
366     opers = sorted(opers, key=lambda name: ops[name]['NAME'])
367     order = funcs + opers
368
369 def get_token():
370     """Reads the next token into 'token' and 'toktype'."""
371
372     global line
373     global token
374     global toktype
375
376     lookahead()
377     if toktype == 'eof':
378         return
379
380     m = re.match(r'([a-zA-Z_][a-zA-Z_.0-9]*)(.*)$', line)
381     if m:
382         token, line = m.groups()
383         toktype = 'id'
384         return
385
386     m = re.match(r'([0-9]+)(.*)$', line)
387     if m:
388         token, line = m.groups()
389         token = int(token)
390         toktype = 'int'
391         return
392
393     m = re.match(r'([][(),*;.])(.*)$', line)
394     if m:
395         token, line = m.groups()
396         toktype = 'punct'
397         return
398
399     m = re.match(r'=\s*(.*)$', line)
400     if m:
401         toktype = 'expression'
402         line = m.group(1)
403         token = accumulate_balanced(';')
404         return
405
406     m = re.match(r'{(.*)$', line)
407     if m:
408         toktype = 'block'
409         line = m.group(1)
410         token = accumulate_balanced('}')
411         token = token.rstrip('\n')
412         return
413
414     sys.stderr.write("bad character '%s' in input\n" % line[0])
415     sys.exit(1)
416
417 def lookahead():
418     """Skip whitespace."""
419     global line
420     if line is None:
421         sys.stderr.write("unexpected end of file\n")
422         sys.exit(1)
423
424     while True:
425         line = line.lstrip()
426         if line != "":
427             break
428         get_line()
429         if line is None:
430             global token
431             global toktype
432             token = 'eof'
433             toktype = 'eof'
434             return
435
436 def accumulate_balanced(end, swallow_end=True):
437     """Accumulates input until a character in 'end' is encountered,
438     except that balanced pairs of (), [], or {} cause 'end' to be
439     ignored.  Returns the input read.
440     """
441     s = ""
442     nest = 0
443     global line
444     while True:
445         for idx, c in enumerate(line):
446             if c in end and nest == 0:
447                 line = line[idx:]
448                 if swallow_end:
449                     line = line[1:]
450                 s = s.strip('\r\n')
451                 return s
452             elif c in "[({":
453                 nest += 1
454             elif c in "])}":
455                 if nest > 0:
456                     nest -= 1
457                 else:
458                     sys.stderr.write('unbalanced parentheses\n')
459                     sys.exit(1)
460             s += c
461         s += '\n'
462         get_line()
463
464 def get_line():
465     """Reads the next line from INPUT into 'line'."""
466     global line
467     global line_number
468     line = in_file.readline()
469     line_number += 1
470     if line == '':
471         line = None
472     else:
473         line = line.rstrip('\r\n')
474         comment_ofs = line.find('//')
475         if comment_ofs >= 0:
476             line = line[:comment_ofs]
477
478 def parse_type():
479     """If the current token is an identifier that names a type, returns
480     the type and skips to the next token.  Otherwise, returns
481     undef.
482     """
483     if toktype == 'id':
484         for type_ in types.values():
485             if type_.get("NAME") == token:
486                 get_token()
487                 return type_
488     return None
489
490 def force(type_):
491     """Makes sure that 'toktype' equals 'type', reads the next token, and
492     returns the previous 'token'.
493
494     """
495     if type_ != toktype:
496         sys.stderr.write("parse error at `%s' expecting %s\n" % (token, type_))
497         sys.exit(1)
498     tok = token
499     get_token()
500     return tok
501
502 def match(tok):
503     """If 'token' equals 'tok', reads the next token and returns true.
504     Otherwise, returns false."""
505     if token == tok:
506         get_token()
507         return True
508     else:
509         return False
510
511 def force_match(tok):
512     """If 'token' equals 'tok', reads the next token.  Otherwise, flags an
513     error in the input.
514     """
515     if not match(tok):
516         sys.stderr.write("parse error at `%s' expecting `%s'\n" % (token, tok))
517         sys.exit(1)
518
519 class Arg:
520     def __init__(self, name, type_, idx, times, condition):
521         self.name = name
522         self.type_ = type_
523         self.idx = idx
524         self.times = times
525         self.condition = condition
526
527 def parse_arg():
528     """Parses and returns a function argument."""
529     type_ = parse_type()
530     if type_ is None:
531         type_ = types['number']
532
533     if toktype != 'id':
534         sys.stderr.write("argument name expected at `%s'\n" % token)
535         sys.exit(1)
536     name = token
537
538     lookahead()
539     global line
540
541     idx = None
542     times = 1
543
544     if line[0] in "[,)":
545         get_token()
546         if match('['):
547             if type_['NAME'] not in ('number', 'string'):
548                 sys.stderr.write('only double and string arrays supported\n')
549                 sys.exit(1)
550             idx = force('id')
551             if match('*'):
552                 times = force('int')
553                 if times != 2:
554                     sys.stderr.write('multiplication factor must be two\n')
555                     sys.exit(1)
556             force_match(']')
557         condition = None
558     else:
559         condition = name + ' ' + accumulate_balanced(',)', swallow_end=False)
560         get_token()
561
562     return Arg(name, type_, idx, times, condition)
563
564 def print_header():
565     """Prints the output file header."""
566     out_file.write("""\
567 /* %s
568    Generated from %s by generate.py.
569    Do not modify! */
570
571 """ % (out_file_name, in_file_name))
572
573 def print_trailer():
574     """Prints the output file trailer."""
575     out_file.write("""\
576
577 /*
578    Local Variables:
579    mode: c
580    buffer-read-only: t
581    End:
582 */
583 """)
584
585 def generate_evaluate_h():
586     out_file.write("#include \"helpers.h\"\n\n")
587
588     for opname in order:
589         op = ops[opname]
590         if op['UNIMPLEMENTED']:
591             continue
592
593         args = []
594         for arg in op['ARGS']:
595             if arg.idx is None:
596                 args += [c_type(arg.type_) + arg.name]
597             else:
598                 args += [c_type(arg.type_) + arg.name + '[]']
599                 args += ['size_t %s' % arg.idx]
600         for aux in op['AUX']:
601             args += [c_type(aux['TYPE']) + aux['NAME']]
602         if not args:
603             args += ['void']
604
605         if 'BLOCK' in op:
606             statements = op['BLOCK'] + '\n'
607         else:
608             statements = "  return %s;\n" % op['EXPRESSION']
609
610         out_file.write("static inline %s\n" % c_type (op['RETURNS']))
611         out_file.write("eval_%s (%s)\n" % (opname, ', '.join(args)))
612         out_file.write("{\n")
613         out_file.write(statements)
614         out_file.write("}\n\n")
615
616 def generate_evaluate_inc():
617     for opname in order:
618         op = ops[opname]
619         if op['UNIMPLEMENTED']:
620             out_file.write("case %s:\n" % opname)
621             out_file.write("  NOT_REACHED ();\n\n")
622             continue
623
624         decls = []
625         args = []
626         for arg in op['ARGS']:
627             type_ = arg.type_
628             ctype = c_type(type_)
629             args += ['arg_%s' % arg.name]
630             if arg.idx is None:
631                 decl = '%sarg_%s' % (ctype, arg.name)
632                 if type_['ROLE'] == 'any':
633                     decls = ['%s = *--%s' % (decl, type_['STACK'])] + decls
634                 elif type_['ROLE'] == 'leaf':
635                     decls += ['%s = op++->%s' % (decl, type_['ATOM'])]
636                 else:
637                     assert False
638             else:
639                 idx = arg.idx
640                 stack = type_['STACK']
641                 decls = ['%s*arg_%s = %s -= arg_%s' % (ctype, arg.name, stack, idx)] + decls
642                 decls = ['size_t arg_%s = op++->integer' % idx] + decls
643
644                 idx = 'arg_%s' % idx
645                 if arg.times != 1:
646                     idx += ' / %s' % arg.times
647                 args += [idx]
648         for aux in op['AUX']:
649             type_ = aux['TYPE']
650             name = aux['NAME']
651             if type_['ROLE'] == 'leaf':
652                 ctype = c_type(type_)
653                 decls += ['%saux_%s = op++->%s' % (ctype, name, type_['ATOM'])]
654                 args += ['aux_%s' % name]
655             elif type_['ROLE'] == 'fixed':
656                 args += [type_['FIXED_VALUE']]
657
658         sysmis_cond = make_sysmis_decl(op, 'op++->integer')
659         if sysmis_cond is not None:
660             decls += [sysmis_cond]
661
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 arg.idx is None:
745                 func = "get_%s_arg" % type_['ATOM']
746                 decls += ["%sarg_%s = %s (node, %s)" % (ctype, name, func, arg_idx)]
747             else:
748                 decl = "size_t arg_%s = node->n_args" % arg.idx
749                 if arg_idx > 0:
750                     decl += " - %s" % arg_idx
751                 decls += [decl]
752
753                 decls += ["%s*arg_%s = get_%s_args  (node, %s, arg_%s, e)" % (ctype, name, type_['ATOM'], arg_idx, arg.idx)]
754             arg_idx += 1
755
756         sysmis_cond = make_sysmis_decl (op, "node->min_valid")
757         if sysmis_cond is not None:
758             decls += [sysmis_cond]
759
760         args = []
761         for arg in op['ARGS']:
762             args += ["arg_%s" % arg.name]
763             if arg.idx is not None:
764                 idx = 'arg_%s' % arg.idx
765                 if arg.times != 1:
766                     idx += " / %s" % arg.times
767                 args += [idx]
768
769         for aux in op['AUX']:
770             type_ = aux['TYPE']
771             if type_['ROLE'] == 'leaf':
772                 func = "get_%s_arg" % type_['ATOM']
773                 args += "%s (node, %s)" % (func, arg_idx)
774                 arg_idx += 1
775             elif type_['ROLE'] == 'fixed':
776                 args += [type_['FIXED_VALUE']]
777             else:
778                 assert False
779
780         result = "eval_%s (%s)" % (op['OPNAME'], ', '.join(args))
781         if decls and sysmis_cond is not None:
782             miss_ret = op['RETURNS']['MISSING_VALUE']
783             decls += ['%sresult = force_sysmis ? %s : %s' % (c_type(op['RETURNS']), miss_ret, result)]
784             result = 'result'
785
786         out_file.write("case %s:\n" % opname)
787         alloc_func = "expr_allocate_%s" % op['RETURNS']['NAME']
788         if decls:
789             out_file.write("  {\n")
790             for decl in decls:
791                 out_file.write("    %s;\n" % decl)
792             out_file.write("    return %s (e, %s);\n" % (alloc_func, result))
793             out_file.write("  }\n")
794         else:
795             out_file.write("  return %s (e, %s);\n" % (alloc_func, result))
796         out_file.write("\n")
797
798 def generate_parse_inc():
799     members = ['""', '""', '0', '0', '0', "{}", '0', '0']
800     out_file.write("{%s},\n" % ', '.join(members))
801
802     for type_ in types.values():
803         if type_['ROLE'] != 'fixed':
804             human_name = type_.get('HUMAN_NAME', type_['NAME'])
805             members = ('"%s"' % type_['NAME'], '"%s"' % human_name, '0', "OP_%s" % type_['NAME'], '0', "{}", '0', '0')
806             out_file.write("{%s},\n" % ', '.join(members))
807
808     for opname in order:
809         op = ops[opname]
810
811         members = []
812         members += ['"%s"' % op['NAME']]
813
814         if op['CATEGORY'] == 'function':
815             args = []
816             opt_args = []
817             for arg in op['ARGS']:
818                 if arg.idx is None:
819                     args += [arg.type_['HUMAN_NAME']]
820
821             array = array_arg(op)
822             if array is not None:
823                 if op['MIN_VALID'] == 0:
824                     array_args = []
825                     for i in range(array.times):
826                         array_args += [array.type_['HUMAN_NAME']]
827                     args += array_args
828                     opt_args = array_args
829                 else:
830                     for i in range(op['MIN_VALID']):
831                         args += [array.type_['HUMAN_NAME']]
832                     opt_args += [array.type_['HUMAN_NAME']]
833             human = "%s(%s" % (op['NAME'], ', '.join(args))
834             if opt_args:
835                 human += '[, %s]...' % ', '.join(opt_args)
836             human += ')'
837             members += ['"%s"' % human]
838         else:
839             members += ['NULL']
840
841         flags = []
842         if op['ABSORB_MISS']:
843             flags += ['OPF_ABSORB_MISS']
844         if array_arg(op):
845             flags += ['OPF_ARRAY_OPERAND']
846         if op['MIN_VALID'] > 0:
847             flags += ['OPF_MIN_VALID']
848         if not op['OPTIMIZABLE']:
849             flags += ['OPF_NONOPTIMIZABLE']
850         if op['EXTENSION']:
851             flags += ['OPF_EXTENSION']
852         if op['UNIMPLEMENTED']:
853             flags += ['OPF_UNIMPLEMENTED']
854         if op['PERM_ONLY']:
855             flags += ['OPF_PERM_ONLY']
856         if op['NO_ABBREV']:
857             flags += ['OPF_NO_ABBREV']
858         members += [' | '.join(flags) if flags else '0']
859
860         members += ['OP_%s' % op['RETURNS']['NAME']]
861
862         members += ['%s' % len(op['ARGS'])]
863
864         arg_types = ["OP_%s" % arg.type_['NAME'] for arg in op['ARGS']]
865         members += ['{%s}' % ', '.join(arg_types)]
866
867         members += ['%s' % op['MIN_VALID']]
868
869         members += ['%s' % (array_arg(op).times if array_arg(op) else 0)]
870
871         out_file.write('{%s},\n' % ', '.join(members))
872 \f
873 # Utilities.
874
875 def make_sysmis_decl(op, min_valid_src):
876     """Returns a declaration for a boolean variable called `force_sysmis',
877     which will be true when operation 'op' should be system-missing.
878     Returns None if there are no such circumstances.
879
880     If 'op' has a minimum number of valid arguments, 'min_valid_src'
881     should be an an expression that evaluates to the minimum number of
882     valid arguments for 'op'.
883
884     """
885     sysmis_cond = []
886     if not op['ABSORB_MISS']:
887         for arg in op['ARGS']:
888             arg_name = 'arg_%s' % arg.name
889             if arg.idx is None:
890                 if arg.type_['NAME'] in ['number', 'boolean']:
891                     sysmis_cond += ["!is_valid (%s)" % arg_name]
892             elif arg.type_['NAME'] == 'number':
893                 a = arg_name
894                 n = 'arg_%s' % arg.idx
895                 sysmis_cond += ['count_valid (%s, %s) < %s' % (a, n, n)]
896     elif op['MIN_VALID'] > 0:
897         args = op['ARGS']
898         arg = args[-1]
899         a = 'arg_%s' % arg.name
900         n = 'arg_%s' % arg.idx
901         sysmis_cond += ["count_valid (%s, %s) < %s" % (a, n, min_valid_src)]
902     for arg in op['ARGS']:
903         if arg.condition is not None:
904             sysmis_cond += ['!(%s)' % arg.condition]
905     if sysmis_cond:
906         return 'bool force_sysmis = %s' % ' || '.join(sysmis_cond)
907     return None
908
909 def array_arg(op):
910     """If 'op' has an array argument, returns it.  Otherwise, returns
911     None."""
912     args = op['ARGS']
913     if not args:
914         return None
915     last_arg = args[-1]
916     if last_arg.idx is not None:
917         return last_arg
918     return None
919
920 def usage():
921     print("""\
922 %(argv0)s, for generating expression parsers and evaluators from definitions
923 usage: generate.pl -o OUTPUT [-i INPUT] [-h]
924   -i INPUT    input file containing definitions (default: operations.def)
925   -o OUTPUT   output file
926   -h          display this help message
927 """ % {'argv0': argv0})
928     sys.exit(0)
929
930 if __name__ == "__main__":
931     try:
932         options, args = getopt.gnu_getopt(sys.argv[1:], 'hi:o:',
933                                           ['input=s',
934                                            'output=s',
935                                            'help'])
936     except getopt.GetoptError as geo:
937         sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
938         sys.exit(1)
939
940     in_file_name = 'operations.def'
941     out_file_name = None
942     for key, value in options:
943         if key in ['-h', '--help']:
944             usage()
945         elif key in ['-i', '--input']:
946             in_file_name = value
947         elif key in ['-o', '--output']:
948             out_file_name = value
949         else:
950             sys.exit(0)
951
952     if out_file_name is None:
953         sys.stderr.write("%(argv0)s: output file must be specified "
954                          "(use --help for help)\n" % {'argv0': argv0})
955         sys.exit(1)
956
957     in_file = open(in_file_name, 'r')
958     out_file = open(out_file_name, 'w')
959
960     init_all_types()
961     parse_input()
962
963     print_header()
964     if out_file_name.endswith('evaluate.h'):
965         generate_evaluate_h()
966     elif out_file_name.endswith('evaluate.inc'):
967         generate_evaluate_inc()
968     elif out_file_name.endswith('operations.h'):
969         generate_operations_h()
970     elif out_file_name.endswith('optimize.inc'):
971         generate_optimize_inc()
972     elif out_file_name.endswith('parse.inc'):
973         generate_parse_inc()
974     else:
975         sys.stderr.write("%(argv0)s: unknown output type\n")
976         sys.exit(1)
977     print_trailer()