use zerodds_ast::Ast;
use zerodds_ast_core::Id;
use zerodds_ast_layout::Endianness;
use zerodds_ast_layout::key::LayoutKey;
use zerodds_lang_cst::Language;
pub use crate::adapter::interface::ServiceMapping;
use crate::emit::CodecTarget;
use crate::emit::encode::CodecOptions;
use crate::encode::EncodeError;
use crate::{ModelGraph, apply, rule};
#[derive(Debug)]
pub enum GenerateError {
Rules(String),
NoForm(String),
NotWired(Language),
Codec(String),
}
impl core::fmt::Display for GenerateError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Rules(detail) => write!(f, "the rules do not apply: {detail}"),
Self::NoForm(detail) | Self::Codec(detail) => write!(f, "{detail}"),
Self::NotWired(language) => write!(
f,
"{language:?} has a materialiser but is not routed through `generate` yet"
),
}
}
}
impl core::error::Error for GenerateError {}
pub const COMPLETE_INTERFACE_LANGUAGES: [Language; 15] = [
Language::Ada,
Language::Cpp,
Language::CSharp,
Language::D,
Language::Elixir,
Language::Go,
Language::Java,
Language::Julia,
Language::Lua,
Language::Nim,
Language::OCaml,
Language::Rust,
Language::Swift,
Language::TypeScript,
Language::Zig,
];
pub const COMPLETE_DECLARATIONS_LANGUAGES: [Language; 13] = [
Language::Cpp,
Language::CSharp,
Language::D,
Language::Elixir,
Language::Go,
Language::Java,
Language::Julia,
Language::Lua,
Language::Nim,
Language::OCaml,
Language::Swift,
Language::TypeScript,
Language::Zig,
];
fn refuse_interface_unless(
ast: &Ast,
language: Language,
complete: &[Language],
) -> Result<(), GenerateError> {
if complete.contains(&language) {
return Ok(());
}
let services = crate::adapter::interface::services(ast)
.map_err(|error| GenerateError::NoForm(error.to_string()))?;
if services.is_empty() {
return Ok(());
}
let supported = complete
.iter()
.copied()
.map(Language::as_str)
.collect::<Vec<_>>()
.join(", ");
let mut named = Vec::with_capacity(services.len());
for service in &services {
let node = ast
.interface(service.id)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
named.push(format!(
"`interface {}` at {}",
service.name,
interface_position(&node.facets.source)
));
}
Err(GenerateError::NoForm(format!(
"{language:?} has no complete DDS-RPC 1.0 binding for an `interface`, and this model \
declares {}: {}. Generate it with one of: {supported}.",
named.len(),
named.join("; "),
)))
}
fn refuse_interface(ast: &Ast, language: Language) -> Result<(), GenerateError> {
refuse_interface_unless(ast, language, &COMPLETE_INTERFACE_LANGUAGES)
}
fn interface_position(source: &zerodds_ast::facet::SourceFacet) -> String {
let where_ = match &source.origin {
zerodds_ast::facet::SourceOrigin::File { path } => path.clone(),
zerodds_ast::facet::SourceOrigin::Synthetic { reason } => {
format!("a synthetic node ({reason})")
}
zerodds_ast::facet::SourceOrigin::Builtin => "a built-in declaration".to_string(),
};
match source.span {
Some(span) => format!("{where_}:{}-{}", span.start, span.end),
None => where_,
}
}
pub fn declarations(ast: &Ast, language: Language) -> Result<String, GenerateError> {
declarations_with_mapping(ast, language, ServiceMapping::Basic)
}
pub fn declarations_with_mapping(
ast: &Ast,
language: Language,
mapping: ServiceMapping,
) -> Result<String, GenerateError> {
refuse_interface_unless(ast, language, &COMPLETE_DECLARATIONS_LANGUAGES)?;
let model = ModelGraph::of(ast);
let applied = apply(&model, &rule::catalogue(language))
.map_err(|error| GenerateError::Rules(format!("{error:?}")))?;
let text = match language {
Language::Lua => {
let items = crate::adapter::lua::lua_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::lua::stat)
.collect::<Vec<_>>()
.join("\n")
}
Language::Ada => {
let items = crate::verbatim::weave_file(
ast,
crate::verbatim::aliases::ADA,
crate::adapter::ada::ada_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?,
);
items
.iter()
.map(zerodds_lang_render::ada::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::C => {
let items = crate::adapter::c::c_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::c::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Cpp => {
let mut items = crate::adapter::cpp::cpp_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
let pair_items = crate::emit::type_pair_cpp::cpp_type_pair_items(ast)
.map_err(GenerateError::NoForm)?;
let has_pair_items = !pair_items.is_empty();
if has_pair_items {
crate::emit::type_pair_cpp::drop_common_declarations(&mut items);
items.insert(
0,
zerodds_lang_cst::cpp::Decl::Verbatim(
crate::emit::type_pair_cpp::RUNTIME_INCLUDE.to_string(),
),
);
let aliases = crate::verbatim::aliases::CPP;
before_end_file(&mut items, ast, aliases, pair_items);
}
if crate::adapter::cpp::cpp_mapping() == crate::adapter::cpp::CppMapping::Psm {
let layer = crate::adapter::cpp_psm::psm_layer(ast)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
if !layer.is_empty() {
before_end_file(
&mut items,
ast,
crate::verbatim::aliases::CPP,
vec![zerodds_lang_cst::cpp::Decl::Verbatim(layer)],
);
}
}
if crate::adapter::cpp::cpp_mapping() == crate::adapter::cpp::CppMapping::Classic {
if has_pair_items {
return Err(GenerateError::NoForm(
"the classic C++ mapping has no form for the service types of DDS-RPC 1.0 §7.3.1.4"
.to_string(),
));
}
let layer = crate::adapter::cpp_classic::classic_layer(ast)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
before_end_file(
&mut items,
ast,
crate::verbatim::aliases::CPP,
vec![zerodds_lang_cst::cpp::Decl::Verbatim(layer)],
);
}
before_end_file(
&mut items,
ast,
crate::verbatim::aliases::CPP,
crate::emit::interface_cpp::cpp_interface_items_with(ast, mapping)
.map_err(|error| GenerateError::NoForm(error.to_string()))?,
);
items
.iter()
.map(zerodds_lang_render::cpp::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::CSharp => {
let file = crate::adapter::csharp::csharp_file(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
zerodds_lang_render::csharp::file(&file)
}
Language::D => {
let items = crate::adapter::d::d_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::d::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Elixir => {
let modules = crate::adapter::elixir::elixir_modules(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
modules
.iter()
.map(zerodds_lang_render::elixir::top_level)
.collect::<Vec<_>>()
.join("\n")
}
Language::Go => {
let items = crate::adapter::go::go_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::go::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Java => {
let mut items = crate::adapter::java::java_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
if crate::emit::type_pair_java::has_pair_types(ast) {
crate::emit::type_pair_java::adjust_declarations(&mut items);
}
before_end_file(
&mut items,
ast,
crate::verbatim::aliases::JAVA,
crate::emit::interface_java::java_interface_items_with(ast, mapping)
.map_err(|error| GenerateError::NoForm(error.to_string()))?,
);
items
.iter()
.map(zerodds_lang_render::java::type_decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Julia => {
let items = crate::adapter::julia::julia_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::julia::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Nim => {
let sections = crate::adapter::nim::nim_sections(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
sections
.iter()
.map(zerodds_lang_render::nim::section)
.collect::<Vec<_>>()
.join("\n")
}
Language::Node => {
let items = crate::adapter::node::node_items(ast)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::node::stat)
.collect::<Vec<_>>()
.join("\n")
}
Language::OCaml => {
let items = crate::adapter::ocaml::ocaml_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::ocaml::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Python => {
let module = crate::adapter::python::python_module(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
zerodds_lang_render::python::module(&module)
}
Language::Rust => {
let items = crate::adapter::cst::rust_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::rust::item)
.collect::<Vec<_>>()
.join("\n")
}
Language::Swift => {
let items = crate::adapter::swift::swift_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::swift::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::TypeScript => {
let mut items = crate::adapter::typescript::prelude();
items.extend(
crate::adapter::typescript::ts_items_with_descriptors(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?,
);
before_end_file(
&mut items,
ast,
crate::verbatim::aliases::TYPESCRIPT,
crate::emit::interface_typescript::ts_interface_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(error.to_string()))?,
);
zerodds_lang_render::typescript::file(&zerodds_lang_cst::typescript::File {
doc: zerodds_lang_cst::common::Doc::none(),
imports: vec![crate::adapter::typescript::descriptor_import(
crate::adapter::typescript::DEFAULT_RUNTIME_PACKAGE,
)],
decls: items,
})
}
Language::Zig => {
let items = crate::adapter::zig::zig_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::zig::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Ada83 => {
let items = crate::verbatim::weave_file(
ast,
crate::verbatim::aliases::ADA83,
crate::adapter::ada83::ada83_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?,
);
items
.iter()
.map(zerodds_lang_render::ada83::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::FSharp => {
let items = crate::adapter::fsharp::fsharp_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::fsharp::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Kotlin => {
let items = crate::adapter::kotlin::kotlin_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
items
.iter()
.map(zerodds_lang_render::kotlin::decl)
.collect::<Vec<_>>()
.join("\n")
}
};
Ok(text)
}
fn before_end_file<D>(items: &mut Vec<D>, ast: &Ast, aliases: &[&str], more: Vec<D>) {
let (_, end_file) = crate::verbatim::file_slots(ast, aliases);
let at = items.len().saturating_sub(end_file.len());
let tail = items.split_off(at);
items.extend(more);
items.extend(tail);
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JavaUnit {
pub class: &'static str,
pub text: String,
}
#[must_use]
pub fn java_units(text: &str) -> Vec<JavaUnit> {
const CLASSES: [(&str, &[&str]); 3] = [
("ZdTypes", &["public final class ZdTypes {"]),
("ZdServices", &["public final class ZdServices {"]),
("ZdCodec", &["public class ZdCodec {"]),
];
let mut starts: Vec<(usize, &'static str)> = Vec::new();
for (class, openings) in CLASSES {
let Some(at) = openings.iter().find_map(|opening| {
text.lines()
.scan(0usize, |offset, line| {
let here = *offset;
*offset += line.len() + 1;
Some((here, line))
})
.find(|(_, line)| line == opening)
.map(|(here, _)| here)
}) else {
continue;
};
let mut begin = at;
let mut lines_above: Vec<(usize, &str)> = text[..at]
.lines()
.scan(0usize, |offset, line| {
let here = *offset;
*offset += line.len() + 1;
Some((here, line))
})
.collect();
while let Some((here, line)) = lines_above.pop() {
let trimmed = line.trim_start();
if trimmed.starts_with("/**") || trimmed.starts_with('*') || trimmed.starts_with("*/") {
begin = here;
if trimmed.starts_with("/**") {
break;
}
} else {
break;
}
}
starts.push((begin, class));
}
starts.sort_unstable();
let mut units = Vec::new();
for (index, (begin, class)) in starts.iter().enumerate() {
let from = if index == 0 { 0 } else { *begin };
let to = starts.get(index + 1).map_or(text.len(), |(next, _)| *next);
units.push(JavaUnit {
class,
text: format!("{}\n", text[from..to].trim_end()),
});
}
units
}
pub fn source(ast: &Ast, language: Language, key: LayoutKey) -> Result<String, GenerateError> {
source_with_options(ast, language, key, CodecOptions::default())
}
pub fn source_with_options(
ast: &Ast,
language: Language,
key: LayoutKey,
options: CodecOptions,
) -> Result<String, GenerateError> {
source_with_mapping(ast, language, key, options, ServiceMapping::Basic)
}
pub fn source_with_mapping(
ast: &Ast,
language: Language,
key: LayoutKey,
options: CodecOptions,
mapping: ServiceMapping,
) -> Result<String, GenerateError> {
refuse_interface(ast, language)?;
if language == Language::Rust {
return rust_source(ast, key, mapping);
}
if language == Language::Ada {
return ada_source(ast, key, options);
}
if language == Language::Ada83 {
return ada83_source(ast, key, options);
}
if language == Language::Cpp
&& (ast.interfaces().next().is_some() || crate::emit::type_pair_cpp::has_pair_types(ast))
{
return cpp_source_with_mapping(ast, key, options, "zd_codec", mapping).map(|c| c.text);
}
let decls = declarations_with_mapping(ast, language, mapping)?;
let codec = codec(ast, language, key, options)?;
let (decls, end_file) = split_end_file(ast, language, &decls)?;
Ok(format!("{decls}\n\n{codec}{end_file}"))
}
fn split_end_file(
ast: &Ast,
language: Language,
decls: &str,
) -> Result<(String, String), GenerateError> {
let (_, end_file) = crate::verbatim::file_slots(ast, crate::verbatim::aliases_of(language));
if end_file.is_empty() {
return Ok((decls.to_string(), String::new()));
}
let tail: Vec<&str> = end_file.iter().flat_map(|text| text.lines()).collect();
let tail = tail.join("\n");
let trimmed = decls.trim_end();
let Some(head) = trimmed.strip_suffix(tail.as_str()) else {
return Err(GenerateError::NoForm(format!(
"the {language:?} declarations do not end in their @verbatim END_FILE text, \
so it cannot be moved past the codec"
)));
};
Ok((head.trim_end().to_string(), format!("\n\n{tail}\n")))
}
pub fn cpp_source_with_codec_namespace(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
codec_namespace: &str,
) -> Result<String, GenerateError> {
cpp_source_with_both_versions(ast, key, options, codec_namespace).map(|both| both.text)
}
#[derive(Debug, Clone)]
pub struct CppSource {
pub text: String,
pub other: Option<(&'static str, zerodds_ast_layout::Encoding)>,
}
pub fn cpp_source_with_both_versions(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
codec_namespace: &str,
) -> Result<CppSource, GenerateError> {
cpp_source_with_mapping(ast, key, options, codec_namespace, ServiceMapping::Basic)
}
pub fn cpp_source_with_mapping(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
codec_namespace: &str,
mapping: ServiceMapping,
) -> Result<CppSource, GenerateError> {
use zerodds_ast_layout::Encoding;
refuse_interface(ast, Language::Cpp)?;
let decls = declarations_with_mapping(ast, Language::Cpp, mapping)?;
let primary = codec(ast, Language::Cpp, key, options)?;
let (decls, end_file) = split_end_file(ast, Language::Cpp, &decls)?;
let (nested, other_encoding) = if key.encoding.is_v2() {
("xcdr1", Encoding::PlainCdr1)
} else {
("xcdr2", Encoding::PlainCdr2)
};
let other_key = LayoutKey {
encoding: other_encoding,
..key
};
let other_decls = crate::emit::decode::decoder_excluding::<crate::emit::cpp::Cpp>(
ast,
other_key,
options,
&crate::emit::type_pair_cpp::excluded_types(ast),
&[],
)
.and_then(|decoders| {
let mut decls = crate::default_cpp::cpp_defaulters_called_by_excluding(
ast,
&decoders,
&crate::emit::type_pair_cpp::excluded_types(ast),
)?;
decls.extend(decoders);
Ok(decls)
})
.map(|decls| {
decls
.iter()
.map(zerodds_lang_render::cpp::decl)
.collect::<Vec<_>>()
.join("\n")
});
let (other_text, other) = match other_decls {
Ok(text) => (
format!(
"\n\n// The same types under the other XCDR version, which a peer may\n\
// choose to write in. DDS-XTypes 1.3 \u{a7}7.6.3.1.2.\n\
namespace {nested} {{\n\n{text}\n\n}} // namespace {nested}"
),
Some((nested, other_encoding)),
),
Err(_) => (String::new(), None),
};
Ok(CppSource {
text: format!(
"{decls}\n\nnamespace {codec_namespace} {{\n\n{primary}{other_text}\n\n}} // namespace {codec_namespace}{end_file}"
),
other,
})
}
pub fn csharp_source_with_runtime(ast: &Ast, key: LayoutKey) -> Result<String, GenerateError> {
csharp_source_with_runtime_and_options(ast, key, CodecOptions::default())
}
pub fn csharp_source_with_runtime_and_options(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
) -> Result<String, GenerateError> {
refuse_interface(ast, Language::CSharp)?;
let model = ModelGraph::of(ast);
let applied = apply(&model, &rule::catalogue(Language::CSharp))
.map_err(|error| GenerateError::Rules(format!("{error:?}")))?;
let mut file = crate::adapter::csharp::csharp_file(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
crate::adapter::csharp::drop_support_namespace(&mut file);
let decls = zerodds_lang_render::csharp::file(&file);
let codec = codec(ast, Language::CSharp, key, options)?;
let (decls, end_file) = split_end_file(ast, Language::CSharp, &decls)?;
Ok(format!("{decls}\n\n{codec}{end_file}"))
}
fn ada_endianness(endianness: Endianness) -> &'static str {
match endianness {
Endianness::Little => "Little",
Endianness::Big => "Big",
}
}
fn ada_source(ast: &Ast, key: LayoutKey, options: CodecOptions) -> Result<String, GenerateError> {
use zerodds_lang_cst::ada;
use zerodds_lang_cst::common::{Doc, Ident, Path};
let model = ModelGraph::of(ast);
let applied = apply(&model, &rule::catalogue(Language::Ada))
.map_err(|error| GenerateError::Rules(format!("{error:?}")))?;
let mut spec = crate::adapter::ada::ada_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
let codec = codec_of::<crate::emit::ada::Ada>(
ast,
key,
options,
crate::default_ada::ada_defaulters_called_by,
)
.map_err(codec_error)?;
let mut bodies = Vec::new();
for decl in codec {
if matches!(decl, ada::Decl::SubprogramBody(_)) {
bodies.push(decl);
} else {
spec.push(decl);
}
}
if !bodies.is_empty() {
spec.insert(
0,
ada::Decl::Object(ada::ObjectDecl {
doc: Doc::lines(&[
"The byte order this unit was generated for. The codec below writes",
"either order -- Zd_Wire.Writer carries it -- so this is what a caller",
"hands Zd_Wire.New_Writer to get the one that was asked for.",
]),
name: Ident::new("Zd_Endianness"),
type_: Path::new(&["Zd_Wire", "Byte_Order"]),
value: ada::Expr::Name(Path::new(&["Zd_Wire", ada_endianness(key.endianness)])),
}),
);
}
let package = Ident::new(crate::adapter::ada::PACKAGE);
let spec_text = zerodds_lang_render::ada::file(&ada::File {
doc: Doc::none(),
withs: Vec::new(),
use_types: Vec::new(),
package: package.clone(),
decls: spec,
});
let (begin_file, end_file) = crate::verbatim::file_slots(ast, crate::verbatim::aliases::ADA);
let head = text_block(&begin_file, "\n");
let tail = text_block(&end_file, "\n\n");
if bodies.is_empty() {
return Ok(format!("{head}{}\n{tail}", spec_text.trim_end()));
}
let body_text = zerodds_lang_render::ada::package_body(&package, &bodies);
Ok(format!(
"{head}{}\n\n{}\n{tail}",
spec_text.trim_end(),
body_text.trim_end()
))
}
fn text_block(texts: &[String], separator: &str) -> String {
if texts.is_empty() {
return String::new();
}
let lines: Vec<&str> = texts.iter().flat_map(|text| text.lines()).collect();
if separator == "\n" {
format!("{}\n", lines.join("\n"))
} else {
format!("\n{}\n", lines.join("\n"))
}
}
fn ada83_source(ast: &Ast, key: LayoutKey, options: CodecOptions) -> Result<String, GenerateError> {
use zerodds_lang_cst::ada83;
use zerodds_lang_cst::common::{Doc, Ident, Path};
let model = ModelGraph::of(ast);
let applied = apply(&model, &rule::catalogue(Language::Ada83))
.map_err(|error| GenerateError::Rules(format!("{error:?}")))?;
let mut spec = crate::adapter::ada83::ada83_items(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
let codec = codec_of::<crate::emit::ada83::Ada83>(
ast,
key,
options,
crate::default_ada83::ada83_defaulters_called_by,
)
.map_err(codec_error)?;
let mut bodies = Vec::new();
for decl in codec {
if matches!(decl, ada83::Decl::SubprogramBody(_)) {
bodies.push(decl);
} else {
spec.push(decl);
}
}
if !bodies.is_empty() {
spec.insert(
0,
ada83::Decl::Object(ada83::ObjectDecl {
doc: Doc::lines(&[
"The byte order this unit was generated for. The codec below writes",
"either order -- Zd_Wire83.Writer carries it -- so this is what a caller",
"hands Zd_Wire83.New_Writer to get the one that was asked for.",
]),
name: Ident::new("Zd_Endianness"),
type_: Path::new(&["Zd_Wire83", "Byte_Order"]),
value: ada83::Expr::Name(Path::new(&["Zd_Wire83", ada_endianness(key.endianness)])),
}),
);
}
let package = Ident::new(crate::adapter::ada83::PACKAGE);
let spec_text = zerodds_lang_render::ada83::file(&ada83::File {
doc: Doc::none(),
withs: Vec::new(),
package: package.clone(),
decls: spec,
});
let (begin_file, end_file) = crate::verbatim::file_slots(ast, crate::verbatim::aliases::ADA83);
let head = text_block(&begin_file, "\n");
let tail = text_block(&end_file, "\n\n");
if bodies.is_empty() {
return Ok(format!("{head}{}\n{tail}", spec_text.trim_end()));
}
let body_text = zerodds_lang_render::ada83::package_body(&package, &bodies);
Ok(format!(
"{head}{}\n\n{}\n{tail}",
spec_text.trim_end(),
body_text.trim_end()
))
}
fn rust_source(
ast: &Ast,
key: LayoutKey,
mapping: ServiceMapping,
) -> Result<String, GenerateError> {
let model = ModelGraph::of(ast);
let applied = apply(&model, &rule::catalogue(Language::Rust))
.map_err(|error| GenerateError::Rules(format!("{error:?}")))?;
let mut pairs = crate::adapter::cst::rust_item_pairs(ast, &applied)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
pairs.extend(rust_codec_pairs(ast, key)?);
let remote_traits: std::collections::BTreeSet<String> =
crate::adapter::interface::services(ast)
.map_err(|error| GenerateError::NoForm(error.to_string()))?
.into_iter()
.filter(crate::adapter::interface::Service::is_remote)
.map(|service| zerodds_lang_cst::rust::escape_keyword(&service.name))
.collect();
pairs.retain(|(_, item)| {
!matches!(
item,
zerodds_lang_cst::rust::Item::Trait(trait_)
if remote_traits.contains(trait_.name.as_str())
)
});
pairs.extend(
crate::emit::interface_rust::rust_interface_pairs_with(ast, mapping)
.map_err(|error| GenerateError::NoForm(error.to_string()))?,
);
pairs.extend(
crate::emit::type_pair_rust::rust_type_pair_items(ast).map_err(GenerateError::NoForm)?,
);
let items = crate::verbatim::weave_file(
ast,
crate::verbatim::aliases::RUST,
crate::adapter::cst::nest_by_module(ast, pairs)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?,
);
Ok(render_rust(&items))
}
fn rust_codec_pairs(
ast: &Ast,
key: LayoutKey,
) -> Result<Vec<(Id<zerodds_ast::ModuleNode>, zerodds_lang_cst::rust::Item)>, GenerateError> {
let mut pairs = Vec::new();
let rpc = ast.interfaces().next().is_some() || crate::emit::type_pair_rust::has_pair_types(ast);
for (id, node) in ast.types() {
if node.name.is_empty() || !crate::encode::has_codec(&node.kind) {
continue;
}
for item in crate::encode::rust_codec(ast, id, key).map_err(codec_error)? {
pairs.push((node.module, item));
}
if rpc {
if let Ok(items) = crate::encode::rust_codec_pair(
ast,
id,
crate::emit::interface_rust::rpc_layout_key_xcdr1(),
) {
for item in items {
pairs.push((node.module, item));
}
}
}
}
Ok(pairs)
}
fn node_exports(decls: &[zerodds_lang_cst::node::Stat]) -> Option<zerodds_lang_cst::node::Stat> {
use zerodds_lang_cst::node::{Expr, Stat};
let names: Vec<(zerodds_lang_cst::common::Ident, Expr)> = decls
.iter()
.filter_map(|decl| match decl {
Stat::Function(function) => {
Some((function.name.clone(), Expr::Name(function.name.clone())))
}
_ => None,
})
.collect();
if names.is_empty() {
return None;
}
Some(Stat::Assign {
doc: zerodds_lang_cst::common::Doc::line(
"What a caller outside this file can reach. CommonJS publishes nothing by default.",
),
place: Expr::Member {
object: Box::new(Expr::name("module")),
name: zerodds_lang_cst::common::Ident::new("exports"),
},
value: Expr::Object(names),
})
}
fn render_rust(items: &[zerodds_lang_cst::rust::Item]) -> String {
items
.iter()
.map(zerodds_lang_render::rust::item)
.collect::<Vec<_>>()
.join("\n")
}
fn codec_error(error: EncodeError) -> GenerateError {
GenerateError::Codec(format!("{error:?}"))
}
fn codec_of<T: CodecTarget>(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
defaulters: impl FnOnce(&Ast, &[T::Decl]) -> Result<Vec<T::Decl>, EncodeError>,
) -> Result<Vec<T::Decl>, EncodeError> {
let mut decls = crate::emit::encode::codec::<T>(ast, key, options)?;
let decoders = crate::emit::decode::decoder::<T>(ast, key, options)?;
decls.extend(defaulters(ast, &decoders)?);
decls.extend(decoders);
Ok(decls)
}
fn cpp_codec_of(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<zerodds_lang_cst::cpp::Decl>, EncodeError> {
use crate::emit::cpp::Cpp;
let excluded = crate::emit::type_pair_cpp::excluded_types(ast);
let mut decls = crate::emit::encode::codec_excluding::<Cpp>(ast, key, options, &excluded, &[])?;
let decoders =
crate::emit::decode::decoder_excluding::<Cpp>(ast, key, options, &excluded, &[])?;
decls.extend(crate::default_cpp::cpp_defaulters_called_by_excluding(
ast, &decoders, &excluded,
)?);
decls.extend(decoders);
Ok(decls)
}
fn java_codec_members(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<zerodds_lang_cst::java::Member>, EncodeError> {
use crate::emit::java::Java;
let excluded = crate::emit::type_pair_java::excluded_types(ast);
let mut reaching = crate::emit::type_pair_java::body_roots(ast);
for id in crate::emit::interface_cpp::remote_member_types(ast).map_err(|error| {
EncodeError::NoLayout {
detail: error.to_string(),
}
})? {
if !reaching.contains(&id) {
reaching.push(id);
}
}
let mut decls =
crate::emit::encode::codec_excluding::<Java>(ast, key, options, &excluded, &reaching)?;
let decoders =
crate::emit::decode::decoder_excluding::<Java>(ast, key, options, &excluded, &reaching)?;
decls.extend(crate::default_java::java_defaulters_called_by(
ast, &decoders,
)?);
decls.extend(decoders);
Ok(decls)
}
fn java_codec_of(
ast: &Ast,
key: LayoutKey,
options: CodecOptions,
) -> Result<Vec<zerodds_lang_cst::java::Member>, EncodeError> {
use zerodds_lang_cst::java;
let mut members = java_codec_members(ast, key, options)?;
members.extend(crate::emit::type_pair_java::header_codecs(ast));
if ast.interfaces().next().is_some() || crate::emit::type_pair_java::has_pair_types(ast) {
let classic = LayoutKey {
encoding: zerodds_ast_layout::Encoding::PlainCdr1,
..key
};
members.push(java::Member::Nested(Box::new(java::TypeDecl::Class(
java::Class {
doc: zerodds_lang_cst::common::Doc::line(
"The codecs of `ZdCodec` under classic CDR (XCDR1). DDS-RPC 1.0 \u{a7}7.5.1.1.",
),
annotations: Vec::new(),
modifiers: vec![java::Modifier::Public, java::Modifier::Static],
name: zerodds_lang_cst::common::Ident::new("Xcdr1"),
generics: Vec::new(),
extends: None,
implements: Vec::new(),
members: java_codec_members(ast, classic, options)?,
},
))));
}
Ok(members)
}
fn indent(text: &str) -> String {
let mut out = String::new();
for line in text.lines() {
if line.is_empty() {
out.push('\n');
} else {
out.push_str(" ");
out.push_str(line);
out.push('\n');
}
}
out
}
#[allow(clippy::too_many_lines)]
fn codec(
ast: &Ast,
language: Language,
key: LayoutKey,
options: CodecOptions,
) -> Result<String, GenerateError> {
let text = match language {
Language::Lua => {
let decls = codec_of::<crate::emit::lua::Lua>(
ast,
key,
options,
crate::default_lua::lua_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::lua::stat)
.collect::<Vec<_>>()
.join("\n")
}
Language::Ada => {
let decls = codec_of::<crate::emit::ada::Ada>(
ast,
key,
options,
crate::default_ada::ada_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::ada::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::C => {
let decls = codec_of::<crate::emit::c::C>(
ast,
key,
options,
crate::default_c::c_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::c::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Cpp => {
let decls = cpp_codec_of(ast, key, options).map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::cpp::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::CSharp => {
let members = codec_of::<crate::emit::csharp::CSharp>(
ast,
key,
options,
crate::default_csharp::csharp_defaulters_called_by,
)
.map_err(codec_error)?;
zerodds_lang_render::csharp::type_decl(&zerodds_lang_cst::csharp::TypeDecl::Class(
zerodds_lang_cst::csharp::Class {
doc: zerodds_lang_cst::common::Doc::none(),
attributes: Vec::new(),
modifiers: vec![
zerodds_lang_cst::csharp::Modifier::Public,
zerodds_lang_cst::csharp::Modifier::Static,
],
name: zerodds_lang_cst::common::Ident::new("ZdCodec"),
generics: Vec::new(),
bases: Vec::new(),
constraints: Vec::new(),
members,
},
))
}
Language::D => {
let decls = codec_of::<crate::emit::d::D>(
ast,
key,
options,
crate::default_d::d_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::d::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Elixir => {
let decls = codec_of::<crate::emit::elixir::Elixir>(
ast,
key,
options,
crate::default_elixir::elixir_defaulters_called_by,
)
.map_err(codec_error)?;
let mut functions = String::new();
for function in &decls {
functions.push('\n');
functions.push_str(&indent(&zerodds_lang_render::elixir::codec_function(
function,
)));
}
let mut out = String::from("defmodule ZdCodec do\n");
if ["&&&", "|||", ">>>", "<<<"]
.iter()
.any(|operator| functions.contains(operator))
{
out.push_str(" import Bitwise\n");
}
out.push_str(&functions);
out.push_str("end\n");
out
}
Language::Go => {
let decls = codec_of::<crate::emit::go::Go>(
ast,
key,
options,
crate::default_go::go_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::go::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Java => {
let members = java_codec_of(ast, key, options).map_err(codec_error)?;
zerodds_lang_render::java::type_decl(&zerodds_lang_cst::java::TypeDecl::Class(
zerodds_lang_cst::java::Class {
doc: zerodds_lang_cst::common::Doc::none(),
annotations: Vec::new(),
modifiers: vec![zerodds_lang_cst::java::Modifier::Public],
name: zerodds_lang_cst::common::Ident::new("ZdCodec"),
generics: Vec::new(),
extends: None,
implements: Vec::new(),
members,
},
))
}
Language::Julia => {
let decls = codec_of::<crate::emit::julia::Julia>(
ast,
key,
options,
crate::default_julia::julia_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::julia::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Nim => {
let decls = codec_of::<crate::emit::nim::Nim>(
ast,
key,
options,
crate::default_nim::nim_defaulters_called_by,
)
.map_err(codec_error)?;
zerodds_lang_render::nim::proc_decls(&decls)
}
Language::Node => {
let mut decls = codec_of::<crate::emit::node::Node>(
ast,
key,
options,
crate::default_node::node_defaulters_called_by,
)
.map_err(codec_error)?;
if let Some(exports) = node_exports(&decls) {
decls.push(exports);
}
decls
.iter()
.map(zerodds_lang_render::node::stat)
.collect::<Vec<_>>()
.join("\n")
}
Language::OCaml => {
let encoders =
crate::emit::encode::codec::<crate::emit::ocaml::OCaml>(ast, key, options)
.map_err(codec_error)?;
let decoders =
crate::emit::decode::decoder::<crate::emit::ocaml::OCaml>(ast, key, options)
.map_err(codec_error)?;
let defaulters = crate::default_ocaml::ocaml_defaulters_called_by(ast, &decoders)
.map_err(codec_error)?;
let mut out = zerodds_lang_render::ocaml::codec_chain(&encoders);
out.push('\n');
out.push_str(&zerodds_lang_render::ocaml::value_chain(&defaulters));
out.push('\n');
out.push_str(&zerodds_lang_render::ocaml::codec_chain(&decoders));
out
}
Language::Python => {
let decls = codec_of::<crate::emit::python::Python>(
ast,
key,
options,
crate::default_python::python_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::python::statement)
.collect::<Vec<_>>()
.join("\n")
}
Language::Rust => {
let items = crate::adapter::cst::nest_by_module(ast, rust_codec_pairs(ast, key)?)
.map_err(|error| GenerateError::NoForm(format!("{error:?}")))?;
render_rust(&items)
}
Language::Swift => {
let decls = codec_of::<crate::emit::swift::Swift>(
ast,
key,
options,
crate::default_swift::swift_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::swift::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::TypeScript => {
let decls = codec_of::<crate::emit::typescript::TypeScript>(
ast,
key,
options,
crate::default_typescript::typescript_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::typescript::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Zig => {
let decls = codec_of::<crate::emit::zig::Zig>(
ast,
key,
options,
crate::default_zig::zig_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::zig::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Ada83 => {
let decls = codec_of::<crate::emit::ada83::Ada83>(
ast,
key,
options,
crate::default_ada83::ada83_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::ada83::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::FSharp => {
let decls = codec_of::<crate::emit::fsharp::Fsharp>(
ast,
key,
options,
crate::default_fsharp::fsharp_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::fsharp::decl)
.collect::<Vec<_>>()
.join("\n")
}
Language::Kotlin => {
let decls = codec_of::<crate::emit::kotlin::Kotlin>(
ast,
key,
options,
crate::default_kotlin::kotlin_defaulters_called_by,
)
.map_err(codec_error)?;
decls
.iter()
.map(zerodds_lang_render::kotlin::decl)
.collect::<Vec<_>>()
.join("\n")
}
};
Ok(text)
}