Skip to content
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
180 changes: 177 additions & 3 deletions aas_core_codegen/cpp/lib/_generate_jsonization.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,29 @@ class ISegment {{
),
Stripped(
f"""\
/**
* Represent an access to a member of a JSON object whose key is known only at
* run time, such as a member of a JSON-able object, on a JSON path.
*/
struct KeySegment : public ISegment {{
{I}/**
{I} * Key of the member in a JSON object
{I} */
{I}std::wstring key;

{I}explicit KeySegment(
{II}std::wstring a_key
{I});

{I}std::wstring ToWstring() const override;

{I}std::unique_ptr<ISegment> Clone() const override;

{I}~KeySegment() override = default;
}}; // struct KeySegment"""
),
Stripped(
f"""\
/**
* Represent a JSON path to some value.
*/
Expand Down Expand Up @@ -306,6 +329,101 @@ class SerializationException : public std::exception {{
return writer.getvalue()


def _generate_json_path_helpers() -> List[Stripped]:
"""Generate the functions shared by the segments of a JSON path."""
return [
Stripped("// region JSON path helpers"),
Stripped(
f"""\
/**
* \\brief Escape \\p text as the content of a JSON string.
*
* The control characters without a short escape are written as Unicode
* escapes, as JSON.stringify does.
*/
std::wstring EscapeForJsonString(
{I}const std::wstring& text
) {{
{I}static const wchar_t* const kHexDigits = L"0123456789abcdef";

{I}std::wstring escaped;
{I}escaped.reserve(text.size());
{I}for (const wchar_t character : text) {{
{II}switch (character) {{
{III}case L'\\\\':
{IIII}escaped.append(L"\\\\\\\\");
{IIII}break;
{III}case L'"':
{IIII}escaped.append(L"\\\\\\"");
{IIII}break;
{III}case L'\\b':
{IIII}escaped.append(L"\\\\b");
{IIII}break;
{III}case L'\\f':
{IIII}escaped.append(L"\\\\f");
{IIII}break;
{III}case L'\\n':
{IIII}escaped.append(L"\\\\n");
{IIII}break;
{III}case L'\\r':
{IIII}escaped.append(L"\\\\r");
{IIII}break;
{III}case L'\\t':
{IIII}escaped.append(L"\\\\t");
{IIII}break;
{III}default:
{IIII}if (static_cast<uint32_t>(character) < 0x20) {{
{IIIII}escaped.append(L"\\\\u00");
{IIIII}escaped.push_back(kHexDigits[(character >> 4) & 0xF]);
{IIIII}escaped.push_back(kHexDigits[character & 0xF]);
{IIII}}} else {{
{IIIII}escaped.push_back(character);
{IIII}}}
{IIII}break;
{II}}}
{I}}}

{I}return escaped;
}}"""
),
Stripped(
f"""\
/**
* \\brief Check whether a JSON path writes the property \\p name after a dot.
*
* The other names are written in brackets as JSON strings,
* <em>e.g.</em>, <code>["$eq"]</code>.
*/
bool IsVariableName(
{I}const std::wstring& name
) {{
{I}if (name.empty()) {{
{II}return false;
{I}}}

{I}for (size_t i = 0; i < name.size(); ++i) {{
{II}const wchar_t character = name[i];

{II}const bool is_letter_or_underscore = (
{III}(character >= L'a' && character <= L'z')
{III}|| (character >= L'A' && character <= L'Z')
{III}|| character == L'_'
{II});

{II}const bool is_digit = character >= L'0' && character <= L'9';

{II}if (!(is_letter_or_underscore || (i > 0 && is_digit))) {{
{III}return false;
{II}}}
{I}}}

{I}return true;
}}"""
),
Stripped("// endregion JSON path helpers"),
]


def _generate_property_segment_implementation() -> List[Stripped]:
"""Generate the implementation of the struct ``PropertySegment``."""
return [
Expand All @@ -322,9 +440,17 @@ def _generate_property_segment_implementation() -> List[Stripped]:
Stripped(
f"""\
std::wstring PropertySegment::ToWstring() const {{
{I}if (IsVariableName(name)) {{
{II}return common::Concat(
{III}L".",
{III}name
{II});
{I}}}

{I}return common::Concat(
{II}L".",
{II}name
{II}L"[\\"",
{II}EscapeForJsonString(name),
{II}L"\\"]"
{I});
}}"""
),
Expand Down Expand Up @@ -371,6 +497,42 @@ def _generate_index_segment_implementation() -> List[Stripped]:
]


def _generate_key_segment_implementation() -> List[Stripped]:
"""Generate the implementation of the struct ``KeySegment``."""
return [
Stripped("// region KeySegment"),
Stripped(
f"""\
KeySegment::KeySegment(
{I}std::wstring a_key
) :
{I}key(std::move(a_key)) {{
{I}// Intentionally empty.
}}"""
),
Stripped(
f"""\
std::wstring KeySegment::ToWstring() const {{
{I}// NOTE (mristin):
{I}// A key can be any string at all, so we always write it as a JSON string in
{I}// brackets, as in the other SDKs.
{I}return common::Concat(
{II}L"[\\"",
{II}EscapeForJsonString(key),
{II}L"\\"]"
{I});
}}"""
),
Stripped(
f"""\
std::unique_ptr<ISegment> KeySegment::Clone() const {{
{I}return common::make_unique<KeySegment>(*this);
}}"""
),
Stripped("// endregion KeySegment"),
]


def _generate_path_implementation() -> List[Stripped]:
"""Generate the implementation of the struct ``Path``."""
return [
Expand Down Expand Up @@ -426,6 +588,16 @@ def _generate_path_implementation() -> List[Stripped]:
{II}parts.emplace_back(segment->ToWstring());
{I}}}

{I}// NOTE (mristin):
{I}// The path starts with the name of the first property without a dot, as in
{I}// the other SDKs, *e.g.*, ``items[0].name``.
{I}const PropertySegment* first_property(
{II}dynamic_cast<const PropertySegment*>(segments.front().get())
{I});
{I}if (first_property != nullptr && IsVariableName(first_property->name)) {{
{II}parts.front() = first_property->name;
{I}}}

{I}size_t out_len = 0;
{I}for (const std::wstring& part : parts) {{
{II}out_len += part.size();
Expand Down Expand Up @@ -903,7 +1075,7 @@ def _generate_deserialize_json_value() -> Stripped:

{III}if (error.has_value()) {{
{IIII}error->path.segments.emplace_front(
{IIIII}common::make_unique<PropertySegment>(
{IIIII}common::make_unique<KeySegment>(
{IIIIII}common::Utf8ToWstring(item.key())
{IIIII})
{IIII});
Expand Down Expand Up @@ -5596,8 +5768,10 @@ def generate_implementation(
#pragma warning(pop)"""
),
cpp_common.generate_namespace_opening(namespace),
*_generate_json_path_helpers(),
*_generate_property_segment_implementation(),
*_generate_index_segment_implementation(),
*_generate_key_segment_implementation(),
*_generate_path_implementation(),
Stripped("// region De-serialization"),
*_generate_deserialization_error_implementation(),
Expand Down
19 changes: 18 additions & 1 deletion aas_core_codegen/csharp/lib/_generate_jsonization.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,17 @@ def _generate_model_type_from_helper() -> Stripped:
{II}return default!;
{I}}}

{I}return StringFrom(modelTypeNode, out error);
{I}string modelType = StringFrom(modelTypeNode, out error);
{I}if (error != null)
{I}{{
{II}// NOTE (mristin):
{II}// A model type of a wrong type is reported at the property, as the type
{II}// errors of all the other properties are, and as in the other SDKs.
{II}error.PrependSegment(
{III}new Reporting.NameSegment("modelType"));
{II}return default!;
{I}}}
{I}return modelType;
}}"""
)

Expand Down Expand Up @@ -683,6 +693,8 @@ def _generate_from_method_for_named_union(
{I}string modelType = StringFrom(modelTypeNode, out error);
{I}if (error != null)
{I}{{
{II}error.PrependSegment(
{III}new Reporting.NameSegment("modelType"));
{II}return default!;
{I}}}

Expand Down Expand Up @@ -1033,9 +1045,14 @@ def _generate_from_method_for_class(
{II}keyValue.Value, out error);
{I}if (error == null && modelType != "{model_type}")
{I}{{
{II}// NOTE (mristin):
{II}// A wrong model type is reported at the instance, as an unknown one is,
{II}// and as in the other SDKs, so we return before the property is
{II}// prepended to the path.
{II}error = new Reporting.Error(
{III}"Expected the model type '{model_type}', " +
{III}$"but got {{modelType}}");
{II}return default!;
{I}}}
{I}break;"""
)
Expand Down
52 changes: 42 additions & 10 deletions aas_core_codegen/csharp/lib/_generate_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,48 @@ def generate(namespace: csharp_common.NamespaceIdentifier) -> str:
private static string EscapeForJsonString(
{I}string text)
{{
{I}return (
{II}text
{III}.Replace("\\\\", "\\\\\\\\")
{III}.Replace("\\"", "\\\\\\"")
{III}.Replace("\\b", "\\\\b")
{III}.Replace("\\f", "\\\\f")
{III}.Replace("\\n", "\\\\n")
{III}.Replace("\\r", "\\\\r")
{III}.Replace("\\t", "\\\\t")
{I});
{I}var builder = new System.Text.StringBuilder(text.Length);
{I}foreach (char character in text)
{I}{{
{II}switch (character)
{II}{{
{III}case '\\\\':
{IIII}builder.Append("\\\\\\\\");
{IIII}break;
{III}case '"':
{IIII}builder.Append("\\\\\\"");
{IIII}break;
{III}case '\\b':
{IIII}builder.Append("\\\\b");
{IIII}break;
{III}case '\\f':
{IIII}builder.Append("\\\\f");
{IIII}break;
{III}case '\\n':
{IIII}builder.Append("\\\\n");
{IIII}break;
{III}case '\\r':
{IIII}builder.Append("\\\\r");
{IIII}break;
{III}case '\\t':
{IIII}builder.Append("\\\\t");
{IIII}break;
{III}default:
{IIII}// NOTE (mristin):
{IIII}// The other control characters can not stand in a JSON string as they
{IIII}// are, so we write them as Unicode escapes, as JSON.stringify does.
{IIII}if (character < 0x20)
{IIII}{{
{IIIII}builder.Append($"\\\\u{{(int)character:x4}}");
{IIII}}}
{IIII}else
{IIII}{{
{IIIII}builder.Append(character);
{IIII}}}
{IIII}break;
{II}}}
{I}}}
{I}return builder.ToString();
}}"""
),
Stripped(
Expand Down
5 changes: 4 additions & 1 deletion aas_core_codegen/golang/lib/_generate_jsonization.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,13 +345,16 @@ def _generate_check_model_type() -> Stripped:
{I}}}

{I}if modelType != expected {{
{II}// NOTE (mristin):
{II}// A wrong model type is reported at the instance, as an unknown one is,
{II}// and as in the other SDKs.
{II}err = newDeserializationError(
{III}fmt.Sprintf(
{IIII}"Expected the model type '%s', but got %s",
{IIII}expected,
{IIII}modelType,
{III}),
{II}).prependName("modelType")
{II})
{I}}}
{I}return
}}"""
Expand Down
Loading
Loading