Generating nested code with context managers

Terry Reedy tjreedy at udel.edu
Tue May 4 16:36:32 EDT 2010


In a current thread, people have claimed that generating properly 
indented nested blocks is a pain because of the need to keep track of 
indent levels. Someone countered with the now rather ancient

http://effbot.org/zone/python-code-generator.htm

The usage example

c = CodeGeneratorBackend()
c.begin(tab="    ")
c.write("for i in range(1000):\n")
c.indent()
c.write("print 'code generation is trivial'")
c.dedent()

illustrates three problems with the CodeGeneratorBackend class. 1) it 
requires explicit \n on all lines (which the second omits, though it is 
non-fatal since it is also the last) 2) the user still has to manually 
match indents and dedents, and 3) the user *cannot* indent lines that 
produce indented code.

The relatively new with statement and associated context managers are 
designed, among other things, for this situation, where one needs to 
alter and restore a global context. So here is my updated (3.1) 
proof-of-concept version.

class PyCodeGen:
     def __init__(self, tab="    "):
         self.code = []
         self.tab = tab
         self.level = 0
	# all attributes should be treated as read-only
     def end(self):
         return '\n'.join(self.code)
     def line(self, string): # new line
         self.code.append(self.tab * self.level + string)

class For:
     def __init__(self, target, in_expression):
         target.line('for ' + in_expression + ':')
         self.target = target
     def __enter__(self):
         self.target.level += 1
     def __exit__(self, t, v, tb):
         self.target.level -= 1

c = PyCodeGen()

with For(c, 'i in range(1000)'):
     c.line('print("Code gen is easy")')
c.line('# done')

print(c.end())

# prints

for i in range(1000):
     print("Code gen is easy")
# done

Note that the absence of .indent and .dedent is intentional. In a 
fleshed out system, there would be a context manager for each compound 
statement and these would handle all indents and dedents.

If one really preferred to write, for instance, 'c.For(s); instead of 
'For(c,s)' in the with statement, one could add a wrapper method like
     def For(self, s): return For(self, s)
for each context manager. I left that out.

Similar methods can be used to auto-match C braces and other open/close 
pairs.

Terry Jan Reedy






More information about the Python-list mailing list