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