use zerodds_idl::ast::Annotation;
use zerodds_idl::semantics::annotations::{
BuiltinAnnotation, ExtensibilityKind, Lowered, lower_annotations,
};
pub(crate) const RUNTIME_PREFIX: &str = "org.zerodds.types";
pub(crate) fn lower_or_empty(anns: &[Annotation]) -> Lowered {
lower_annotations(anns).unwrap_or_default()
}
pub(crate) fn member_annotation_lines(anns: &[Annotation]) -> Vec<String> {
let lowered = lower_or_empty(anns);
let mut out: Vec<String> = Vec::new();
let mut has_key = false;
let mut has_optional = false;
let mut has_must = false;
let mut has_external = false;
let mut has_shared = false;
let mut explicit_id: Option<u32> = None;
for b in &lowered.builtins {
match b {
BuiltinAnnotation::Key => has_key = true,
BuiltinAnnotation::Id(n) => explicit_id = Some(*n),
BuiltinAnnotation::Optional => has_optional = true,
BuiltinAnnotation::MustUnderstand => has_must = true,
BuiltinAnnotation::External => has_external = true,
BuiltinAnnotation::Shared => has_shared = true,
_ => {}
}
}
if has_key {
out.push(format!("@{RUNTIME_PREFIX}.Key"));
}
if let Some(n) = explicit_id {
out.push(format!("@{RUNTIME_PREFIX}.Id({n})"));
}
if has_optional {
out.push(format!("@{RUNTIME_PREFIX}.Optional"));
}
if has_must {
out.push(format!("@{RUNTIME_PREFIX}.MustUnderstand"));
}
if has_external {
out.push(format!("@{RUNTIME_PREFIX}.External"));
}
if has_shared {
out.push(format!("@{RUNTIME_PREFIX}.Shared"));
}
out
}
pub(crate) fn type_annotation_lines(anns: &[Annotation]) -> Vec<String> {
let lowered = lower_or_empty(anns);
let mut out: Vec<String> = Vec::new();
if has_nested(&lowered) {
out.push(format!("@{RUNTIME_PREFIX}.Nested"));
}
if let Some(kind) = lowered.extensibility() {
let lit = match kind {
ExtensibilityKind::Final => "FINAL",
ExtensibilityKind::Appendable => "APPENDABLE",
ExtensibilityKind::Mutable => "MUTABLE",
};
out.push(format!(
"@{RUNTIME_PREFIX}.Extensibility({RUNTIME_PREFIX}.Extensibility.Kind.{lit})",
));
}
out
}
pub(crate) fn has_nested(lowered: &Lowered) -> bool {
lowered
.builtins
.iter()
.any(|b| matches!(b, BuiltinAnnotation::Nested))
}
pub(crate) fn enum_value_override(anns: &[Annotation]) -> Option<String> {
let lowered = lower_or_empty(anns);
lowered.builtins.iter().find_map(|b| match b {
BuiltinAnnotation::Value(s) => Some(s.clone()),
_ => None,
})
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic)]
use super::*;
use zerodds_idl::config::ParserConfig;
fn parse(src: &str) -> zerodds_idl::ast::Specification {
zerodds_idl::parse(src, &ParserConfig::default()).expect("parse")
}
fn first_member_anns(src: &str) -> Vec<Annotation> {
let ast = parse(src);
for d in &ast.definitions {
if let zerodds_idl::ast::Definition::Type(zerodds_idl::ast::TypeDecl::Constr(
zerodds_idl::ast::ConstrTypeDecl::Struct(zerodds_idl::ast::StructDcl::Def(s)),
)) = d
{
if let Some(m) = s.members.first() {
return m.annotations.clone();
}
}
}
Vec::new()
}
fn struct_anns(src: &str) -> Vec<Annotation> {
let ast = parse(src);
for d in &ast.definitions {
if let zerodds_idl::ast::Definition::Type(zerodds_idl::ast::TypeDecl::Constr(
zerodds_idl::ast::ConstrTypeDecl::Struct(zerodds_idl::ast::StructDcl::Def(s)),
)) = d
{
return s.annotations.clone();
}
}
Vec::new()
}
#[test]
fn key_emits_key_annotation() {
let anns = first_member_anns("struct S { @key long id; };");
assert_eq!(
member_annotation_lines(&anns),
vec!["@org.zerodds.types.Key".to_string()],
);
}
#[test]
fn id_emits_id_with_value() {
let anns = first_member_anns("struct S { @id(7) long x; };");
assert_eq!(
member_annotation_lines(&anns),
vec!["@org.zerodds.types.Id(7)".to_string()],
);
}
#[test]
fn optional_emits_optional_annotation() {
let anns = first_member_anns("struct S { @optional long x; };");
assert_eq!(
member_annotation_lines(&anns),
vec!["@org.zerodds.types.Optional".to_string()],
);
}
#[test]
fn must_understand_emits_marker() {
let anns = first_member_anns("struct S { @must_understand long x; };");
assert_eq!(
member_annotation_lines(&anns),
vec!["@org.zerodds.types.MustUnderstand".to_string()],
);
}
#[test]
fn external_emits_marker() {
let anns = first_member_anns("struct S { @external long x; };");
assert_eq!(
member_annotation_lines(&anns),
vec!["@org.zerodds.types.External".to_string()],
);
}
#[test]
fn key_id_optional_combine_in_deterministic_order() {
let anns = first_member_anns("struct S { @optional @id(3) @key long x; };");
assert_eq!(
member_annotation_lines(&anns),
vec![
"@org.zerodds.types.Key".to_string(),
"@org.zerodds.types.Id(3)".to_string(),
"@org.zerodds.types.Optional".to_string(),
],
);
}
#[test]
fn nested_struct_emits_nested_type_annotation() {
let anns = struct_anns("@nested struct S { long x; };");
assert_eq!(
type_annotation_lines(&anns),
vec!["@org.zerodds.types.Nested".to_string()],
);
}
#[test]
fn final_struct_emits_extensibility_annotation() {
let anns = struct_anns("@final struct S { long x; };");
assert_eq!(
type_annotation_lines(&anns),
vec![
"@org.zerodds.types.Extensibility(\
org.zerodds.types.Extensibility.Kind.FINAL)"
.to_string()
],
);
}
#[test]
fn mutable_struct_emits_extensibility_mutable() {
let anns = struct_anns("@mutable struct S { long x; };");
assert_eq!(
type_annotation_lines(&anns),
vec![
"@org.zerodds.types.Extensibility(\
org.zerodds.types.Extensibility.Kind.MUTABLE)"
.to_string()
],
);
}
#[test]
fn appendable_explicit_extensibility_emits() {
let anns = struct_anns("@extensibility(APPENDABLE) struct S { long x; };");
assert_eq!(
type_annotation_lines(&anns),
vec![
"@org.zerodds.types.Extensibility(\
org.zerodds.types.Extensibility.Kind.APPENDABLE)"
.to_string()
],
);
}
#[test]
fn no_annotations_yields_empty_lists() {
let anns = first_member_anns("struct S { long x; };");
assert!(member_annotation_lines(&anns).is_empty());
let tann = struct_anns("struct S { long x; };");
assert!(type_annotation_lines(&tann).is_empty());
}
#[test]
fn enum_value_override_returns_literal() {
let anns = first_member_anns("struct S { @value(7) long x; };");
assert_eq!(enum_value_override(&anns), Some("7".to_string()));
}
#[test]
fn enum_value_override_absent_returns_none() {
let anns = first_member_anns("struct S { long x; };");
assert_eq!(enum_value_override(&anns), None);
}
}