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

Feature: optional dictionaries, also with polymorphic types #34

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
24 changes: 23 additions & 1 deletion code-gen/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
# poly-scribe-code-gen

Generate code for serializing data structures from WebIDL definitions.
Generate code for serializing data structures from WebIDL definitions.

## Documentation

### Fixed size arrays

In order to get a fixed-size array or vector, the extra argument `Size` can be used.
For example, an array with 4 `double` can be defined as:

```
[Size=4] sequence<double> four_elements;
```

### Polymorphic default values

Default values for polymorphic types are tricky.
The syntax is as follows:

```
[Default=DerivedType] BaseType default_poly = {};
```

However for this to work correctly, all members of the DerivedType must either have a default value or be optional/nullable.
3 changes: 3 additions & 0 deletions code-gen/src/poly_scribe_code_gen/cpp_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ def _transformer(type_input):
for member in struct["members"]:
member["type"] = _transformer(member["type"])

if "shared_ptr" in member["type"] and isinstance(member["default"], dict):
member["default"] = f"std::make_shared<{member["default"]["value"]}>()"

for type_def in parsed_idl["type_defs"]:
type_def["type"] = _transformer(type_def["type"])

Expand Down
3 changes: 3 additions & 0 deletions code-gen/src/poly_scribe_code_gen/matlab_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ def _matlab_transformer(type_input):
else:
member["default"] = f"cell{variable_shape}"

if isinstance(member["default"], dict):
member["default"] = member["default"]["value"]

member["validation"] = {
"must_be": ", ".join(f'"{t}"' for t in foo[0]),
"size": variable_shape,
Expand Down
17 changes: 16 additions & 1 deletion code-gen/src/poly_scribe_code_gen/parse_idl.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,28 @@ def _flatten_members(members):
output = []
for member in members:
if member["type"] == "field":
default_value = None
if member["default"]:
if member["default"]["type"] == "dictionary":
default_value_attr = [x for x in member["ext_attrs"] if x["name"] == "Default"]
if len(default_value_attr) > 1:
raise LookupError("Multiple default values found.")
if default_value_attr:
default_value = {"value": default_value_attr[0]["rhs"]["value"]}
elif member["default"]["type"] == "sequence":
pass
elif member["default"]["type"] == "string":
default_value = f'"{member["default"]["value"]}"'
elif member["default"]["value"]:
default_value = member["default"]["value"]

output.append(
{
"name": member["name"],
"ext_attrs": member["ext_attrs"],
"type": _flatten_type(member["idl_type"]),
"required": True if member["required"] == "true" else False,
"default": member["default"]["value"] if member["default"] and member["default"]["value"] else None,
"default": default_value,
}
)
output[-1]["type"]["ext_attrs"] = output[-1]["type"]["ext_attrs"] + member["ext_attrs"]
Expand Down
9 changes: 6 additions & 3 deletions code-gen/src/poly_scribe_code_gen/py_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,11 @@ def _transformer(type_input):
if extra_data.polymorphic:
member["type"] = f"Annotated[{member['type']}, Field(discriminator=\"type\")]"

for _, derived_types in parsed_idl["inheritance_data"].items():
if struct["name"] in derived_types:
if isinstance(member["default"], dict):
member["default"] = f"{member["default"]["value"]}()"

for base_type, derived_types in parsed_idl["inheritance_data"].items():
if (struct["name"] in derived_types or struct["name"] == base_type) and len(derived_types) > 1:
# check if there is no member in struct is already named "type"
if not any(member["name"] == "type" for member in struct["members"]):
struct_name = struct["name"]
Expand All @@ -140,7 +143,7 @@ def _transformer(type_input):
"name": "type",
"type": f'Literal["{struct_name}"]',
"extra_data": ExtraData(),
"default": f"{struct_name}",
"default": f"\"{struct_name}\"",
}
)

Expand Down
2 changes: 1 addition & 1 deletion code-gen/src/poly_scribe_code_gen/templates/python.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class {{ enum.name }}(IntEnum):
{% for struct in structs%}
class {{ struct.name }}{% if struct.inheritance %}({{ struct.inheritance }}){% else %}(BaseModel){% endif %}:
{% for member in struct.members %}
{{ member.name }}: {{ member.type }}{% if member.default %} = "{{ member.default }}"{% endif +%}
{{ member.name }}: {{ member.type }}{% if member.default %} = {{ member.default }}{% endif +%}
{% endfor %}
{% if not struct.members %}
pass
Expand Down
25 changes: 25 additions & 0 deletions test/integration.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "poly-scribe-structs/integration_data.hpp"

Check failure on line 1 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, msvc, Visual Studio 17 2022, Debug)

Cannot open include file: 'poly-scribe-structs/integration_data.hpp': No such file or directory [D:\a\poly-scribe\poly-scribe\build\test\tests.vcxproj]

Check failure on line 1 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, msvc, Visual Studio 17 2022, Release)

Cannot open include file: 'poly-scribe-structs/integration_data.hpp': No such file or directory [D:\a\poly-scribe\poly-scribe\build\test\tests.vcxproj]

Check failure on line 1 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Release)

'poly-scribe-structs/integration_data.hpp' file not found [clang-diagnostic-error]

Check failure on line 1 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Debug)

'poly-scribe-structs/integration_data.hpp' file not found [clang-diagnostic-error]
#include "test-helper.hpp"

#include <catch2/catch_test_macros.hpp>
Expand All @@ -17,7 +17,7 @@
/// \param t_lhs left hand side
/// \param t_rhs right hand side
///
void compare_derived_one( const integration_space::DerivedOne& t_lhs, const integration_space::DerivedOne& t_rhs )

Check failure on line 20 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Release)

2 adjacent parameters of 'compare_derived_one' of similar type ('const int &') are easily swapped by mistake [bugprone-easily-swappable-parameters,-warnings-as-errors]

Check failure on line 20 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Debug)

2 adjacent parameters of 'compare_derived_one' of similar type ('const int &') are easily swapped by mistake [bugprone-easily-swappable-parameters,-warnings-as-errors]
{
REQUIRE( t_lhs.string_map == t_rhs.string_map );
}
Expand All @@ -27,7 +27,7 @@
/// \param t_lhs left hand side
/// \param t_rhs right hand side
///
void compare_derived_two( const integration_space::DerivedTwo& t_lhs, const integration_space::DerivedTwo& t_rhs )

Check failure on line 30 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Release)

2 adjacent parameters of 'compare_derived_two' of similar type ('const int &') are easily swapped by mistake [bugprone-easily-swappable-parameters,-warnings-as-errors]

Check failure on line 30 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Debug)

2 adjacent parameters of 'compare_derived_two' of similar type ('const int &') are easily swapped by mistake [bugprone-easily-swappable-parameters,-warnings-as-errors]
{
REQUIRE( t_lhs.optional_value == t_rhs.optional_value );
}
Expand All @@ -37,7 +37,7 @@
/// \param t_lhs left hand side pointer
/// \param t_rhs right hand side pointer
///
void compare_pointers_to_base_type( const std::shared_ptr<integration_space::Base>& t_lhs, const std::shared_ptr<integration_space::Base>& t_rhs )

Check failure on line 40 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Release)

2 adjacent parameters of 'compare_pointers_to_base_type' of similar type ('const int &') are easily swapped by mistake [bugprone-easily-swappable-parameters,-warnings-as-errors]

Check failure on line 40 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Debug)

2 adjacent parameters of 'compare_pointers_to_base_type' of similar type ('const int &') are easily swapped by mistake [bugprone-easily-swappable-parameters,-warnings-as-errors]
{
REQUIRE( t_lhs->vec_3d == t_rhs->vec_3d );
// REQUIRE( t_lhs->union_member == t_rhs->union_member );
Expand Down Expand Up @@ -66,11 +66,11 @@
/// \param t_lhs left hand side json value
/// \param t_rhs right hand side pointer
///
void compare_json_to_base( const rapidjson::Value& t_lhs, const std::shared_ptr<integration_space::Base>& t_rhs )

Check failure on line 69 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Release)

2 adjacent parameters of 'compare_json_to_base' of similar type are easily swapped by mistake [bugprone-easily-swappable-parameters,-warnings-as-errors]

Check failure on line 69 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Debug)

2 adjacent parameters of 'compare_json_to_base' of similar type are easily swapped by mistake [bugprone-easily-swappable-parameters,-warnings-as-errors]
{
REQUIRE( t_lhs.HasMember( "vec_3d" ) );
REQUIRE( t_lhs["vec_3d"].IsArray( ) );

Check failure on line 72 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Release)

confusing array subscript expression, usually the index is inside the [] [readability-misplaced-array-index,-warnings-as-errors]

Check failure on line 72 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Release)

do not use array subscript when the index is not an integer constant expression [cppcoreguidelines-pro-bounds-constant-array-index,-warnings-as-errors]

Check failure on line 72 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Debug)

confusing array subscript expression, usually the index is inside the [] [readability-misplaced-array-index,-warnings-as-errors]

Check failure on line 72 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Debug)

do not use array subscript when the index is not an integer constant expression [cppcoreguidelines-pro-bounds-constant-array-index,-warnings-as-errors]
REQUIRE( t_lhs["vec_3d"].Size( ) == 3 );

Check failure on line 73 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Release)

confusing array subscript expression, usually the index is inside the [] [readability-misplaced-array-index,-warnings-as-errors]

Check failure on line 73 in test/integration.cpp

View workflow job for this annotation

GitHub Actions / Test (windows-2022, llvm-16.0.2, Ninja Multi-Config, Debug)

confusing array subscript expression, usually the index is inside the [] [readability-misplaced-array-index,-warnings-as-errors]
REQUIRE( t_lhs["vec_3d"][0].IsDouble( ) );
REQUIRE( t_lhs["vec_3d"][0].GetDouble( ) == t_rhs->vec_3d[0] );
REQUIRE( t_lhs["vec_3d"][1].IsDouble( ) );
Expand Down Expand Up @@ -182,18 +182,21 @@
REQUIRE( t_json_value.HasMember( "object_array" ) );
REQUIRE( t_json_value.HasMember( "enum_value" ) );
REQUIRE( t_json_value.HasMember( "non_poly_derived" ) );
REQUIRE( t_json_value.HasMember( "optional_poly" ) );

rapidjson::Value json_object_map;
rapidjson::Value json_object_vec;
rapidjson::Value json_object_array;
rapidjson::Value json_enum_value;
rapidjson::Value json_non_poly_derived;
rapidjson::Value json_optional_poly;

REQUIRE_NOTHROW( json_object_map = t_json_value["object_map"] );
REQUIRE_NOTHROW( json_object_vec = t_json_value["object_vec"] );
REQUIRE_NOTHROW( json_object_array = t_json_value["object_array"] );
REQUIRE_NOTHROW( json_enum_value = t_json_value["enum_value"] );
REQUIRE_NOTHROW( json_non_poly_derived = t_json_value["non_poly_derived"] );
REQUIRE_NOTHROW( json_optional_poly = t_json_value["optional_poly"] );

REQUIRE( json_object_map.IsObject( ) );

Expand All @@ -211,6 +214,17 @@
REQUIRE( json_non_poly_derived["value"].IsInt( ) );
REQUIRE( json_non_poly_derived["value"].GetInt( ) == t_data.non_poly_derived.value );

REQUIRE( json_optional_poly.IsObject( ) );
REQUIRE( json_optional_poly.HasMember( "type" ) );
REQUIRE( json_optional_poly["type"].IsString( ) );
REQUIRE( json_optional_poly["type"].GetString( ) == std::string( "OptionalPolyDerived2" ) );
REQUIRE( json_optional_poly.HasMember( "value" ) );
REQUIRE( json_optional_poly["value"].IsInt( ) );
REQUIRE( json_optional_poly["value"].GetInt( ) == 100 );
REQUIRE( json_optional_poly.HasMember( "name" ) );
REQUIRE( json_optional_poly["name"].IsString( ) );
REQUIRE( json_optional_poly["name"].GetString( ) == std::string( "default" ) );

for( const auto& [key, value]: t_data.object_map )
{
REQUIRE( json_object_map.HasMember( key.c_str( ) ) );
Expand Down Expand Up @@ -309,6 +323,12 @@
auto data = generate_random_integration_dict( );
auto name = GENERATE_RANDOM_STRING( 10 );

// test the default values
const auto casted_optional_poly = std::dynamic_pointer_cast<integration_space::OptionalPolyDerived2>( data.optional_poly );
REQUIRE( casted_optional_poly );
REQUIRE( casted_optional_poly->value == 100 );
REQUIRE( casted_optional_poly->name == "default" );

std::stringstream string_stream;
{
cereal::JSONOutputArchive archive( string_stream ); // NOLINT(misc-const-correctness)
Expand All @@ -322,6 +342,11 @@
archive( poly_scribe::make_scribe_wrap( name, read_object ) );
}

const auto casted_optional_poly_read = std::dynamic_pointer_cast<integration_space::OptionalPolyDerived2>( read_object.optional_poly );
REQUIRE( casted_optional_poly_read );
REQUIRE( casted_optional_poly_read->value == 100 );
REQUIRE( casted_optional_poly_read->name == "default" );

// test that all members of read_object are equal the members in data
REQUIRE( data.object_map.size( ) == read_object.object_map.size( ) );
REQUIRE( data.object_vec.size( ) == read_object.object_vec.size( ) );
Expand Down
17 changes: 17 additions & 0 deletions test/integration.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ dictionary NonPolyDerived : NonPolyBase
int value;
};

dictionary OptionalPolyBase
{
};

dictionary OptionalPolyDerived : OptionalPolyBase
{
int value = 42;
};

dictionary OptionalPolyDerived2 : OptionalPolyBase
{
int value = 100;
string name = "default";
};

dictionary IntegrationTest
{
record<ByteString, Base> object_map;
Expand All @@ -42,4 +57,6 @@ dictionary IntegrationTest
Enumeration enum_value;

NonPolyDerived non_poly_derived;

[Default=OptionalPolyDerived2] OptionalPolyBase optional_poly = {};
};
Loading