#!/usr/bin/env python3 import re import sys from argparse import ArgumentParser field_file = "nodes.ads" kind_file = "iirs.ads" node_file = "iirs.ads" template_file = "iirs.adb.in" meta_base_file = "nodes_meta" prefix_name = "Iir_Kind_" prefix_range_name = "Iir_Kinds_" type_name = "Iir_Kind" node_type = "Iir" conversions = ["uc", "pos", "grp"] class FuncDesc: def __init__(self, name, fields, conv, acc, pname, ptype, rname, rtype): self.name = name self.fields = fields # List of physical fields used self.conv = conv self.acc = acc # access: Chain, Chain_Next, Ref, Of_Ref, Maybe_Ref, # Forward_Ref, Maybe_Forward_Ref self.pname = pname # Parameter mame self.ptype = ptype # Parameter type self.rname = rname # value name (for procedure) self.rtype = rtype # value type class NodeDesc: def __init__(self, name, format, fields, attrs): self.name = name self.format = format self.fields = fields # {field: FuncDesc} dict, defined for all fields self.attrs = attrs # A {attr: FuncDesc} dict self.order = [] # List of fields name, in order of appearance. class line: def __init__(self, string, no): self.l = string self.n = no class EndOfFile(Exception): def __init__(self, filename): self.filename = filename def __str__(self): return "end of file " + self.filename class linereader: def __init__(self, filename): self.filename = filename self.f = open(filename) self.lineno = 0 self.l = "" def get(self): self.l = self.f.readline() if not self.l: raise EndOfFile(self.filename) self.lineno = self.lineno + 1 return self.l class ParseError(Exception): def __init__(self, lr, msg): self.lr = lr self.msg = msg def __str__(self): return f"Parse error at {self.lr.filename}:{self.lr.lineno}: {self.msg}" # Return fields description. # This is a dictionary. The keys represent the possible format of a node. # The values are dictionaries representing fields. Keys are fields name, and # values are fields type. def read_fields(file): fields = {} formats = [] lr = linereader(file) # Search for 'type Format_Type is' while lr.get() != " type Format_Type is\n": pass # Skip '(' if lr.get() != " (\n": raise Exception("no open parenthesis after Format_Type") # Read formats l = lr.get() pat_field_name = re.compile(" Format_(\w+),?\n") while l != " );\n": m = pat_field_name.match(l) if m is None: print(l) raise Exception("bad literal within Format_Type") name = m.group(1) formats.append(name) fields[name] = {} l = lr.get() # Read fields l = lr.get() pat_fields = re.compile(" -- Fields of Format_(\w+):\n") pat_field_desc = re.compile(" -- (\w+) : (\w+).*\n") common_desc = {} # Read until common fields. while l != " -- Common fields are:\n": l = lr.get() format_name = "Common" nbr_formats = 0 while True: # 1) Read field description l = lr.get() desc = common_desc.copy() while True: m = pat_field_desc.match(l) if m is None: break desc[m.group(1)] = m.group(2) l = lr.get() # print 'For: ' + format_name + ': ' + m.group(1) # 2) Disp if format_name == "Common": common_desc = desc else: fields[format_name] = desc # 3) Read next format if l == "\n": if nbr_formats == len(fields): break else: l = lr.get() # One for a format m = pat_fields.match(l) if m is not None: format_name = m.group(1) if format_name not in fields: raise ParseError(lr, "Format " + format_name + " is unknown") nbr_formats = nbr_formats + 1 else: raise ParseError(lr, "unhandled format line") return (formats, fields) # Read kinds and kinds ranges. def read_kinds(filename): lr = linereader(filename) kinds = [] # Search for 'type Iir_Kind is' while lr.get() != " type " + type_name + " is\n": pass # Skip '(' if lr.get() != " (\n": raise ParseError(lr, 'no open parenthesis after "type ' + type_name + '"') # Read literals pat_node = re.compile(" " + prefix_name + "(\w+),?( +-- .*)?\n") pat_comment = re.compile("( +-- .*)?\n") while True: l = lr.get() if l == " );\n": break m = pat_node.match(l) if m: kinds.append(m.group(1)) continue m = pat_comment.match(l) if not m: raise ParseError(lr, "Unknown line within kind declaration") # Check subtypes pat_subtype = re.compile(" subtype " + r"(\w+) is " + type_name + " range\n") pat_first = re.compile(" " + prefix_name + r"(\w+) ..\n") pat_last = re.compile(" " + prefix_name + r"(\w+);\n") pat_middle = re.compile(" --" + prefix_name + r"(\w+)\n") kinds_ranges = {} while True: l = lr.get() # Start of methods is also end of subtypes. if l == " -- General methods.\n": break # Found a subtype. m = pat_subtype.match(l) if m: # Check first bound name = m.group(1) if not name.startswith(prefix_range_name): raise ParseError(lr, "incorrect prefix for subtype") name = name[len(prefix_range_name):] l = lr.get() mf = pat_first.match(l) if not mf: raise ParseError(lr, "badly formatted first bound of subtype") first = kinds.index(mf.group(1)) idx = first has_middle = None # Read until last bound while True: l = lr.get() ml = pat_middle.match(l) if ml: # Check element in the middle n = ml.group(1) if n not in kinds: raise ParseError(lr, "unknown kind " + n + " in subtype") if kinds.index(n) != idx + 1: raise ParseError( lr, "missing " + kinds[idx + 1] + " in subtype" ) has_middle = True idx = idx + 1 else: # Check last bound ml = pat_last.match(l) if ml: last = kinds.index(ml.group(1)) if last != idx + 1 and has_middle: raise ParseError( lr, "missing " + kinds[idx] + " in subtype" ) break raise ParseError(lr, "unhandled line in subtype") kinds_ranges[name] = kinds[first: last + 1] return (kinds, kinds_ranges) # Read functions def read_methods(filename): lr = linereader(filename) # Note: this is a list so that the output is deterministic. # Duplicates are not detected, but they will be by the Ada compiler. # TODO: use an ordered dict ? funcs = [] pat_field = re.compile(r" -- Field: ([\w,]+)( \w+)?( \(\w+\))?\n") pat_conv = re.compile(r"^ \((\w+)\)$") pat_func = re.compile(r" function Get_(\w+) \((\w+) : (\w+)\) return (\w+);\n") pat_proc = re.compile(r" procedure Set_(\w+) \((\w+) : (\w+); (\w+) : (\w+)\);\n") pat_end = re.compile("end [A-Za-z.]+;\n") while True: l = lr.get() # Start of methods if l == " -- General methods.\n": break while True: l = lr.get() if pat_end.match(l): break m = pat_field.match(l) if m: fields = m.group(1).split(",") # Extract access modifier acc = m.group(2) if acc: acc = acc.strip() # Extract conversion conv = m.group(3) if conv: mc = pat_conv.match(conv) if not mc: raise ParseError(lr, "conversion ill formed") conv = mc.group(1) if conv not in conversions: raise ParseError(lr, "unknown conversion " + conv) else: conv = None if len(fields) > 1 and conv != "grp": raise ParseError(lr, "bad conversion for multiple fields") # Read function l = lr.get() mf = pat_func.match(l) if not mf: raise ParseError(lr, "function declaration expected after Field") # Read procedure l = lr.get() mp = pat_proc.match(l) if not mp: raise ParseError(lr, "procedure declaration expected after function") # Consistency check between function and procedure if mf.group(1) != mp.group(1): raise ParseError(lr, "function and procedure name mismatch") if mf.group(2) != mp.group(2): raise ParseError(lr, "parameter name mismatch with function") if mf.group(3) != mp.group(3): raise ParseError(lr, "parameter type mismatch with function") if mf.group(4) != mp.group(5): raise ParseError(lr, "result type mismatch with function") funcs.append( FuncDesc( mf.group(1), fields, conv, acc, mp.group(2), mp.group(3), mp.group(4), mp.group(5), ) ) return funcs # Read description for one node # LR is the line reader. NAMES is the list of (node name, format) # (one description may describe several nodes). # A comment start at column 2 or 4 or later. def read_nodes_fields(lr, names, fields, nodes, funcs_dict): pat_only = re.compile(" -- Only for " + prefix_name + "(\w+):\n") pat_only_bad = re.compile(" -- *Only for.*\n") pat_field = re.compile(" -- Get/Set_(\w+) \((Alias )?([\w,]+)\)\n") pat_comment = re.compile(" --(| [^ ].*| .*)\n") # Create nodes cur_nodes = [] for (nm, fmt) in names: if fmt not in fields: raise ParseError(lr, f'unknown format "{fmt}"') n = NodeDesc(nm, fmt, {x: None for x in fields[fmt]}, {}) nodes[nm] = n cur_nodes.append(n) # Skip comments l = lr.l while pat_comment.match(l): l = lr.get() # Look for fields while l != "\n": # Skip comments while pat_comment.match(l): l = lr.get() # Handle 'Only ...' m = pat_only.match(l) if m: only_nodes = [] while True: name = m.group(1) n = nodes.get(name, None) if n is None: raise ParseError(lr, "node is unknown") if n not in cur_nodes: raise ParseError(lr, "node not currently described") only_nodes.append(n) l = lr.get() m = pat_only.match(l) if not m: break else: # By default a field applies to all nodes. only_nodes = cur_nodes # Skip comments while pat_comment.match(l): l = lr.get() # Handle field: '-- Get/Set_FUNC (Alias? FIELD)' m = pat_field.match(l) if not m: if pat_only_bad.match(l): raise ParseError(lr, "misleading 'Only for' comment") else: raise ParseError(lr, "bad line in node description") func = m.group(1) alias = m.group(2) fields = m.group(3).split(",") # Check the function exists and if the field is correct. if func not in funcs_dict: raise ParseError(lr, "unknown function") func = funcs_dict[func] if func.fields != fields: raise ParseError(lr, "fields mismatch") for c in only_nodes: for f in fields: if f not in c.fields: raise ParseError(lr, "field " + f + " does not exist in node") if not alias: for f in fields: if c.fields[f]: raise ParseError(lr, "field " + f + " already used") c.fields[f] = func c.order.append(f) c.attrs[func.name] = func l = lr.get() def read_nodes(filename, kinds, kinds_ranges, fields, funcs): """Read description for all nodes.""" lr = linereader(filename) funcs_dict = {x.name: x for x in funcs} nodes = {} # Skip until start while lr.get() != " -- Start of " + type_name + ".\n": pass pat_decl = re.compile(" -- " + prefix_name + "(\w+) \((\w+)\)\n") pat_decls = re.compile(" -- " + prefix_range_name + "(\w+) \((\w+)\)\n") pat_comment_line = re.compile(" --+\n") pat_comment_box = re.compile(" --( .*)?\n") while True: l = lr.get() if l == " -- End of " + type_name + ".\n": break if l == "\n": continue m = pat_decl.match(l) if m: # List of nodes being described by the current description. names = [] # Declaration of the first node while True: name = m.group(1) if name not in kinds: raise ParseError(lr, "unknown node") fmt = m.group(2) names.append((name, fmt)) if name in nodes: raise ParseError(lr, "node {} already described".format(name)) # There might be several nodes described at once. l = lr.get() m = pat_decl.match(l) if not m: break read_nodes_fields(lr, names, fields, nodes, funcs_dict) continue m = pat_decls.match(l) if m: # List of nodes being described by the current description. name = m.group(1) fmt = m.group(2) names = [(k, fmt) for k in kinds_ranges[name]] lr.get() read_nodes_fields(lr, names, fields, nodes, funcs_dict) continue if pat_comment_line.match(l) or pat_comment_box.match(l): continue raise ParseError(lr, "bad line in node description") for k in kinds: if k not in nodes: raise ParseError(lr, 'no description for "{}"'.format(k)) return nodes def gen_choices(choices): """Generate a choice 'when A | B ... Z =>' using elements of CHOICES.""" is_first = True for c in choices: ch = prefix_name + c if is_first: is_first = False print(" when " + ch, end='') else: print() print(" | " + ch, end='') print(" =>") def gen_get_format(formats, nodes, kinds=None): """Generate the Get_Format function.""" print(" function Get_Format (Kind : " + type_name + ") " + "return Format_Type is") print(" begin") print(" case Kind is") for f in formats: choices = [k for k in kinds if nodes[k].format == f] gen_choices(choices) print(" return Format_" + f + ";") print(" end case;") print(" end Get_Format;") def gen_subprg_header(decl): if len(decl) < 76: print(decl + " is") else: print(decl) print(" is") print(" begin") def gen_assert(func): print(" pragma Assert (" + func.pname + " /= Null_" + node_type + ");") cond = "(Has_" + func.name + " (Get_Kind (" + func.pname + "))," msg = '"no field ' + func.name + '");' if len(cond) < 60: print(" pragma Assert " + cond) print(" " + msg) else: print(" pragma Assert") print(" " + cond) print(" " + msg) def get_field_type(fields, f): for fld in list(fields.values()): if f in fld: return fld[f] return None def gen_get_set(func, nodes, fields): """Generate Get_XXX/Set_XXX subprograms for FUNC.""" rtype = func.rtype # If the function needs several fields, it must be user defined if func.conv == "grp": print(" type %s_Conv is record" % rtype) for f in func.fields: print(" %s: %s;" % (f, get_field_type(fields, f))) print(" end record;") print(" pragma Pack (%s_Conv);" % rtype) print(" pragma Assert (%s_Conv'Size = %s'Size);" % (rtype, rtype)) print() else: f = func.fields[0] g = "Get_" + f + " (" + func.pname + ")" s = func.rname if func.conv: if func.conv == "uc": field_type = get_field_type(fields, f) g = field_type + "_To_" + rtype + " (" + g + ")" s = rtype + "_To_" + field_type + " (" + s + ")" elif func.conv == "pos": g = rtype + "'Val (" + g + ")" s = rtype + "'Pos (" + s + ")" subprg = ( " function Get_" + func.name + " (" + func.pname + " : " + func.ptype + ") return " + rtype ) if func.conv == "grp": print(subprg) print(" is") print(" function To_%s is new Ada.Unchecked_Conversion" % func.rtype) print(" (%s_Conv, %s);" % (rtype, rtype)) print(" Conv : %s_Conv;" % rtype) print(" begin") else: gen_subprg_header(subprg) gen_assert(func) if func.conv == "grp": for f in func.fields: print(" Conv.%s := Get_%s (%s);" % (f, f, func.pname)) g = "To_%s (Conv)" % rtype print(" return " + g + ";") print(" end Get_" + func.name + ";") print() subprg = ( " procedure Set_" + func.name + " (" + func.pname + " : " + func.ptype + "; " + func.rname + " : " + func.rtype + ")" ) if func.conv == "grp": print(subprg) print(" is") print(" function To_%s_Conv is new Ada.Unchecked_Conversion" % func.rtype) print(" (%s, %s_Conv);" % (rtype, rtype)) print(" Conv : %s_Conv;" % rtype) print(" begin") else: gen_subprg_header(subprg) gen_assert(func) if func.conv == "grp": print(" Conv := To_%s_Conv (%s);" % (rtype, func.rname)) for f in func.fields: print(" Set_%s (%s, Conv.%s);" % (f, func.pname, f)) else: print(" Set_" + f + " (" + func.pname + ", " + s + ");") print(" end Set_" + func.name + ";") print() def funcs_of_node(n): return sorted([fv.name for fv in list(n.fields.values()) if fv]) def gen_has_func_spec(name, suff): spec = " function Has_" + name + " (K : " + type_name + ")" ret = " return Boolean" + suff if len(spec) < 60: print(spec + ret) else: print(spec) print(" " + ret) def do_disp_formats(): for fmt in fields: print("Fields of Format_" + fmt) fld = fields[fmt] for k in fld: print(" " + k + " (" + fld[k] + ")") def do_disp_kinds(): print("Kinds are:") for k in kinds: print(" " + prefix_name + k) def do_disp_funcs(): print("Functions are:") for f in funcs: s = "{0} ({1}: {2}".format(f.name, f.fields, f.rtype) if f.acc: s += " acc:" + f.acc if f.conv: s += " conv:" + f.conv s += ")" print(s) def do_disp_types(): print("Types are:") s = set([]) for f in funcs: s |= {f.rtype} for t in sorted(s): print(" " + t) def do_disp_nodes(): for k in kinds: v = nodes[k] print(prefix_name + k + " (" + v.format + ")") flds = [fk for fk, fv in list(v.fields.items()) if fv] for fk in sorted(flds): print(" " + fk + ": " + v.fields[fk].name) def do_get_format(): gen_get_format(formats, nodes) def do_body(): lr = linereader(template_file) while True: l = lr.get().rstrip() print(l) if l == " -- Subprograms": gen_get_format(formats, nodes, kinds) print() for f in funcs: gen_get_set(f, nodes, fields) if l[0:3] == "end": break def get_types(): s = set([]) for f in funcs: s |= {f.rtype} return [t for t in sorted(s)] def get_attributes(): s = set([]) for f in funcs: if f.acc: s |= {f.acc} res = [t for t in sorted(s)] res.insert(0, "None") return res def gen_enum(prefix, vals): last = None for v in vals: if last: print(last + ",") last = prefix + v print(last) def do_meta_specs(): lr = linereader(meta_base_file + ".ads.in") types = get_types() while True: l = lr.get().rstrip() if l == " -- TYPES": gen_enum(" Type_", types) elif l == " -- FIELDS": gen_enum(" Field_", [f.name for f in funcs]) elif l == " -- ATTRS": gen_enum(" Attr_", get_attributes()) elif l == " -- FUNCS": for t in types: print(" function Get_" + t) print(" (N : " + node_type + "; F : Fields_Enum) return " + t + ";") print(" procedure Set_" + t) print(" (N : " + node_type + "; F : Fields_Enum; V: " + t + ");") print() for f in funcs: gen_has_func_spec(f.name, ";") elif l[0:3] == "end": print(l) break else: print(l) def do_meta_body(): lr = linereader(meta_base_file + ".adb.in") while True: l = lr.get().rstrip() if l == " -- FIELDS_TYPE": last = None for f in funcs: if last: print(last + ",") last = " Field_" + f.name + " => Type_" + f.rtype print(last) elif l == " -- FIELD_IMAGE": for f in funcs: print(" when Field_" + f.name + " =>") print(' return "' + f.name.lower() + '";') elif l == " -- IIR_IMAGE": for k in kinds: print(" when " + prefix_name + k + " =>") print(' return "' + k.lower() + '";') elif l == " -- FIELD_ATTRIBUTE": for f in funcs: print(" when Field_" + f.name + " =>") if f.acc: attr = f.acc else: attr = "None" print(" return Attr_" + attr + ";") elif l == " -- FIELDS_ARRAY": last = None nodes_types = [node_type, node_type + "_List", node_type + "_Flist"] for k in kinds: v = nodes[k] if last: print(last + ",") last = None print(" -- " + prefix_name + k) # Get list of physical fields for V, in some order. if flag_keep_order: flds = v.order else: # First non Iir and no Iir_List. flds = sorted( [ fk for fk, fv in list(v.fields.items()) if fv and fv.rtype not in nodes_types ] ) # Then Iir and Iir_List in order of appearance flds += (fv for fv in v.order if v.fields[fv].rtype in nodes_types) # Print the corresponding node field, but remove duplicate due # to 'grp'. fldsn = [] for fk in flds: if last: print(last + ",") # Remove duplicate fn = v.fields[fk].name if fn not in fldsn: last = " Field_" + fn fldsn.append(fn) else: last = None if last: print(last) elif l == " -- FIELDS_ARRAY_POS": pos = -1 last = None for k in kinds: v = nodes[k] # Create a set to remove duplicate for 'grp'. flds = set([fv.name for fk, fv in list(v.fields.items()) if fv]) pos += len(flds) if last: print(last + ",") last = " " + prefix_name + k + " => {}".format(pos) print(last) elif l == " -- FUNCS_BODY": # Build list of types s = set([]) for f in funcs: s |= {f.rtype} types = [t for t in sorted(s)] for t in types: print(" function Get_" + t) print(" (N : " + node_type + "; F : Fields_Enum) return " + t + " is") print(" begin") print(" pragma Assert (Fields_Type (F) = Type_" + t + ");") print(" case F is") for f in funcs: if f.rtype == t: print(" when Field_" + f.name + " =>") print(" return Get_" + f.name + " (N);") print(" when others =>") print(" raise Internal_Error;") print(" end case;") print(" end Get_" + t + ";") print() print(" procedure Set_" + t) print(" (N : " + node_type + "; F : Fields_Enum; V: " + t + ") is") print(" begin") print(" pragma Assert (Fields_Type (F) = Type_" + t + ");") print(" case F is") for f in funcs: if f.rtype == t: print(" when Field_" + f.name + " =>") print(" Set_" + f.name + " (N, V);") print(" when others =>") print(" raise Internal_Error;") print(" end case;") print(" end Set_" + t + ";") print() for f in funcs: gen_has_func_spec(f.name, " is") choices = [k for k in kinds if f.name in nodes[k].attrs] if len(choices) == 0: print(" pragma Unreferenced (K);") print(" begin") if len(choices) == 0: print(" return False;") elif len(choices) == 1: print(" return K = " + prefix_name + choices[0] + ";") else: print(" case K is") gen_choices(choices) print(" return True;") print(" when others =>") print(" return False;") print(" end case;") print(" end Has_" + f.name + ";") print() elif l[0:3] == "end": print(l) break else: print(l) actions = { "disp-nodes": do_disp_nodes, "disp-kinds": do_disp_kinds, "disp-formats": do_disp_formats, "disp-funcs": do_disp_funcs, "disp-types": do_disp_types, "get_format": do_get_format, "body": do_body, "meta_specs": do_meta_specs, "meta_body": do_meta_body, } def _generateCLIParser() -> ArgumentParser: """""" parser = ArgumentParser(description="Meta-grammar processor") parser.add_argument("action", choices=list(actions.keys()), default="disp-nodes") parser.add_argument( "--field-file", dest="field_file", default="nodes.ads", help="specify file which defines fields", ) parser.add_argument( "--kind-file", dest="kind_file", default="iirs.ads", help="specify file which defines nodes kind", ) parser.add_argument( "--node-file", dest="node_file", default="iirs.ads", help="specify file which defines nodes and methods", ) parser.add_argument( "--template-file", dest="template_file", default="iirs.adb.in", help="specify template body file", ) parser.add_argument( "--meta-basename", dest="meta_basename", default="nodes_meta", help="specify base name of meta files", ) parser.add_argument( "--kind-type", dest="kind_type", default="Iir_Kind", help="name of kind type" ) parser.add_argument( "--kind-prefix", dest="kind_prefix", default="Iir_Kind_", help="prefix for kind literals", ) parser.add_argument( "--kind-range-prefix", dest="kind_range_prefix", default="Iir_Kinds_", help="prefix for kind subtype (range)", ) parser.add_argument( "--node-type", dest="node_type", default="Iir", help="name of the node type" ) parser.add_argument( "--keep-order", dest="flag_keep_order", action="store_true", help="keep field order of nodes", ) parser.set_defaults(flag_keep_order=False) return parser def main(): parser = _generateCLIParser() args = parser.parse_args() # At some point, it would be simpler to create a class... global formats, fields, nodes, kinds, kinds_ranges, funcs global type_name, prefix_name, template_file, node_type, meta_base_file global prefix_range_name, flag_keep_order, kind_file type_name = args.kind_type prefix_name = args.kind_prefix prefix_range_name = args.kind_range_prefix template_file = args.template_file node_type = args.node_type meta_base_file = args.meta_basename flag_keep_order = args.flag_keep_order field_file = args.field_file kind_file = args.kind_file node_file = args.node_file try: (formats, fields) = read_fields(field_file) (kinds, kinds_ranges) = read_kinds(kind_file) funcs = read_methods(node_file) nodes = read_nodes(node_file, kinds, kinds_ranges, fields, funcs) except ParseError as e: print(e, file=sys.stderr) print("in {0}:{1}:{2}".format(e.lr.filename, e.lr.lineno, e.lr.l), file=sys.stderr) sys.exit(1) f = actions.get(args.action, None) if not f: print("Action {0} is unknown".format(args.action), file=sys.stderr) sys.exit(1) f() if __name__ == "__main__": main()