diff --git a/.vscode/cspell.json b/.vscode/cspell.json index a2df8ebc..2ebc0921 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -23,6 +23,7 @@ "nonoverlapping", "nonterminal", "peekable", + "repr", "rfind", "rsplit", "RUSTDOCFLAGS", diff --git a/slice-codec/src/slice2/encoding.rs b/slice-codec/src/slice2/encoding.rs index 75421634..e3966d66 100644 --- a/slice-codec/src/slice2/encoding.rs +++ b/slice-codec/src/slice2/encoding.rs @@ -6,9 +6,14 @@ use crate::encode_into::*; use crate::encoder::Encoder; use crate::{Error, InvalidDataErrorKind, Result}; -// We only support `BTreeMap` if the `alloc` crate is available through the `alloc` feature flag. +// We only support 'owned' sequence/dictionary types if the `alloc` crate is available through the `alloc` feature flag. +// Note that we always support encoding views into these types (which don't require allocating memory). #[cfg(feature = "alloc")] use alloc::collections::BTreeMap; +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; // We only support `HashMap` if the standard library is available through the `std` feature flag. #[cfg(feature = "std")] @@ -151,6 +156,13 @@ impl EncodeInto for &str { } } +#[cfg(feature = "alloc")] +impl EncodeInto for &String { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + self.as_str().encode_into(encoder) + } +} + /// TODO impl<'a, T> EncodeInto for &'a [T] where @@ -166,6 +178,16 @@ where } } +#[cfg(feature = "alloc")] +impl<'a, T> EncodeInto for &'a Vec +where + &'a T: EncodeInto, +{ + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + self.as_slice().encode_into(encoder) + } +} + // ============================================================================= // Dictionary type implementations // ============================================================================= diff --git a/slicec/src/definition_types.rs b/slicec/src/definition_types.rs new file mode 100644 index 00000000..89b7f97e --- /dev/null +++ b/slicec/src/definition_types.rs @@ -0,0 +1,332 @@ +// Copyright (c) ZeroC, Inc. + +//! This module contains the handwritten encoding code for the Slice-compiler definitions. +//! After rust code-gen has been implemented, this file will be deleted, and we will use generated definitions instead. + +use slice_codec::slice2::Slice2; + +use slice_codec::buffer::OutputTarget; +use slice_codec::encode_into::*; +use slice_codec::encoder::Encoder; +use slice_codec::Result; + +/// TAG_END_MARKER must be encoded at the end of every non-compact type. +const TAG_END_MARKER: i32 = -1; + +/// This macro implements `EncodeInto` for a Rust struct (which is mapped from a non-compact Slice struct). +/// It encodes all the struct's fields (in definition order), followed by `TAG_END_MARKER`. +/// +/// It uses macro-function-syntax, and should be called like: +/// `implement_encode_into_for_struct!(struct_type_name, field1, field2, ...);` +macro_rules! implement_encode_into_for_struct { + ($type_name:ty$(, $field_name:ident)*$(,)?) => { + impl EncodeInto for &$type_name { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + $(encoder.encode(&self.$field_name)?;)* + encoder.encode_varint(TAG_END_MARKER)?; + Ok(()) + } + } + } +} + +// ================= // +// Hand-mapped types // +// ================= // + +pub type EntityId = String; +pub type TypeId = String; +pub type Message = Vec; + +#[derive(Clone, Debug)] +pub struct Attribute { + pub directive: String, + pub args: Vec, +} +implement_encode_into_for_struct!(Attribute, directive, args); + +#[derive(Clone, Debug)] +pub struct TypeRef { + pub type_id: TypeId, + pub is_optional: bool, + pub type_attributes: Vec, +} +implement_encode_into_for_struct!(TypeRef, type_id, is_optional, type_attributes); + +#[derive(Clone, Debug)] +pub struct EntityInfo { + pub identifier: String, + pub attributes: Vec, + pub comment: Option, +} +impl EncodeInto for &EntityInfo { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + encoder.encode(&self.identifier)?; + encoder.encode(&self.attributes)?; + // encoder.encode_tagged(0, &self.comment)?; TODO add doc-comments after adding tag encoding support. + encoder.encode_varint(TAG_END_MARKER)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Module { + pub identifier: String, + pub attributes: Vec, +} +implement_encode_into_for_struct!(Module, identifier, attributes); + +#[derive(Clone, Debug)] +pub struct Struct { + pub entity_info: EntityInfo, + pub is_compact: bool, + pub fields: Vec, +} +implement_encode_into_for_struct!(Struct, entity_info, is_compact, fields); + +#[derive(Clone, Debug)] +pub struct Field { + pub entity_info: EntityInfo, + pub tag: Option, // TODO: varint32 isn't a real type? + pub data_type: TypeRef, +} +impl EncodeInto for &Field { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + // Encode the bit-sequence. With only one optional, this is just a bool. + encoder.encode(self.tag.is_some())?; + + // Encode the actual fields. + encoder.encode(&self.entity_info)?; + if let Some(tag_value) = self.tag { + encoder.encode_varint(tag_value)?; + } + encoder.encode(&self.data_type)?; + encoder.encode_varint(TAG_END_MARKER)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Interface { + pub entity_info: EntityInfo, + pub bases: Vec, + pub operations: Vec, +} +implement_encode_into_for_struct!(Interface, entity_info, bases, operations); + +#[derive(Clone, Debug)] +pub struct Operation { + pub entity_info: EntityInfo, + pub is_idempotent: bool, + pub parameters: Vec, + pub has_streamed_parameter: bool, + pub return_type: Vec, + pub has_streamed_return: bool, +} +implement_encode_into_for_struct!( + Operation, + entity_info, + is_idempotent, + parameters, + has_streamed_parameter, + return_type, + has_streamed_return, +); + +#[derive(Clone, Debug)] +pub struct Enum { + pub entity_info: EntityInfo, + pub is_compact: bool, + pub is_unchecked: bool, + pub underlying: Option, + pub enumerators: Vec, +} +impl EncodeInto for &Enum { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + // Encode the bit-sequence. With only one optional, this is just a bool. + encoder.encode(self.underlying.is_some())?; + + // Encode the actual fields. + encoder.encode(&self.entity_info)?; + encoder.encode(self.is_compact)?; + encoder.encode(self.is_unchecked)?; + if let Some(underlying_value) = &self.underlying { + encoder.encode(underlying_value)?; + } + encoder.encode(&self.enumerators)?; + encoder.encode_varint(TAG_END_MARKER)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Enumerator { + pub entity_info: EntityInfo, + pub value: Discriminant, + pub fields: Vec, +} +implement_encode_into_for_struct!(Enumerator, entity_info, value, fields); + +#[derive(Clone, Debug)] +pub struct Discriminant { + pub absolute_value: u64, + pub is_positive: bool, +} +implement_encode_into_for_struct!(Discriminant, absolute_value, is_positive); + +#[derive(Clone, Debug)] +pub struct CustomType { + pub entity_info: EntityInfo, +} +implement_encode_into_for_struct!(CustomType, entity_info); + +#[derive(Clone, Debug)] +pub struct TypeAlias { + pub entity_info: EntityInfo, + pub underlying_type: TypeRef, // Can never be optional. +} +implement_encode_into_for_struct!(TypeAlias, entity_info, underlying_type); + +#[derive(Clone, Debug)] +pub struct SequenceType { + pub element_type: TypeRef, +} +implement_encode_into_for_struct!(SequenceType, element_type); + +#[derive(Clone, Debug)] +pub struct DictionaryType { + pub key_type: TypeRef, // Can never be optional. + pub value_type: TypeRef, +} +implement_encode_into_for_struct!(DictionaryType, key_type, value_type); + +#[derive(Clone, Debug)] +pub struct ResultType { + pub success_type: TypeRef, + pub failure_type: TypeRef, +} +implement_encode_into_for_struct!(ResultType, success_type, failure_type); + +#[derive(Clone, Debug)] +pub struct DocComment { + pub overview: Message, + pub see_tags: Vec, +} +implement_encode_into_for_struct!(DocComment, overview, see_tags); + +#[repr(u8)] +#[derive(Clone, Debug)] +pub enum MessageComponent { + Text(String) = 0, + Link(EntityId) = 1, +} +impl EncodeInto for &MessageComponent { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + // Write the discriminant value. + // SAFETY: this cast is guaranteed to be safe because the enum is marked with `repr(u8)`, so it's safe to cast + // it directly to a `u8`. + unsafe { + let discriminant = *<*const _>::from(self).cast::(); + encoder.encode_varint(discriminant)?; + } + + // Encode the actual value. + match self { + MessageComponent::Text(v) => encoder.encode(v)?, + MessageComponent::Link(v) => encoder.encode(v)?, + } + + encoder.encode_varint(TAG_END_MARKER)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct SliceFile { + pub path: String, + pub module_declaration: Module, + pub attributes: Vec, + pub contents: Vec, +} +implement_encode_into_for_struct!(SliceFile, path, module_declaration, attributes, contents); + +#[derive(Clone, Debug)] +pub struct GeneratedFile { + pub path: String, + pub contents: String, +} +implement_encode_into_for_struct!(GeneratedFile, path, contents); + +#[repr(u8)] +#[derive(Clone, Debug)] +pub enum Symbol { + Interface(Interface) = 0, + Enum(Enum) = 1, + Struct(Struct) = 2, + CustomType(CustomType) = 3, + SequenceType(SequenceType) = 4, + DictionaryType(DictionaryType) = 5, + ResultType(ResultType) = 6, // TODO make result come before dictionary! + TypeAlias(TypeAlias) = 7, +} +impl EncodeInto for &Symbol { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + // Write the discriminant value. + // SAFETY: this cast is guaranteed to be safe because the enum is marked with `repr(u8)`, which means we know + // the first 'field' of this type's data layout must be a u8. This lets us read without offsetting the pointer. + unsafe { + let discriminant = *<*const _>::from(self).cast::(); + encoder.encode_varint(discriminant)?; + } + + // Encode the actual value. + match self { + Symbol::Interface(v) => encoder.encode(v)?, + Symbol::Enum(v) => encoder.encode(v)?, + Symbol::Struct(v) => encoder.encode(v)?, + Symbol::CustomType(v) => encoder.encode(v)?, + Symbol::SequenceType(v) => encoder.encode(v)?, + Symbol::DictionaryType(v) => encoder.encode(v)?, + Symbol::ResultType(v) => encoder.encode(v)?, + Symbol::TypeAlias(v) => encoder.encode(v)?, + } + + encoder.encode_varint(TAG_END_MARKER)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Diagnostic { + pub level: DiagnosticLevel, + pub message: String, + pub source: Option, +} +impl EncodeInto for &Diagnostic { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + // Encode the bit-sequence. With only one optional, this is just a bool. + encoder.encode(self.source.is_some())?; + + // Encode the actual fields. + encoder.encode(&self.level)?; + encoder.encode(&self.message)?; + if let Some(source_value) = &self.source { + encoder.encode(source_value)?; + } + encoder.encode_varint(TAG_END_MARKER)?; + Ok(()) + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum DiagnosticLevel { + Info, + Warning, + Error, +} +impl EncodeInto for &DiagnosticLevel { + fn encode_into(self, encoder: &mut Encoder) -> Result<()> { + encoder.encode(*self as u8) + } +} diff --git a/slicec/src/main.rs b/slicec/src/main.rs index a91aa0f6..45fe89aa 100644 --- a/slicec/src/main.rs +++ b/slicec/src/main.rs @@ -1,9 +1,50 @@ // Copyright (c) ZeroC, Inc. +use std::io::Write; + use clap::Parser; + +use slice_codec::encoder::Encoder; + use slicec::compilation_state::CompilationState; use slicec::slice_options::SliceOptions; +pub mod definition_types; +pub mod slice_file_converter; + +/// Attempts to encode a set of parsed Slice files into a byte-buffer. +/// If the encoding succeeds, this returns `Ok` with the encoded bytes, +/// otherwise this returns `Err` with an error describing the failure. +fn encode_generate_code_request(parsed_files: &[slicec::slice_file::SliceFile]) -> Result, slice_codec::Error> { + // Create a buffer to encode into, and an encoder over-top of it. + let mut encoding_buffer: Vec = Vec::new(); + let mut slice_encoder = Encoder::from(&mut encoding_buffer); + + // Encode the 'operation name'. + slice_encoder.encode("generateCode")?; + + // Sort the parsed files into two groups: source files and reference files. + // We also convert from the AST representation to the Slice representation at this time. + let mut source_files = Vec::new(); + let mut reference_files = Vec::new(); + for parsed_file in parsed_files { + // Convert the Slice file from AST representation to Slice representation. + let converted_file = crate::definition_types::SliceFile::from(parsed_file); + // Determine whether this is a source or reference file and place it accordingly. + match parsed_file.is_source { + true => source_files.push(converted_file), + false => reference_files.push(converted_file), + } + } + + // Encode the Slice-files as 2 sequences; one of source files, and one of reference files. + slice_encoder.encode(&source_files)?; + slice_encoder.encode(&reference_files)?; + + // We're done! + Ok(encoding_buffer) +} + fn main() { // Parse the command-line input. let slice_options = SliceOptions::parse(); @@ -16,13 +57,37 @@ fn main() { let updated_diagnostics = diagnostics.into_updated(&ast, &files, &slice_options); let totals = slicec::diagnostics::get_totals(&updated_diagnostics); - // Print output to stdout. - print!("Diagnostics: "); - println!("{totals:?}"); - for diagnostic in updated_diagnostics { - println!("{diagnostic:?}"); + // TODO: replace this by forking a code-gen plugin once they exist. + // For now, if there are any diagnostics, we emit those and NOT the encoded definitions. + // Code-generators can tell if it's okay to decode or not by the presence of the `"generateCode"` string. + if totals.0 + totals.1 > 0 { + // If there were diagnostics, print them to 'stdout' and don't encode the Slice definitions. + print!("Diagnostics: "); + println!("{totals:?}"); + for diagnostic in updated_diagnostics { + println!("{diagnostic:?}"); + } + } else { + // Encode the parsed Slice definitions. + let encoded_bytes = match encode_generate_code_request(&files) { + Ok(bytes) => bytes, + Err(error) => { + eprintln!("{error:?}"); + std::process::exit(11); + } + }; + + // Obtain an exclusive handle to 'stdout', and write the encoded bytes to it. + let mut stdout = std::io::stdout().lock(); + match stdout.write_all(&encoded_bytes) { + Ok(_) => {} + Err(error) => { + eprintln!("{error:?}"); + std::process::exit(12); + } + } } - println!("{ast:?}"); - std::process::exit(i32::from(totals.1 != 0)); + // Success. + std::process::exit(0); } diff --git a/slicec/src/slice_file_converter.rs b/slicec/src/slice_file_converter.rs new file mode 100644 index 00000000..cfffcaf9 --- /dev/null +++ b/slicec/src/slice_file_converter.rs @@ -0,0 +1,359 @@ +// Copyright (c) ZeroC, Inc. + +// Pull in the core compiler types using aliases to disambiguate them from the Slice-compiler definitions. +// Any type that starts with 'Compiler' is a compiler type, not a mapped Slice-compiler definition type. +#![cfg_attr(rustfmt, rustfmt_skip)] // skip the `use ... as ...` to keep them one-per-line. +use slicec::grammar::Attribute as CompilerAttribute; +use slicec::grammar::CustomType as CompilerCustomType; +use slicec::grammar::Definition as CompilerDefinition; +use slicec::grammar::Dictionary as CompilerDictionary; +use slicec::grammar::DocComment as CompilerDocComment; +use slicec::grammar::Enum as CompilerEnum; +use slicec::grammar::Enumerator as CompilerEnumerator; +use slicec::grammar::Field as CompilerField; +use slicec::grammar::Identifier as CompilerIdentifier; +use slicec::grammar::Interface as CompilerInterface; +use slicec::grammar::MessageComponent as CompilerMessageComponent; +use slicec::grammar::Operation as CompilerOperation; +use slicec::grammar::Parameter as CompilerParameter; +use slicec::grammar::ResultType as CompilerResultType; +use slicec::grammar::Sequence as CompilerSequence; +use slicec::grammar::Struct as CompilerStruct; +use slicec::grammar::Types as CompilerTypes; +use slicec::grammar::TypeAlias as CompilerTypeAlias; +use slicec::grammar::TypeRef as CompilerTypeRef; +use slicec::slice_file::SliceFile as CompilerSliceFile; + +// Pull in all the mapped compiler-definition types. +use crate::definition_types::*; +// Pull in traits from 'slicec' so we can call their functions. +use slicec::grammar::{Attributable, Commentable, Contained, Entity, Member, NamedSymbol, Type}; +// Pull in the attribute types without aliases, since they're not ambiguous. +use slicec::grammar::attributes::{Allow, Compress, Deprecated, Oneway, SlicedFormat, Unparsed}; + +/// Returns an [EntityInfo] describing the provided element. +fn get_entity_info_for(element: &impl Commentable) -> EntityInfo { + EntityInfo { + identifier: element.identifier().to_owned(), + attributes: get_attributes_from(element.attributes()), + comment: element.comment().map(Into::into), + } +} + +/// Returns a [`DocComment`] describing the provided parameter if one is present. +/// +/// In Slice, doc-comments are not allowed on parameters. Instead, you would use a '@param' tag applied to an enclosing +/// operation. But this is an implementation detail of the language, not something code-generators should deal with. +fn get_doc_comment_for_parameter(parameter: &CompilerParameter) -> Option { + let operation_comment = parameter.parent().comment()?; + + // We get the parameter's doc-comment in 3 steps: + // 1) Try to find a matching '@param' tag on the operation's doc-comment. + // 2) If one was present, extract just it's `Message` field, and convert it to the mapped type. + // 3) Construct a mapped `DocComment` which contains the mapped message. + operation_comment.params.iter() + .find(|param_tag| param_tag.identifier.value == parameter.identifier()) + .map(|param_tag| param_tag.message.value.iter().map(Into::into).collect()) + .map(|message| DocComment { + overview: message, + see_tags: Vec::new(), + }) +} + +/// Helper function to convert the result of `tag.linked_entity()` into an [`EntityId`]. +fn convert_doc_comment_link(link_result: Result<&dyn Entity, &CompilerIdentifier>) -> EntityId { + match link_result { + Ok(entity) => entity.parser_scoped_identifier(), + Err(identifier) => identifier.value.clone(), + } +} + +/// Helper function to convert a [`Vec`] of compiler-attributes to mapped-attributes. +fn get_attributes_from(attributes: Vec<&CompilerAttribute>) -> Vec { + attributes.into_iter().map(|attribute| Attribute { + directive: attribute.kind.directive().to_owned(), + args: get_attribute_args(attribute), + }) + .collect() +} + +// TODO this is a temporary hack because we know all the possible attributes. +// The `Attribute` API doesn't offer a way to convert parsed-arguments back to a string. +// And this entire API will be rewritten after porting slicec-cs, so no point changing it now. +fn get_attribute_args(attribute: &CompilerAttribute) -> Vec { + if let Some(unparsed) = attribute.downcast::() { + return unparsed.args.clone(); + } + + if let Some(allow) = attribute.downcast::() { + return allow.allowed_lints.clone(); + } + + if let Some(compress) = attribute.downcast::() { + let mut args = Vec::new(); + if compress.compress_args { + args.push("Args".to_owned()); + } + if compress.compress_return { + args.push("Return".to_owned()); + } + return args; + } + + if let Some(deprecated) = attribute.downcast::() { + return deprecated.reason.iter().cloned().collect(); + } + + if attribute.downcast::().is_some() { + return Vec::new(); + } + + if let Some(sliced_format) = attribute.downcast::() { + let mut args = Vec::new(); + if sliced_format.sliced_args { + args.push("Args".to_owned()); + } + if sliced_format.sliced_return { + args.push("Return".to_owned()); + } + return args; + } + + panic!("Impossible attribute encountered") +} + +// =========================== // +// Direct conversion functions // +// =========================== // + +impl From<&CompilerSliceFile> for SliceFile { + fn from(slice_file: &CompilerSliceFile) -> Self { + // Convert the slice_file's module declaration. + // TODO this crashes on an empty Slice file, we need to filter out empty files at an earlier stage. + let module = slice_file.module.as_ref().unwrap().borrow(); + let converted_module = Module { + identifier: module.identifier().to_owned(), + attributes: get_attributes_from(module.attributes()), + }; + + // Return a converted slice file. + SliceFile { + path: slice_file.relative_path.clone(), + module_declaration: converted_module, + attributes: get_attributes_from(slice_file.attributes()), + contents: SliceFileContentsConverter::convert(&slice_file.contents), + } + } +} + +impl From<&CompilerDocComment> for DocComment { + fn from(doc_comment: &CompilerDocComment) -> Self { + let overview = doc_comment.overview.as_ref().map(|message| { + message.value.iter().map(Into::into) + }); + + let see_tags = doc_comment.see.iter().map(|tag| { + convert_doc_comment_link(tag.linked_entity()) + }); + + DocComment { + overview: overview.map_or(Vec::new(), |v| v.collect()), + see_tags: see_tags.collect(), + } + } +} + +impl From<&CompilerMessageComponent> for MessageComponent { + fn from(component: &CompilerMessageComponent) -> Self { + match component { + CompilerMessageComponent::Text(text) => MessageComponent::Text(text.clone()), + CompilerMessageComponent::Link(tag) => { + MessageComponent::Link(convert_doc_comment_link(tag.linked_entity())) + } + } + } +} + +/// This struct exposes a function ([`SliceFileContentsConverter::convert`]) that converts the contents of a Slice file +/// from their AST representation, to a representation that can be encoded with the Slice encoding. +#[derive(Debug)] +pub struct SliceFileContentsConverter { + converted_contents: Vec, +} + +impl SliceFileContentsConverter { + /// Converts the contents of SliceFile from their representation in the AST (as [`CompilerDefinition`]s), to their + /// representation in the `Compiler` Slice module (as [`Symbol`]s). + /// + /// Specifically, this iterates through the top-level definitions of a Slice-file (in definition order) converting + /// and storing them. In addition to top-level definitions, the returned [`Vec`] also contains [`Symbol`]s for each + /// anonymous type encountered while iterating. Anonymous types always appear in the returned contents _before_ + /// the [`Symbol`]s that referenced them. + pub fn convert(contents: &[CompilerDefinition]) -> Vec { + // Create a new converter. + let mut converter = SliceFileContentsConverter { + converted_contents: Vec::new() + }; + + // Iterate through the provided file's contents, and convert each of it's top-level definitions. + for definition in contents { + let converted = match definition { + CompilerDefinition::Struct(v) => Symbol::Struct(converter.convert_struct(v.borrow())), + CompilerDefinition::Interface(v) => Symbol::Interface(converter.convert_interface(v.borrow())), + CompilerDefinition::Enum(v) => Symbol::Enum(converter.convert_enum(v.borrow())), + CompilerDefinition::CustomType(v) => Symbol::CustomType(converter.convert_custom_type(v.borrow())), + CompilerDefinition::TypeAlias(v) => Symbol::TypeAlias(converter.convert_type_alias(v.borrow())), + _ => panic!("TODO: remove classes and exceptions"), + }; + converter.converted_contents.push(converted); + } + + // Return all the converted elements, consuming the converter. + converter.converted_contents + } + + fn convert_type_ref(&mut self, type_ref: &CompilerTypeRef) -> TypeRef { + TypeRef { + type_id: self.get_type_id_for(type_ref), + is_optional: type_ref.is_optional, + type_attributes: get_attributes_from(type_ref.attributes()), + } + } + + fn convert_struct(&mut self, struct_def: &CompilerStruct) -> Struct { + Struct { + entity_info: get_entity_info_for(struct_def), + is_compact: struct_def.is_compact, + fields: struct_def.fields().into_iter().map(|e| self.convert_field(e)).collect(), + } + } + + fn convert_field(&mut self, field: &CompilerField) -> Field { + Field { + entity_info: get_entity_info_for(field), + tag: field.tag.as_ref().map(|integer| integer.value as i32), + data_type: self.convert_type_ref(field.data_type()), + } + } + + fn convert_interface(&mut self, interface_def: &CompilerInterface) -> Interface { + let bases = interface_def.base_interfaces(); + + Interface { + entity_info: get_entity_info_for(interface_def), + bases: bases.into_iter().map(|i| i.module_scoped_identifier()).collect(), + operations: interface_def.operations().into_iter().map(|e| self.convert_operation(e)).collect(), + } + } + + fn convert_operation(&mut self, operation: &CompilerOperation) -> Operation { + Operation { + entity_info: get_entity_info_for(operation), + is_idempotent: operation.is_idempotent, + parameters: operation.parameters().into_iter().map(|e| self.convert_parameter(e)).collect(), + has_streamed_parameter: operation.streamed_parameter().is_some(), + return_type: operation.return_members().into_iter().map(|e| self.convert_parameter(e)).collect(), + has_streamed_return: operation.streamed_return_member().is_some(), + } + } + + fn convert_parameter(&mut self, parameter: &CompilerParameter) -> Field { + let parameter_info = EntityInfo { + identifier: parameter.identifier().to_owned(), + attributes: get_attributes_from(parameter.attributes()), + comment: get_doc_comment_for_parameter(parameter), + }; + + Field { + entity_info: parameter_info, + tag: parameter.tag.as_ref().map(|integer| integer.value as i32), + data_type: self.convert_type_ref(parameter.data_type()), + } + } + + fn convert_enum(&mut self, enum_def: &CompilerEnum) -> Enum { + Enum { + entity_info: get_entity_info_for(enum_def), + is_compact: enum_def.is_compact, + is_unchecked: enum_def.is_unchecked, + underlying: enum_def.underlying.as_ref().map(|type_ref| type_ref.type_string()), + enumerators: enum_def.enumerators().into_iter().map(|e| self.convert_enumerator(e)).collect(), + } + } + + fn convert_enumerator(&mut self, enumerator: &CompilerEnumerator) -> Enumerator { + let entity_info = get_entity_info_for(enumerator); + let raw_value = enumerator.value(); + let value = Discriminant { + absolute_value: raw_value.unsigned_abs() as u64, + is_positive: raw_value.is_positive(), + }; + let fields = enumerator.fields().into_iter().map(|e| self.convert_field(e)).collect(); + + Enumerator { entity_info, value, fields } + } + + fn convert_custom_type(&mut self, custom_type: &CompilerCustomType) -> CustomType { + CustomType { + entity_info: get_entity_info_for(custom_type) + } + } + + fn convert_type_alias(&mut self, type_alias: &CompilerTypeAlias) -> TypeAlias { + TypeAlias { + entity_info: get_entity_info_for(type_alias), + underlying_type: self.convert_type_ref(&type_alias.underlying), + } + } + + fn convert_sequence(&mut self, sequence: &CompilerSequence) -> SequenceType { + SequenceType { + element_type: self.convert_type_ref(&sequence.element_type), + } + } + + fn convert_dictionary(&mut self, dictionary: &CompilerDictionary) -> DictionaryType { + DictionaryType { + key_type: self.convert_type_ref(&dictionary.key_type), + value_type: self.convert_type_ref(&dictionary.value_type), + } + } + + fn convert_result_type(&mut self, result_type: &CompilerResultType) -> ResultType { + ResultType { + success_type: self.convert_type_ref(&result_type.success_type), + failure_type: self.convert_type_ref(&result_type.failure_type), + } + } + + /// Returns a [TypeId] for the provided `type_ref`. This is a fully-scoped identifier for user-defined types, + /// the corresponding keyword for primitive types, and for anonymous types, we do the following: + /// 1) Recursively convert the anonymous type (and any nested types) to the mapped definition types. + /// 2) Add these directly to [Self::converted_contents] (so these types appear in the contents before their users) + /// 3) Return its index in [Self::converted_contents] as a numeric TypeId. + fn get_type_id_for(&mut self, type_ref: &CompilerTypeRef) -> TypeId { + match type_ref.concrete_type() { + CompilerTypes::Struct(v) => v.module_scoped_identifier(), + CompilerTypes::Enum(v) => v.module_scoped_identifier(), + CompilerTypes::CustomType(v) => v.module_scoped_identifier(), + CompilerTypes::Primitive(v) => v.type_string(), + CompilerTypes::ResultType(v) => { + let converted_symbol = Symbol::ResultType(self.convert_result_type(v)); + self.converted_contents.push(converted_symbol); + (self.converted_contents.len() - 1).to_string() + } + CompilerTypes::Sequence(v) => { + let converted_symbol = Symbol::SequenceType(self.convert_sequence(v)); + self.converted_contents.push(converted_symbol); + (self.converted_contents.len() - 1).to_string() + } + CompilerTypes::Dictionary(v) => { + let converted_symbol = Symbol::DictionaryType(self.convert_dictionary(v)); + self.converted_contents.push(converted_symbol); + (self.converted_contents.len() - 1).to_string() + } + + CompilerTypes::Class(_) => panic!("TODO: remove classes!"), + } + } +}