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