#![forbid(unsafe_code)]
#![warn(missing_docs)]
mod annotations;
pub(crate) mod bitset;
pub(crate) mod corba_traits;
pub mod emitter;
pub mod error;
pub mod keywords;
pub mod type_map;
pub(crate) mod typesupport;
pub(crate) mod verbatim;
pub use error::CsGenError;
use zerodds_idl::ast::Specification;
#[derive(Debug, Clone)]
pub struct CsGenOptions {
pub root_namespace: Option<String>,
pub indent_width: usize,
pub use_records: bool,
pub emit_corba_traits: bool,
}
impl Default for CsGenOptions {
fn default() -> Self {
Self {
root_namespace: None,
indent_width: 4,
use_records: true,
emit_corba_traits: false,
}
}
}
pub fn generate_csharp(ast: &Specification, opts: &CsGenOptions) -> Result<String, CsGenError> {
let mut out = emitter::emit_source(ast, opts)?;
if opts.emit_corba_traits {
corba_traits::emit_corba_traits(&mut out, ast)?;
}
Ok(out)
}
pub fn generate_csharp_with_corba_traits(
ast: &Specification,
opts: &CsGenOptions,
) -> Result<String, CsGenError> {
let opts = CsGenOptions {
emit_corba_traits: true,
..opts.clone()
};
generate_csharp(ast, &opts)
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic)]
use super::*;
use zerodds_idl::config::ParserConfig;
fn gen_cs(src: &str) -> String {
let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse must succeed");
generate_csharp(&ast, &CsGenOptions::default()).expect("gen must succeed")
}
#[test]
fn empty_source_emits_only_preamble() {
let cs = gen_cs("");
assert!(cs.contains("// Generated by zerodds idl-csharp"));
assert!(cs.contains("#nullable enable"));
assert!(cs.contains("using System;"));
assert!(!cs.contains("namespace M"));
}
#[test]
fn empty_module_emits_namespace() {
let cs = gen_cs("module M {};");
assert!(cs.contains("namespace M"));
assert!(cs.contains("} // namespace M"));
}
#[test]
fn three_level_modules_nest() {
let cs = gen_cs("module A { module B { module C {}; }; };");
assert!(cs.contains("namespace A"));
assert!(cs.contains("namespace B"));
assert!(cs.contains("namespace C"));
}
#[test]
fn primitive_struct_member_uses_correct_cs_types() {
let cs = gen_cs(
"struct S { boolean b; octet o; short s; long l; long long ll; \
unsigned short us; unsigned long ul; unsigned long long ull; \
float f; double d; };",
);
assert!(cs.contains("public bool B"));
assert!(cs.contains("public byte O"));
assert!(cs.contains("public short S"));
assert!(cs.contains("public int L "));
assert!(cs.contains("public long Ll"));
assert!(cs.contains("public ushort Us"));
assert!(cs.contains("public uint Ul "));
assert!(cs.contains("public ulong Ull"));
assert!(cs.contains("public float F"));
assert!(cs.contains("public double D"));
}
#[test]
fn string_member_uses_string() {
let cs = gen_cs("struct S { string name; };");
assert!(cs.contains("public string Name"));
}
#[test]
fn sequence_member_uses_isequence() {
let cs = gen_cs("struct S { sequence<long> data; };");
assert!(cs.contains("using System.Collections.Generic;"));
assert!(cs.contains("using Omg.Types;"));
assert!(cs.contains("ISequence<int>"));
}
#[test]
fn bounded_sequence_member_uses_ibounded_sequence() {
let cs = gen_cs("struct S { sequence<long, 100> data; };");
assert!(cs.contains("using Omg.Types;"));
assert!(cs.contains("IBoundedSequence<int>"));
}
#[test]
fn array_member_uses_jagged_array() {
let cs = gen_cs("struct S { long matrix[3][4]; };");
assert!(cs.contains("int[][]"));
}
#[test]
fn enum_emits_int_backed_enum() {
let cs = gen_cs("enum Color { RED, GREEN, BLUE };");
assert!(cs.contains("public enum Color : int"));
assert!(cs.contains("RED,"));
assert!(cs.contains("BLUE,"));
}
#[test]
fn typedef_emits_alias_record() {
let cs = gen_cs("typedef long MyInt;");
assert!(cs.contains("public sealed record class MyInt(int Value);"));
}
#[test]
fn inheritance_emits_record_inheritance() {
let cs = gen_cs("struct Parent { long x; }; struct Child : Parent { long y; };");
assert!(cs.contains("record class Child : Parent"));
}
#[test]
fn keyed_struct_marker_appears() {
let cs = gen_cs("struct S { @key long id; long val; };");
assert!(cs.contains("[Key]"));
}
#[test]
fn optional_member_uses_nullable() {
let cs = gen_cs("struct S { @optional long maybe; };");
assert!(cs.contains("int? Maybe"));
}
#[test]
fn exception_inherits_exception() {
let cs = gen_cs("exception NotFound { string what_; };");
assert!(cs.contains("class NotFound : Exception"));
}
#[test]
fn union_uses_discriminator_record() {
let cs = gen_cs(
"union U switch (long) { case 1: long a; case 2: double b; default: octet c; };",
);
assert!(cs.contains("record class U"));
assert!(cs.contains("public int Discriminator"));
assert!(cs.contains("public object? Value"));
assert!(cs.contains("// case default"));
}
#[test]
fn header_starts_with_generated_marker() {
let cs = gen_cs("");
assert!(cs.starts_with("// Generated by zerodds idl-csharp."));
}
#[test]
fn nullable_enable_appears_exactly_once() {
let cs = gen_cs("module M { struct S { long x; }; };");
let count = cs.matches("#nullable enable").count();
assert_eq!(count, 1);
}
#[test]
fn record_class_is_init_only() {
let cs = gen_cs("struct S { long x; };");
assert!(cs.contains("get; init;"));
}
#[test]
fn root_namespace_option_wraps_output() {
let ast =
zerodds_idl::parse("struct S { long x; };", &ParserConfig::default()).expect("parse");
let opts = CsGenOptions {
root_namespace: Some("Zerodds.Generated".into()),
..Default::default()
};
let cs = generate_csharp(&ast, &opts).expect("gen");
assert!(cs.contains("namespace Zerodds.Generated"));
assert!(cs.contains("} // namespace Zerodds.Generated"));
}
#[test]
fn non_service_interface_emits_csharp_interface() {
let ast = zerodds_idl::parse("interface I { void op(); };", &ParserConfig::default())
.expect("parse");
let cs = generate_csharp(&ast, &CsGenOptions::default()).expect("ok");
assert!(cs.contains("public interface I"));
}
#[test]
fn const_decl_emits_const() {
let cs = gen_cs("const long MAX = 100;");
assert!(cs.contains("public const int MAX = 100;"));
}
#[test]
fn options_have_sensible_defaults() {
let o = CsGenOptions::default();
assert_eq!(o.indent_width, 4);
assert!(o.root_namespace.is_none());
assert!(o.use_records);
}
#[test]
fn options_clone_works() {
let o = CsGenOptions {
root_namespace: Some("Foo".into()),
indent_width: 2,
use_records: false,
emit_corba_traits: false,
};
let cloned = o.clone();
assert_eq!(cloned.indent_width, 2);
assert_eq!(cloned.root_namespace.as_deref(), Some("Foo"));
assert!(!cloned.use_records);
}
}