return error_path(self.info['parent']) + \
"%s:%d: %s" % (self.info['file'], self.info['line'], self.msg)
-class QAPISchema:
+class QAPISchemaParser(object):
def __init__(self, fp, previously_included = [], incl_info = None):
abs_fname = os.path.abspath(fp.name)
except IOError, e:
raise QAPIExprError(expr_info,
'%s: %s' % (e.strerror, include))
- exprs_include = QAPISchema(fobj, previously_included,
- expr_info)
+ exprs_include = QAPISchemaParser(fobj, previously_included,
+ expr_info)
self.exprs.extend(exprs_include.exprs)
else:
expr_elem = {'expr': expr,
#
# Semantic analysis of schema expressions
+# TODO fold into QAPISchema
+# TODO catching name collisions in generated code would be nice
#
def find_base_fields(base):
allow_dict = False, allow_optional = False,
allow_star = False, allow_metas = []):
global all_names
- orig_value = value
if value is None:
return
"%s: array type must contain single type name"
% source)
value = value[0]
- orig_value = "array of %s" %value
# Check if type name for value is okay
if isinstance(value, str):
if not value in all_names:
raise QAPIExprError(expr_info,
"%s uses unknown type '%s'"
- % (source, orig_value))
+ % (source, value))
if not all_names[value] in allow_metas:
raise QAPIExprError(expr_info,
"%s cannot use %s type '%s'"
- % (source, all_names[value], orig_value))
+ % (source, all_names[value], value))
return
- # value is a dictionary, check that each member is okay
- if not isinstance(value, OrderedDict):
- raise QAPIExprError(expr_info,
- "%s should be a dictionary" % source)
if not allow_dict:
raise QAPIExprError(expr_info,
"%s should be a type name" % source)
+
+ if not isinstance(value, OrderedDict):
+ raise QAPIExprError(expr_info,
+ "%s should be a dictionary or type name" % source)
+
+ # value is a dictionary, check that each member is okay
for (key, arg) in value.items():
check_name(expr_info, "Member of %s" % source, key,
allow_optional=allow_optional)
members = expr['data']
values = { 'MAX': '(automatic)' }
- # If the object has a member 'base', its value must name a struct,
- # and there must be a discriminator.
- if base is not None:
- if discriminator is None:
- raise QAPIExprError(expr_info,
- "Union '%s' requires a discriminator to go "
- "along with base" %name)
-
# Two types of unions, determined by discriminator.
# With no discriminator it is a simple union.
def check_enum(expr, expr_info):
name = expr['enum']
members = expr.get('data')
+ prefix = expr.get('prefix')
values = { 'MAX': '(automatic)' }
if not isinstance(members, list):
raise QAPIExprError(expr_info,
"Enum '%s' requires an array for 'data'" % name)
+ if prefix is not None and not isinstance(prefix, str):
+ raise QAPIExprError(expr_info,
+ "Enum '%s' requires a string for 'prefix'" % name)
for member in members:
check_name(expr_info, "Member of enum '%s'" %name, member,
enum_member=True)
expr = expr_elem['expr']
info = expr_elem['info']
if expr.has_key('enum'):
- check_keys(expr_elem, 'enum', ['data'])
+ check_keys(expr_elem, 'enum', ['data'], ['prefix'])
add_enum(expr['enum'], info, expr['data'])
elif expr.has_key('union'):
check_keys(expr_elem, 'union', ['data'],
else:
assert False, 'unexpected meta type'
- return map(lambda expr_elem: expr_elem['expr'], exprs)
+ return exprs
+
+
+#
+# Schema compiler frontend
+#
+
+class QAPISchemaEntity(object):
+ def __init__(self, name, info):
+ assert isinstance(name, str)
+ self.name = name
+ self.info = info
+
+ def c_name(self):
+ return c_name(self.name)
+
+ def check(self, schema):
+ pass
+
+
+class QAPISchemaType(QAPISchemaEntity):
+ def c_type(self, is_param=False):
+ return c_name(self.name) + pointer_suffix
+
+ def c_null(self):
+ return 'NULL'
+
+ def json_type(self):
+ pass
+
+ def alternate_qtype(self):
+ json2qtype = {
+ 'string': 'QTYPE_QSTRING',
+ 'number': 'QTYPE_QFLOAT',
+ 'int': 'QTYPE_QINT',
+ 'boolean': 'QTYPE_QBOOL',
+ 'object': 'QTYPE_QDICT'
+ }
+ return json2qtype.get(self.json_type())
+
+
+class QAPISchemaBuiltinType(QAPISchemaType):
+ def __init__(self, name, json_type, c_type, c_null):
+ QAPISchemaType.__init__(self, name, None)
+ assert not c_type or isinstance(c_type, str)
+ assert json_type in ('string', 'number', 'int', 'boolean', 'null',
+ 'value')
+ self._json_type_name = json_type
+ self._c_type_name = c_type
+ self._c_null_val = c_null
+
+ def c_name(self):
+ return self.name
+
+ def c_type(self, is_param=False):
+ if is_param and self.name == 'str':
+ return 'const ' + self._c_type_name
+ return self._c_type_name
+
+ def c_null(self):
+ return self._c_null_val
+
+ def json_type(self):
+ return self._json_type_name
+
+
+class QAPISchemaEnumType(QAPISchemaType):
+ def __init__(self, name, info, values, prefix):
+ QAPISchemaType.__init__(self, name, info)
+ for v in values:
+ assert isinstance(v, str)
+ assert prefix is None or isinstance(prefix, str)
+ self.values = values
+ self.prefix = prefix
+
+ def check(self, schema):
+ assert len(set(self.values)) == len(self.values)
+
+ def c_type(self, is_param=False):
+ return c_name(self.name)
+
+ def c_null(self):
+ return c_enum_const(self.name, (self.values + ['MAX'])[0],
+ self.prefix)
+
+ def json_type(self):
+ return 'string'
+
+
+class QAPISchemaArrayType(QAPISchemaType):
+ def __init__(self, name, info, element_type):
+ QAPISchemaType.__init__(self, name, info)
+ assert isinstance(element_type, str)
+ self._element_type_name = element_type
+ self.element_type = None
+
+ def check(self, schema):
+ self.element_type = schema.lookup_type(self._element_type_name)
+ assert self.element_type
+
+ def json_type(self):
+ return 'array'
+
+
+class QAPISchemaObjectType(QAPISchemaType):
+ def __init__(self, name, info, base, local_members, variants):
+ QAPISchemaType.__init__(self, name, info)
+ assert base is None or isinstance(base, str)
+ for m in local_members:
+ assert isinstance(m, QAPISchemaObjectTypeMember)
+ assert (variants is None or
+ isinstance(variants, QAPISchemaObjectTypeVariants))
+ self._base_name = base
+ self.base = None
+ self.local_members = local_members
+ self.variants = variants
+ self.members = None
+
+ def check(self, schema):
+ assert self.members is not False # not running in cycles
+ if self.members:
+ return
+ self.members = False # mark as being checked
+ if self._base_name:
+ self.base = schema.lookup_type(self._base_name)
+ assert isinstance(self.base, QAPISchemaObjectType)
+ assert not self.base.variants # not implemented
+ self.base.check(schema)
+ members = list(self.base.members)
+ else:
+ members = []
+ seen = {}
+ for m in members:
+ seen[m.name] = m
+ for m in self.local_members:
+ m.check(schema, members, seen)
+ if self.variants:
+ self.variants.check(schema, members, seen)
+ self.members = members
+
+ def c_name(self):
+ assert self.info
+ return QAPISchemaType.c_name(self)
+
+ def c_type(self, is_param=False):
+ assert self.info
+ return QAPISchemaType.c_type(self)
+
+ def json_type(self):
+ return 'object'
+
+
+class QAPISchemaObjectTypeMember(object):
+ def __init__(self, name, typ, optional):
+ assert isinstance(name, str)
+ assert isinstance(typ, str)
+ assert isinstance(optional, bool)
+ self.name = name
+ self._type_name = typ
+ self.type = None
+ self.optional = optional
+
+ def check(self, schema, all_members, seen):
+ assert self.name not in seen
+ self.type = schema.lookup_type(self._type_name)
+ assert self.type
+ all_members.append(self)
+ seen[self.name] = self
+
+
+class QAPISchemaObjectTypeVariants(object):
+ def __init__(self, tag_name, tag_enum, variants):
+ assert tag_name is None or isinstance(tag_name, str)
+ assert tag_enum is None or isinstance(tag_enum, str)
+ for v in variants:
+ assert isinstance(v, QAPISchemaObjectTypeVariant)
+ self.tag_name = tag_name
+ if tag_name:
+ assert not tag_enum
+ self.tag_member = None
+ else:
+ self.tag_member = QAPISchemaObjectTypeMember('type', tag_enum,
+ False)
+ self.variants = variants
+
+ def check(self, schema, members, seen):
+ if self.tag_name:
+ self.tag_member = seen[self.tag_name]
+ else:
+ self.tag_member.check(schema, members, seen)
+ assert isinstance(self.tag_member.type, QAPISchemaEnumType)
+ for v in self.variants:
+ vseen = dict(seen)
+ v.check(schema, self.tag_member.type, vseen)
+
+
+class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
+ def __init__(self, name, typ):
+ QAPISchemaObjectTypeMember.__init__(self, name, typ, False)
+
+ def check(self, schema, tag_type, seen):
+ QAPISchemaObjectTypeMember.check(self, schema, [], seen)
+ assert self.name in tag_type.values
+
+
+class QAPISchemaAlternateType(QAPISchemaType):
+ def __init__(self, name, info, variants):
+ QAPISchemaType.__init__(self, name, info)
+ assert isinstance(variants, QAPISchemaObjectTypeVariants)
+ assert not variants.tag_name
+ self.variants = variants
+
+ def check(self, schema):
+ self.variants.check(schema, [], {})
+
+ def json_type(self):
+ return 'value'
+
+
+class QAPISchemaCommand(QAPISchemaEntity):
+ def __init__(self, name, info, arg_type, ret_type, gen, success_response):
+ QAPISchemaEntity.__init__(self, name, info)
+ assert not arg_type or isinstance(arg_type, str)
+ assert not ret_type or isinstance(ret_type, str)
+ self._arg_type_name = arg_type
+ self.arg_type = None
+ self._ret_type_name = ret_type
+ self.ret_type = None
+ self.gen = gen
+ self.success_response = success_response
+
+ def check(self, schema):
+ if self._arg_type_name:
+ self.arg_type = schema.lookup_type(self._arg_type_name)
+ assert isinstance(self.arg_type, QAPISchemaObjectType)
+ assert not self.arg_type.variants # not implemented
+ if self._ret_type_name:
+ self.ret_type = schema.lookup_type(self._ret_type_name)
+ assert isinstance(self.ret_type, QAPISchemaType)
+
+
+class QAPISchemaEvent(QAPISchemaEntity):
+ def __init__(self, name, info, arg_type):
+ QAPISchemaEntity.__init__(self, name, info)
+ assert not arg_type or isinstance(arg_type, str)
+ self._arg_type_name = arg_type
+ self.arg_type = None
+
+ def check(self, schema):
+ if self._arg_type_name:
+ self.arg_type = schema.lookup_type(self._arg_type_name)
+ assert isinstance(self.arg_type, QAPISchemaObjectType)
+ assert not self.arg_type.variants # not implemented
+
+
+class QAPISchema(object):
+ def __init__(self, fname):
+ try:
+ self.exprs = check_exprs(QAPISchemaParser(open(fname, "r")).exprs)
+ except (QAPISchemaError, QAPIExprError), err:
+ print >>sys.stderr, err
+ exit(1)
+ self._entity_dict = {}
+ self._def_predefineds()
+ self._def_exprs()
+ self.check()
+
+ def get_exprs(self):
+ return [expr_elem['expr'] for expr_elem in self.exprs]
+
+ def _def_entity(self, ent):
+ assert ent.name not in self._entity_dict
+ self._entity_dict[ent.name] = ent
+
+ def lookup_entity(self, name, typ=None):
+ ent = self._entity_dict.get(name)
+ if typ and not isinstance(ent, typ):
+ return None
+ return ent
+
+ def lookup_type(self, name):
+ return self.lookup_entity(name, QAPISchemaType)
+
+ def _def_builtin_type(self, name, json_type, c_type, c_null):
+ self._def_entity(QAPISchemaBuiltinType(name, json_type,
+ c_type, c_null))
+ if name != '**':
+ self._make_array_type(name) # TODO really needed?
+
+ def _def_predefineds(self):
+ for t in [('str', 'string', 'char' + pointer_suffix, 'NULL'),
+ ('number', 'number', 'double', '0'),
+ ('int', 'int', 'int64_t', '0'),
+ ('int8', 'int', 'int8_t', '0'),
+ ('int16', 'int', 'int16_t', '0'),
+ ('int32', 'int', 'int32_t', '0'),
+ ('int64', 'int', 'int64_t', '0'),
+ ('uint8', 'int', 'uint8_t', '0'),
+ ('uint16', 'int', 'uint16_t', '0'),
+ ('uint32', 'int', 'uint32_t', '0'),
+ ('uint64', 'int', 'uint64_t', '0'),
+ ('size', 'int', 'uint64_t', '0'),
+ ('bool', 'boolean', 'bool', 'false'),
+ ('**', 'value', None, None)]:
+ self._def_builtin_type(*t)
+
+ def _make_implicit_enum_type(self, name, values):
+ name = name + 'Kind'
+ self._def_entity(QAPISchemaEnumType(name, None, values, None))
+ return name
+
+ def _make_array_type(self, element_type):
+ name = element_type + 'List'
+ if not self.lookup_type(name):
+ self._def_entity(QAPISchemaArrayType(name, None, element_type))
+ return name
+
+ def _make_implicit_object_type(self, name, role, members):
+ if not members:
+ return None
+ name = ':obj-%s-%s' % (name, role)
+ if not self.lookup_entity(name, QAPISchemaObjectType):
+ self._def_entity(QAPISchemaObjectType(name, None, None,
+ members, None))
+ return name
+
+ def _def_enum_type(self, expr, info):
+ name = expr['enum']
+ data = expr['data']
+ prefix = expr.get('prefix')
+ self._def_entity(QAPISchemaEnumType(name, info, data, prefix))
+ self._make_array_type(name) # TODO really needed?
+
+ def _make_member(self, name, typ):
+ optional = False
+ if name.startswith('*'):
+ name = name[1:]
+ optional = True
+ if isinstance(typ, list):
+ assert len(typ) == 1
+ typ = self._make_array_type(typ[0])
+ return QAPISchemaObjectTypeMember(name, typ, optional)
+
+ def _make_members(self, data):
+ return [self._make_member(key, value)
+ for (key, value) in data.iteritems()]
+
+ def _def_struct_type(self, expr, info):
+ name = expr['struct']
+ base = expr.get('base')
+ data = expr['data']
+ self._def_entity(QAPISchemaObjectType(name, info, base,
+ self._make_members(data),
+ None))
+ self._make_array_type(name) # TODO really needed?
+
+ def _make_variant(self, case, typ):
+ return QAPISchemaObjectTypeVariant(case, typ)
+
+ def _make_simple_variant(self, case, typ):
+ if isinstance(typ, list):
+ assert len(typ) == 1
+ typ = self._make_array_type(typ[0])
+ typ = self._make_implicit_object_type(typ, 'wrapper',
+ [self._make_member('data', typ)])
+ return QAPISchemaObjectTypeVariant(case, typ)
+
+ def _make_tag_enum(self, type_name, variants):
+ return self._make_implicit_enum_type(type_name,
+ [v.name for v in variants])
+
+ def _def_union_type(self, expr, info):
+ name = expr['union']
+ data = expr['data']
+ base = expr.get('base')
+ tag_name = expr.get('discriminator')
+ tag_enum = None
+ if tag_name:
+ variants = [self._make_variant(key, value)
+ for (key, value) in data.iteritems()]
+ else:
+ variants = [self._make_simple_variant(key, value)
+ for (key, value) in data.iteritems()]
+ tag_enum = self._make_tag_enum(name, variants)
+ self._def_entity(
+ QAPISchemaObjectType(name, info, base,
+ self._make_members(OrderedDict()),
+ QAPISchemaObjectTypeVariants(tag_name,
+ tag_enum,
+ variants)))
+ self._make_array_type(name) # TODO really needed?
+
+ def _def_alternate_type(self, expr, info):
+ name = expr['alternate']
+ data = expr['data']
+ variants = [self._make_variant(key, value)
+ for (key, value) in data.iteritems()]
+ tag_enum = self._make_tag_enum(name, variants)
+ self._def_entity(
+ QAPISchemaAlternateType(name, info,
+ QAPISchemaObjectTypeVariants(None,
+ tag_enum,
+ variants)))
+ self._make_array_type(name) # TODO really needed?
+
+ def _def_command(self, expr, info):
+ name = expr['command']
+ data = expr.get('data')
+ rets = expr.get('returns')
+ gen = expr.get('gen', True)
+ success_response = expr.get('success-response', True)
+ if isinstance(data, OrderedDict):
+ data = self._make_implicit_object_type(name, 'arg',
+ self._make_members(data))
+ if isinstance(rets, list):
+ assert len(rets) == 1
+ rets = self._make_array_type(rets[0])
+ self._def_entity(QAPISchemaCommand(name, info, data, rets, gen,
+ success_response))
+
+ def _def_event(self, expr, info):
+ name = expr['event']
+ data = expr.get('data')
+ if isinstance(data, OrderedDict):
+ data = self._make_implicit_object_type(name, 'arg',
+ self._make_members(data))
+ self._def_entity(QAPISchemaEvent(name, info, data))
+
+ def _def_exprs(self):
+ for expr_elem in self.exprs:
+ expr = expr_elem['expr']
+ info = expr_elem['info']
+ if 'enum' in expr:
+ self._def_enum_type(expr, info)
+ elif 'struct' in expr:
+ self._def_struct_type(expr, info)
+ elif 'union' in expr:
+ self._def_union_type(expr, info)
+ elif 'alternate' in expr:
+ self._def_alternate_type(expr, info)
+ elif 'command' in expr:
+ self._def_command(expr, info)
+ elif 'event' in expr:
+ self._def_event(expr, info)
+ else:
+ assert False
+
+ def check(self):
+ for ent in self._entity_dict.values():
+ ent.check(self)
-def parse_schema(fname):
- try:
- schema = QAPISchema(open(fname, "r"))
- return check_exprs(schema.exprs)
- except (QAPISchemaError, QAPIExprError), e:
- print >>sys.stderr, e
- exit(1)
#
# Code generation helpers
new_name += c
return new_name.lstrip('_').upper()
-def c_enum_const(type_name, const_name):
+def c_enum_const(type_name, const_name, prefix=None):
+ if prefix is not None:
+ type_name = prefix
return camel_to_upper(type_name + '_' + const_name)
c_name_trans = string.maketrans('.-', '__')
raw = code % kwds
if indent_level:
indent = genindent(indent_level)
- raw = re.subn("^.", indent + r'\g<0>', raw, 0, re.MULTILINE)
+ # re.subn() lacks flags support before Python 2.7, use re.compile()
+ raw = re.subn(re.compile("^.", re.MULTILINE),
+ indent + r'\g<0>', raw)
raw = raw[0]
return re.sub(re.escape(eatspace) + ' *', '', raw)
c_file = output_dir + prefix + c_file
h_file = output_dir + prefix + h_file
- try:
- os.makedirs(output_dir)
- except os.error, e:
- if e.errno != errno.EEXIST:
- raise
+ if output_dir:
+ try:
+ os.makedirs(output_dir)
+ except os.error, e:
+ if e.errno != errno.EEXIST:
+ raise
def maybe_open(really, name, opt):
if really: