summaryrefslogtreecommitdiffstats
path: root/xlators/cluster/nsr-server/src/codegen.py
blob: 709f5662f61ee72f83c0fafa198529b73ea7dacf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/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")