diff options
Diffstat (limited to 'generator/plugins/rust/rust_structs.py')
-rw-r--r-- | generator/plugins/rust/rust_structs.py | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/generator/plugins/rust/rust_structs.py b/generator/plugins/rust/rust_structs.py new file mode 100644 index 0000000..571cd62 --- /dev/null +++ b/generator/plugins/rust/rust_structs.py @@ -0,0 +1,460 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Dict, Iterable, List, Optional + +import generator.model as model + +from .rust_commons import ( + TypeData, + fix_lsp_method_name, + generate_extras, + generate_literal_struct_name, + generate_property, + get_extended_properties, + get_message_type_name, + get_type_name, + struct_wrapper, + type_alias_wrapper, +) +from .rust_lang_utils import get_parts, lines_to_doc_comments, to_upper_camel_case + + +def generate_type_aliases(spec: model.LSPModel, types: TypeData) -> None: + for alias in spec.typeAliases: + if not types.has_id(alias): + generate_type_alias(alias, types, spec) + + +def _get_doc(doc: Optional[str]) -> str: + if doc: + return lines_to_doc_comments(doc.splitlines(keepends=False)) + return [] + + +def _is_some_array_type(items: Iterable[model.LSP_TYPE_SPEC]) -> bool: + items_list = list(items) + assert len(items_list) == 2 + item1, item2 = items_list + + if item1.kind == "array" and item2.kind == "reference": + return item1.element.kind == "reference" and item1.element.name == item2.name + + if item2.kind == "array" and item1.kind == "reference": + return item2.element.kind == "reference" and item2.element.name == item1.name + return False + + +def _get_some_array_code( + items: Iterable[model.LSP_TYPE_SPEC], + types: Dict[str, List[str]], + spec: model.LSPModel, +) -> List[str]: + assert _is_some_array_type(items) + items_list = list(items) + item1 = items_list[0] + item2 = items_list[1] + + if item1.kind == "array" and item2.kind == "reference": + return [ + f" One({get_type_name(item2, types, spec)}),", + f" Many({get_type_name(item1, types, spec)}),", + ] + + if item2.kind == "array" and item1.kind == "reference": + return [ + f" One({get_type_name(item1, types, spec)}),", + f" Many({get_type_name(item2, types, spec)}),", + ] + return [] + + +def _get_common_name(items: Iterable[model.LSP_TYPE_SPEC], kind: str) -> List[str]: + names = [get_parts(item.name) for item in list(items) if item.kind == kind] + if len(names) < 2: + return [] + + smallest = min(names, key=len) + common = [] + for i in range(len(smallest)): + if all(name[i] == smallest[i] for name in names): + common.append(smallest[i]) + return common + + +def _is_all_reference_similar_type(alias: model.TypeAlias) -> bool: + items_list = list(alias.type.items) + return all(item.kind in ["reference", "base", "literal"] for item in items_list) + + +def _get_all_reference_similar_code( + alias: model.TypeAlias, + types: TypeData, + spec: model.LSPModel, +) -> List[str]: + items = alias.type.items + assert _is_all_reference_similar_type(alias) + + # Ensure all literal types have a name + for item in list(items): + if item.kind == "literal": + get_type_name(item, types, spec, None, alias.name) + + common_name = [ + i.lower() + for i in ( + _get_common_name(items, "reference") + + _get_common_name(items, "literal") + + ["struct"] + ) + ] + + lines = [] + value = 0 + field_names = [] + for item in list(items): + if item.kind == "base" and item.name == "null": + lines += ["None,"] + field_names += ["None"] + elif item.kind == "base": + name = _base_to_field_name(item.name) + lines += [f"{name}({get_type_name(item, types, spec)}),"] + field_names += [name] + elif item.kind == "reference": + name = [ + part for part in get_parts(item.name) if part.lower() not in common_name + ] + if len(name) == 0: + name = [f"Value{value}"] + value += 1 + common_name += [n.lower() for n in name] + name = to_upper_camel_case("".join(name)) + field_names += [name] + lines += [f"{name}({get_type_name(item, types, spec)}),"] + elif item.kind == "literal": + name = [ + part for part in get_parts(item.name) if part.lower() not in common_name + ] + optional_props = [p for p in item.value.properties if p.optional] + required_props = [p for p in item.value.properties if not p.optional] + + # Try picking a name using required props first and then optional props + if len(name) == 0: + for p in required_props + optional_props: + name = [ + part + for part in get_parts(p.name) + if part.lower() not in common_name + ] + if len(name) != 0: + break + + # If we still don't have a name, then try picking a name using required props + # and then optional props without checking for common name list. But check + # that the name is not already used. + if len(name) == 0: + for p in required_props + optional_props: + if to_upper_camel_case(p.name) not in field_names: + name = get_parts(p.name) + break + + # If we still don't have a name, then just use a generic "Value{int}" as name + if len(name) == 0: + name = [f"Value{value}"] + value += 1 + common_name += [n.lower() for n in name] + name = to_upper_camel_case("".join(name)) + field_names += [name] + lines += [f"{name}({item.name}),"] + else: + raise ValueError(f"Unknown type {item}") + return lines + + +def _base_to_field_name(base_name: str) -> str: + if base_name == "boolean": + return "Bool" + if base_name == "integer": + return "Int" + if base_name == "decimal": + return "Real" + if base_name == "string": + return "String" + if base_name == "uinteger": + return "UInt" + if base_name == "null": + return "None" + raise ValueError(f"Unknown base type {base_name}") + + +def _get_literal_field_name(literal: model.LiteralType, types: TypeData) -> str: + properties = list(literal.value.properties) + + if len(properties) == 1 and properties[0].kind == "base": + return _base_to_field_name(properties[0].name) + + if len(properties) == 1 and properties[0].kind == "reference": + return to_upper_camel_case(properties[0].name) + + return generate_literal_struct_name(literal, types) + + +def _generate_or_type_alias( + alias_def: model.TypeAlias, types: Dict[str, List[str]], spec: model.LSPModel +) -> List[str]: + inner = [] + + if len(alias_def.type.items) == 2 and _is_some_array_type(alias_def.type.items): + inner += _get_some_array_code(alias_def.type.items, types, spec) + elif _is_all_reference_similar_type(alias_def): + inner += _get_all_reference_similar_code(alias_def, types, spec) + else: + index = 0 + + for sub_type in alias_def.type.items: + if sub_type.kind == "base" and sub_type.name == "null": + inner += [f"None,"] + else: + inner += [f"ValueType{index}({get_type_name(sub_type, types, spec)}),"] + index += 1 + return type_alias_wrapper(alias_def, inner) + + +def generate_type_alias( + alias_def: model.TypeAlias, types: TypeData, spec: model.LSPModel +) -> List[str]: + doc = _get_doc(alias_def.documentation) + doc += generate_extras(alias_def) + + lines = [] + if alias_def.type.kind == "reference": + lines += doc + lines += [f"pub type {alias_def.name} = {alias_def.type.name};"] + elif alias_def.type.kind == "array": + lines += doc + lines += [ + f"pub type {alias_def.name} = {get_type_name(alias_def.type, types, spec)};" + ] + elif alias_def.type.kind == "or": + lines += _generate_or_type_alias(alias_def, types, spec) + elif alias_def.type.kind == "and": + raise ValueError("And type not supported") + elif alias_def.type.kind == "literal": + lines += doc + lines += [ + f"pub type {alias_def.name} = {get_type_name(alias_def.type, types, spec)};" + ] + elif alias_def.type.kind == "base": + lines += doc + lines += [ + f"pub type {alias_def.name} = {get_type_name(alias_def.type, types, spec)};" + ] + else: + pass + + types.add_type_info(alias_def, alias_def.name, lines) + + +def generate_structures(spec: model.LSPModel, types: TypeData) -> Dict[str, List[str]]: + for struct in spec.structures: + if not types.has_id(struct): + generate_struct(struct, types, spec) + return types + + +def generate_struct( + struct_def: model.Structure, types: TypeData, spec: model.LSPModel +) -> None: + inner = [] + for prop_def in get_extended_properties(struct_def, spec): + inner += generate_property(prop_def, types, spec) + + lines = struct_wrapper(struct_def, inner) + types.add_type_info(struct_def, struct_def.name, lines) + + +def generate_notifications( + spec: model.LSPModel, types: TypeData +) -> Dict[str, List[str]]: + for notification in spec.notifications: + if not types.has_id(notification): + generate_notification(notification, types, spec) + return types + + +def required_rpc_properties(name: Optional[str] = None) -> List[model.Property]: + props = [ + model.Property( + name="jsonrpc", + type=model.BaseType(kind="base", name="string"), + optional=False, + documentation="The version of the JSON RPC protocol.", + ), + ] + if name: + props += [ + model.Property( + name="method", + type=model.ReferenceType(kind="reference", name=name), + optional=False, + documentation="The method to be invoked.", + ), + ] + return props + + +def generate_notification( + notification_def: model.Notification, types: TypeData, spec: model.LSPModel +) -> None: + properties = required_rpc_properties("LSPNotificationMethods") + if notification_def.params: + properties += [ + model.Property( + name="params", + type=notification_def.params, + ) + ] + + inner = [] + for prop_def in properties: + inner += generate_property(prop_def, types, spec) + + lines = struct_wrapper(notification_def, inner) + types.add_type_info( + notification_def, get_message_type_name(notification_def), lines + ) + + +def generate_required_request_types( + spec: model.LSPModel, types: TypeData +) -> Dict[str, List[str]]: + lsp_id = model.TypeAlias( + name="LSPId", + documentation="An identifier to denote a specific request.", + type=model.OrType( + kind="or", + items=[ + model.BaseType(kind="base", name="integer"), + model.BaseType(kind="base", name="string"), + ], + ), + ) + generate_type_alias(lsp_id, types, spec) + + lsp_id_optional = model.TypeAlias( + name="LSPIdOptional", + documentation="An identifier to denote a specific response.", + type=model.OrType( + kind="or", + items=[ + model.BaseType(kind="base", name="integer"), + model.BaseType(kind="base", name="string"), + model.BaseType(kind="base", name="null"), + ], + ), + ) + generate_type_alias(lsp_id_optional, types, spec) + + +def generate_requests(spec: model.LSPModel, types: TypeData) -> Dict[str, List[str]]: + generate_required_request_types(spec, types) + for request in spec.requests: + if not types.has_id(request): + generate_request(request, types, spec) + generate_response(request, types, spec) + generate_partial_result(request, types, spec) + generate_registration_options(request, types, spec) + return types + + +def generate_request( + request_def: model.Request, types: TypeData, spec: model.LSPModel +) -> None: + properties = required_rpc_properties("LSPRequestMethods") + + properties += [ + model.Property( + name="id", + type=model.ReferenceType(kind="reference", name="LSPId"), + optional=False, + documentation="The request id.", + ) + ] + if request_def.params: + properties += [ + model.Property( + name="params", + type=request_def.params, + ) + ] + + inner = [] + for prop_def in properties: + inner += generate_property(prop_def, types, spec) + + lines = struct_wrapper(request_def, inner) + types.add_type_info(request_def, get_message_type_name(request_def), lines) + + +def generate_response( + request_def: model.Request, types: TypeData, spec: model.LSPModel +) -> None: + properties = required_rpc_properties("LSPRequestMethods") + properties += [ + model.Property( + name="id", + type=model.ReferenceType(kind="reference", name="LSPIdOptional"), + optional=False, + documentation="The request id.", + ) + ] + if request_def.result: + if request_def.result.kind == "base" and request_def.result.name == "null": + properties += [ + model.Property( + name="result", + type=model.ReferenceType(kind="reference", name="LSPNull"), + ) + ] + else: + properties += [ + model.Property( + name="result", + type=request_def.result, + ) + ] + name = fix_lsp_method_name(request_def.method) + response_def = model.Structure( + name=f"{name}Response", + documentation=f"Response to the [{name}Request].", + properties=properties, + since=request_def.since, + deprecated=request_def.deprecated, + ) + + inner = [] + for prop_def in properties: + inner += generate_property(prop_def, types, spec) + + lines = struct_wrapper(response_def, inner) + types.add_type_info(response_def, response_def.name, lines) + + +def generate_partial_result( + request_def: model.Request, types: TypeData, spec: model.LSPModel +) -> None: + if not request_def.partialResult: + return + + if request_def.partialResult.kind not in ["and", "or"]: + return + + +def generate_registration_options( + request_def: model.Request, types: TypeData, spec: model.LSPModel +) -> None: + if not request_def.registrationOptions: + return + + if request_def.registrationOptions.kind not in ["and", "or"]: + return |