Better encap.
[pspp] / src / language / expressions / generate.py
index 6cb6a9f257370e5be8ac4132798a445e0009968a..d76e2e6bd98be844b14b6cbc9cde6a41650cc53f 100644 (file)
@@ -32,149 +32,132 @@ def init_all_types():
     types = {}
 
     # Common user-visible types used throughout evaluation trees.
-    init_type('number', 'any', C_TYPE='double',
-              ATOM='number', MANGLE='n', HUMAN_NAME='number',
-              STACK='ns', MISSING_VALUE='SYSMIS')
-    init_type('string', 'any', C_TYPE='struct substring',
-              ATOM='string', MANGLE='s', HUMAN_NAME='string',
-              STACK='ss', MISSING_VALUE='empty_string')
-    init_type('boolean', 'any', C_TYPE='double',
-              ATOM='number', MANGLE='n', HUMAN_NAME='boolean',
-              STACK='ns', MISSING_VALUE='SYSMIS')
+    init_type(Type.new_any('number', 'double', 'number', 'n', 'number', 'ns', 'SYSMIS'))
+    init_type(Type.new_any('string', 'struct substring', 'string', 's', 'string', 'ss', 'empty_string'))
+    init_type(Type.new_any('boolean', 'double', 'number', 'n', 'boolean', 'ns', 'SYSMIS'))
 
     # Format types.
-    init_type('format', 'atom')
-    init_type('ni_format', 'leaf', C_TYPE='const struct fmt_spec *',
-              ATOM='format', MANGLE='f',
-              HUMAN_NAME='num_input_format')
-    init_type('no_format', 'leaf', C_TYPE='const struct fmt_spec *',
-              ATOM='format', MANGLE='f',
-              HUMAN_NAME='num_output_format')
+    init_type(Type.new_atom('format'))
+    init_type(Type.new_leaf('ni_format', 'const struct fmt_spec *', 'format', 'f', 'num_input_format'))
+    init_type(Type.new_leaf('no_format', 'const struct fmt_spec *', 'format', 'f', 'num_output_format'))
 
     # Integer types.
-    init_type('integer', 'leaf', C_TYPE='int',
-              ATOM='integer', MANGLE='n', HUMAN_NAME='integer')
-    init_type('pos_int', 'leaf', C_TYPE='int',
-              ATOM='integer', MANGLE='n',
-              HUMAN_NAME='positive_integer_constant')
+    init_type(Type.new_leaf('integer', 'int', 'integer', 'n', 'integer'))
+    init_type(Type.new_leaf('pos_int', 'int', 'integer', 'n', 'positive_integer_constant'))
 
     # Variable names.
-    init_type('variable', 'atom')
-    init_type('num_var', 'leaf', C_TYPE='const struct variable *',
-              ATOM='variable', MANGLE='Vn',
-              HUMAN_NAME='num_variable')
-    init_type('str_var', 'leaf', C_TYPE='const struct variable *',
-              ATOM='variable', MANGLE='Vs',
-              HUMAN_NAME='string_variable')
-    init_type('var', 'leaf', C_TYPE='const struct variable *',
-              ATOM='variable', MANGLE='V',
-              HUMAN_NAME='variable')
+    init_type(Type.new_atom('variable'))
+    init_type(Type.new_leaf('num_var', 'const struct variable *', 'variable', 'Vn', 'num_variable'))
+    init_type(Type.new_leaf('str_var', 'const struct variable *', 'variable', 'Vs', 'string_variable'))
+    init_type(Type.new_leaf('var', 'const struct variable *', 'variable', 'V', 'variable'))
 
     # Vectors.
-    init_type('vector', 'leaf', C_TYPE='const struct vector *',
-              ATOM='vector', MANGLE='v', HUMAN_NAME='vector')
+    init_type(Type.new_leaf('vector', 'const struct vector *', 'vector', 'v', 'vector'))
 
     # Fixed types.
-    init_type('expression', 'fixed', C_TYPE='struct expression *',
-              FIXED_VALUE='e')
-    init_type('case', 'fixed', C_TYPE='const struct ccase *',
-              FIXED_VALUE='c')
-    init_type('case_idx', 'fixed', C_TYPE='size_t',
-              FIXED_VALUE='case_idx')
-    init_type('dataset', 'fixed', C_TYPE='struct dataset *',
-              FIXED_VALUE='ds')
+    init_type(Type.new_fixed('expression', 'struct expression *', 'e'))
+    init_type(Type.new_fixed('case', 'const struct ccase *', 'c'))
+    init_type(Type.new_fixed('case_idx', 'size_t', 'case_idx'))
+    init_type(Type.new_fixed('dataset', 'struct dataset *', 'ds'))
 
     # One of these is emitted at the end of each expression as a sentinel
     # that tells expr_evaluate() to return the value on the stack.
-    init_type('return_number', 'atom')
-    init_type('return_string', 'atom')
+    init_type(Type.new_atom('return_number'))
+    init_type(Type.new_atom('return_string'))
 
     # Used only for debugging purposes.
-    init_type('operation', 'atom')
+    init_type(Type.new_atom('operation'))
 
-def init_type(name, role, **rest):
-    """
-    init_type has 2 required arguments:
-    
-      NAME: Type name.
-    
-              'name' is the type's name in operations.def.
-    
-              `OP_$name' is the terminal's type in operations.h.
-    
-              `expr_allocate_$name()' allocates a node of the given type.
-    
-      ROLE: How the type may be used:
-    
-              "any": Usable as operands and function arguments, and
-              function and operator results.
-    
-              "leaf": Usable as operands and function arguments, but
-              not function arguments or results.  (Thus, they appear
-              only in leaf nodes in the parse type.)
-    
-              "fixed": Not allowed either as an operand or argument
-              type or a result type.  Used only as auxiliary data.
-    
-              "atom": Not allowed anywhere; just adds the name to
-              the list of atoms.
-    
-    All types except those with "atom" as their role also require:
-    
-      C_TYPE: The C type that represents this abstract type.
-    
-    Types with "any" or "leaf" role require:
-    
-      ATOM:
-    
-              `$atom' is the `struct operation_data' member name.
-    
-              get_$atom_name() obtains the corresponding data from a
-              node.
-    
-      MANGLE: Short string for name mangling.  Use identical strings
-      if two types should not be overloaded.
-    
-      HUMAN_NAME: Name for a type when we describe it to the user.
-    
-    Types with role "any" require:
-    
-      STACK: Name of the local variable in expr_evaluate(), used for
-      maintaining the stack for this type.
-    
-      MISSING_VALUE: Expression used for the missing value of this
-      type.
-    
-    Types with role "fixed" require:
-    
-      FIXED_VALUE: Expression used for the value of this type.
 """
-    global types
-    new_type = { 'NAME': name, 'ROLE': role } | rest
-
-    need_keys = ['NAME', 'ROLE']
-    if role == 'any':
-        need_keys += ['C_TYPE', 'ATOM', 'MANGLE', 'HUMAN_NAME', 'STACK', 'MISSING_VALUE']
-    elif role == 'leaf':
-        need_keys += ['C_TYPE', 'ATOM', 'MANGLE', 'HUMAN_NAME']
-    elif role == 'fixed':
-        need_keys += ['C_TYPE', 'FIXED_VALUE']
-    elif role == 'atom':
-        pass
-    else:
-        sys.stderr.write("no role '%s'\n" % role)
-        sys.exit(1)
+init_type has 2 required arguments:
 
-    for key in new_type.keys():
-        if not key in new_type:
-            sys.stderr.write("%s lacks %s\n" % (name, key))
-            sys.exit(1)
-    for key in need_keys:
-        if not key in need_keys:
-            sys.stderr.write("%s has superfluous key %s\n" % (name, key))
-            sys.exit(1)
+  NAME: Type name.
+
+          'name' is the type's name in operations.def.
+
+          `OP_$name' is the terminal's type in operations.h.
+
+          `expr_allocate_$name()' allocates a node of the given type.
+
+  ROLE: How the type may be used:
+
+          "any": Usable as operands and function arguments, and
+          function and operator results.
+
+          "leaf": Usable as operands and function arguments, but not
+          results.  (Thus, they appear only in leaf nodes in the parse
+          tree.)
+
+          "fixed": Not allowed either as an operand or argument
+          type or a result type.  Used only as auxiliary data.
+
+          "atom": Not allowed anywhere; just adds the name to
+          the list of atoms.
+
+All types except those with "atom" as their role also require:
+
+  C_TYPE: The C type that represents this abstract type.
+
+Types with "any" or "leaf" role require:
 
-    types[name] = new_type
+  ATOM:
+
+          `$atom' is the `struct operation_data' member name.
+
+          get_$atom_name() obtains the corresponding data from a
+          node.
+
+  MANGLE: Short string for name mangling.  Use identical strings
+  if two types should not be overloaded.
+
+  HUMAN_NAME: Name for a type when we describe it to the user.
+
+Types with role "any" require:
+
+  STACK: Name of the local variable in expr_evaluate(), used for
+  maintaining the stack for this type.
+
+  MISSING_VALUE: Expression used for the missing value of this
+  type.
+
+Types with role "fixed" require:
+
+  FIXED_VALUE: Expression used for the value of this type.
+"""
+class Type:
+    def __init__(self, name, role, human_name):
+        self.name = name
+        self.role = role
+        self.human_name = human_name
+
+    def new_atom(name):
+        return Type(name, 'atom', name)
+
+    def new_any(name, c_type, atom, mangle, human_name, stack, missing_value):
+        new = Type(name, 'any', human_name)
+        new.c_type = c_type
+        new.atom = atom
+        new.mangle = mangle
+        new.stack = stack
+        new.missing_value = missing_value
+        return new
+
+    def new_leaf(name, c_type, atom, mangle, human_name):
+        new = Type(name, 'leaf', human_name)
+        new.c_type = c_type
+        new.atom = atom
+        new.mangle = mangle
+        return new
+
+    def new_fixed(name, c_type, fixed_value):
+        new = Type(name, 'fixed', name)
+        new.c_type = c_type
+        new.fixed_value = fixed_value
+        return new
+
+def init_type(type_):
+    global types
+    types[type_.name] = type_
 
 # c_type(type).
 #
@@ -183,13 +166,40 @@ def c_type(type_):
     prepended to a variable name to produce a declaration.  (That
     won't work in general but it works well enough for our types.)
     """
-    c_type = type_["C_TYPE"]
+    c_type = type_.c_type
     if not c_type.endswith('*'):
         c_type += ' '
     return c_type
 \f
 # Input parsing.
 
+class Op:
+    def __init__(self,
+                 name, category,
+                 returns, args, aux,
+                 expression, block,
+                 min_valid,
+                 optimizable, unimplemented, extension, perm_only, absorb_miss, no_abbrev):
+        self.name = name
+        self.category = category
+        self.returns = returns
+        self.args = args
+        self.aux = aux
+        self.expression = expression
+        self.block = block
+        self.min_valid = min_valid
+        self.optimizable = optimizable
+        self.unimplemented = unimplemented
+        self.extension = extension
+        self.perm_only = perm_only
+        self.absorb_miss = absorb_miss
+        self.no_abbrev = no_abbrev
+
+        self.opname = ('OP_%s' % name).replace('.', '_')
+        if category == 'function':
+            self.mangle = ''.join([a.type_.mangle for a in args])
+            self.opname += '_%s' % self.mangle
+
 def parse_input():
     """Parses the entire input.
 
@@ -213,47 +223,44 @@ def parse_input():
     opers = []
 
     while toktype != 'eof':
-        op = {
-            'OPTIMIZABLE': True,
-            'UNIMPLEMENTED': False,
-            'EXTENSION': False,
-            'PERM_ONLY': False,
-            'ABSORB_MISS': False,
-            'NO_ABBREV': False,
-        }
+        optimizable = True
+        unimplemented = False
+        extension = False
+        perm_only = False
+        absorb_miss = False
+        no_abbrev = False
         while True:
             if match('extension'):
-                op['EXTENSION'] = True
+                extension = True
             elif match('no_opt'):
-                op['OPTIMIZABLE'] = False
+                optimizable = False
             elif match('absorb_miss'):
-                op['ABSORB_MISS'] = True
+                absorb_miss = True
             elif match('perm_only'):
-                op['PERM_ONLY'] = True
+                perm_only = True
             elif match('no_abbrev'):
-                op['NO_ABBREV'] = True
+                no_abbrev = True
             else:
                 break
 
         return_type = parse_type()
         if return_type is None:
             return_type = types['number']
-        if return_type['NAME'] not in ['number', 'string', 'boolean']:
-            sys.stderr.write('%s is not a valid return type\n' % return_type['NAME'])
+        if return_type.name not in ['number', 'string', 'boolean']:
+            sys.stderr.write('%s is not a valid return type\n' % return_type.name)
             sys.exit(1)
-        op['RETURNS'] = return_type
 
-        op['CATEGORY'] = token
-        if op['CATEGORY'] not in ['operator', 'function']:
+        category = token
+        if category not in ['operator', 'function']:
             sys.stderr.write("'operator' or 'function' expected at '%s'" % token)
             sys.exit(1)
         get_token()
 
         name = force('id')
-        if op['CATEGORY'] == 'function' and '_' in name:
+        if category == 'function' and '_' in name:
             sys.stderr.write("function name '%s' may not contain underscore\n" % name)
             sys.exit(1)
-        elif op['CATEGORY'] == 'operator' and '.' in name:
+        elif category == 'operator' and '.' in name:
             sys.stderr.write("operator name '%s' may not contain period\n" % name)
             sys.exit(1)
 
@@ -261,17 +268,16 @@ def parse_input():
         if m:
             prefix, suffix = m.groups()
             name = prefix
-            op['MIN_VALID'] = int(suffix)
-            op['ABSORB_MISS'] = True
+            min_valid = int(suffix)
+            absorb_miss = True
         else:
-            op['MIN_VALID'] = 0
-        op['NAME'] = name
+            min_valid = 0
 
         force_match('(')
-        op['ARGS'] = []
+        args = []
         while not match(')'):
             arg = parse_arg()
-            op['ARGS'] += [arg]
+            args += [arg]
             if arg.idx is not None:
                 if match(')'):
                     break
@@ -281,89 +287,90 @@ def parse_input():
                 force_match(')')
                 break
 
-        for arg in op['ARGS']:
+        for arg in args:
             if arg.condition is not None:
-                any_arg = '|'.join([a.name for a in op['ARGS']])
+                any_arg = '|'.join([a.name for a in args])
                 arg.condition = re.sub(r'\b(%s)\b' % any_arg, r'arg_\1', arg.condition)
 
-        opname = 'OP_' + op['NAME']
-        opname = opname.replace('.', '_')
-        if op['CATEGORY'] == 'function':
-            mangle = ''.join([a.type_['MANGLE'] for a in op['ARGS']])
-            op['MANGLE'] = mangle
-            opname += '_' + mangle
-        op['OPNAME'] = opname
-
-        if op['MIN_VALID'] > 0:
-            aa = array_arg(op)
-            if aa is None:
-                sys.stderr.write("can't have minimum valid count without array arg\n")
-                sys.exit(1)
-            if aa.type_['NAME'] != 'number':
-                sys.stderr.write('minimum valid count allowed only with double array\n')
-                sys.exit(1)
-            if aa.times != 1:
-                sys.stderr.write("can't have minimu valid count if array has multiplication factor\n")
-                sys.exit(1)
-
-        op['AUX'] = []
+        aux = []
         while toktype == 'id':
             type_ = parse_type()
             if type_ is None:
                 sys.stderr.write('parse error\n')
                 sys.exit(1)
-            if type_['ROLE'] not in ['leaf', 'fixed']:
-                sys.stderr.write("'%s' is not allowed as auxiliary data\n"
-                                 % type_['NAME'])
+            if type_.role not in ['leaf', 'fixed']:
+                sys.stderr.write("'%s' is not allowed as auxiliary data\n" % type_.name)
                 sys.exit(1)
-            name = force('id')
-            op['AUX'] += [{'TYPE': type_, 'NAME': name}]
+            aux_name = force('id')
+            aux += [{'TYPE': type_, 'NAME': aux_name}]
             force_match(';')
 
-        if op['OPTIMIZABLE']:
-            if op['NAME'].startswith('RV.'):
+        if optimizable:
+            if name.startswith('RV.'):
                 sys.stderr.write("random variate functions must be marked 'no_opt'\n")
                 sys.exit(1)
             for key in ['CASE', 'CASE_IDX']:
-                if key in op['AUX']:
+                if key in aux:
                     sys.stderr.write("operators with %s aux data must be marked 'no_opt'\n" % key)
                     sys.exit(1)
 
-        if op['RETURNS']['NAME'] == 'string' and not op['ABSORB_MISS']:
-            for arg in op['ARGS']:
-                if arg.type_['NAME'] in ['number', 'boolean']:
+        if return_type.name == 'string' and not absorb_miss:
+            for arg in args:
+                if arg.type_.name in ['number', 'boolean']:
                     sys.stderr.write("'%s' returns string and has double or bool "
                                      "argument, but is not marked ABSORB_MISS\n"
-                                     % op['NAME'])
+                                     % name)
                     sys.exit(1)
                 if arg.condition is not None:
                     sys.stderr.write("'%s' returns string but has argument with condition\n")
                     sys.exit(1)
 
         if toktype == 'block':
-            op['BLOCK'] = force('block')
+            block = force('block')
+            expression = None
         elif toktype == 'expression':
             if token == 'unimplemented':
-                op['UNIMPLEMENTED'] = True
+                unimplemented = True
             else:
-                op['EXPRESSION'] = token
+                expression = token
+            block = None
             get_token()
         else:
             sys.stderr.write("block or expression expected\n")
             sys.exit(1)
 
-        if opname in ops:
-            sys.stderr.write("duplicate operation name %s\n" % opname)
+        op = Op(name, category,
+                return_type, args, aux,
+                expression, block,
+                min_valid,
+                optimizable, unimplemented, extension, perm_only, absorb_miss,
+                no_abbrev)
+        if op.opname in ops:
+            sys.stderr.write("duplicate operation name %s\n" % op.opname)
             sys.exit(1)
-        ops[opname] = op
-        if op['CATEGORY'] == 'function':
-            funcs += [opname]
+
+        if min_valid > 0:
+            aa = array_arg(op)
+            if aa is None:
+                sys.stderr.write("can't have minimum valid count without array arg\n")
+                sys.exit(1)
+            if aa.type_.name != 'number':
+                sys.stderr.write('minimum valid count allowed only with double array\n')
+                sys.exit(1)
+            if aa.times != 1:
+                sys.stderr.write("can't have minimu valid count if array has multiplication factor\n")
+                sys.exit(1)
+
+        ops[op.opname] = op
+        if category == 'function':
+            funcs += [op.opname]
         else:
-            opers += [opname]
+            opers += [op.opname]
+
     in_file.close()
 
-    funcs = sorted(funcs, key=lambda name: (ops[name]['NAME'], ops[name]['OPNAME']))
-    opers = sorted(opers, key=lambda name: ops[name]['NAME'])
+    funcs = sorted(funcs, key=lambda name: (ops[name].name, ops[name].opname))
+    opers = sorted(opers, key=lambda name: ops[name].name)
     order = funcs + opers
 
 def get_token():
@@ -482,7 +489,7 @@ def parse_type():
     """
     if toktype == 'id':
         for type_ in types.values():
-            if type_.get("NAME") == token:
+            if type_.name == token:
                 get_token()
                 return type_
     return None
@@ -544,7 +551,7 @@ def parse_arg():
     if line[0] in "[,)":
         get_token()
         if match('['):
-            if type_['NAME'] not in ('number', 'string'):
+            if type_.name not in ('number', 'string'):
                 sys.stderr.write('only double and string arrays supported\n')
                 sys.exit(1)
             idx = force('id')
@@ -587,27 +594,27 @@ def generate_evaluate_h():
 
     for opname in order:
         op = ops[opname]
-        if op['UNIMPLEMENTED']:
+        if op.unimplemented:
             continue
 
         args = []
-        for arg in op['ARGS']:
+        for arg in op.args:
             if arg.idx is None:
                 args += [c_type(arg.type_) + arg.name]
             else:
                 args += [c_type(arg.type_) + arg.name + '[]']
                 args += ['size_t %s' % arg.idx]
-        for aux in op['AUX']:
+        for aux in op.aux:
             args += [c_type(aux['TYPE']) + aux['NAME']]
         if not args:
             args += ['void']
 
-        if 'BLOCK' in op:
-            statements = op['BLOCK'] + '\n'
+        if op.block:
+            statements = op.block + '\n'
         else:
-            statements = "  return %s;\n" % op['EXPRESSION']
+            statements = "  return %s;\n" % op.expression
 
-        out_file.write("static inline %s\n" % c_type (op['RETURNS']))
+        out_file.write("static inline %s\n" % c_type (op.returns))
         out_file.write("eval_%s (%s)\n" % (opname, ', '.join(args)))
         out_file.write("{\n")
         out_file.write(statements)
@@ -616,52 +623,51 @@ def generate_evaluate_h():
 def generate_evaluate_inc():
     for opname in order:
         op = ops[opname]
-        if op['UNIMPLEMENTED']:
+        if op.unimplemented:
             out_file.write("case %s:\n" % opname)
             out_file.write("  NOT_REACHED ();\n\n")
             continue
 
         decls = []
         args = []
-        for arg in op['ARGS']:
+        for arg in op.args:
             type_ = arg.type_
             ctype = c_type(type_)
             args += ['arg_%s' % arg.name]
             if arg.idx is None:
                 decl = '%sarg_%s' % (ctype, arg.name)
-                if type_['ROLE'] == 'any':
-                    decls = ['%s = *--%s' % (decl, type_['STACK'])] + decls
-                elif type_['ROLE'] == 'leaf':
-                    decls += ['%s = op++->%s' % (decl, type_['ATOM'])]
+                if type_.role == 'any':
+                    decls = ['%s = *--%s' % (decl, type_.stack)] + decls
+                elif type_.role == 'leaf':
+                    decls += ['%s = op++->%s' % (decl, type_.atom)]
                 else:
                     assert False
             else:
                 idx = arg.idx
-                stack = type_['STACK']
-                decls = ['%s*arg_%s = %s -= arg_%s' % (ctype, arg.name, stack, idx)] + decls
+                decls = ['%s*arg_%s = %s -= arg_%s' % (ctype, arg.name, type_.stack, idx)] + decls
                 decls = ['size_t arg_%s = op++->integer' % idx] + decls
 
                 idx = 'arg_%s' % idx
                 if arg.times != 1:
                     idx += ' / %s' % arg.times
                 args += [idx]
-        for aux in op['AUX']:
+        for aux in op.aux:
             type_ = aux['TYPE']
             name = aux['NAME']
-            if type_['ROLE'] == 'leaf':
+            if type_.role == 'leaf':
                 ctype = c_type(type_)
-                decls += ['%saux_%s = op++->%s' % (ctype, name, type_['ATOM'])]
+                decls += ['%saux_%s = op++->%s' % (ctype, name, type_.atom)]
                 args += ['aux_%s' % name]
-            elif type_['ROLE'] == 'fixed':
-                args += [type_['FIXED_VALUE']]
+            elif type_.role == 'fixed':
+                args += [type_.fixed_value]
 
         sysmis_cond = make_sysmis_decl(op, 'op++->integer')
         if sysmis_cond is not None:
             decls += [sysmis_cond]
 
-        result = 'eval_%s (%s)' % (op['OPNAME'], ', '.join(args))
+        result = 'eval_%s (%s)' % (op.opname, ', '.join(args))
 
-        stack = op['RETURNS']['STACK']
+        stack = op.returns.stack
 
         out_file.write("case %s:\n" % opname)
         if decls:
@@ -669,7 +675,7 @@ def generate_evaluate_inc():
             for decl in decls:
                 out_file.write("    %s;\n" % decl)
             if sysmis_cond is not None:
-                miss_ret = op['RETURNS']['MISSING_VALUE']
+                miss_ret = op.returns.missing_value
                 out_file.write("    *%s++ = force_sysmis ? %s : %s;\n" % (stack, miss_ret, result))
             else:
                 out_file.write("    *%s++ = %s;\n" % (stack, result))
@@ -686,8 +692,8 @@ def generate_operations_h():
     out_file.write("  {\n")
     atoms = []
     for type_ in types.values():
-        if type_['ROLE'] != 'fixed':
-            atoms += ["OP_%s" % type_['NAME']]
+        if type_.role != 'fixed':
+            atoms += ["OP_%s" % type_.name]
 
     print_operations('atom', 1, atoms)
     print_operations('function', "OP_atom_last + 1", funcs)
@@ -730,19 +736,19 @@ def generate_optimize_inc():
     for opname in order:
         op = ops[opname]
 
-        if not op['OPTIMIZABLE'] or op['UNIMPLEMENTED']:
+        if not op.optimizable or op.unimplemented:
             out_file.write("case %s:\n" % opname)
             out_file.write("  NOT_REACHED ();\n\n")
             continue
 
         decls = []
         arg_idx = 0
-        for arg in op['ARGS']:
+        for arg in op.args:
             name = arg.name
             type_ = arg.type_
             ctype = c_type(type_)
             if arg.idx is None:
-                func = "get_%s_arg" % type_['ATOM']
+                func = "get_%s_arg" % type_.atom
                 decls += ["%sarg_%s = %s (node, %s)" % (ctype, name, func, arg_idx)]
             else:
                 decl = "size_t arg_%s = node->n_args" % arg.idx
@@ -750,7 +756,7 @@ def generate_optimize_inc():
                     decl += " - %s" % arg_idx
                 decls += [decl]
 
-                decls += ["%s*arg_%s = get_%s_args  (node, %s, arg_%s, e)" % (ctype, name, type_['ATOM'], arg_idx, arg.idx)]
+                decls += ["%s*arg_%s = get_%s_args  (node, %s, arg_%s, e)" % (ctype, name, type_.atom, arg_idx, arg.idx)]
             arg_idx += 1
 
         sysmis_cond = make_sysmis_decl (op, "node->min_valid")
@@ -758,7 +764,7 @@ def generate_optimize_inc():
             decls += [sysmis_cond]
 
         args = []
-        for arg in op['ARGS']:
+        for arg in op.args:
             args += ["arg_%s" % arg.name]
             if arg.idx is not None:
                 idx = 'arg_%s' % arg.idx
@@ -766,25 +772,25 @@ def generate_optimize_inc():
                     idx += " / %s" % arg.times
                 args += [idx]
 
-        for aux in op['AUX']:
+        for aux in op.aux:
             type_ = aux['TYPE']
-            if type_['ROLE'] == 'leaf':
-                func = "get_%s_arg" % type_['ATOM']
+            if type_.role == 'leaf':
+                func = "get_%s_arg" % type_.atom
                 args += "%s (node, %s)" % (func, arg_idx)
                 arg_idx += 1
-            elif type_['ROLE'] == 'fixed':
-                args += [type_['FIXED_VALUE']]
+            elif type_.role == 'fixed':
+                args += [type_.fixed_value]
             else:
                 assert False
 
-        result = "eval_%s (%s)" % (op['OPNAME'], ', '.join(args))
+        result = "eval_%s (%s)" % (op.opname, ', '.join(args))
         if decls and sysmis_cond is not None:
-            miss_ret = op['RETURNS']['MISSING_VALUE']
-            decls += ['%sresult = force_sysmis ? %s : %s' % (c_type(op['RETURNS']), miss_ret, result)]
+            miss_ret = op.returns.missing_value
+            decls += ['%sresult = force_sysmis ? %s : %s' % (c_type(op.returns), miss_ret, result)]
             result = 'result'
 
         out_file.write("case %s:\n" % opname)
-        alloc_func = "expr_allocate_%s" % op['RETURNS']['NAME']
+        alloc_func = "expr_allocate_%s" % op.returns.name
         if decls:
             out_file.write("  {\n")
             for decl in decls:
@@ -800,37 +806,36 @@ def generate_parse_inc():
     out_file.write("{%s},\n" % ', '.join(members))
 
     for type_ in types.values():
-        if type_['ROLE'] != 'fixed':
-            human_name = type_.get('HUMAN_NAME', type_['NAME'])
-            members = ('"%s"' % type_['NAME'], '"%s"' % human_name, '0', "OP_%s" % type_['NAME'], '0', "{}", '0', '0')
+        if type_.role != 'fixed':
+            members = ('"%s"' % type_.name, '"%s"' % type_.human_name, '0', "OP_%s" % type_.name, '0', "{}", '0', '0')
             out_file.write("{%s},\n" % ', '.join(members))
 
     for opname in order:
         op = ops[opname]
 
         members = []
-        members += ['"%s"' % op['NAME']]
+        members += ['"%s"' % op.name]
 
-        if op['CATEGORY'] == 'function':
+        if op.category == 'function':
             args = []
             opt_args = []
-            for arg in op['ARGS']:
+            for arg in op.args:
                 if arg.idx is None:
-                    args += [arg.type_['HUMAN_NAME']]
+                    args += [arg.type_.human_name]
 
             array = array_arg(op)
             if array is not None:
-                if op['MIN_VALID'] == 0:
+                if op.min_valid == 0:
                     array_args = []
                     for i in range(array.times):
-                        array_args += [array.type_['HUMAN_NAME']]
+                        array_args += [array.type_.human_name]
                     args += array_args
                     opt_args = array_args
                 else:
-                    for i in range(op['MIN_VALID']):
-                        args += [array.type_['HUMAN_NAME']]
-                    opt_args += [array.type_['HUMAN_NAME']]
-            human = "%s(%s" % (op['NAME'], ', '.join(args))
+                    for i in range(op.min_valid):
+                        args += [array.type_.human_name]
+                    opt_args += [array.type_.human_name]
+            human = "%s(%s" % (op.name, ', '.join(args))
             if opt_args:
                 human += '[, %s]...' % ', '.join(opt_args)
             human += ')'
@@ -839,32 +844,32 @@ def generate_parse_inc():
             members += ['NULL']
 
         flags = []
-        if op['ABSORB_MISS']:
+        if op.absorb_miss:
             flags += ['OPF_ABSORB_MISS']
         if array_arg(op):
             flags += ['OPF_ARRAY_OPERAND']
-        if op['MIN_VALID'] > 0:
+        if op.min_valid > 0:
             flags += ['OPF_MIN_VALID']
-        if not op['OPTIMIZABLE']:
+        if not op.optimizable:
             flags += ['OPF_NONOPTIMIZABLE']
-        if op['EXTENSION']:
+        if op.extension:
             flags += ['OPF_EXTENSION']
-        if op['UNIMPLEMENTED']:
+        if op.unimplemented:
             flags += ['OPF_UNIMPLEMENTED']
-        if op['PERM_ONLY']:
+        if op.perm_only:
             flags += ['OPF_PERM_ONLY']
-        if op['NO_ABBREV']:
+        if op.no_abbrev:
             flags += ['OPF_NO_ABBREV']
         members += [' | '.join(flags) if flags else '0']
 
-        members += ['OP_%s' % op['RETURNS']['NAME']]
+        members += ['OP_%s' % op.returns.name]
 
-        members += ['%s' % len(op['ARGS'])]
+        members += ['%s' % len(op.args)]
 
-        arg_types = ["OP_%s" % arg.type_['NAME'] for arg in op['ARGS']]
+        arg_types = ["OP_%s" % arg.type_.name for arg in op.args]
         members += ['{%s}' % ', '.join(arg_types)]
 
-        members += ['%s' % op['MIN_VALID']]
+        members += ['%s' % op.min_valid]
 
         members += ['%s' % (array_arg(op).times if array_arg(op) else 0)]
 
@@ -883,23 +888,23 @@ def make_sysmis_decl(op, min_valid_src):
 
     """
     sysmis_cond = []
-    if not op['ABSORB_MISS']:
-        for arg in op['ARGS']:
+    if not op.absorb_miss:
+        for arg in op.args:
             arg_name = 'arg_%s' % arg.name
             if arg.idx is None:
-                if arg.type_['NAME'] in ['number', 'boolean']:
+                if arg.type_.name in ['number', 'boolean']:
                     sysmis_cond += ["!is_valid (%s)" % arg_name]
-            elif arg.type_['NAME'] == 'number':
+            elif arg.type_.name == 'number':
                 a = arg_name
                 n = 'arg_%s' % arg.idx
                 sysmis_cond += ['count_valid (%s, %s) < %s' % (a, n, n)]
-    elif op['MIN_VALID'] > 0:
-        args = op['ARGS']
+    elif op.min_valid > 0:
+        args = op.args
         arg = args[-1]
         a = 'arg_%s' % arg.name
         n = 'arg_%s' % arg.idx
         sysmis_cond += ["count_valid (%s, %s) < %s" % (a, n, min_valid_src)]
-    for arg in op['ARGS']:
+    for arg in op.args:
         if arg.condition is not None:
             sysmis_cond += ['!(%s)' % arg.condition]
     if sysmis_cond:
@@ -909,7 +914,7 @@ def make_sysmis_decl(op, min_valid_src):
 def array_arg(op):
     """If 'op' has an array argument, returns it.  Otherwise, returns
     None."""
-    args = op['ARGS']
+    args = op.args
     if not args:
         return None
     last_arg = args[-1]