Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[StubGen/JsonGen] Add floating-point types support #46

Merged
merged 3 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions JsonGenerator/source/documentation_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def _TableObj(name, obj, parentName="", parent=None, prefix="", parentOptional=F

if name or prefix:
if "type" not in obj:
raise DocumentationError("missing 'type' for object %s" % (parentName + "/" + name))
raise DocumentationError("'%s': missing 'type' for this object" % (parentName + "/" + name))

row = (("<sup>" + italics("(optional)") + "</sup>" + " ") if optional else "")

Expand All @@ -127,6 +127,11 @@ def _TableObj(name, obj, parentName="", parent=None, prefix="", parentOptional=F
if optional and "default" in obj:
row += " (default: " + (italics("%s") % str(obj["default"]) + ")")

if obj["type"] == "number":
# correct number to integer
if ("float" not in obj or not obj["float"]) and ("example" not in obj or '.' not in str(obj["example"])):
obj["type"] = "integer"

MdRow([prefix, "opaque object" if obj.get("opaque") else obj["type"], row])

if obj["type"] == "object":
Expand Down Expand Up @@ -172,9 +177,18 @@ def ExampleObj(name, obj, root=False):

if obj_type == "string":
json_data += "{ }" if obj.get("opaque") else ('"%s"' % (default if default else "..."))
elif obj_type in ["integer", "number"]:
elif obj_type == "integer":
if default and not str(default).isnumeric():
raise DocumentationError("'%s': invalid example syntax for this integer type (see '%s')" % (name, default))

json_data += '%s' % (default if default else 0)
elif obj_type in ["float", "double"]:
elif obj_type == "number":
if default and not str(default).replace('.','').isnumeric():
raise DocumentationError("'%s': invalid example syntax for this numeric (floating-point) type (see '%s')" % (name, default))

if default and '.' not in str(default):
default = default * 1.0

json_data += '%s' % (default if default else 0.0)
elif obj_type == "boolean":
json_data += '%s' % str(default if default else False).lower()
Expand All @@ -192,7 +206,7 @@ def ExampleObj(name, obj, root=False):

def MethodDump(method, props, classname, section, is_notification=False, is_property=False, include=None):
method = (method.rsplit(".", 1)[1] if "." in method else method)
type = "property" if is_property else "event" if is_notification else "method"
type = "property" if is_property else "event" if is_notification else "method"

log.Info("Emitting documentation for %s '%s'..." % (type, method))

Expand Down Expand Up @@ -252,7 +266,7 @@ def MethodDump(method, props, classname, section, is_notification=False, is_prop

if "index" in props:
if "name" not in props["index"] or "example" not in props["index"]:
raise DocumentationError("in %s: index field needs 'name' and 'example' properties" % method)
raise DocumentationError("'%s': index field requires 'name' and 'example' properties" % method)

extra_paragraph = "> The *%s* argument shall be passed as the index to the property, e.g. *%s.1.%s@%s*.%s" % (
props["index"]["name"].lower(), classname, method, props["index"]["example"],
Expand All @@ -276,7 +290,7 @@ def MethodDump(method, props, classname, section, is_notification=False, is_prop
if is_notification:
if "id" in props:
if "name" not in props["id"] or "example" not in props["id"]:
raise DocumentationError("in %s: id field needs 'name' and 'example' properties" % method)
raise DocumentationError("'%s': id field needs 'name' and 'example' properties" % method)

MdParagraph("> The *%s* argument shall be passed within the designator, e.g. *%s.client.events.1*." %
(props["id"]["name"], props["id"]["example"]))
Expand Down Expand Up @@ -333,7 +347,7 @@ def MethodDump(method, props, classname, section, is_notification=False, is_prop
object_pairs_hook=OrderedDict), indent=2)
MdCode(jsonResponse, "json")
elif "noresult" not in props or not props["noresult"]:
raise DocumentationError("missing 'result' in %s" % method)
raise DocumentationError("'%s': missing 'result' in this method" % method)

if is_property:
MdHeader("Set Response", 4)
Expand Down Expand Up @@ -421,7 +435,7 @@ def MethodDump(method, props, classname, section, is_notification=False, is_prop
elif status == "production" or status == "prod":
rating = 3
else:
raise DocumentationError("invalid status")
raise DocumentationError("invalid status (see '%s')" % status)

plugin_class = None

Expand Down
2 changes: 1 addition & 1 deletion JsonGenerator/source/header_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def ConvertType(var):
result = [ "integer", { "size": size, "signed": cppType.signed } ]
# Float
elif isinstance(cppType, CppParser.Float):
result = [ "float", { "size": 32 if cppType.type == "float" else 64 if cppType.type == "double" else 128 } ]
result = [ "number", { "float": True, "size": 32 if cppType.type == "float" else 64 if cppType.type == "double" else 128 } ]
# Null
elif isinstance(cppType, CppParser.Void):
result = [ "null", {} ]
Expand Down
55 changes: 30 additions & 25 deletions JsonGenerator/source/json_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,26 +252,25 @@ def cpp_native_type(self):
return "bool"


class JsonNumber(JsonNative, JsonType):
class JsonInteger(JsonNative, JsonType):
def __init__(self, name, parent, schema, size = config.DEFAULT_INT_SIZE, signed = False):
JsonType.__init__(self, name, parent, schema)
self.size = schema["size"] if "size" in schema else size

if self.size != 8 and self.size != 16 and self.size != 32 and self.size != 64:
raise JsonParseError("Invalid integer number size: '%s'" % self.print_name)

self.signed = schema["signed"] if "signed" in schema else signed
self._cpp_class = CoreJson("Dec%sInt%i" % ("S" if self.signed else "U", self.size))
self._cpp_native_type = "%sint%i_t" % ("" if self.signed else "u", self.size)
self.__cpp_class = CoreJson("Dec%sInt%i" % ("S" if self.signed else "U", self.size))
self.__cpp_native_type = "%sint%i_t" % ("" if self.signed else "u", self.size)

@property
def cpp_class(self):
return self._cpp_class
return self.__cpp_class

@property
def cpp_native_type(self):
return self._cpp_native_type


class JsonInteger(JsonNumber):
# Identical as Number
pass
return self.__cpp_native_type


class AuxJsonInteger(JsonInteger):
Expand All @@ -288,24 +287,27 @@ def cpp_native_type(self):
return "auto"


class JsonFloat(JsonNative, JsonType):
@property
def cpp_class(self):
return CoreJson("Float")
class JsonNumber(JsonNative, JsonType):
def __init__(self, name, parent, schema):
JsonType.__init__(self, name, parent, schema)
self.size = schema["size"] if "size" in schema else 32

@property
def cpp_native_type(self):
return "float"
if self.size > 64:
raise JsonParseError("Floating point numbers over 64 bits are not supported: '%s'" % self.print_name)

if self.size != 32 and self.size != 64:
raise JsonParseError("Invalid floating point number size: '%s'" % self.print_name)

self.__cpp_class = CoreJson("Float") if self.size == 32 else CoreJson("Double")
self.__cpp_native_type = "float" if self.size == 32 else "double"

class JsonDouble(JsonNative, JsonType):
@property
def cpp_class(self):
return CoreJson("Double")
return self.__cpp_class

@property
def cpp_native_type(self):
return "double"
return self.__cpp_native_type


class JsonString(JsonNative, JsonType):
Expand Down Expand Up @@ -876,12 +878,15 @@ def JsonItem(name, parent, schema, included=None):
elif schema["type"] == "integer":
return JsonInteger(name, parent, schema)
elif schema["type"] == "number":
return JsonNumber(name, parent, schema)
elif schema["type"] == "float":
return JsonFloat(name, parent, schema)
elif schema["type"] == "double":
return JsonDouble(name, parent, schema)
if ("float" in schema and schema["float"]) or ("example" in schema and '.' in str(schema["example"])):
return JsonNumber(name, parent, schema)
else:
log.Info("'%s': type is 'number' but assuming integer value" % name)
return JsonInteger(name, parent, schema)
else:
if schema["type"] == "float":
log.Print("For floating point type use '\"type\": \"number\"' with '\"float\": True' or with \"example\" using a decimal point")

raise JsonParseError("unsupported JSON type: '%s'" % schema["type"])
else:
raise JsonParseError("missing 'type' for item: '%s'" % name)
Expand Down
4 changes: 2 additions & 2 deletions ProxyStubGenerator/CppParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,10 @@ def __init__(self, parent_block, parent, string, valid_specifiers, tags_allowed=
for s in "".join(string[i + 1]).split(".."):
try:
if '.' not in s:
v = eval(s.lower().replace("k","*1024").replace("m","*1024*1024").replace("g","*1024*1024*1024"))
v = int(eval(s.lower().replace("k","*1024").replace("m","*1024*1024").replace("g","*1024*1024*1024")))
else:
v = eval(s)
self.meta.range.append(int(v))
self.meta.range.append(v)
except:
raise ParserError("failed to evaluate range in @restrict: '%s'" % s)
if len(self.meta.range) == 2:
Expand Down
4 changes: 2 additions & 2 deletions ProxyStubGenerator/Log.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, name, verbose = False, warnings = True, doc_issues = False):
self.name = name
self.file = ""
if os.name == "posix":
self.info = "\033[0mINFO"
self.cinfo = "\033[36mINFO"
self.cwarn = "\033[33mWARNING"
self.cerror = "\033[31mERROR"
self.cdocissue = "\033[37mDOCUMENTATION"
Expand All @@ -30,7 +30,7 @@ def __Print(self, text, file=""):
def Info(self, text, file=""):
if self.show_infos:
if not file: file = self.file
self.infos.append("%s: %s: %s%s%s" % (self.name, self.info, file, ": " if file else "", text))
self.infos.append("%s: %s%s: %s%s%s" % (self.name, self.cinfo, self.creset, file, ": " if file else "", text))
self.__Print(self.infos[-1])

def DocIssue(self, text, file=""):
Expand Down
41 changes: 37 additions & 4 deletions ProxyStubGenerator/StubGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,10 @@ def Convert(paramtype, retval, vars, hresult=False):

if param.IsPointer():
parsed = ParseLength(param, meta.length if meta.length else meta.maxlength, vars)

if parsed[1]:
length_param = parsed[1]

value = "BUFFER" + parsed[0]
else:
if paramtype.type.TypeName().endswith(HRESULT):
Expand All @@ -342,6 +344,7 @@ def Convert(paramtype, retval, vars, hresult=False):
value = "INT32"
elif p.size == "long long":
value = "INT64"

if not p.signed:
value = "U" + value

Expand All @@ -358,6 +361,7 @@ def Convert(paramtype, retval, vars, hresult=False):
break

rvalue = ["type = Type." + value]

if length_param:
rvalue.append("length_param = \"%s\"" % length_param)

Expand All @@ -369,18 +373,28 @@ def Convert(paramtype, retval, vars, hresult=False):
elif isinstance(p, CppParser.Bool):
return ["type = Type.BOOL"]

elif isinstance(p, CppParser.Float):
print(p)
if p.type == "float":
return ["type = Type.FLOAT32"]
elif p.type == "double":
return ["type = Type.FLOAT64"]

elif isinstance(p, CppParser.Class):
if param.IsPointer():
return ["type = Type.OBJECT", "class = \"%s\"" % Flatten(param.TypeName())]
else:
value = ["type = Type.POD", "class = \"%s\"" % Flatten(param.type.full_name)]
pod_params = []

for v in p.vars:
param_info = Convert(v, None, p.vars)
text = []
text.append("name = " + v.name)

if param_info:
text.extend(param_info)

pod_params.append("{ %s }" % ", ".join(text))

if pod_params:
Expand All @@ -391,14 +405,17 @@ def Convert(paramtype, retval, vars, hresult=False):
elif isinstance(p, CppParser.Enum):
value = "32"
signed = "U"

if p.type.Type().size == "char":
value = "8"
elif p.type.Type().size == "short":
value = "16"

if p.type.Type().signed:
signed = ""

name = Flatten(param.type.full_name)

if name not in enums_list:
data = dict()
for e in p.items:
Expand Down Expand Up @@ -598,7 +615,7 @@ def as_rvalue(self):

@property
def storage_size(self):
if isinstance(self.kind, (CppParser.Integer, CppParser.Enum, CppParser.BuiltinInteger)):
if isinstance(self.kind, (CppParser.Integer, CppParser.Float, CppParser.Enum, CppParser.BuiltinInteger)):
return "sizeof(%s)" % self.type_name
else:
Unreachable()
Expand Down Expand Up @@ -927,6 +944,9 @@ def _FindLength(length_name, variable_name):
if ((self.restrict_range[1] >= (64*1024)) and (self.restrict_range[1] < (16*1024*1024))):
self.type_name = "Core::Frame::UInt24"

if isinstance(self.kind, CppParser.Fundamental) and self.type.IsReference() and self.is_input_only:
log.WarnLine(self.identifier, "%s: input-only fundamental type passed by reference" % self.trace_proto)

@property
def as_rvalue(self):
def maybe_cast(expr):
Expand Down Expand Up @@ -985,8 +1005,14 @@ def read_rpc_type(self):
elif isinstance(self.kind, CppParser.Enum):
return "Number<%s>()" % self.type_name

# Floating point types
elif isinstance(self.kind, CppParser.Float):
if "long" not in self.type_name:
return "Number<%s>()" % self.type_name
else:
raise TypenameError(self.identifier, "long double type is not supported (see '%s')" % (self.trace_proto))
else:
raise TypenameError(self.identifier, "%s: unable to deserialise this type" % self.trace_proto)
raise TypenameError(self.identifier, "%s: unable to deserialise this type (see '%s')" % (self.type_name, self.trace_proto))

@property
def write_rpc_type(self):
Expand All @@ -1013,8 +1039,15 @@ def write_rpc_type(self):
elif isinstance(self.kind, CppParser.BuiltinInteger):
return "Number<%s>(%s)" % (self.type_name, self.as_rvalue)

# Floating point types
elif isinstance(self.kind, CppParser.Float):
if "long" not in self.type_name:
return "Number<%s>(%s)" % (self.type_name, self.as_rvalue)
else:
raise TypenameError(self.identifier, "long double is not supported (see '%s')" % (self.trace_proto))

else:
raise TypenameError(self.identifier, "%s: sorry, unable to serialise this type" % self.trace_proto)
raise TypenameError(self.identifier, "%s: sorry, unable to serialise this type (see '%s')" % (self.type_name, self.trace_proto))

@property
def storage_size(self):
Expand All @@ -1027,7 +1060,7 @@ def storage_size(self):
return "(sizeof(uint32_t))"
else:
return "Core::Frame::RealSize<%s>()" % self.peek_length.type_name
elif isinstance(self.kind, (CppParser.Integer, CppParser.Enum, CppParser.BuiltinInteger)):
elif isinstance(self.kind, (CppParser.Integer, CppParser.Float, CppParser.Enum, CppParser.BuiltinInteger)):
return "Core::Frame::RealSize<%s>()" % self.type_name
elif isinstance(self.kind, CppParser.Bool):
return 1 # always one byte
Expand Down