Added JSON schema compiler support for serialized types

Currently supports string->int, string->int64, and int->string.

BUG=139076


Review URL: https://chromiumcodereview.appspot.com/10825029

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149302 0039d316-1c4b-4281-b951-d872f2087c98
parent 0933b60e
......@@ -253,20 +253,36 @@ class CCGenerator(object):
else:
if prop.optional:
if prop.type_ == PropertyType.ENUM:
c.Sblock('if (%s != %s)' %
c.Sblock('if (%s != %s) {' %
(prop.unix_name,
self._cpp_type_generator.GetEnumNoneValue(prop)))
elif prop.type_ == PropertyType.CHOICES:
c.Sblock('if (%s_type != %s)' %
c.Sblock('if (%s_type != %s) {' %
(prop.unix_name,
self._cpp_type_generator.GetEnumNoneValue(prop)))
else:
c.Sblock('if (%s.get())' % prop.unix_name)
c.Sblock('if (%s.get()) {' % prop.unix_name)
if prop.type_ == prop.compiled_type:
c.Append('value->SetWithoutPathExpansion("%s", %s);' % (
prop.name,
self._CreateValueFromProperty(prop, 'this->' + prop.unix_name)))
else:
conversion_src = 'this->' + prop.unix_name
if prop.optional:
conversion_src = '*' + conversion_src
(c.Append('%s %s;' % (self._cpp_type_generator.GetType(prop),
prop.unix_name))
.Append(cpp_util.GenerateCompiledTypeToTypeConversion(
self._cpp_type_generator.GetReferencedProperty(prop),
conversion_src,
prop.unix_name) + ';')
.Append('value->SetWithoutPathExpansion("%s", %s);' % (
prop.unix_name,
self._CreateValueFromProperty(prop, prop.unix_name)))
)
if prop.optional:
c.Eblock();
c.Eblock('}');
(c.Append()
.Append('return value.Pass();')
.Eblock('}')
......@@ -337,7 +353,11 @@ class CCGenerator(object):
self._cpp_type_generator.GetReferencedProperty(prop), var,
prop.optional)
elif self._IsFundamentalOrFundamentalRef(prop):
if prop.optional:
# If prop.type != prop.compiled_type, then no asterisk is necessary
# because the target is a local variable and not a dereferenced scoped
# pointer. The asterisk is instead prepended to conversion_src around line
# 273.
if prop.optional and prop.type_ == prop.compiled_type:
var = '*' + var
prop = self._cpp_type_generator.GetReferencedProperty(prop);
return {
......@@ -448,16 +468,42 @@ class CCGenerator(object):
value_var,
'&temp'))
.Append(' return %(failure_value)s;')
.Append('%(dst)s->%(name)s.reset(new %(ctype)s(temp));')
)
if prop.type_ != prop.compiled_type:
(c.Append('%(compiled_ctype)s temp2;')
.Append('if (!%s)' %
cpp_util.GenerateTypeToCompiledTypeConversion(
self._cpp_type_generator.GetReferencedProperty(prop),
'temp',
'temp2'))
.Append(' return %(failure_value)s;')
.Append('%(dst)s->%(name)s.reset(new %(compiled_ctype)s(temp2));')
)
else:
c.Append('%(dst)s->%(name)s.reset(new %(ctype)s(temp));')
else:
if prop.type_ == prop.compiled_type:
assignment_target = '&%s->%s' % (dst, prop.unix_name)
else:
c.Append('%(ctype)s temp;')
assignment_target = '&temp'
(c.Append('if (!%s)' %
cpp_util.GetAsFundamentalValue(
self._cpp_type_generator.GetReferencedProperty(prop),
value_var,
'&%s->%s' % (dst, prop.unix_name)))
assignment_target))
.Append(' return %(failure_value)s;')
)
if prop.type_ != prop.compiled_type:
(c.Append('if (!%s)' %
cpp_util.GenerateTypeToCompiledTypeConversion(
self._cpp_type_generator.GetReferencedProperty(prop),
'temp',
'%s->%s' % (dst, prop.unix_name)))
.Append(' return %(failure_value)s;')
)
elif self._IsObjectOrObjectRef(prop):
if prop.optional:
(c.Append('const base::DictionaryValue* dictionary = NULL;')
......@@ -546,6 +592,7 @@ class CCGenerator(object):
}
if prop.type_ not in (PropertyType.CHOICES, PropertyType.ANY):
sub['ctype'] = self._cpp_type_generator.GetType(prop)
sub['compiled_ctype'] = self._cpp_type_generator.GetCompiledType(prop)
sub['value_type'] = cpp_util.GetValueType(self._cpp_type_generator
.GetReferencedProperty(prop).type_)
c.Substitute(sub)
......@@ -721,10 +768,20 @@ class CCGenerator(object):
# scoped_ptr if it's optional.
param_copy = param.Copy()
param_copy.optional = False
c.Append('create_results->Append(%s);' %
self._CreateValueFromProperty(param_copy, param_copy.unix_name))
declaration_list.append("const %s" % cpp_util.GetParameterDeclaration(
param_copy, self._cpp_type_generator.GetType(param_copy)))
param_copy, self._cpp_type_generator.GetCompiledType(param_copy)))
param_name = param_copy.unix_name
if param_copy.type_ != param_copy.compiled_type:
param_name = 'temp_' + param_name
(c.Append('%s %s;' % (self._cpp_type_generator.GetType(param_copy),
param_name))
.Append(cpp_util.GenerateCompiledTypeToTypeConversion(
param_copy,
param_copy.unix_name,
param_name) + ';')
)
c.Append('create_results->Append(%s);' %
self._CreateValueFromProperty(param_copy, param_name))
c.Append('return create_results.Pass();')
c.Eblock('}')
......
......@@ -116,6 +116,14 @@ class CppTypeGenerator(object):
return cpp_util.Classname(prop.name) + 'Type'
def GetType(self, prop, pad_for_generics=False, wrap_optional=False):
return self._GetTypeHelper(prop, pad_for_generics, wrap_optional)
def GetCompiledType(self, prop, pad_for_generics=False, wrap_optional=False):
return self._GetTypeHelper(prop, pad_for_generics, wrap_optional,
use_compiled_type=True)
def _GetTypeHelper(self, prop, pad_for_generics=False, wrap_optional=False,
use_compiled_type=False):
"""Translates a model.Property into its C++ type.
If REF types from different namespaces are referenced, will resolve
......@@ -125,9 +133,13 @@ class CppTypeGenerator(object):
Use wrap_optional to wrap the type in a scoped_ptr<T> if the Property is
optional.
Use use_compiled_type when converting from prop.type_ to prop.compiled_type.
"""
cpp_type = None
if prop.type_ == PropertyType.REF:
type_ = prop.type_ if not use_compiled_type else prop.compiled_type
if type_ == PropertyType.REF:
dependency_namespace = self._ResolveTypeNamespace(prop.ref_type)
if not dependency_namespace:
raise KeyError('Cannot find referenced type: %s' % prop.ref_type)
......@@ -136,28 +148,30 @@ class CppTypeGenerator(object):
schema_util.StripSchemaNamespace(prop.ref_type))
else:
cpp_type = schema_util.StripSchemaNamespace(prop.ref_type)
elif prop.type_ == PropertyType.BOOLEAN:
elif type_ == PropertyType.BOOLEAN:
cpp_type = 'bool'
elif prop.type_ == PropertyType.INTEGER:
elif type_ == PropertyType.INTEGER:
cpp_type = 'int'
elif prop.type_ == PropertyType.DOUBLE:
elif type_ == PropertyType.INT64:
cpp_type = 'int64'
elif type_ == PropertyType.DOUBLE:
cpp_type = 'double'
elif prop.type_ == PropertyType.STRING:
elif type_ == PropertyType.STRING:
cpp_type = 'std::string'
elif prop.type_ == PropertyType.ENUM:
elif type_ == PropertyType.ENUM:
cpp_type = cpp_util.Classname(prop.name)
elif prop.type_ == PropertyType.ADDITIONAL_PROPERTIES:
elif type_ == PropertyType.ADDITIONAL_PROPERTIES:
cpp_type = 'base::DictionaryValue'
elif prop.type_ == PropertyType.ANY:
elif type_ == PropertyType.ANY:
cpp_type = any_helper.ANY_CLASS
elif prop.type_ == PropertyType.OBJECT:
elif type_ == PropertyType.OBJECT:
cpp_type = cpp_util.Classname(prop.name)
elif prop.type_ == PropertyType.FUNCTION:
elif type_ == PropertyType.FUNCTION:
# Functions come into the json schema compiler as empty objects. We can
# record these as empty DictionaryValue's so that we know if the function
# was passed in or not.
cpp_type = 'base::DictionaryValue'
elif prop.type_ == PropertyType.ARRAY:
elif type_ == PropertyType.ARRAY:
item_type = prop.item_type
if item_type.type_ == PropertyType.REF:
item_type = self.GetReferencedProperty(item_type)
......@@ -168,14 +182,14 @@ class CppTypeGenerator(object):
cpp_type = 'std::vector<%s> '
cpp_type = cpp_type % self.GetType(
prop.item_type, pad_for_generics=True)
elif prop.type_ == PropertyType.BINARY:
elif type_ == PropertyType.BINARY:
cpp_type = 'std::string'
else:
raise NotImplementedError(prop.type_)
raise NotImplementedError(type_)
# Enums aren't wrapped because C++ won't allow it. Optional enums have a
# NONE value generated instead.
if wrap_optional and prop.optional and prop.type_ != PropertyType.ENUM:
if wrap_optional and prop.optional and type_ != PropertyType.ENUM:
cpp_type = 'scoped_ptr<%s> ' % cpp_type
if pad_for_generics:
return cpp_type
......@@ -222,6 +236,8 @@ class CppTypeGenerator(object):
self._cpp_namespaces[dependency])
for dependency in self._NamespaceTypeDependencies().keys()]):
c.Append('#include "%s"' % header)
c.Append('#include "base/string_number_conversions.h"')
if self._namespace.events:
c.Append('#include "base/json/json_writer.h"')
return c
......
......@@ -48,6 +48,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
manager = CppTypeGenerator('', self.windows, self.windows.unix_name)
manager.AddNamespace(self.tabs, self.tabs.unix_name)
self.assertEquals('#include "path/to/tabs.h"\n'
'#include "base/string_number_conversions.h"\n'
'#include "base/json/json_writer.h"',
manager.GenerateIncludes().Render())
self.assertEquals('namespace tabs {\n'
......@@ -58,7 +59,8 @@ class CppTypeGeneratorTest(unittest.TestCase):
'} // windows',
manager.GenerateForwardDeclarations().Render())
manager = CppTypeGenerator('', self.permissions, self.permissions.unix_name)
self.assertEquals('#include "base/json/json_writer.h"',
self.assertEquals('#include "base/string_number_conversions.h"\n'
'#include "base/json/json_writer.h"',
manager.GenerateIncludes().Render())
self.assertEquals('namespace permissions {\n'
'struct Permissions;\n'
......@@ -66,7 +68,8 @@ class CppTypeGeneratorTest(unittest.TestCase):
manager.GenerateForwardDeclarations().Render())
manager = CppTypeGenerator('', self.content_settings,
self.content_settings.unix_name)
self.assertEquals('', manager.GenerateIncludes().Render())
self.assertEquals('#include "base/string_number_conversions.h"',
manager.GenerateIncludes().Render())
def testGenerateIncludesAndForwardDeclarationsMultipleTypes(self):
......@@ -83,6 +86,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
manager = CppTypeGenerator('', windows, self.windows.unix_name)
manager.AddNamespace(tabs_namespace, self.tabs.unix_name)
self.assertEquals('#include "path/to/tabs.h"\n'
'#include "base/string_number_conversions.h"\n'
'#include "base/json/json_writer.h"',
manager.GenerateIncludes().Render())
self.assertEquals('namespace tabs {\n'
......@@ -109,7 +113,8 @@ class CppTypeGeneratorTest(unittest.TestCase):
manager.AddNamespace(browser_action_namespace,
self.browser_action.unix_name)
self.assertEquals('#include "path/to/browser_action.h"\n'
'#include "path/to/font_settings.h"',
'#include "path/to/font_settings.h"\n'
'#include "base/string_number_conversions.h"',
manager.GenerateIncludes().Render())
self.assertEquals('namespace browserAction {\n'
'typedef std::vector<int> ColorArray;\n'
......
......@@ -81,3 +81,41 @@ def GenerateIfndefName(path, filename):
"""
return (('%s_%s_H__' % (path, filename))
.upper().replace(os.sep, '_').replace('/', '_'))
def GenerateTypeToCompiledTypeConversion(prop, from_, to):
try:
return _GenerateTypeConversionHelper(prop.type_, prop.compiled_type, from_,
to)
except KeyError:
raise NotImplementedError('Conversion from %s to %s in %s not supported' %
(prop.type_, prop.compiled_type, prop.name))
def GenerateCompiledTypeToTypeConversion(prop, from_, to):
try:
return _GenerateTypeConversionHelper(prop.compiled_type, prop.type_, from_,
to)
except KeyError:
raise NotImplementedError('Conversion from %s to %s in %s not supported' %
(prop.compiled_type, prop.type_, prop.name))
def _GenerateTypeConversionHelper(from_type, to_type, from_, to):
"""Converts from PropertyType from_type to PropertyType to_type.
from_type: The PropertyType to be converted from.
to_type: The PropertyType to be converted to.
from_: The variable name of the type to be converted from.
to: The variable name of the type to be converted to.
"""
# TODO(mwrosen): Add support for more from/to combinations as necessary.
return {
PropertyType.STRING: {
PropertyType.INTEGER: 'base::StringToInt(%(from)s, &%(to)s)',
PropertyType.INT64: 'base::StringToInt64(%(from)s, &%(to)s)',
},
PropertyType.INTEGER: {
PropertyType.STRING: '%(to)s = base::IntToString(%(from)s)',
},
PropertyType.INT64: {
PropertyType.STRING: '%(to)s = base::Int64ToString(%(from)s)',
}
}[from_type][to_type] % {'from': from_, 'to': to}
......@@ -159,7 +159,7 @@ class HGenerator(object):
if prop.description:
c.Comment(prop.description)
(c.Append('%s %s;' % (
self._cpp_type_generator.GetType(prop, wrap_optional=True),
self._cpp_type_generator.GetCompiledType(prop, wrap_optional=True),
prop.unix_name))
.Append()
)
......@@ -183,7 +183,7 @@ class HGenerator(object):
c.Comment(type_.description)
c.Append('typedef std::vector<%(item_type)s> %(classname)s;')
c.Substitute({'classname': classname, 'item_type':
self._cpp_type_generator.GetType(type_.item_type,
self._cpp_type_generator.GetCompiledType(type_.item_type,
wrap_optional=True)})
elif type_.type_ == PropertyType.STRING:
if type_.description:
......@@ -294,7 +294,7 @@ class HGenerator(object):
[choice.type_.name for choice in prop.choices.values()]))
c.Concat(self._GeneratePropertyStructures(prop.choices.values()))
elif prop.type_ == PropertyType.ENUM:
enum_name = self._cpp_type_generator.GetType(prop)
enum_name = self._cpp_type_generator.GetCompiledType(prop)
c.Concat(self._GenerateEnumDeclaration(
enum_name,
prop,
......@@ -339,7 +339,7 @@ class HGenerator(object):
if param.description:
c.Comment(param.description)
declaration_list.append('const %s' % cpp_util.GetParameterDeclaration(
param, self._cpp_type_generator.GetType(param)))
param, self._cpp_type_generator.GetCompiledType(param)))
c.Append('scoped_ptr<base::ListValue> Create(%s);' %
', '.join(declaration_list))
if generate_to_json:
......
......@@ -151,6 +151,8 @@ class Property(object):
- |optional| a boolean representing whether the property is optional
- |description| a description of the property (if provided)
- |type_| the model.PropertyType of this property
- |compiled_type| the model.PropertyType that this property should be
compiled to from the JSON. Defaults to |type_|.
- |ref_type| the type that the REF property is referencing. Can be used to
map to its model.Type
- |item_type| a model.Property representing the type of each element in an
......@@ -190,40 +192,23 @@ class Property(object):
self.enum_values.append(value)
self.type_ = PropertyType.ENUM
elif 'type' in json:
json_type = json['type']
if json_type == 'string':
self.type_ = PropertyType.STRING
elif json_type == 'any':
self.type_ = PropertyType.ANY
elif json_type == 'boolean':
self.type_ = PropertyType.BOOLEAN
elif json_type == 'integer':
self.type_ = PropertyType.INTEGER
elif json_type == 'number':
self.type_ = PropertyType.DOUBLE
elif json_type == 'array':
self.type_ = self._JsonTypeToPropertyType(json['type'])
if self.type_ == PropertyType.ARRAY:
self.item_type = Property(self, name + "Element", json['items'],
from_json=from_json,
from_client=from_client)
self.type_ = PropertyType.ARRAY
elif json_type == 'object':
self.type_ = PropertyType.OBJECT
elif self.type_ == PropertyType.OBJECT:
# These members are read when this OBJECT Property is used as a Type
type_ = Type(self, self.name, json)
# self.properties will already have some value from |_AddProperties|.
self.properties.update(type_.properties)
self.functions = type_.functions
elif json_type == 'function':
self.type_ = PropertyType.FUNCTION
elif json_type == 'binary':
self.type_ = PropertyType.BINARY
else:
raise ParseException(self, 'type ' + json_type + ' not recognized')
elif 'choices' in json:
if not json['choices'] or len(json['choices']) == 0:
raise ParseException(self, 'Choices has no choices')
self.choices = {}
self.type_ = PropertyType.CHOICES
self.compiled_type = self.type_
for choice_json in json['choices']:
choice = Property(self, self.name, choice_json,
from_json=from_json,
......@@ -237,6 +222,7 @@ class Property(object):
self.value = json['value']
if type(self.value) == int:
self.type_ = PropertyType.INTEGER
self.compiled_type = self.type_
else:
# TODO(kalman): support more types as necessary.
raise ParseException(
......@@ -244,6 +230,30 @@ class Property(object):
else:
raise ParseException(
self, 'Property has no type, $ref, choices, or value')
if 'compiled_type' in json:
if 'type' in json:
self.compiled_type = self._JsonTypeToPropertyType(json['compiled_type'])
else:
raise ParseException(self, 'Property has compiled_type but no type')
else:
self.compiled_type = self.type_
def _JsonTypeToPropertyType(self, json_type):
try:
return {
'any': PropertyType.ANY,
'array': PropertyType.ARRAY,
'binary': PropertyType.BINARY,
'boolean': PropertyType.BOOLEAN,
'integer': PropertyType.INTEGER,
'int64': PropertyType.INT64,
'function': PropertyType.FUNCTION,
'number': PropertyType.DOUBLE,
'object': PropertyType.OBJECT,
'string': PropertyType.STRING,
}[json_type]
except KeyError:
raise NotImplementedError('Type %s not recognized' % json_type)
def GetUnixName(self):
"""Gets the property's unix_name. Raises AttributeError if not set.
......@@ -288,6 +298,7 @@ class PropertyType(object):
return self.name
INTEGER = _Info(True, "INTEGER")
INT64 = _Info(True, "INT64")
DOUBLE = _Info(True, "DOUBLE")
BOOLEAN = _Info(True, "BOOLEAN")
STRING = _Info(True, "STRING")
......
......@@ -66,7 +66,7 @@ class ModelTest(unittest.TestCase):
def testPropertyNotImplemented(self):
(self.permissions_json[0]['types'][0]
['properties']['permissions']['type']) = 'something'
self.assertRaises(model.ParseException, self.model.AddNamespace,
self.assertRaises(NotImplementedError, self.model.AddNamespace,
self.permissions_json[0], 'path/to/something.json')
def testDescription(self):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment