 e6c42b96b9
			
		
	
	
		e6c42b96b9
		
	
	
	
	
		
			
			The QAPI code generator clocks in at some 3100 SLOC in 8 source files. Almost 60% of the code is in qapi/common.py. Split it into more focused modules: * Move QAPISchemaPragma and QAPISourceInfo to qapi/source.py. * Move QAPIError and its sub-classes to qapi/error.py. * Move QAPISchemaParser and QAPIDoc to parser.py. Use the opportunity to put QAPISchemaParser first. * Move check_expr() & friends to qapi/expr.py. Use the opportunity to put the code into a more sensible order. * Move QAPISchema & friends to qapi/schema.py * Move QAPIGen and its sub-classes, ifcontext, QAPISchemaModularCVisitor, and QAPISchemaModularCVisitor to qapi/gen.py * Delete camel_case(), it's unused since commit e98859a9b9 "qapi: Clean up after recent conversions to QAPISchemaVisitor" A number of helper functions remain in qapi/common.py. I considered moving the code generator helpers to qapi/gen.py, but decided not to. Perhaps we should rewrite them as methods of QAPIGen some day. Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-Id: <20191018074345.24034-7-armbru@redhat.com> [Add "# -*- coding: utf-8 -*-" lines]
		
			
				
	
	
		
			292 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| #
 | |
| # QAPI code generation
 | |
| #
 | |
| # Copyright (c) 2018-2019 Red Hat Inc.
 | |
| #
 | |
| # Authors:
 | |
| #  Markus Armbruster <armbru@redhat.com>
 | |
| #  Marc-André Lureau <marcandre.lureau@redhat.com>
 | |
| #
 | |
| # This work is licensed under the terms of the GNU GPL, version 2.
 | |
| # See the COPYING file in the top-level directory.
 | |
| 
 | |
| 
 | |
| import errno
 | |
| import os
 | |
| import re
 | |
| import sys
 | |
| from contextlib import contextmanager
 | |
| 
 | |
| from qapi.common import *
 | |
| from qapi.schema import QAPISchemaVisitor
 | |
| 
 | |
| 
 | |
| class QAPIGen(object):
 | |
| 
 | |
|     def __init__(self, fname):
 | |
|         self.fname = fname
 | |
|         self._preamble = ''
 | |
|         self._body = ''
 | |
| 
 | |
|     def preamble_add(self, text):
 | |
|         self._preamble += text
 | |
| 
 | |
|     def add(self, text):
 | |
|         self._body += text
 | |
| 
 | |
|     def get_content(self):
 | |
|         return self._top() + self._preamble + self._body + self._bottom()
 | |
| 
 | |
|     def _top(self):
 | |
|         return ''
 | |
| 
 | |
|     def _bottom(self):
 | |
|         return ''
 | |
| 
 | |
|     def write(self, output_dir):
 | |
|         pathname = os.path.join(output_dir, self.fname)
 | |
|         dir = os.path.dirname(pathname)
 | |
|         if dir:
 | |
|             try:
 | |
|                 os.makedirs(dir)
 | |
|             except os.error as e:
 | |
|                 if e.errno != errno.EEXIST:
 | |
|                     raise
 | |
|         fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
 | |
|         if sys.version_info[0] >= 3:
 | |
|             f = open(fd, 'r+', encoding='utf-8')
 | |
|         else:
 | |
|             f = os.fdopen(fd, 'r+')
 | |
|         text = self.get_content()
 | |
|         oldtext = f.read(len(text) + 1)
 | |
|         if text != oldtext:
 | |
|             f.seek(0)
 | |
|             f.truncate(0)
 | |
|             f.write(text)
 | |
|         f.close()
 | |
| 
 | |
| 
 | |
| def _wrap_ifcond(ifcond, before, after):
 | |
|     if before == after:
 | |
|         return after   # suppress empty #if ... #endif
 | |
| 
 | |
|     assert after.startswith(before)
 | |
|     out = before
 | |
|     added = after[len(before):]
 | |
|     if added[0] == '\n':
 | |
|         out += '\n'
 | |
|         added = added[1:]
 | |
|     out += gen_if(ifcond)
 | |
|     out += added
 | |
|     out += gen_endif(ifcond)
 | |
|     return out
 | |
| 
 | |
| 
 | |
| class QAPIGenCCode(QAPIGen):
 | |
| 
 | |
|     def __init__(self, fname):
 | |
|         QAPIGen.__init__(self, fname)
 | |
|         self._start_if = None
 | |
| 
 | |
|     def start_if(self, ifcond):
 | |
|         assert self._start_if is None
 | |
|         self._start_if = (ifcond, self._body, self._preamble)
 | |
| 
 | |
|     def end_if(self):
 | |
|         assert self._start_if
 | |
|         self._wrap_ifcond()
 | |
|         self._start_if = None
 | |
| 
 | |
|     def _wrap_ifcond(self):
 | |
|         self._body = _wrap_ifcond(self._start_if[0],
 | |
|                                   self._start_if[1], self._body)
 | |
|         self._preamble = _wrap_ifcond(self._start_if[0],
 | |
|                                       self._start_if[2], self._preamble)
 | |
| 
 | |
|     def get_content(self):
 | |
|         assert self._start_if is None
 | |
|         return QAPIGen.get_content(self)
 | |
| 
 | |
| 
 | |
| class QAPIGenC(QAPIGenCCode):
 | |
| 
 | |
|     def __init__(self, fname, blurb, pydoc):
 | |
|         QAPIGenCCode.__init__(self, fname)
 | |
|         self._blurb = blurb
 | |
|         self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
 | |
|                                                   re.MULTILINE))
 | |
| 
 | |
|     def _top(self):
 | |
|         return mcgen('''
 | |
| /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
 | |
| 
 | |
| /*
 | |
| %(blurb)s
 | |
|  *
 | |
|  * %(copyright)s
 | |
|  *
 | |
|  * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
 | |
|  * See the COPYING.LIB file in the top-level directory.
 | |
|  */
 | |
| 
 | |
| ''',
 | |
|                      blurb=self._blurb, copyright=self._copyright)
 | |
| 
 | |
|     def _bottom(self):
 | |
|         return mcgen('''
 | |
| 
 | |
| /* Dummy declaration to prevent empty .o file */
 | |
| char qapi_dummy_%(name)s;
 | |
| ''',
 | |
|                      name=c_fname(self.fname))
 | |
| 
 | |
| 
 | |
| class QAPIGenH(QAPIGenC):
 | |
| 
 | |
|     def _top(self):
 | |
|         return QAPIGenC._top(self) + guardstart(self.fname)
 | |
| 
 | |
|     def _bottom(self):
 | |
|         return guardend(self.fname)
 | |
| 
 | |
| 
 | |
| @contextmanager
 | |
| def ifcontext(ifcond, *args):
 | |
|     """A 'with' statement context manager to wrap with start_if()/end_if()
 | |
| 
 | |
|     *args: any number of QAPIGenCCode
 | |
| 
 | |
|     Example::
 | |
| 
 | |
|         with ifcontext(ifcond, self._genh, self._genc):
 | |
|             modify self._genh and self._genc ...
 | |
| 
 | |
|     Is equivalent to calling::
 | |
| 
 | |
|         self._genh.start_if(ifcond)
 | |
|         self._genc.start_if(ifcond)
 | |
|         modify self._genh and self._genc ...
 | |
|         self._genh.end_if()
 | |
|         self._genc.end_if()
 | |
|     """
 | |
|     for arg in args:
 | |
|         arg.start_if(ifcond)
 | |
|     yield
 | |
|     for arg in args:
 | |
|         arg.end_if()
 | |
| 
 | |
| 
 | |
| class QAPIGenDoc(QAPIGen):
 | |
| 
 | |
|     def _top(self):
 | |
|         return (QAPIGen._top(self)
 | |
|                 + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
 | |
| 
 | |
| 
 | |
| class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
 | |
| 
 | |
|     def __init__(self, prefix, what, blurb, pydoc):
 | |
|         self._prefix = prefix
 | |
|         self._what = what
 | |
|         self._genc = QAPIGenC(self._prefix + self._what + '.c',
 | |
|                               blurb, pydoc)
 | |
|         self._genh = QAPIGenH(self._prefix + self._what + '.h',
 | |
|                               blurb, pydoc)
 | |
| 
 | |
|     def write(self, output_dir):
 | |
|         self._genc.write(output_dir)
 | |
|         self._genh.write(output_dir)
 | |
| 
 | |
| 
 | |
| class QAPISchemaModularCVisitor(QAPISchemaVisitor):
 | |
| 
 | |
|     def __init__(self, prefix, what, blurb, pydoc):
 | |
|         self._prefix = prefix
 | |
|         self._what = what
 | |
|         self._blurb = blurb
 | |
|         self._pydoc = pydoc
 | |
|         self._genc = None
 | |
|         self._genh = None
 | |
|         self._module = {}
 | |
|         self._main_module = None
 | |
| 
 | |
|     @staticmethod
 | |
|     def _is_user_module(name):
 | |
|         return name and not name.startswith('./')
 | |
| 
 | |
|     @staticmethod
 | |
|     def _is_builtin_module(name):
 | |
|         return not name
 | |
| 
 | |
|     def _module_dirname(self, what, name):
 | |
|         if self._is_user_module(name):
 | |
|             return os.path.dirname(name)
 | |
|         return ''
 | |
| 
 | |
|     def _module_basename(self, what, name):
 | |
|         ret = '' if self._is_builtin_module(name) else self._prefix
 | |
|         if self._is_user_module(name):
 | |
|             basename = os.path.basename(name)
 | |
|             ret += what
 | |
|             if name != self._main_module:
 | |
|                 ret += '-' + os.path.splitext(basename)[0]
 | |
|         else:
 | |
|             name = name[2:] if name else 'builtin'
 | |
|             ret += re.sub(r'-', '-' + name + '-', what)
 | |
|         return ret
 | |
| 
 | |
|     def _module_filename(self, what, name):
 | |
|         return os.path.join(self._module_dirname(what, name),
 | |
|                             self._module_basename(what, name))
 | |
| 
 | |
|     def _add_module(self, name, blurb):
 | |
|         basename = self._module_filename(self._what, name)
 | |
|         genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
 | |
|         genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
 | |
|         self._module[name] = (genc, genh)
 | |
|         self._set_module(name)
 | |
| 
 | |
|     def _add_user_module(self, name, blurb):
 | |
|         assert self._is_user_module(name)
 | |
|         if self._main_module is None:
 | |
|             self._main_module = name
 | |
|         self._add_module(name, blurb)
 | |
| 
 | |
|     def _add_system_module(self, name, blurb):
 | |
|         self._add_module(name and './' + name, blurb)
 | |
| 
 | |
|     def _set_module(self, name):
 | |
|         self._genc, self._genh = self._module[name]
 | |
| 
 | |
|     def write(self, output_dir, opt_builtins=False):
 | |
|         for name in self._module:
 | |
|             if self._is_builtin_module(name) and not opt_builtins:
 | |
|                 continue
 | |
|             (genc, genh) = self._module[name]
 | |
|             genc.write(output_dir)
 | |
|             genh.write(output_dir)
 | |
| 
 | |
|     def _begin_user_module(self, name):
 | |
|         pass
 | |
| 
 | |
|     def visit_module(self, name):
 | |
|         if name in self._module:
 | |
|             self._set_module(name)
 | |
|         elif self._is_builtin_module(name):
 | |
|             # The built-in module has not been created.  No code may
 | |
|             # be generated.
 | |
|             self._genc = None
 | |
|             self._genh = None
 | |
|         else:
 | |
|             self._add_user_module(name, self._blurb)
 | |
|             self._begin_user_module(name)
 | |
| 
 | |
|     def visit_include(self, name, info):
 | |
|         relname = os.path.relpath(self._module_filename(self._what, name),
 | |
|                                   os.path.dirname(self._genh.fname))
 | |
|         self._genh.preamble_add(mcgen('''
 | |
| #include "%(relname)s.h"
 | |
| ''',
 | |
|                                       relname=relname))
 |