#!/usr/bin/python # This module lets us auto-generate boilerplate versions of fops and cbks, # both for the client side and (eventually) on the server side as well. This # allows us to implement common logic (e.g. leader fan-out and sequencing) # once, without all the problems that come with copying and pasting the same # code into dozens of functions (or failing to). # # I've tried to make this code pretty generic, since it's already likely to # be used multiple ways within NSR. Really, we should use something like this # to generate defaults.[ch] as well, to avoid the same sorts of mismatches # that we've already seen and to which this approach makes NSR immune. That # would require using something other than defaults.h as the input, but that # format could be even simpler so that's a good thing too. import re import sys decl_re = re.compile("([a-z0-9_]+)$") tmpl_re = re.compile("// template-name (.*)") class CodeGenerator: def __init__ (self): self.decls = {} self.skip = 0 self.templates = {} self.make_defaults = self._make_defaults # Redefine this to preprocess the name in a declaration, e.g. # fop_lookup_t => nsrc_lookup def munge_name (self, orig): return orig # By default, this will convert the argument string into a sequence of # (type, name) tuples minus the first self.skip (default zero) arguments. # You can redefine it to skip the conversion, do a different conversion, # or rearrange the arguments however you like. def munge_args (self, orig): args = [] for decl in orig.strip("(); ").split(","): m = decl_re.search(decl) if m: args.append((m.group(1),decl[:m.start(1)].strip())) else: raise RuntimeError("can't split %s into type+name"%decl) return args[self.skip:] def add_decl (self, fname, ftype, fargs): self.decls[self.munge_name(fname)] = (ftype, self.munge_args(fargs)) def parse_decls (self, path, pattern): regex = re.compile(pattern) f = open(path,"r") have_decl = False while True: line = f.readline() if not line: break m = regex.search(line) if m: if have_decl: self.add_decl(f_name,f_type,f_args) f_name = m.group(2) f_type = m.group(1) f_args = line[m.end(0):-1].strip() if f_args.rfind(")") >= 0: self.add_decl(f_name,f_type,f_args) else: have_decl = True elif have_decl: if line.strip() == "": self.add_decl(f_name,f_type,f_args) have_decl = False else: f_args += " " f_args += line[:-1].strip() if have_decl: self.add_decl(f_name,f_type,f_args) # Legacy function (yeah, already) to load a single template. If you're # using multiple templates, you're better off loading them all from one # file using load_templates (note plural) instead. def load_template (self, name, path): self.templates[name] = open(path,"r").readlines() # Load multiple templates. Each is introduced by a special comment of # the form # # // template-name xyz # # One side effect is that the block before the first such comment will be # ignored. This seems like it might be useful some day so I'll leave it # in, but if people trip over it maybe it will change. # # It is recommended to define templates in expected execution order, to # make the result more readable than the inverted order (e.g. callback # then fop) common in the rest of our code. def load_templates (self, path): t_name = None for line in open(path,"r").readlines(): if not line: break m = tmpl_re.match(line) if m: if t_name: self.templates[t_name] = t_contents t_name = m.group(1).strip() t_contents = [] elif t_name: t_contents.append(line) if t_name: self.templates[t_name] = t_contents # Emit the template, with the following expansions: # # $NAME$ => function name (as passed in) # $TYPE$ => function return value # $ARGS_SHORT$ => argument list, including types # $ARGS_LONG$ => argument list, *not* including types # $DEFAULTS$ => default callback args (see below) # # The $DEFAULTS$ substitution is for the case where a fop (which has one # set of arguments) needs to signal an error via STACK_UNWIND (which # requires a different set of arguments). In this case we look up the # argument list for the opposite direction, using self.make_defaults which # the user must explicitly set to the method for the opposite direction. # If an argument is a pointer, we replace it with NULL; otherwise we # replace it with zero. It's a hack, but it's the only thing we do that # doesn't require specific knowledge of our environment and the specific # call we're handling. If this doesn't suffice, we'll have to add # something like $ARG0$ which can be passed in for specific cases. def emit (self, f_name, tmpl): args = self.decls[f_name][1] zipper = lambda x: x[0] a_short = ", ".join(map(zipper,args)) zipper = lambda x: x[1] + " " + x[0] a_long = ", ".join(map(zipper,args)) for line in self.templates[tmpl]: line = line.replace("$NAME$",f_name) line = line.replace("$TYPE$",self.decls[f_name][0]) line = line.replace("$ARGS_SHORT$",a_short) line = line.replace("$ARGS_LONG$",a_long) line = line.replace("$DEFAULTS$",self.make_defaults(f_name)) print(line.rstrip()) def _make_defaults (self, f_name): result = [] for arg in self.decls[f_name][1]: if arg[1][-1] == "*": result.append("NULL") else: result.append("0") return ", ".join(result) if __name__ == "__main__": type_re = "([a-z_0-9]+)" name_re = "\(\*fop_([a-z0-9]+)_t\)" full_re = type_re + " *" + name_re cg = CodeGenerator() cg.skip = 2 cg.parse_decls(sys.argv[1],full_re) """ for k, v in cg.decls.iteritems(): print("=== %s" % k) print(" return type %s" % v[0]) for arg in v[1]: print(" arg %s (type %s)" % arg) """ cg.load_template("fop",sys.argv[2]) cg.emit("lookup","fop") cg.emit("rename","fop") cg.emit("setxattr","fop")